diff --git a/BUILD.gn b/BUILD.gn
index 77de65b..5479f5f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -832,24 +832,6 @@
         "//media/gpu:video_encode_accelerator_unittest",
       ]
     }
-
-    # The following run-time dependencies are needed to deploy chrome to a
-    # ChromeOS device. See the link for the full list:
-    # https://codesearch.chromium.org/chromium/src/third_party/chromite/lib/chrome_util.py?l=341
-    # Most of these are copy targets, for which GN doesn't add their outputs
-    # as runtime-deps. See the link below for more details:
-    # https://chromium.googlesource.com/chromium/src/+/master/tools/gn/docs/reference.md#actions-and-copies
-    data_deps = [
-      "//chrome:xdg_mime",
-      "//mojo/core:shared_library_arc32",
-      "//mojo/core:shared_library_arc64",
-    ]
-
-    # TODO(bpastene): Figure out what's generating resources/chromeos/ and
-    # declare it as a dep instead of adding the dir directly.
-    data = [
-      "$root_out_dir/resources/chromeos/",
-    ]
   }
 }
 
diff --git a/DEPS b/DEPS
index 392a104..0d02b59 100644
--- a/DEPS
+++ b/DEPS
@@ -105,11 +105,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '64cc576b1fa7afaffd10b8fd8f445070f2d2b401',
+  'skia_revision': 'da77c7f91f1de8048245d084a6099b0f6441cdda',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': '8319a20f9884ea4030e231586ed2e6fa3db478d2',
+  'v8_revision': 'e82ab6954ceed14cc09f3554332d0c4ee6badcaa',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -117,7 +117,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'cc73f241145d24daea0c96460462f7aac5b83d8c',
+  'angle_revision': '81f891d043cb735e54af0e588c61461192ef057e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -129,7 +129,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'f733d7da42cdbb589fc74c58ebc61f83792bdeac',
+  'pdfium_revision': '2958a8faf500b9c01ca968ee46fe89795eafe2a7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -153,7 +153,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling NaCl
   # and whatever else without interference from each other.
-  'nacl_revision': 'fa95e2a7489d9efe3f8dd770c204b4d968eb9b02',
+  'nacl_revision': '64cb8d9d67fbe924ff2c48d1fd9c73437d735344',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -165,7 +165,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': 'c988ddf7b193a624b43b05c1e2fe3cda8f3146d8',
+  'catapult_revision': 'd38bbdff043da3482cc0c60d44f97b172a82c1f4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -213,7 +213,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': 'a97c1d911ae7b5aa40b34b21e680e7496a9e6a55',
+  'spv_tools_revision': 'd38a0a3b4476798744c4223acbefab3af97698fd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -286,7 +286,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'abbc6a8b2afc648c18569dc9b4b01d0f1a72bc0f',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '3a50c7a3b2f4ec760d2d2405b6b9787e5493c049',
       'condition': 'checkout_ios',
   },
 
@@ -552,7 +552,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7d059ce0a2a39bed6379c6e2310065e210224184',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'ee09b25fcad3adb2edf60d13e726c04f3e34451d',
       'condition': 'checkout_linux',
   },
 
@@ -567,7 +567,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + '6d8535d43743a52430b276b3013c5ed12e573a78',
+      'url': Var('chromium_git') + '/chromiumos/platform/system_api.git' + '@' + 'a2a458bd5d690a1b7bba24d73650b19f3b9549d1',
       'condition': 'checkout_linux',
   },
 
@@ -577,7 +577,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6f812e132d25df1b561f7182b04903004a3055e4',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '29b7b99e5d9fa6ae4d3ce6273b537cab2dc89b2f',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1081,7 +1081,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0da902e4fde4337e8b6fd6444517c077bb927793',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ca942e227a7a3d74782c2384ed55969dd368aa1e',
     'condition': 'checkout_src_internal',
   },
 
@@ -1920,21 +1920,6 @@
                 '-d', 'src/tools/luci-go/linux64',
     ],
   },
-  # Pull the Syzygy binaries, used for optimization and instrumentation.
-  # Remove this as soon as the zap_timestamp.exe utility is no longer used.
-  # See https://crbug.com/821764#c3.
-  {
-    'name': 'syzygy-binaries',
-    'pattern': '.',
-    'condition': 'host_os == "win"',
-    'action': ['python',
-               'src/build/get_syzygy_binaries.py',
-               '--output-dir=src/third_party/syzygy/binaries',
-               '--revision=8164b24ebde9c5649c9a09e88a7fc0b0fcbd1bc5',
-               '--overwrite',
-               '--copy-dia-binaries',
-    ],
-  },
   {
     'name': 'apache_win32',
     'pattern': '\\.sha1',
diff --git a/WATCHLISTS b/WATCHLISTS
index ae6d0b7..15016d64 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1858,7 +1858,7 @@
     'blink_indexed_db': ['cmumford@chromium.org',
                          'jsbell+idb@chromium.org'],
     'blink_input': ['dtapuska+blinkwatch@chromium.org',
-                    'nzolghadr@chromium.org'],
+                    'nzolghadr+blinkwatch@chromium.org'],
     'blink_layers': ['blink-layers+watch@chromium.org'],
     'blink_layout': ['blink-reviews-layout@chromium.org',
                      'eae+blinkwatch@chromium.org',
diff --git a/android_webview/browser/aw_field_trial_creator.cc b/android_webview/browser/aw_field_trial_creator.cc
index 263e2cf..f4915224 100644
--- a/android_webview/browser/aw_field_trial_creator.cc
+++ b/android_webview/browser/aw_field_trial_creator.cc
@@ -42,19 +42,6 @@
       new variations::SHA1EntropyProvider(client_id));
 }
 
-// This experiment is for testing and doesn't control any features. Log it so QA
-// can check whether variations is working.
-// TODO(crbug/841623): Remove this after launch.
-void LogTestExperiment() {
-  static const char* const test_name = "First-WebView-Experiment";
-  base::FieldTrial* test_trial = base::FieldTrialList::Find(test_name);
-  if (test_trial) {
-    LOG(INFO) << test_name << " found, group=" << test_trial->group_name();
-  } else {
-    LOG(INFO) << test_name << " not found";
-  }
-}
-
 }  // anonymous namespace
 
 AwFieldTrialCreator::AwFieldTrialCreator()
@@ -111,8 +98,6 @@
       std::vector<std::string>(), CreateLowEntropyProvider(client_id),
       std::make_unique<base::FeatureList>(), aw_field_trials_.get(),
       &ignored_safe_seed_manager);
-
-  LogTestExperiment();
 }
 
 PrefService* AwFieldTrialCreator::GetLocalState() {
diff --git a/android_webview/glue/BUILD.gn b/android_webview/glue/BUILD.gn
index a514a6fa..a64e649c 100644
--- a/android_webview/glue/BUILD.gn
+++ b/android_webview/glue/BUILD.gn
@@ -28,6 +28,7 @@
     "java/src/com/android/webview/chromium/ApiHelperForN.java",
     "java/src/com/android/webview/chromium/ApiHelperForO.java",
     "java/src/com/android/webview/chromium/ApiHelperForOMR1.java",
+    "java/src/com/android/webview/chromium/ApiHelperForP.java",
     "java/src/com/android/webview/chromium/SharedStatics.java",
     "java/src/com/android/webview/chromium/CallbackConverter.java",
     "java/src/com/android/webview/chromium/ContentSettingsAdapter.java",
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/ApiHelperForP.java b/android_webview/glue/java/src/com/android/webview/chromium/ApiHelperForP.java
new file mode 100644
index 0000000..71ee0058
--- /dev/null
+++ b/android_webview/glue/java/src/com/android/webview/chromium/ApiHelperForP.java
@@ -0,0 +1,31 @@
+// Copyright 2018 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 com.android.webview.chromium;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import org.chromium.base.annotations.DoNotInline;
+
+/**
+ * Utility class to use new APIs that were added in P (API level 28). These need to exist in a
+ * separate class so that Android framework can successfully verify WebView classes without
+ * encountering the new APIs.
+ */
+@DoNotInline
+@TargetApi(Build.VERSION_CODES.P)
+public final class ApiHelperForP {
+    private ApiHelperForP() {}
+
+    /**
+     * See {@link
+     * TracingControllerAdapter#TracingControllerAdapter(WebViewChromiumFactoryProvider,
+     * AwTracingController)}, which was added in N.
+     */
+    public static TracingControllerAdapter createTracingControllerAdapter(
+            WebViewChromiumFactoryProvider provider, WebViewChromiumAwInit awInit) {
+        return new TracingControllerAdapter(provider, awInit.getAwTracingController());
+    }
+}
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 fbeb4b18..8b99f83 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
@@ -545,8 +545,7 @@
             // waiting for startup. Hence check the mTracingControler here to ensure
             // the singleton property.
             if (mTracingController == null) {
-                mTracingController =
-                        new TracingControllerAdapter(this, mAwInit.getAwTracingController());
+                mTracingController = ApiHelperForP.createTracingControllerAdapter(this, mAwInit);
             }
         }
         return mTracingController;
diff --git a/android_webview/tools/system_webview_shell/BUILD.gn b/android_webview/tools/system_webview_shell/BUILD.gn
index af852420..c4013a46 100644
--- a/android_webview/tools/system_webview_shell/BUILD.gn
+++ b/android_webview/tools/system_webview_shell/BUILD.gn
@@ -28,6 +28,7 @@
     "apk/src/org/chromium/webview_shell/WebViewBrowserActivity.java",
     "apk/src/org/chromium/webview_shell/WebViewLayoutTestActivity.java",
     "apk/src/org/chromium/webview_shell/WebViewThreadTestActivity.java",
+    "apk/src/org/chromium/webview_shell/WebViewTracingActivity.java",
   ]
   android_manifest = "apk/AndroidManifest.xml"
   deps = [
@@ -45,18 +46,16 @@
   apk_name = "SystemWebViewShellPageCycler"
   apk_under_test = ":system_webview_shell_apk"
   android_manifest = "page_cycler/AndroidManifest.xml"
-  java_files = [
-    "page_cycler/src/org/chromium/webview_shell/page_cycler/PageCyclerTest.java",
-  ]
+  java_files = [ "page_cycler/src/org/chromium/webview_shell/page_cycler/PageCyclerTest.java" ]
   deps = [
     "//base:base_java",
     "//base:base_java_test_support",
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
     "//testing/android/reporter:reporter_java",
-    "//third_party/junit",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
+    "//third_party/junit",
   ]
   enable_multidex = true
 }
diff --git a/android_webview/tools/system_webview_shell/apk/AndroidManifest.xml b/android_webview/tools/system_webview_shell/apk/AndroidManifest.xml
index fb33f532..d414b04 100644
--- a/android_webview/tools/system_webview_shell/apk/AndroidManifest.xml
+++ b/android_webview/tools/system_webview_shell/apk/AndroidManifest.xml
@@ -115,6 +115,13 @@
             android:exported="true">
         </activity>
 
+        <activity
+            android:name="org.chromium.webview_shell.WebViewTracingActivity"
+            android:label="@string/title_activity_telemetry"
+            android:noHistory="true"
+            android:exported="true">
+        </activity>
+
         <uses-library android:name="android.test.runner" />
     </application>
 </manifest>
diff --git a/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewTracingActivity.java b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewTracingActivity.java
new file mode 100644
index 0000000..8f0c26b8
--- /dev/null
+++ b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebViewTracingActivity.java
@@ -0,0 +1,142 @@
+// Copyright 2018 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.webview_shell;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.webkit.TracingConfig;
+import android.webkit.TracingController;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+
+/**
+ * This activity is designed for telemetry testing of WebView Tracing API.
+ *
+ * In particular it allows to measure time for loading a given URL with or without
+ * tracing enabled. It also provides the overall tracing time, i.e. time it takes from
+ * starting tracing until completion when all traces are written to disk.
+ *
+ * Example usage:
+ * $ adb shell am start -n org.chromium.webview_shell/.WebViewTracingActivity -a VIEW -d \
+ *   http://www.google.com --ez enableTracing true
+ *
+ */
+public class WebViewTracingActivity extends Activity {
+    private static final String TAG = "WebViewTracingActivity";
+
+    private static long sUrlLoadTimeStartMs;
+    private static long sUrlLoadTimeEndMs;
+
+    private static long sOverallTracingTimeStartMs;
+    private static long sOverallTracingTimeEndMs;
+
+    private static class TracingLogger extends FileOutputStream {
+        private long mByteCount;
+        private Activity mActivity;
+
+        public TracingLogger(String fileName, Activity activity) throws FileNotFoundException {
+            super(fileName);
+            mActivity = activity;
+        }
+
+        @Override
+        public void write(byte[] chunk) throws IOException {
+            mByteCount += chunk.length;
+            super.write(chunk);
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+            sOverallTracingTimeEndMs = SystemClock.elapsedRealtime();
+            android.util.Log.i(TAG,
+                    "TracingLogger.complete(), byte count = " + getByteCount()
+                            + ", overall duration (ms) = "
+                            + (sOverallTracingTimeEndMs - sOverallTracingTimeStartMs));
+            mActivity.finish();
+        }
+
+        public long getByteCount() {
+            return mByteCount;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().setTitle("WebViewTracingActivity");
+
+        Intent intent = getIntent();
+        String url = getUrlFromIntent(intent);
+        if (url == null) {
+            url = "about:blank";
+        }
+
+        boolean enableTracing = false;
+        if (intent.getExtras() != null) {
+            enableTracing = intent.getExtras().getBoolean("enableTracing", false);
+        }
+
+        loadUrl(url, enableTracing);
+    }
+
+    @SuppressLint("NewApi") // TracingController related methods require API level 28.
+    private void loadUrl(final String url, boolean enableTracing) {
+        final Activity activity = this;
+        WebView webView = new WebView(this);
+        setContentView(webView);
+        WebSettings settings = webView.getSettings();
+        settings.setJavaScriptEnabled(true);
+        final TracingController tracingController = TracingController.getInstance();
+
+        webView.setWebViewClient(new WebViewClient() {
+            @SuppressLint("NewApi") // TracingController related methods require API level 28.
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                super.onPageFinished(view, url);
+                sUrlLoadTimeEndMs = SystemClock.elapsedRealtime();
+                android.util.Log.i(TAG,
+                        "onPageFinished(), enableTracing = " + enableTracing + ", url = " + url
+                                + ", urlLoadTimeMillis = "
+                                + (sUrlLoadTimeEndMs - sUrlLoadTimeStartMs));
+
+                if (enableTracing) {
+                    String outFileName = getFilesDir() + "/webview_tracing.json";
+                    try {
+                        tracingController.stop(new TracingLogger(outFileName, activity),
+                                Executors.newSingleThreadExecutor());
+                    } catch (FileNotFoundException e) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    activity.finish();
+                }
+            }
+        });
+
+        if (enableTracing) {
+            sOverallTracingTimeStartMs = SystemClock.elapsedRealtime();
+            tracingController.start(new TracingConfig.Builder()
+                                            .addCategories(TracingConfig.CATEGORIES_WEB_DEVELOPER)
+                                            .setTracingMode(TracingConfig.RECORD_UNTIL_FULL)
+                                            .build());
+        }
+        sUrlLoadTimeStartMs = SystemClock.elapsedRealtime();
+        webView.loadUrl(url);
+    }
+
+    private static String getUrlFromIntent(Intent intent) {
+        return intent != null ? intent.getDataString() : null;
+    }
+}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index ed3e1b0..4e2343c 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -397,6 +397,8 @@
     "login/ui/note_action_launch_button.h",
     "login/ui/pin_keyboard_animation.cc",
     "login/ui/pin_keyboard_animation.h",
+    "login/ui/public_account_warning_dialog.cc",
+    "login/ui/public_account_warning_dialog.h",
     "login/ui/scrollable_users_list_view.cc",
     "login/ui/scrollable_users_list_view.h",
     "login/ui/user_switch_flip_animation.cc",
@@ -1115,8 +1117,6 @@
     "wm/overview/overview_animation_type.h",
     "wm/overview/overview_utils.cc",
     "wm/overview/overview_utils.h",
-    "wm/overview/overview_window_animation_observer.cc",
-    "wm/overview/overview_window_animation_observer.h",
     "wm/overview/overview_window_drag_controller.cc",
     "wm/overview/overview_window_drag_controller.h",
     "wm/overview/rounded_rect_view.cc",
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index 95b7e19..671f782 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -591,8 +591,7 @@
   forward_view_list.push_back(contents_view()
                                   ->search_result_answer_card_view_for_test()
                                   ->GetSearchAnswerContainerViewForTest());
-  SearchResultListView* list_view =
-      contents_view()->search_result_list_view_for_test();
+  SearchResultListView* list_view = contents_view()->search_result_list_view();
   for (int i = 0; i < kListResults; ++i)
     forward_view_list.push_back(list_view->GetResultViewAt(i));
   forward_view_list.push_back(search_box_view()->search_box());
@@ -744,8 +743,7 @@
   forward_view_list.push_back(contents_view()
                                   ->search_result_answer_card_view_for_test()
                                   ->GetSearchAnswerContainerViewForTest());
-  SearchResultListView* list_view =
-      contents_view()->search_result_list_view_for_test();
+  SearchResultListView* list_view = contents_view()->search_result_list_view();
   for (int i = 0; i < kListResults; ++i)
     forward_view_list.push_back(list_view->GetResultViewAt(i));
   forward_view_list.push_back(search_box_view()->search_box());
@@ -1031,8 +1029,7 @@
   search_box_view()->search_box()->InsertText(base::ASCIIToUTF16("test"));
   const int kListResults = 2;
   SetUpSearchResults(0, kListResults, false);
-  SearchResultListView* list_view =
-      contents_view()->search_result_list_view_for_test();
+  SearchResultListView* list_view = contents_view()->search_result_list_view();
   EXPECT_EQ(search_box_view()->search_box(), focused_view());
   EXPECT_EQ(list_view->GetResultViewAt(0),
             contents_view()->search_results_page_view()->first_result_view());
@@ -1075,8 +1072,7 @@
   search_box_view()->search_box()->InsertText(base::ASCIIToUTF16("test1"));
   const int kListResults = 2;
   SetUpSearchResults(0, kListResults, false);
-  SearchResultListView* list_view =
-      contents_view()->search_result_list_view_for_test();
+  SearchResultListView* list_view = contents_view()->search_result_list_view();
   SearchResultBaseView* first_result_view =
       contents_view()->search_results_page_view()->first_result_view();
   EXPECT_EQ(search_box_view()->search_box(), focused_view());
diff --git a/ash/app_list/views/contents_view.h b/ash/app_list/views/contents_view.h
index 4ebf3b3..dba44373 100644
--- a/ash/app_list/views/contents_view.h
+++ b/ash/app_list/views/contents_view.h
@@ -108,7 +108,7 @@
       const {
     return search_result_tile_item_list_view_;
   }
-  SearchResultListView* search_result_list_view_for_test() const {
+  SearchResultListView* search_result_list_view() const {
     return search_result_list_view_;
   }
   HorizontalPageContainer* horizontal_page_container() const {
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index 877a3ef9..1beebba 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -298,6 +298,43 @@
   ContentsChanged(search_box(), empty_query);
 }
 
+void SearchBoxView::OnWallpaperColorsChanged() {
+  GetWallpaperProminentColors(
+      base::BindOnce(&SearchBoxView::OnWallpaperProminentColorsReceived,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void SearchBoxView::ProcessAutocomplete(bool is_search_result_list_view_first) {
+  if (!is_search_result_list_view_first || last_key_pressed_ == ui::VKEY_BACK ||
+      search_model_->results()->item_count() == 0) {
+    // No first result exists, backspace was pressed, or no results exist.
+    ClearAutocompleteText();
+    return;
+  }
+
+  const base::string16& current_text = search_box()->text();
+  const base::string16& details =
+      search_model_->results()->GetItemAt(0)->details();
+  const base::string16& search_text =
+      search_model_->results()->GetItemAt(0)->title();
+  if (base::StartsWith(details, current_text,
+                       base::CompareCase::INSENSITIVE_ASCII)) {
+    // Current text in the search_box matches the first result's url.
+    SetAutocompleteText(details);
+    return;
+  }
+  if (base::StartsWith(search_text, current_text,
+                       base::CompareCase::INSENSITIVE_ASCII)) {
+    // Current text in the search_box matches the first result's search result
+    // text.
+    SetAutocompleteText(search_text);
+    return;
+  }
+  // Current text in the search_box does not match the first result's url or
+  // search result text.
+  ClearAutocompleteText();
+}
+
 void SearchBoxView::GetWallpaperProminentColors(
     AppListViewDelegate::GetWallpaperProminentColorsCallback callback) {
   view_delegate_->GetWallpaperProminentColors(std::move(callback));
@@ -322,15 +359,86 @@
   SchedulePaint();
 }
 
+void SearchBoxView::AcceptAutocompleteText() {
+  if (highlight_range_.start() != search_box()->text().length())
+    ContentsChanged(search_box(), search_box()->text());
+}
+
+void SearchBoxView::AcceptOneCharInAutocompleteText() {
+  highlight_range_.set_start(highlight_range_.start() + 1);
+  highlight_range_.set_end(search_box()->text().length());
+  const base::string16 original_text = search_box()->text();
+  search_box()->SetText(
+      search_box()->text().substr(0, highlight_range_.start()));
+  ContentsChanged(search_box(), search_box()->text());
+  search_box()->SetText(original_text);
+  search_box()->SetSelectionRange(highlight_range_);
+}
+
+void SearchBoxView::ClearAutocompleteText() {
+  search_box()->SetText(
+      search_box()->text().substr(0, highlight_range_.start()));
+}
+
 void SearchBoxView::ContentsChanged(views::Textfield* sender,
                                     const base::string16& new_contents) {
+  // Update autocomplete text highlight range to track user typed text.
+  highlight_range_.set_start(search_box()->text().length());
   search_box::SearchBoxViewBase::ContentsChanged(sender, new_contents);
   app_list_view_->SetStateFromSearchBoxView(
       IsSearchBoxTrimmedQueryEmpty(), true /*triggered_by_contents_change*/);
 }
 
+void SearchBoxView::SetAutocompleteText(
+    const base::string16& autocomplete_text) {
+  const base::string16& current_text = search_box()->text();
+  // Currrent text is a prefix of autocomplete text.
+  DCHECK(base::StartsWith(autocomplete_text, current_text,
+                          base::CompareCase::INSENSITIVE_ASCII));
+  // Don't set autocomplete text if it's the same as current search box text.
+  if (autocomplete_text.length() == current_text.length())
+    return;
+
+  search_box()->SetText(autocomplete_text);
+  highlight_range_.set_end(autocomplete_text.length());
+  search_box()->SelectRange(highlight_range_);
+}
+
+void SearchBoxView::UpdateAutocompleteSelectionRange(uint32_t start,
+                                                     uint32_t end) {
+  highlight_range_.set_start(start);
+  highlight_range_.set_end(end);
+}
+
 bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
                                    const ui::KeyEvent& key_event) {
+  if (search_box()->HasFocus() && is_search_box_active() &&
+      !search_box()->text().empty()) {
+    // If the search box has no text in it currently, autocomplete should not
+    // work.
+    last_key_pressed_ = key_event.key_code();
+    if (key_event.type() == ui::ET_KEY_PRESSED &&
+        key_event.key_code() != ui::VKEY_BACK) {
+      if (key_event.key_code() == ui::VKEY_RIGHT ||
+          key_event.key_code() == ui::VKEY_LEFT ||
+          key_event.key_code() == ui::VKEY_DOWN ||
+          key_event.key_code() == ui::VKEY_UP ||
+          key_event.key_code() == ui::VKEY_TAB) {
+        AcceptAutocompleteText();
+      } else {
+        const base::string16 pending_text = search_box()->GetSelectedText();
+        // Hitting the next key in the autocompete suggestion continues
+        // autocomplete suggestion. If the selected range doesn't match the
+        // recorded highlight range, the selection should be overwritten.
+        if (!pending_text.empty() &&
+            key_event.GetCharacter() == pending_text[0] &&
+            pending_text.length() == highlight_range_.length()) {
+          AcceptOneCharInAutocompleteText();
+          return true;
+        }
+      }
+    }
+  }
   if (key_event.type() == ui::ET_KEY_PRESSED &&
       key_event.key_code() == ui::VKEY_RETURN) {
     if (!IsSearchBoxTrimmedQueryEmpty()) {
@@ -401,10 +509,4 @@
   UpdateSearchIcon();
 }
 
-void SearchBoxView::OnWallpaperColorsChanged() {
-  GetWallpaperProminentColors(
-      base::BindOnce(&SearchBoxView::OnWallpaperProminentColorsReceived,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
 }  // namespace app_list
diff --git a/ash/app_list/views/search_box_view.h b/ash/app_list/views/search_box_view.h
index a850f6c..65aa8dc6 100644
--- a/ash/app_list/views/search_box_view.h
+++ b/ash/app_list/views/search_box_view.h
@@ -80,6 +80,9 @@
   // Called when the wallpaper colors change.
   void OnWallpaperColorsChanged();
 
+  // Sets the autocomplete text if autocomplete conditions are met.
+  void ProcessAutocomplete(bool is_search_result_list_view_first);
+
  private:
   // Gets the wallpaper prominent colors.
   void GetWallpaperProminentColors(
@@ -90,6 +93,21 @@
   void OnWallpaperProminentColorsReceived(
       const std::vector<SkColor>& prominent_colors);
 
+  // Notifies SearchBoxViewDelegate that the autocomplete text is valid.
+  void AcceptAutocompleteText();
+
+  // Accepts one character in the autocomplete text and fires query.
+  void AcceptOneCharInAutocompleteText();
+
+  // Removes all autocomplete text.
+  void ClearAutocompleteText();
+
+  // After verifying autocomplete text is valid, sets the current searchbox
+  // text to the autocomplete text and sets the text highlight.
+  void SetAutocompleteText(const base::string16& autocomplete_text);
+
+  void UpdateAutocompleteSelectionRange(uint32_t start, uint32_t end);
+
   // Overridden from views::TextfieldController:
   void ContentsChanged(views::Textfield* sender,
                        const base::string16& new_contents) override;
@@ -104,6 +122,12 @@
   void Update() override;
   void SearchEngineChanged() override;
 
+  // The range of highlighted text for autocomplete.
+  gfx::Range highlight_range_;
+
+  // The key most recently pressed.
+  ui::KeyboardCode last_key_pressed_ = ui::VKEY_UNKNOWN;
+
   AppListViewDelegate* view_delegate_;   // Not owned.
   SearchModel* search_model_ = nullptr;  // Owned by the profile-keyed service.
 
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 229e24fc..2c34077 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -278,6 +278,14 @@
 
   first_result_view_ = result_container_views_[0]->GetFirstResultView();
 
+  // Update SearchBoxView search box autocomplete as necessary based on new
+  // first result view.
+  if (first_result_view_) {
+    AppListPage::contents_view()->GetSearchBoxView()->ProcessAutocomplete(
+        result_container_views_[0] ==
+        AppListPage::contents_view()->search_result_list_view());
+  }
+
   // If one of the search result is focused, do not highlight the first search
   // result.
   if (Contains(focused_view))
diff --git a/ash/app_list/views/search_result_page_view_unittest.cc b/ash/app_list/views/search_result_page_view_unittest.cc
index e271f953..43a774a 100644
--- a/ash/app_list/views/search_result_page_view_unittest.cc
+++ b/ash/app_list/views/search_result_page_view_unittest.cc
@@ -78,7 +78,7 @@
     view_ = contents_view->search_results_page_view();
     tile_list_view_ =
         contents_view->search_result_tile_item_list_view_for_test();
-    list_view_ = contents_view->search_result_list_view_for_test();
+    list_view_ = contents_view->search_result_list_view();
   }
   void TearDown() override {
     app_list_view_->GetWidget()->Close();
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index b2d5f8e..b2ac302 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1513,6 +1513,21 @@
       <message name="IDS_ASH_LOGIN_COME_BACK_MESSAGE" desc="Message shown to user indicating a future time when the user is allowed to use the device again.">
         Come back at <ph name="COME_BACK_TIME">$1<ex>7:30 AM</ex></ph>.
       </message>
+      <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO" desc="Text shown in the warning dialog after user clicks on the learn more link, notifying the user of potential security and privacy implications of using the device">
+        The device admin may monitor the following:
+      </message>
+      <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_1" desc="Text shown in the warning dialog after user clicks on the learn more link, notifying the user of potential security and privacy implications of using the device">
+        Access your browsing activity
+      </message>
+      <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_2" desc="Text shown in the warning dialog after user clicks on the learn more link, notifying the user of potential security and privacy implications of using the device">
+        Manipulate settings that specify whether websites can use features such as geolocation, microphone, camera, etc.
+      </message>
+      <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_3" desc="Text shown in the warning dialog after user clicks on the learn more link, notifying the user of potential security and privacy implications of using the device">
+        Manage your apps, extensions, and themes
+      </message>
+      <message name="IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_4" desc="Text shown in the warning dialog after user clicks on the learn more link, notifying the user of potential security and privacy implications of using the device">
+        Manipulate privacy-related settings
+      </message>
 
       <!-- Multi-profiles intro dialog -->
       <message name="IDS_ASH_MULTIPROFILES_INTRO_HEADLINE" desc="Describes which feature multi-profiles intro dialog presents.">
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO.png.sha1
new file mode 100644
index 0000000..393cff7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO.png.sha1
@@ -0,0 +1 @@
+b57a43652610329c02562ec19ced8c3591ca42c6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_1.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_1.png.sha1
new file mode 100644
index 0000000..393cff7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_1.png.sha1
@@ -0,0 +1 @@
+b57a43652610329c02562ec19ced8c3591ca42c6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_2.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_2.png.sha1
new file mode 100644
index 0000000..393cff7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_2.png.sha1
@@ -0,0 +1 @@
+b57a43652610329c02562ec19ced8c3591ca42c6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_3.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_3.png.sha1
new file mode 100644
index 0000000..393cff7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_3.png.sha1
@@ -0,0 +1 @@
+b57a43652610329c02562ec19ced8c3591ca42c6
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_4.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_4.png.sha1
new file mode 100644
index 0000000..393cff7
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_4.png.sha1
@@ -0,0 +1 @@
+b57a43652610329c02562ec19ced8c3591ca42c6
\ No newline at end of file
diff --git a/ash/assistant/assistant_interaction_controller.cc b/ash/assistant/assistant_interaction_controller.cc
index 164c92d1..3a6dedf 100644
--- a/ash/assistant/assistant_interaction_controller.cc
+++ b/ash/assistant/assistant_interaction_controller.cc
@@ -189,6 +189,14 @@
   assistant_interaction_model_.SetInteractionState(InteractionState::kInactive);
   assistant_interaction_model_.SetMicState(MicState::kClosed);
 
+  // If the interaction was finished due to mic timeout, we only want to clear
+  // the pending query/response state for that interaction.
+  if (resolution == AssistantInteractionResolution::kMicTimeout) {
+    assistant_interaction_model_.ClearPendingQuery();
+    assistant_interaction_model_.ClearPendingResponse();
+    return;
+  }
+
   // The interaction has finished, so we finalize the pending response if it
   // hasn't already been finalized.
   if (assistant_interaction_model_.pending_response())
@@ -202,6 +210,13 @@
     return;
   }
 
+  // If this occurs, the server has broken our response ordering agreement. We
+  // should not crash but we cannot handle the response so we ignore it.
+  if (!assistant_interaction_model_.pending_response()) {
+    NOTREACHED();
+    return;
+  }
+
   assistant_interaction_model_.pending_response()->AddUiElement(
       std::make_unique<AssistantCardElement>(response));
 }
@@ -228,6 +243,13 @@
     return;
   }
 
+  // If this occurs, the server has broken our response ordering agreement. We
+  // should not crash but we cannot handle the response so we ignore it.
+  if (!assistant_interaction_model_.pending_response()) {
+    NOTREACHED();
+    return;
+  }
+
   assistant_interaction_model_.pending_response()->AddSuggestions(
       std::move(response));
 }
@@ -239,6 +261,13 @@
     return;
   }
 
+  // If this occurs, the server has broken our response ordering agreement. We
+  // should not crash but we cannot handle the response so we ignore it.
+  if (!assistant_interaction_model_.pending_response()) {
+    NOTREACHED();
+    return;
+  }
+
   assistant_interaction_model_.pending_response()->AddUiElement(
       std::make_unique<AssistantTextElement>(response));
 }
diff --git a/ash/assistant/ui/main_stage/assistant_header_view.cc b/ash/assistant/ui/main_stage/assistant_header_view.cc
index 60d1694..ffef4dd 100644
--- a/ash/assistant/ui/main_stage/assistant_header_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_header_view.cc
@@ -14,8 +14,12 @@
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/logo_view/base_logo_view.h"
 #include "ash/assistant/ui/main_stage/assistant_progress_indicator.h"
+#include "ash/assistant/util/animation_util.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "base/time/time.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/layer_animation_sequence.h"
+#include "ui/compositor/layer_animator.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/box_layout.h"
@@ -28,6 +32,18 @@
 constexpr int kIconSizeDip = 24;
 constexpr int kInitialHeightDip = 72;
 
+// Molecule icon animation.
+constexpr base::TimeDelta kMoleculeIconAnimationTranslationDuration =
+    base::TimeDelta::FromMilliseconds(333);
+constexpr base::TimeDelta kMoleculeIconAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(50);
+constexpr base::TimeDelta kMoleculeIconAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(83);
+constexpr base::TimeDelta kMoleculeIconAnimationFadeOutDelay =
+    base::TimeDelta::FromMilliseconds(33);
+constexpr base::TimeDelta kMoleculeIconAnimationFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(83);
+
 }  // namespace
 
 AssistantHeaderView::AssistantHeaderView(
@@ -56,11 +72,12 @@
 }
 
 void AssistantHeaderView::ChildVisibilityChanged(views::View* child) {
-  layout_manager_->set_cross_axis_alignment(
-      greeting_label_->visible()
-          ? views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER
-          : views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_START);
-
+  if (!assistant::ui::kIsMotionSpecEnabled) {
+    layout_manager_->set_cross_axis_alignment(
+        greeting_label_->visible()
+            ? views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER
+            : views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_START);
+  }
   PreferredSizeChanged();
 }
 
@@ -72,11 +89,16 @@
       views::BoxLayout::CrossAxisAlignment::CROSS_AXIS_ALIGNMENT_CENTER);
 
   // Molecule icon.
-  BaseLogoView* molecule_icon = BaseLogoView::Create();
-  molecule_icon->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
-  molecule_icon->SetState(BaseLogoView::State::kMoleculeWavy,
-                          /*animate=*/false);
-  AddChildView(molecule_icon);
+  molecule_icon_ = BaseLogoView::Create();
+  molecule_icon_->SetPreferredSize(gfx::Size(kIconSizeDip, kIconSizeDip));
+  molecule_icon_->SetState(BaseLogoView::State::kMoleculeWavy,
+                           /*animate=*/false);
+
+  // The molecule icon will be animated on its own layer.
+  molecule_icon_->SetPaintToLayer();
+  molecule_icon_->layer()->SetFillsBoundsOpaquely(false);
+
+  AddChildView(molecule_icon_);
 
   // Greeting label.
   greeting_label_ = new views::Label(
@@ -109,14 +131,55 @@
 
 void AssistantHeaderView::OnResponseChanged(const AssistantResponse& response) {
   progress_indicator_->SetVisible(false);
+
+  // We only handle the first response when animating the molecule icon. For
+  // all subsequent responses the molecule icon remains unchanged.
+  if (!is_first_response_)
+    return;
+
+  is_first_response_ = false;
+
+  if (!assistant::ui::kIsMotionSpecEnabled)
+    return;
+
+  using namespace assistant::util;
+
+  // The molecule icon will be animated from the center of its parent, to the
+  // left hand side.
+  gfx::Transform transform;
+  transform.Translate(-(width() - molecule_icon_->width()) / 2, 0);
+
+  // Animate the molecule icon.
+  molecule_icon_->layer()->GetAnimator()->StartTogether(
+      {// Animate the translation.
+       CreateLayerAnimationSequence(CreateTransformElement(
+           transform, kMoleculeIconAnimationTranslationDuration)),
+       // Animate the opacity.
+       CreateLayerAnimationSequence(
+           // Pause...
+           ui::LayerAnimationElement::CreatePauseElement(
+               ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+               kMoleculeIconAnimationFadeOutDelay),
+           // ...then fade out...
+           CreateOpacityElement(0.f, kMoleculeIconAnimationFadeOutDuration),
+           // ...hold...
+           ui::LayerAnimationElement::CreatePauseElement(
+               ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+               kMoleculeIconAnimationFadeInDelay),
+           // ...and fade back in.
+           CreateOpacityElement(1.f, kMoleculeIconAnimationFadeInDuration))});
 }
 
 void AssistantHeaderView::OnUiVisibilityChanged(bool visible,
                                                 AssistantSource source) {
+  // Only when the Assistant UI is being hidden do we need to restore the
+  // initial state for the next session.
   if (visible)
     return;
 
-  // When Assistant UI is being hidden, we need to restore default view state.
+  is_first_response_ = true;
+
+  molecule_icon_->layer()->SetTransform(gfx::Transform());
   greeting_label_->SetVisible(true);
   progress_indicator_->SetVisible(false);
 }
diff --git a/ash/assistant/ui/main_stage/assistant_header_view.h b/ash/assistant/ui/main_stage/assistant_header_view.h
index dc7fd19..172823ab 100644
--- a/ash/assistant/ui/main_stage/assistant_header_view.h
+++ b/ash/assistant/ui/main_stage/assistant_header_view.h
@@ -19,6 +19,7 @@
 
 class AssistantController;
 class AssistantProgressIndicator;
+class BaseLogoView;
 
 // AssistantHeaderView is the child of UiElementContainerView which provides
 // the Assistant icon. On first launch, it also displays a greeting to the user.
@@ -47,9 +48,14 @@
   AssistantController* const assistant_controller_;  // Owned by Shell.
 
   views::BoxLayout* layout_manager_;  // Owned by view hierarchy.
+  BaseLogoView* molecule_icon_;       // Owned by view hierarchy.
   views::Label* greeting_label_;      // Owned by view hierarchy.
   AssistantProgressIndicator* progress_indicator_;  // Owned by view hierarchy.
 
+  // True if this is the first query response received for the current Assistant
+  // UI session, false otherwise.
+  bool is_first_response_ = true;
+
   DISALLOW_COPY_AND_ASSIGN(AssistantHeaderView);
 };
 
diff --git a/ash/frame/custom_frame_header.cc b/ash/frame/custom_frame_header.cc
index feea8e5..136c045 100644
--- a/ash/frame/custom_frame_header.cc
+++ b/ash/frame/custom_frame_header.cc
@@ -91,7 +91,8 @@
     canvas->DrawColor(opaque_background_color);
     if (!frame_image.isNull()) {
       canvas->TileImageInt(frame_image, image_inset_x, 0, 0, 0, bounds.width(),
-                           bounds.height());
+                           bounds.height(), 1.0f, SkShader::kRepeat_TileMode,
+                           SkShader::kMirror_TileMode);
     }
     if (!frame_overlay_image.isNull())
       canvas->DrawImageInt(frame_overlay_image, 0, 0);
diff --git a/ash/frame/custom_frame_view_ash_unittest.cc b/ash/frame/custom_frame_view_ash_unittest.cc
index d7c5f140..8de42a08 100644
--- a/ash/frame/custom_frame_view_ash_unittest.cc
+++ b/ash/frame/custom_frame_view_ash_unittest.cc
@@ -32,6 +32,7 @@
 #include "ui/aura/window_targeter.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/test_accelerator_target.h"
+#include "ui/compositor/test/draw_waiter_for_test.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/image/image_skia.h"
@@ -139,6 +140,31 @@
             delegate->custom_frame_view()->GetHeaderView()->height());
 }
 
+// Regression test for https://crbug.com/839955
+TEST_F(CustomFrameViewAshTest, ActiveStateOfButtonMatchesWidget) {
+  CustomFrameTestWidgetDelegate* delegate = new CustomFrameTestWidgetDelegate;
+  std::unique_ptr<views::Widget> widget = CreateTestWidget(delegate);
+  FrameCaptionButtonContainerView::TestApi test_api(
+      delegate->custom_frame_view()
+          ->GetHeaderView()
+          ->caption_button_container());
+
+  widget->Show();
+  EXPECT_TRUE(widget->IsActive());
+  // The paint state doesn't change till the next paint.
+  ui::DrawWaiterForTest::WaitForCommit(widget->GetLayer()->GetCompositor());
+  EXPECT_TRUE(test_api.size_button()->paint_as_active());
+
+  // Activate a different widget so the original one loses activation.
+  std::unique_ptr<views::Widget> widget2 =
+      CreateTestWidget(new CustomFrameTestWidgetDelegate);
+  widget2->Show();
+  ui::DrawWaiterForTest::WaitForCommit(widget->GetLayer()->GetCompositor());
+
+  EXPECT_FALSE(widget->IsActive());
+  EXPECT_FALSE(test_api.size_button()->paint_as_active());
+}
+
 // Verify that CustomFrameViewAsh returns the correct minimum and maximum frame
 // sizes when the client view does not specify any size constraints.
 TEST_F(CustomFrameViewAshTest, NoSizeConstraints) {
diff --git a/ash/login/login_screen_controller.cc b/ash/login/login_screen_controller.cc
index 7f52f9a..38d36f18 100644
--- a/ash/login/login_screen_controller.cc
+++ b/ash/login/login_screen_controller.cc
@@ -440,6 +440,10 @@
   login_screen_client_->ShowResetScreen();
 }
 
+void LoginScreenController::ShowAccountAccessHelpApp() {
+  login_screen_client_->ShowAccountAccessHelpApp();
+}
+
 void LoginScreenController::DoAuthenticateUser(const AccountId& account_id,
                                                const std::string& password,
                                                bool authenticated_by_pin,
diff --git a/ash/login/login_screen_controller.h b/ash/login/login_screen_controller.h
index 3aa7d02..4e70e5e 100644
--- a/ash/login/login_screen_controller.h
+++ b/ash/login/login_screen_controller.h
@@ -85,6 +85,7 @@
   void LaunchKioskApp(const std::string& app_id);
   void LaunchArcKioskApp(const AccountId& account_id);
   void ShowResetScreen();
+  void ShowAccountAccessHelpApp();
 
   // Add or remove an observer.
   void AddObserver(LoginScreenControllerObserver* observer);
diff --git a/ash/login/mock_login_screen_client.h b/ash/login/mock_login_screen_client.h
index ad7594b..a2a9097 100644
--- a/ash/login/mock_login_screen_client.h
+++ b/ash/login/mock_login_screen_client.h
@@ -71,6 +71,7 @@
   MOCK_METHOD1(LaunchKioskApp, void(const std::string& app_id));
   MOCK_METHOD1(LaunchArcKioskApp, void(const AccountId& account_id));
   MOCK_METHOD0(ShowResetScreen, void());
+  MOCK_METHOD0(ShowAccountAccessHelpApp, void());
 
  private:
   bool authenticate_user_callback_result_ = true;
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index d05f793e..2fc8ba1 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -128,7 +128,7 @@
   }
 
   void ButtonPressed(Button* sender, const ui::Event& event) override {
-    // TODO(qnnguyen): Launch the help app for signin trouble.
+    Shell::Get()->login_screen_controller()->ShowAccountAccessHelpApp();
   }
 };
 
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index 866042a7..df608a81 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -14,6 +14,7 @@
 #include "ash/login/ui/login_button.h"
 #include "ash/login/ui/login_menu_view.h"
 #include "ash/login/ui/login_user_view.h"
+#include "ash/login/ui/public_account_warning_dialog.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -25,7 +26,6 @@
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/styled_label.h"
 #include "ui/views/controls/styled_label_listener.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
@@ -217,9 +217,10 @@
                       public views::ButtonListener,
                       public views::StyledLabelListener {
  public:
-  RightPaneView()
+  explicit RightPaneView(const base::RepeatingClosure& on_learn_more_tapped)
       : language_menu_(std::make_unique<LoginBubble>()),
         keyboard_menu_(std::make_unique<LoginBubble>()),
+        on_learn_more_tapped_(on_learn_more_tapped),
         weak_factory_(this) {
     SetPreferredSize(
         gfx::Size(kExpandedViewWidthDp / 2, kExpandedViewHeightDp));
@@ -238,21 +239,22 @@
     size_t offset;
     const base::string16 text = l10n_util::GetStringFUTF16(
         IDS_ASH_LOGIN_PUBLIC_ACCOUNT_SIGNOUT_REMINDER, link, &offset);
-    views::StyledLabel* bottom_label = new views::StyledLabel(text, this);
+    learn_more_label_ = new views::StyledLabel(text, this);
 
     views::StyledLabel::RangeStyleInfo style;
-    style.custom_font = bottom_label->GetDefaultFontList().Derive(
+    style.custom_font = learn_more_label_->GetDefaultFontList().Derive(
         0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL);
     style.override_color = SK_ColorWHITE;
-    bottom_label->AddStyleRange(gfx::Range(0, offset), style);
+    learn_more_label_->AddStyleRange(gfx::Range(0, offset), style);
 
     views::StyledLabel::RangeStyleInfo link_style =
         views::StyledLabel::RangeStyleInfo::CreateForLink();
     link_style.override_color = kPublicSessionBlueColor;
-    bottom_label->AddStyleRange(gfx::Range(offset, offset + link.length()),
-                                link_style);
-    bottom_label->set_auto_color_readability_enabled(false);
-    labels_view_->AddChildView(bottom_label);
+    learn_more_label_->AddStyleRange(gfx::Range(offset, offset + link.length()),
+                                     link_style);
+    learn_more_label_->set_auto_color_readability_enabled(false);
+
+    labels_view_->AddChildView(learn_more_label_);
 
     // Create button to show/hide advanced view.
     advanced_view_button_ = new SelectionButtonView(
@@ -394,7 +396,7 @@
   void StyledLabelLinkClicked(views::StyledLabel* label,
                               const gfx::Range& range,
                               int event_flags) override {
-    NOTIMPLEMENTED();
+    on_learn_more_tapped_.Run();
   }
 
   void UpdateForUser(const mojom::LoginUserInfoPtr& user) {
@@ -494,11 +496,9 @@
     language_changed_by_user_ = false;
   }
 
-  SelectionButtonView* advanced_view_button() { return advanced_view_button_; }
-  ArrowButtonView* submit_button() { return submit_button_; }
-  views::View* advanced_view() { return advanced_view_; }
-
  private:
+  friend class LoginExpandedPublicAccountView::TestApi;
+
   bool show_advanced_view_ = false;
   mojom::LoginUserInfoPtr current_user_;
 
@@ -508,6 +508,7 @@
   SelectionButtonView* language_selection_ = nullptr;
   SelectionButtonView* keyboard_selection_ = nullptr;
   ArrowButtonView* submit_button_ = nullptr;
+  views::StyledLabel* learn_more_label_ = nullptr;
 
   std::unique_ptr<LoginBubble> language_menu_;
   std::unique_ptr<LoginBubble> keyboard_menu_;
@@ -525,6 +526,8 @@
   bool show_advanced_changed_by_user_ = false;
   bool language_changed_by_user_ = false;
 
+  base::RepeatingClosure on_learn_more_tapped_;
+
   base::WeakPtrFactory<RightPaneView> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RightPaneView);
@@ -537,21 +540,32 @@
 LoginExpandedPublicAccountView::TestApi::~TestApi() = default;
 
 views::View* LoginExpandedPublicAccountView::TestApi::advanced_view_button() {
-  return view_->right_pane_->advanced_view_button();
+  return view_->right_pane_->advanced_view_button_;
 }
 
 ArrowButtonView* LoginExpandedPublicAccountView::TestApi::submit_button() {
-  return view_->right_pane_->submit_button();
+  return view_->right_pane_->submit_button_;
 }
 
 views::View* LoginExpandedPublicAccountView::TestApi::advanced_view() {
-  return view_->right_pane_->advanced_view();
+  return view_->right_pane_->advanced_view_;
+}
+
+PublicAccountWarningDialog*
+LoginExpandedPublicAccountView::TestApi::warning_dialog() {
+  return view_->warning_dialog_;
+}
+
+views::StyledLabel*
+LoginExpandedPublicAccountView::TestApi::learn_more_label() {
+  return view_->right_pane_->learn_more_label_;
 }
 
 LoginExpandedPublicAccountView::LoginExpandedPublicAccountView(
     const OnPublicSessionViewDismissed& on_dismissed)
     : NonAccessibleView(kLoginExpandedPublicAccountViewClassName),
-      on_dismissed_(on_dismissed) {
+      on_dismissed_(on_dismissed),
+      weak_factory_(this) {
   Shell::Get()->AddPreTargetHandler(this);
   SetLayoutManager(
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
@@ -578,7 +592,9 @@
   left_pane->SetBorder(
       views::CreateSolidSidedBorder(0, 0, 0, kBorderThicknessDp, kBorderColor));
 
-  right_pane_ = new RightPaneView();
+  right_pane_ = new RightPaneView(
+      base::BindRepeating(&LoginExpandedPublicAccountView::ShowWarningDialog,
+                          base::Unretained(this)));
   AddChildView(right_pane_);
 }
 
@@ -591,6 +607,10 @@
   if (!visible())
     return;
 
+  // Keep this view to be visible until warning dialog is dismissed.
+  if (warning_dialog_ && warning_dialog_->IsVisible())
+    return;
+
   if (GetBoundsInScreen().Contains(event->root_location()))
     return;
 
@@ -631,6 +651,16 @@
   on_dismissed_.Run();
 }
 
+void LoginExpandedPublicAccountView::ShowWarningDialog() {
+  DCHECK(!warning_dialog_);
+  warning_dialog_ = new PublicAccountWarningDialog(weak_factory_.GetWeakPtr());
+  warning_dialog_->Show();
+}
+
+void LoginExpandedPublicAccountView::OnWarningDialogClosed() {
+  warning_dialog_ = nullptr;
+}
+
 void LoginExpandedPublicAccountView::OnPaint(gfx::Canvas* canvas) {
   views::View::OnPaint(canvas);
 
@@ -657,6 +687,10 @@
   if (!visible() || event->type() != ui::ET_KEY_PRESSED)
     return;
 
+  // Give warning dialog a chance to handle key event.
+  if (warning_dialog_ && warning_dialog_->IsVisible())
+    return;
+
   if (event->key_code() == ui::KeyboardCode::VKEY_ESCAPE) {
     Hide();
   }
diff --git a/ash/login/ui/login_expanded_public_account_view.h b/ash/login/ui/login_expanded_public_account_view.h
index f733dd6..87ce91d 100644
--- a/ash/login/ui/login_expanded_public_account_view.h
+++ b/ash/login/ui/login_expanded_public_account_view.h
@@ -9,6 +9,7 @@
 #include "ash/login/ui/non_accessible_view.h"
 #include "ash/public/interfaces/login_user_info.mojom.h"
 #include "ui/events/event_handler.h"
+#include "ui/views/controls/styled_label.h"
 #include "ui/views/view.h"
 
 namespace ash {
@@ -16,6 +17,7 @@
 class ArrowButtonView;
 class LoginUserView;
 class RightPaneView;
+class PublicAccountWarningDialog;
 
 // Implements an expanded view for the public acount user to select language
 // and keyboard options.
@@ -30,6 +32,8 @@
     views::View* advanced_view_button();
     ArrowButtonView* submit_button();
     views::View* advanced_view();
+    PublicAccountWarningDialog* warning_dialog();
+    views::StyledLabel* learn_more_label();
 
    private:
     LoginExpandedPublicAccountView* const view_;
@@ -44,6 +48,8 @@
   void UpdateForUser(const mojom::LoginUserInfoPtr& user);
   const mojom::LoginUserInfoPtr& current_user() const;
   void Hide();
+  void ShowWarningDialog();
+  void OnWarningDialogClosed();
 
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
@@ -57,6 +63,9 @@
   LoginUserView* user_view_ = nullptr;
   RightPaneView* right_pane_ = nullptr;
   OnPublicSessionViewDismissed on_dismissed_;
+  PublicAccountWarningDialog* warning_dialog_ = nullptr;
+
+  base::WeakPtrFactory<LoginExpandedPublicAccountView> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LoginExpandedPublicAccountView);
 };
diff --git a/ash/login/ui/login_expanded_public_account_view_unittest.cc b/ash/login/ui/login_expanded_public_account_view_unittest.cc
index 265c696a..a46048d 100644
--- a/ash/login/ui/login_expanded_public_account_view_unittest.cc
+++ b/ash/login/ui/login_expanded_public_account_view_unittest.cc
@@ -5,6 +5,7 @@
 #include "ash/login/ui/login_expanded_public_account_view.h"
 #include "ash/login/ui/login_test_base.h"
 #include "ash/login/ui/login_test_utils.h"
+#include "ash/login/ui/public_account_warning_dialog.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/test/event_generator.h"
 #include "ui/views/layout/box_layout.h"
@@ -29,21 +30,25 @@
     LoginTestBase::SetUp();
 
     user_ = CreatePublicAccountUser("user@domain.com");
-    view_ = new LoginExpandedPublicAccountView(base::DoNothing());
-    view_->UpdateForUser(user_);
+    public_account_ = new LoginExpandedPublicAccountView(base::DoNothing());
+    public_account_->UpdateForUser(user_);
+
+    other_view_ = new views::View();
 
     container_ = new views::View();
     container_->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
-    container_->AddChildView(view_);
+    container_->AddChildView(public_account_);
+    container_->AddChildView(other_view_);
     SetWidget(CreateWidgetWithContent(container_));
   }
 
   mojom::LoginUserInfoPtr user_;
+
   // Owned by test widget view hierarchy.
   views::View* container_ = nullptr;
-  // Owned by test widget view hierarchy
-  LoginExpandedPublicAccountView* view_ = nullptr;
+  LoginExpandedPublicAccountView* public_account_ = nullptr;
+  views::View* other_view_ = nullptr;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoginExpandedPublicAccountViewTest);
@@ -53,22 +58,22 @@
 
 // Verifies toggle advanced view will update the layout correctly.
 TEST_F(LoginExpandedPublicAccountViewTest, ToggleAdvancedView) {
-  view_->SizeToPreferredSize();
-  EXPECT_EQ(view_->width(), kBubbleTotalWidthDp);
-  EXPECT_EQ(view_->height(), kBubbleTotalHeightDp);
+  public_account_->SizeToPreferredSize();
+  EXPECT_EQ(public_account_->width(), kBubbleTotalWidthDp);
+  EXPECT_EQ(public_account_->height(), kBubbleTotalHeightDp);
 
-  LoginExpandedPublicAccountView::TestApi test_api(view_);
+  LoginExpandedPublicAccountView::TestApi test_api(public_account_);
   EXPECT_FALSE(user_->public_account_info->show_advanced_view);
   EXPECT_FALSE(test_api.advanced_view()->visible());
 
   // Toggle show_advanced_view.
   user_->public_account_info->show_advanced_view = true;
-  view_->UpdateForUser(user_);
+  public_account_->UpdateForUser(user_);
 
   // Advanced view is shown and the overall size does not change.
   EXPECT_TRUE(test_api.advanced_view()->visible());
-  EXPECT_EQ(view_->width(), kBubbleTotalWidthDp);
-  EXPECT_EQ(view_->height(), kBubbleTotalHeightDp);
+  EXPECT_EQ(public_account_->width(), kBubbleTotalWidthDp);
+  EXPECT_EQ(public_account_->height(), kBubbleTotalHeightDp);
 
   // Click on the show advanced button.
   ui::test::EventGenerator* generator = GetEventGenerator();
@@ -78,8 +83,44 @@
 
   // Advanced view is hidden and the overall size does not change.
   EXPECT_FALSE(test_api.advanced_view()->visible());
-  EXPECT_EQ(view_->width(), kBubbleTotalWidthDp);
-  EXPECT_EQ(view_->height(), kBubbleTotalHeightDp);
+  EXPECT_EQ(public_account_->width(), kBubbleTotalWidthDp);
+  EXPECT_EQ(public_account_->height(), kBubbleTotalHeightDp);
+}
+
+// Verifies warning dialog shows up correctly.
+TEST_F(LoginExpandedPublicAccountViewTest, ShowWarningDialog) {
+  LoginExpandedPublicAccountView::TestApi test_api(public_account_);
+  views::StyledLabel::TestApi styled_label_test(test_api.learn_more_label());
+  EXPECT_EQ(test_api.warning_dialog(), nullptr);
+  EXPECT_EQ(styled_label_test.link_targets().size(), 1U);
+
+  // Tap on the learn more link.
+  views::View* link_view = styled_label_test.link_targets().begin()->first;
+  GetEventGenerator()->MoveMouseTo(
+      link_view->GetBoundsInScreen().CenterPoint());
+  GetEventGenerator()->ClickLeftButton();
+  EXPECT_NE(test_api.warning_dialog(), nullptr);
+  EXPECT_TRUE(test_api.warning_dialog()->IsVisible());
+
+  // When warning dialog is shown, tap outside of public account expanded view
+  // should not hide it.
+  GetEventGenerator()->MoveMouseTo(
+      other_view_->GetBoundsInScreen().CenterPoint());
+  GetEventGenerator()->ClickLeftButton();
+  EXPECT_TRUE(public_account_->visible());
+  EXPECT_NE(test_api.warning_dialog(), nullptr);
+  EXPECT_TRUE(test_api.warning_dialog()->IsVisible());
+
+  // If the warning dialog is shown, escape key should close the waring dialog,
+  // but not the public account view.
+  GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_ESCAPE, 0);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(test_api.warning_dialog(), nullptr);
+  EXPECT_TRUE(public_account_->visible());
+
+  // Press escape again should hide the public account expanded view.
+  GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_ESCAPE, 0);
+  EXPECT_FALSE(public_account_->visible());
 }
 
 }  // namespace ash
diff --git a/ash/login/ui/public_account_warning_dialog.cc b/ash/login/ui/public_account_warning_dialog.cc
new file mode 100644
index 0000000..fde4c9c6
--- /dev/null
+++ b/ash/login/ui/public_account_warning_dialog.cc
@@ -0,0 +1,142 @@
+// Copyright 2018 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 "ash/login/ui/public_account_warning_dialog.h"
+
+#include "ash/login/ui/login_expanded_public_account_view.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/border.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kDialogWidthDp = 400;
+constexpr int kDialogHeightDp = 145;
+constexpr int kDialogContentMarginDp = 13;
+
+constexpr int kBulletRadiusDp = 2;
+constexpr int kBulletContainerSizeDp = 22;
+
+constexpr int kLineHeightDp = 15;
+constexpr int kBetweenLabelPaddingDp = 4;
+
+class BulletView : public views::View {
+ public:
+  explicit BulletView(SkColor color, int radius)
+      : color_(color), radius_(radius) {}
+
+  ~BulletView() override = default;
+
+  // views::View:
+  void OnPaint(gfx::Canvas* canvas) override {
+    View::OnPaint(canvas);
+
+    SkPath path;
+    path.addCircle(GetLocalBounds().CenterPoint().x(),
+                   GetLocalBounds().CenterPoint().y(), radius_);
+    cc::PaintFlags flags;
+    flags.setStyle(cc::PaintFlags::kStrokeAndFill_Style);
+    flags.setColor(color_);
+    flags.setAntiAlias(true);
+
+    canvas->DrawPath(path, flags);
+  }
+
+ private:
+  SkColor color_;
+  int radius_;
+
+  DISALLOW_COPY_AND_ASSIGN(BulletView);
+};
+
+}  // namespace
+
+PublicAccountWarningDialog::PublicAccountWarningDialog(
+    base::WeakPtr<LoginExpandedPublicAccountView> controller)
+    : controller_(controller) {
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::kVertical, gfx::Insets(), kBetweenLabelPaddingDp));
+  SetBorder(views::CreateEmptyBorder(gfx::Insets(kDialogContentMarginDp)));
+
+  auto add_bulleted_label = [&](const base::string16& text) {
+    auto* container = new views::View();
+    container->SetLayoutManager(
+        std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
+
+    auto* label = new views::Label(text);
+    label->SetMultiLine(true);
+    label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    label->SetLineHeight(kLineHeightDp);
+    label->SetFontList(views::Label::GetDefaultFontList().Derive(
+        1, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::NORMAL));
+    label->SetEnabledColor(SK_ColorGRAY);
+
+    auto* bullet_view = new BulletView(label->enabled_color(), kBulletRadiusDp);
+    bullet_view->SetPreferredSize(
+        gfx::Size(kBulletContainerSizeDp, kBulletContainerSizeDp));
+
+    container->AddChildView(bullet_view);
+    container->AddChildView(label);
+    AddChildView(container);
+  };
+
+  add_bulleted_label(l10n_util::GetStringUTF16(
+      IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_1));
+  add_bulleted_label(l10n_util::GetStringUTF16(
+      IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_2));
+  add_bulleted_label(l10n_util::GetStringUTF16(
+      IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_3));
+  add_bulleted_label(l10n_util::GetStringUTF16(
+      IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO_ITEM_4));
+
+  // Widget will take the owership of this view.
+  views::DialogDelegate::CreateDialogWidget(
+      this, nullptr, controller->GetWidget()->GetNativeView());
+}
+
+PublicAccountWarningDialog::~PublicAccountWarningDialog() {
+  if (controller_)
+    controller_->OnWarningDialogClosed();
+}
+
+bool PublicAccountWarningDialog::IsVisible() {
+  return GetWidget() && GetWidget()->IsVisible();
+}
+
+void PublicAccountWarningDialog::Show() {
+  if (GetWidget())
+    GetWidget()->Show();
+}
+
+int PublicAccountWarningDialog::GetDialogButtons() const {
+  return ui::DIALOG_BUTTON_NONE;
+}
+
+void PublicAccountWarningDialog::AddedToWidget() {
+  std::unique_ptr<views::Label> title_label =
+      views::BubbleFrameView::CreateDefaultTitleLabel(l10n_util::GetStringUTF16(
+          IDS_ASH_LOGIN_PUBLIC_ACCOUNT_MONITORING_INFO));
+  title_label->SetFontList(title_label->font_list().Derive(
+      1, gfx::Font::NORMAL, gfx::Font::Weight::BOLD));
+  auto* frame_view = static_cast<views::BubbleFrameView*>(
+      GetWidget()->non_client_view()->frame_view());
+  frame_view->SetTitleView(std::move(title_label));
+}
+
+ui::ModalType PublicAccountWarningDialog::GetModalType() const {
+  return ui::MODAL_TYPE_SYSTEM;
+}
+
+gfx::Size PublicAccountWarningDialog::CalculatePreferredSize() const {
+  return gfx::Size(kDialogWidthDp, kDialogHeightDp);
+}
+
+}  // namespace ash
diff --git a/ash/login/ui/public_account_warning_dialog.h b/ash/login/ui/public_account_warning_dialog.h
new file mode 100644
index 0000000..25f7da9
--- /dev/null
+++ b/ash/login/ui/public_account_warning_dialog.h
@@ -0,0 +1,45 @@
+// Copyright 2018 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 ASH_LOGIN_UI_PUBLIC_ACCOUNT_WARNING_DIALOG_H_
+#define ASH_LOGIN_UI_PUBLIC_ACCOUNT_WARNING_DIALOG_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace ash {
+
+class LoginExpandedPublicAccountView;
+
+// Dialog for displaying public session warning. This is shown when a user
+// clicks on the learn more link on the pubic account expanded view.
+class ASH_EXPORT PublicAccountWarningDialog : public views::DialogDelegateView {
+ public:
+  PublicAccountWarningDialog(
+      base::WeakPtr<LoginExpandedPublicAccountView> controller);
+  ~PublicAccountWarningDialog() override;
+
+  bool IsVisible();
+  void Show();
+
+  // views::DialogDelegate:
+  int GetDialogButtons() const override;
+  void AddedToWidget() override;
+
+  // views::WidgetDelegate:
+  ui::ModalType GetModalType() const override;
+
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+
+ private:
+  base::WeakPtr<LoginExpandedPublicAccountView> controller_;
+
+  DISALLOW_COPY_AND_ASSIGN(PublicAccountWarningDialog);
+};
+
+}  // namespace ash
+
+#endif  // ASH_LOGIN_UI_PUBLIC_ACCOUNT_WARNING_DIALOG_H_
diff --git a/ash/public/interfaces/login_screen.mojom b/ash/public/interfaces/login_screen.mojom
index 2b9c593..008256de 100644
--- a/ash/public/interfaces/login_screen.mojom
+++ b/ash/public/interfaces/login_screen.mojom
@@ -237,4 +237,7 @@
 
   // Show the powerwash (device reset) dialog.
   ShowResetScreen();
+
+  // Show the help app for when users have trouble signing in to their account.
+  ShowAccountAccessHelpApp();
 };
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index 7130817..51b2ca96 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -10,6 +10,7 @@
 
   icons = [
     "assistant.icon",
+    "auto_hide.icon",
     "captive_portal.icon",
     "check_circle.icon",
     "dictation_menu.icon",
@@ -101,6 +102,7 @@
     "shelf_overflow.icon",
     "shelf_overflow_horizontal_dots.icon",
     "shelf_overview.icon",
+    "shelf_position.icon",
     "shelf_shutdown_button.icon",
     "shelf_sign_out_button.icon",
     "shelf_unlock_button.icon",
@@ -214,6 +216,7 @@
     "unified_menu_volume_medium.icon",
     "unified_menu_volume_mute.icon",
     "unified_menu_vpn.icon",
+    "wallpaper.icon",
     "window_control_back.icon",
     "window_control_dezoom.icon",
     "window_control_menu.icon",
diff --git a/ash/resources/vector_icons/auto_hide.icon b/ash/resources/vector_icons/auto_hide.icon
new file mode 100644
index 0000000..f64612b
--- /dev/null
+++ b/ash/resources/vector_icons/auto_hide.icon
@@ -0,0 +1,45 @@
+// Copyright 2018 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 11, 8,
+R_V_LINE_TO, 2,
+H_LINE_TO, 9,
+V_LINE_TO, 8,
+R_H_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 2, 0,
+R_H_LINE_TO, 1,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, -1,
+V_LINE_TO, 8,
+CLOSE,
+MOVE_TO, 7, 8,
+R_V_LINE_TO, 2,
+H_LINE_TO, 5,
+V_LINE_TO, 8,
+R_H_LINE_TO, 2,
+CLOSE,
+MOVE_TO, 3, 8,
+R_V_LINE_TO, 2,
+H_LINE_TO, 2,
+V_LINE_TO, 8,
+R_H_LINE_TO, 1,
+CLOSE,
+MOVE_TO, 2, 0,
+R_H_LINE_TO, 12,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, 2,
+R_V_LINE_TO, 10,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, 2,
+H_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, -2,
+V_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, -2,
+CLOSE,
+R_MOVE_TO, 0, 2,
+R_V_LINE_TO, 10,
+R_H_LINE_TO, 12,
+V_LINE_TO, 2,
+H_LINE_TO, 2,
+CLOSE
diff --git a/ash/resources/vector_icons/shelf_position.icon b/ash/resources/vector_icons/shelf_position.icon
new file mode 100644
index 0000000..db0cc1c
--- /dev/null
+++ b/ash/resources/vector_icons/shelf_position.icon
@@ -0,0 +1,35 @@
+// Copyright 2018 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 6, 8,
+R_H_LINE_TO, 4,
+V_LINE_TO, 2,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 6,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 2,
+H_LINE_TO, 2,
+V_LINE_TO, 8,
+R_H_LINE_TO, 2,
+V_LINE_TO, 2,
+R_H_LINE_TO, 2,
+R_V_LINE_TO, 6,
+CLOSE,
+MOVE_TO, 2, 0,
+R_H_LINE_TO, 12,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, 2,
+R_V_LINE_TO, 10,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, 2,
+H_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, -2,
+V_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, -2,
+CLOSE,
+R_MOVE_TO, 0, 2,
+R_V_LINE_TO, 10,
+R_H_LINE_TO, 12,
+V_LINE_TO, 2,
+H_LINE_TO, 2,
+CLOSE
diff --git a/ash/resources/vector_icons/wallpaper.icon b/ash/resources/vector_icons/wallpaper.icon
new file mode 100644
index 0000000..434cd28
--- /dev/null
+++ b/ash/resources/vector_icons/wallpaper.icon
@@ -0,0 +1,28 @@
+// Copyright 2018 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 2, 0,
+R_H_LINE_TO, 12,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, 2,
+R_V_LINE_TO, 10,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, 2,
+H_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, -2, -2,
+V_LINE_TO, 2,
+R_ARC_TO, 2, 2, 0, 0, 1, 2, -2,
+CLOSE,
+R_MOVE_TO, 0, 2,
+R_V_LINE_TO, 10,
+R_H_LINE_TO, 12,
+V_LINE_TO, 2,
+H_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 2, 8,
+R_LINE_TO, 3, -6,
+R_LINE_TO, 2, 4,
+R_LINE_TO, 1, -1.5f,
+R_LINE_TO, 2, 3.5f,
+H_LINE_TO, 4,
+CLOSE
diff --git a/ash/shelf/shelf_context_menu_model.cc b/ash/shelf/shelf_context_menu_model.cc
index bb9e15b5..5837ee5a 100644
--- a/ash/shelf/shelf_context_menu_model.cc
+++ b/ash/shelf/shelf_context_menu_model.cc
@@ -14,6 +14,7 @@
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_prefs.h"
 #include "ash/public/cpp/shelf_types.h"
+#include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
@@ -26,6 +27,8 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/gfx/image/image.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/menu/menu_config.h"
 
 using l10n_util::GetStringUTF16;
 using SubmenuList = std::vector<std::unique_ptr<ui::MenuModel>>;
@@ -58,24 +61,41 @@
   const bool is_tablet_mode = Shell::Get()
                                   ->tablet_mode_controller()
                                   ->IsTabletModeWindowManagerEnabled();
+
+  // When touchable app context menus are enabled in tablet mode, auto hide and
+  // shelf alignment options are not shown.
+  const bool skip_clamshell_only_options =
+      features::IsTouchableAppContextMenuEnabled() && is_tablet_mode;
+
+  const views::MenuConfig& menu_config = views::MenuConfig::instance();
+
   // In fullscreen, the shelf is either hidden or auto-hidden, depending on
   // the type of fullscreen. Do not show the auto-hide menu item while in
   // fullscreen because it is confusing when the preference appears not to
   // apply.
-  if (CanUserModifyShelfAutoHide(prefs) && !IsFullScreenMode(display_id)) {
+  if (CanUserModifyShelfAutoHide(prefs) && !IsFullScreenMode(display_id) &&
+      !skip_clamshell_only_options) {
     mojom::MenuItemPtr auto_hide(mojom::MenuItem::New());
-    auto_hide->type = ui::MenuModel::TYPE_CHECK;
+    if (features::IsTouchableAppContextMenuEnabled()) {
+      auto_hide->type = ui::MenuModel::TYPE_COMMAND;
+      auto_hide->image =
+          gfx::CreateVectorIcon(kAutoHideIcon, menu_config.touchable_icon_size,
+                                menu_config.touchable_icon_color);
+    } else {
+      auto_hide->type = ui::MenuModel::TYPE_CHECK;
+      auto_hide->checked = GetShelfAutoHideBehaviorPref(prefs, display_id) ==
+                           SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
+    }
     auto_hide->command_id = ShelfContextMenuModel::MENU_AUTO_HIDE;
     auto_hide->label = GetStringUTF16(IDS_ASH_SHELF_CONTEXT_MENU_AUTO_HIDE);
-    auto_hide->checked = GetShelfAutoHideBehaviorPref(prefs, display_id) ==
-                         SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
     auto_hide->enabled = !is_tablet_mode;
     menu->push_back(std::move(auto_hide));
   }
 
   // Only allow shelf alignment modifications by the owner or user.
   LoginStatus status = Shell::Get()->session_controller()->login_status();
-  if (status == LoginStatus::USER || status == LoginStatus::OWNER) {
+  if ((status == LoginStatus::USER || status == LoginStatus::OWNER) &&
+      !skip_clamshell_only_options) {
     const ShelfAlignment alignment = GetShelfAlignmentPref(prefs, display_id);
     mojom::MenuItemPtr alignment_menu(mojom::MenuItem::New());
     alignment_menu->type = ui::MenuModel::TYPE_SUBMENU;
@@ -83,6 +103,11 @@
     alignment_menu->label = GetStringUTF16(IDS_ASH_SHELF_CONTEXT_MENU_POSITION);
     alignment_menu->submenu = MenuItemList();
     alignment_menu->enabled = !is_tablet_mode;
+    if (features::IsTouchableAppContextMenuEnabled()) {
+      alignment_menu->image = gfx::CreateVectorIcon(
+          kShelfPositionIcon, menu_config.touchable_icon_size,
+          menu_config.touchable_icon_color);
+    }
 
     mojom::MenuItemPtr left(mojom::MenuItem::New());
     left->type = ui::MenuModel::TYPE_RADIO;
@@ -117,6 +142,11 @@
     wallpaper->command_id = ShelfContextMenuModel::MENU_CHANGE_WALLPAPER;
     wallpaper->label = GetStringUTF16(IDS_AURA_SET_DESKTOP_WALLPAPER);
     wallpaper->enabled = true;
+    if (features::IsTouchableAppContextMenuEnabled()) {
+      wallpaper->image =
+          gfx::CreateVectorIcon(kWallpaperIcon, menu_config.touchable_icon_size,
+                                menu_config.touchable_icon_color);
+    }
     menu->push_back(std::move(wallpaper));
   }
 }
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index c285e4ba..fd901cd 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -496,11 +496,12 @@
 
 void ShelfLayoutManager::OnKeyboardAppearanceChanged(
     const keyboard::KeyboardStateDescriptor& state) {
+  // If displaced bounds changed, then change the work area too.
+  bool change_work_area = state.displaced_bounds != keyboard_displaced_bounds_;
+
   keyboard_occluded_bounds_ = state.occluded_bounds;
   keyboard_displaced_bounds_ = state.displaced_bounds;
 
-  // If there are displaced bounds, then change the work area.
-  bool change_work_area = !keyboard_displaced_bounds_.IsEmpty();
   LayoutShelfAndUpdateBounds(change_work_area);
 }
 
diff --git a/ash/shelf/shelf_layout_manager_unittest.cc b/ash/shelf/shelf_layout_manager_unittest.cc
index 7bc8ad4d..8ed95b9 100644
--- a/ash/shelf/shelf_layout_manager_unittest.cc
+++ b/ash/shelf/shelf_layout_manager_unittest.cc
@@ -2181,7 +2181,6 @@
   // Open keyboard in non-sticky mode.
   kb_controller->ShowKeyboard(false);
   NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());
-  layout_manager->LayoutShelf();
 
   // Shelf position should not be changed.
   EXPECT_EQ(orig_bounds, GetShelfWidget()->GetWindowBoundsInScreen());
@@ -2200,7 +2199,6 @@
   // Open keyboard in non-sticky mode.
   kb_controller->ShowKeyboard(false);
   NotifyKeyboardChanging(layout_manager, false, keyboard_bounds());
-  layout_manager->LayoutShelf();
 
   // Work area should not be changed.
   EXPECT_EQ(orig_work_area,
@@ -2208,7 +2206,6 @@
 
   kb_controller->HideKeyboardExplicitlyBySystem();
   NotifyKeyboardChanging(layout_manager, false, gfx::Rect());
-  layout_manager->LayoutShelf();
   EXPECT_EQ(orig_work_area,
             display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
 }
@@ -2224,11 +2221,18 @@
   // Open keyboard in sticky mode.
   kb_controller->ShowKeyboard(true);
   NotifyKeyboardChanging(layout_manager, true, keyboard_bounds());
-  layout_manager->LayoutShelf();
 
   // Work area should be changed.
   EXPECT_NE(orig_work_area,
             display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
+
+  // Hide the keyboard.
+  kb_controller->HideKeyboardByUser();
+  NotifyKeyboardChanging(layout_manager, true, gfx::Rect());
+
+  // Work area should be reset to its original value.
+  EXPECT_EQ(orig_work_area,
+            display::Screen::GetScreen()->GetPrimaryDisplay().work_area());
 }
 
 }  // namespace ash
diff --git a/ash/strings/ash_strings_am.xtb b/ash/strings/ash_strings_am.xtb
index f59dca33..6d4df82 100644
--- a/ash/strings/ash_strings_am.xtb
+++ b/ash/strings/ash_strings_am.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">በማያ ገጽ ላይ የቁልፍ ሰሌዳ ነቅቷል</translation>
 <translation id="1823873187264960516">ኤተርኔት፦ <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">ረዳት (በመጫን ላይ...)</translation>
+<translation id="1841545962859478868">የመሣሪያው አስተዳዳሪ የሚከተሉትን ሊከታተል ይችላል፦</translation>
 <translation id="1850504506766569011">Wi-Fi ጠፍቷል።</translation>
 <translation id="1864454756846565995">USB-C መሣሪያ (የኋላ ወደብ)</translation>
 <translation id="1882897271359938046">ወደ <ph name="DISPLAY_NAME" /> በማንጸባረቅ ላይ</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">በመያያዝ ላይ</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">በ<ph name="TIMEOUT_SECONDS" /> ውስጥ ወደ ቀድሞው ጥራት በመመለስ ላይ</translation>
+<translation id="7228479291753472782">ድር ጣቢያዎች እንደ ጂዮአካባቢ፣ ማይክሮፎን፣ ካሜራ፣ ወዘተ. ያሉ ባህሪያትን መጠቀም ይችሉ እንደሆነ የሚገልጹ ቅንብሮችን ይቆጣጠራል።</translation>
 <translation id="7256634071279256947">የኋላ ማይክሮፎን</translation>
 <translation id="726276584504105859">የተከፈለ ማያ ገጽን ለመጠቀም ወደዚህ ይጎትቱ</translation>
 <translation id="7348093485538360975">የታይታ የቁልፍ ሰሌዳ</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">መስኮትን ወደ ሌላ ዴስክቶፕ መውሰድ ያልተጠበቀ ባህሪን ሊያስከትል ይችላል። ተከትለው የሚመጡ ማሳወቂያዎችን፣ መስኮቶች እና መገናኛዎች በዴስክቶፖች መካከል ሊከፈሉ ይችላሉ።</translation>
 <translation id="7569509451529460200">ብሬይል እና ChromeVox ነቅተዋል</translation>
 <translation id="7593891976182323525">Search ወይም Shift</translation>
+<translation id="7604942372593434070">የአሰሳ እንቅስቃሴዎን ይድረሱ</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (ባለቤት)</translation>
 <translation id="7647488630410863958">የእርስዎን ማሳወቂያዎች ለማየት መሣሪያውን ይክፈቱ</translation>
 <translation id="7649070708921625228">እገዛ</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">ጠንካራ ሲግናል</translation>
 <translation id="7842569679327885685">ማስጠንቀቂያ፦ የሙከራ ባህሪ</translation>
 <translation id="7846634333498149051">የቁልፍ ሰሌዳ</translation>
+<translation id="790040513076446191">ከግላዊነት ጋር የተገናኙ ቅንብሮችን ይቆጣጠሩ</translation>
 <translation id="7904094684485781019">የዚህ መለያ አስተዳዳሪ ባለብዙ መለያ መግባትን ከልክሏል።</translation>
 <translation id="7933084174919150729">Google ረዳቱ ለዋናው መገለጫ ብቻ የሚገኝ ነው።</translation>
 <translation id="79341161159229895">መለያው በ<ph name="FIRST_PARENT_EMAIL" /> እና <ph name="SECOND_PARENT_EMAIL" /> ነው የሚቀናበረው</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">የግል አውታረ መረብ</translation>
 <translation id="8152119955266188852">የሙሉ ማያ ገጽ ማጉያውን አቋራጭ ተጭነዋል። ሊያበሩት ይፈልጋሉ?</translation>
 <translation id="8190698733819146287">ቋንቋዎችን እና ግብአቶችን አብጅ...</translation>
+<translation id="8191230140820435481">መተግበሪያዎችዎን፣ ቅጥያዎችዎን እና ገጽታዎችዎን ያቀናብራል</translation>
 <translation id="8261506727792406068">ሰርዝ</translation>
 <translation id="8297006494302853456">ደካማ</translation>
 <translation id="8308637677604853869">ቀዳሚ ምናሌ</translation>
diff --git a/ash/strings/ash_strings_ar.xtb b/ash/strings/ash_strings_ar.xtb
index 7d1fe40..8629e6d 100644
--- a/ash/strings/ash_strings_ar.xtb
+++ b/ash/strings/ash_strings_ar.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">تم تفعيل لوحة المفاتيح على الشاشة</translation>
 <translation id="1823873187264960516">الإيثرنت: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">المساعد (جارٍ التحميل...)</translation>
+<translation id="1841545962859478868">قد يراقب مشرف الجهاز ما يلي:</translation>
 <translation id="1850504506766569011">‏تم إيقاف تشغيل Wi-Fi.</translation>
 <translation id="1864454756846565995">‏جهاز USB-C (المنفذ الخلفي)</translation>
 <translation id="1882897271359938046">نسخ إلى <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">اتصال</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">سيتم الرجوع إلى درجة الدقة القديمة في غضون <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">معالجة الإعدادات التي تحدد ما إذا كان بإمكان مواقع الويب استخدام ميزات مثل المواقع الجغرافية والميكروفون والكاميرا، وغيرها</translation>
 <translation id="7256634071279256947">الميكروفون الخلفي</translation>
 <translation id="726276584504105859">السحب هنا لاستخدام وضع تقسيم الشاشة</translation>
 <translation id="7348093485538360975">لوحة المفاتيح على الشاشة</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">قد ينتج عن نقل النوافذ إلى سطح مكتب آخر سلوك غير متوقع. من الممكن أن يتم تقسيم مربعات الحوار والنوافذ والإشعارات المتتالية بين سطحَي المكتب.</translation>
 <translation id="7569509451529460200">‏تم تفعيل Braille وChromeVox</translation>
 <translation id="7593891976182323525">‏مفتاح البحث أو Shift</translation>
+<translation id="7604942372593434070">الدخول إلى نشاط التصفح</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (المالك)</translation>
 <translation id="7647488630410863958">إلغاء قفل الجهاز لعرض الإشعارات</translation>
 <translation id="7649070708921625228">مساعدة</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">إشارة قوية</translation>
 <translation id="7842569679327885685">تحذير: ميزة تجريبية</translation>
 <translation id="7846634333498149051">لوحة المفاتيح</translation>
+<translation id="790040513076446191">معالجة الإعدادات المتعلقة بالخصوصية</translation>
 <translation id="7904094684485781019">لقد حظر مشرف هذا الحساب إمكانية الدخول المتعدد.</translation>
 <translation id="7933084174919150729">‏لا يتوفر مساعد Google إلا للملف الشخصي الأساسي.</translation>
 <translation id="79341161159229895">يُدير <ph name="FIRST_PARENT_EMAIL" /> و<ph name="SECOND_PARENT_EMAIL" /> الحساب.</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">الشبكة الخاصة</translation>
 <translation id="8152119955266188852">لقد ضغطت على اختصار المُكبِّر بملء الشاشة. هل ترغب في تفعيله؟</translation>
 <translation id="8190698733819146287">تخصيص اللغات والإدخال...</translation>
+<translation id="8191230140820435481">إدارة التطبيقات والإضافات والمظاهر</translation>
 <translation id="8261506727792406068">حذف</translation>
 <translation id="8297006494302853456">ضعيفة</translation>
 <translation id="8308637677604853869">القائمة السابقة</translation>
diff --git a/ash/strings/ash_strings_bg.xtb b/ash/strings/ash_strings_bg.xtb
index 2c37268..8d54462 100644
--- a/ash/strings/ash_strings_bg.xtb
+++ b/ash/strings/ash_strings_bg.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Екранната клавиатура е активирана</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Асистент (зарежда се...)</translation>
+<translation id="1841545962859478868">Администраторът на устройството може да наблюдава следното:</translation>
 <translation id="1850504506766569011">Wi-Fi е изключен.</translation>
 <translation id="1864454756846565995">USB-C устройство (задният порт)</translation>
 <translation id="1882897271359938046">Дублира се на „<ph name="DISPLAY_NAME" />“</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Свързва се</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Старата разделителна способност ще се възстанови след <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Управление на настройките, които посочват дали уебсайтовете могат да използват функции като геолокация, микрофон, камера и др.</translation>
 <translation id="7256634071279256947">Заден микрофон</translation>
 <translation id="726276584504105859">Преместете тук с плъзгане, за да използвате режим за разделен екран</translation>
 <translation id="7348093485538360975">Екранна клавиатура</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Преместването на прозорци към друг работен плот може да предизвика неочаквано поведение. Последващите известия, прозорци и диалогови прозорци може да бъдат разделени между работните плотове.</translation>
 <translation id="7569509451529460200">Активирахте функцията за брайлово писмо и ChromeVox</translation>
 <translation id="7593891976182323525">„търсене“ или „Shift“</translation>
+<translation id="7604942372593434070">Достъп до активността ви при сърфиране</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (собственик)</translation>
 <translation id="7647488630410863958">Отключете устройството, за да прегледате известията си</translation>
 <translation id="7649070708921625228">Помощ</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">силен сигнал</translation>
 <translation id="7842569679327885685">Предупреждение: Експериментална функция</translation>
 <translation id="7846634333498149051">Клавиатура</translation>
+<translation id="790040513076446191">Манипулиране на свързаните с поверителността настройки</translation>
 <translation id="7904094684485781019">Администраторът на този профил е забранил централизирания вход.</translation>
 <translation id="7933084174919150729">Google Асистент е налице само за основния потребителски профил.</translation>
 <translation id="79341161159229895">Профилът се управлява от <ph name="FIRST_PARENT_EMAIL" /> и <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Частна мрежа</translation>
 <translation id="8152119955266188852">Използвахте клавишната комбинация за лупата за увеличаване на целия екран. Искате ли да включите функцията?</translation>
 <translation id="8190698733819146287">Персонализиране на езиците и въвеждането...</translation>
+<translation id="8191230140820435481">Управление на вашите приложения, разширения и теми</translation>
 <translation id="8261506727792406068">Изтриване</translation>
 <translation id="8297006494302853456">Слаб</translation>
 <translation id="8308637677604853869">Предишно меню</translation>
diff --git a/ash/strings/ash_strings_bn.xtb b/ash/strings/ash_strings_bn.xtb
index a6724ae..e05b3d50 100644
--- a/ash/strings/ash_strings_bn.xtb
+++ b/ash/strings/ash_strings_bn.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">অন-স্ক্রিন কীবোর্ড সক্ষম করা রয়েছে</translation>
 <translation id="1823873187264960516">ইথারনেট: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">সহায়ক (লোড হচ্ছে...)</translation>
+<translation id="1841545962859478868">ডিভাইস প্রশাসক নিম্নল্লিখিত বিষয়গুলি পর্যবেক্ষণ করতে পারেন:</translation>
 <translation id="1850504506766569011">ওয়াই-ফাই বন্ধ আছে৷</translation>
 <translation id="1864454756846565995">USB-C ডিভাইস (পিছনের পোর্ট)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> তে প্রতিবিম্বিত হচ্ছে</translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">সংযুক্ত হচ্ছে</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> এ পুরানো রেসুলিউশানে ফেরানো হচ্ছে</translation>
+<translation id="7228479291753472782">ভৌগোলিক লোকেশন, মাইক্রোফোন, ক্যামেরা, ইত্যাদির মতো বৈশিষ্ট্যগুলিকে ওয়েবসাইটগুলি ব্যবহার করতে পারবে কিনা তা নির্দিষ্ট করতে সেটিংস নিয়ন্ত্রণ করে।</translation>
 <translation id="7256634071279256947">পেছনের মাইক্রোফোন</translation>
 <translation id="726276584504105859">বিভক্ত স্ক্রিন ব্যবহার করতে এখানে টেনে আনুন</translation>
 <translation id="7348093485538360975">অন-স্ক্রীণ কীবোর্ড</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">কোনও উইন্ডো অন্য ডেস্কটপে সরানো হলে সেটির আচরণে অপ্রত্যাশিত পরিবর্তন হতে পারে। পরবর্তী বিজ্ঞপ্তি, উইন্ডো এবং ডায়ালগ দুটি ডেস্কটপ মিলিয়ে দেখানো হতে পারে।</translation>
 <translation id="7569509451529460200">ব্রেল এবং ChromeVox সক্ষম করেছেন</translation>
 <translation id="7593891976182323525">Search অথবা Shift</translation>
+<translation id="7604942372593434070">আপনার ব্রাউজিং অ্যাক্টিভিটি অ্যাক্সেস করুন</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (মালিক)</translation>
 <translation id="7647488630410863958">আপনার বিজ্ঞপ্তিগুলি দেখতে ডিভাইস আনলক করুন</translation>
 <translation id="7649070708921625228">সহায়তা</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">জোরালো সিগন্যাল</translation>
 <translation id="7842569679327885685">সতর্কতা: পরীক্ষামূলক বৈশিষ্ট্য</translation>
 <translation id="7846634333498149051">কীবোর্ড</translation>
+<translation id="790040513076446191">গোপনীয়তা-সংক্রান্ত সেটিংস নিপূণভাবে ব্যবহার করুন</translation>
 <translation id="7904094684485781019">এই অ্যাকাউন্টের প্রশাসক একাধিক প্রবেশ করুন অননুমোদিত করেছেন৷</translation>
 <translation id="7933084174919150729">Google সহায়ক শুধুমাত্র প্রাথমিক প্রোফাইলেই ব্যবহার করা যায়।</translation>
 <translation id="79341161159229895">অ্যাকাউন্টটি <ph name="FIRST_PARENT_EMAIL" /> এবং <ph name="SECOND_PARENT_EMAIL" /> পরিচালনা করছেন</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">ব্যক্তিগত নেটওয়ার্ক</translation>
 <translation id="8152119955266188852">ফুল-স্ক্রিন ম্যাগনিফায়ারের জন্য আপনি শর্টকাট প্রেস করেছেন। আপনি কি এটি চালু করতে চান?</translation>
 <translation id="8190698733819146287">ভাষা এবং ইনপুট কাস্টমাইজ করুন...</translation>
+<translation id="8191230140820435481">আপনার অ্যাপ্লিকেশন, এক্সটেনশন, এবং থিম পরিচালনা করুন</translation>
 <translation id="8261506727792406068">মুছুন</translation>
 <translation id="8297006494302853456">দুর্বল</translation>
 <translation id="8308637677604853869">পূর্ববর্তী মেনু</translation>
diff --git a/ash/strings/ash_strings_ca.xtb b/ash/strings/ash_strings_ca.xtb
index aa73897..3150266 100644
--- a/ash/strings/ash_strings_ca.xtb
+++ b/ash/strings/ash_strings_ca.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Teclat en pantalla activat</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (s'està carregant...)</translation>
+<translation id="1841545962859478868">És possible que l'administrador del dispositiu supervisi el següent:</translation>
 <translation id="1850504506766569011">La Wi-Fi està desactivada.</translation>
 <translation id="1864454756846565995">Dispositiu USB-C (port posterior)</translation>
 <translation id="1882897271359938046">S'està replicant <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">S'està connectant</translation>
 <translation id="7165278925115064263">Alt+Maj+K</translation>
 <translation id="7168224885072002358">Es revertirà a la resolució anterior d'aquí a <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipula la configuració que determina si els llocs web poden utilitzar funcions com ara la geolocalització, el micròfon, la càmera, etc.</translation>
 <translation id="7256634071279256947">Micròfon posterior</translation>
 <translation id="726276584504105859">Arrossega-la aquí per utilitzar la pantalla dividida</translation>
 <translation id="7348093485538360975">Teclat en pantalla</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Moure finestres a un altre escriptori pot provocar un comportament imprevist. És possible que les notificacions, les finestres i els quadres de diàleg següents quedin dividits entre escriptoris.</translation>
 <translation id="7569509451529460200">S'ha activat el braille i ChromeVox</translation>
 <translation id="7593891976182323525">Cerca o Maj</translation>
+<translation id="7604942372593434070">Accedeix a l'activitat de navegació</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (propietari)</translation>
 <translation id="7647488630410863958">Desbloqueja el dispositiu per veure les notificacions</translation>
 <translation id="7649070708921625228">Ajuda</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Senyal potent</translation>
 <translation id="7842569679327885685">Advertiment: funció experimental</translation>
 <translation id="7846634333498149051">Teclat</translation>
+<translation id="790040513076446191">Manipular la configuració relacionada amb la privadesa</translation>
 <translation id="7904094684485781019">L'administrador d'aquest compte no ha permès l'inici de sessió múltiple.</translation>
 <translation id="7933084174919150729">L'Assistent de Google només està disponible per al perfil principal.</translation>
 <translation id="79341161159229895">Compte gestionat per <ph name="FIRST_PARENT_EMAIL" /> i <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Xarxa privada</translation>
 <translation id="8152119955266188852">Has premut la drecera de la lupa de pantalla completa. Vols activar-la?</translation>
 <translation id="8190698733819146287">Personalitza idiomes i introducció de text...</translation>
+<translation id="8191230140820435481">Gestionar les aplicacions, les extensions i els temes</translation>
 <translation id="8261506727792406068">Suprimeix</translation>
 <translation id="8297006494302853456">Feble</translation>
 <translation id="8308637677604853869">Menú anterior</translation>
diff --git a/ash/strings/ash_strings_cs.xtb b/ash/strings/ash_strings_cs.xtb
index dec11ea..e1093b1 100644
--- a/ash/strings/ash_strings_cs.xtb
+++ b/ash/strings/ash_strings_cs.xtb
@@ -7,6 +7,7 @@
 <translation id="1037492556044956303">Bylo přidáno zařízení <ph name="DEVICE_NAME" /></translation>
 <translation id="1056775291175587022">Žádné sítě</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation>
+<translation id="1104084341931202936">Zobrazit nastavení přístupnosti</translation>
 <translation id="1104621072296271835">Společně budou vaše zařízení fungovat ještě lépe</translation>
 <translation id="112308213915226829">Automaticky skrývat poličku</translation>
 <translation id="1153356358378277386">Spárovaná zařízení</translation>
@@ -20,17 +21,20 @@
 <translation id="1279938420744323401"><ph name="DISPLAY_NAME" /> (<ph name="ANNOTATION" />)</translation>
 <translation id="1290331692326790741">Slabý signál</translation>
 <translation id="1293264513303784526">Zařízení USB Type-C (levý port)</translation>
+<translation id="1302880136325416935">Zobrazit nastavení Bluetooth. <ph name="STATE_TEXT" /></translation>
 <translation id="1346748346194534595">Doprava</translation>
 <translation id="1351937230027495976">Sbalit nabídku</translation>
 <translation id="1383876407941801731">Vyhledávání</translation>
 <translation id="1467432559032391204">Doleva</translation>
 <translation id="1484102317210609525"><ph name="DEVICE_NAME" /> (HDMI/DP)</translation>
 <translation id="1510238584712386396">Spouštěč</translation>
+<translation id="1520303207432623762">{NUM_APPS,plural, =1{Zobrazit nastavení oznámení. U jedné aplikace jsou oznámení vypnutá}few{Zobrazit nastavení oznámení. U # aplikací jsou oznámení vypnutá}many{Zobrazit nastavení oznámení. U # aplikace jsou oznámení vypnutá}other{Zobrazit nastavení oznámení. U # aplikací jsou oznámení vypnutá}}</translation>
 <translation id="1525508553941733066">ZAVŘÍT</translation>
 <translation id="1537254971476575106">Lupa celé obrazovky</translation>
 <translation id="15373452373711364">Velký kurzor myši</translation>
 <translation id="1550523713251050646">Kliknutím zobrazíte další možnosti</translation>
 <translation id="1567387640189251553">Od minulého zadání hesla byla připojena jiná klávesnice. Je možné, že se pokouší odcizit vaše stisknutí kláves.</translation>
+<translation id="1570871743947603115">Přepnout Bluetooth. <ph name="STATE_TEXT" /></translation>
 <translation id="1608626060424371292">Odebrat tohoto uživatele</translation>
 <translation id="1621499497873603021">Čas zbývající do vybití baterie: <ph name="TIME_LEFT" /></translation>
 <translation id="1658406695958299976">Je nám líto, vaše heslo se stále nedaří ověřit. Poznámka: Pokud jste heslo nedávno změnili, bude nové heslo možné použít až po odhlášení. Zde použijte staré heslo.</translation>
@@ -40,8 +44,10 @@
 <translation id="1743570585616704562">Nerozpoznáno</translation>
 <translation id="1746730358044914197">Metody zadávání nastavuje váš administrátor.</translation>
 <translation id="1747827819627189109">Softwarová klávesnice je aktivována</translation>
+<translation id="1761222317188459878">Přepnout připojení k síti. <ph name="STATE_TEXT" /></translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistent (načítání...)</translation>
+<translation id="1841545962859478868">Správce zařízení může sledovat toto:</translation>
 <translation id="1850504506766569011">Připojení Wi-Fi je vypnuto.</translation>
 <translation id="1864454756846565995">Zařízení USB Type-C (zadní port)</translation>
 <translation id="1882897271359938046">Zrcadlení na displej <ph name="DISPLAY_NAME" /></translation>
@@ -62,6 +68,7 @@
 <translation id="2049639323467105390">Zařízení je spravováno doménou <ph name="DOMAIN" />.</translation>
 <translation id="2050339315714019657">Na výšku</translation>
 <translation id="2067602449040652523">Jas klávesnice</translation>
+<translation id="2075212959500165896">Příliš mnoho pokusů. Zkuste to později.</translation>
 <translation id="2081529251031312395">$1 se bude moci později přihlásit.</translation>
 <translation id="2127372758936585790">Nabíječka má příliš nízký výkon</translation>
 <translation id="2135456203358955318">Zadokovaná lupa</translation>
@@ -75,12 +82,15 @@
 <translation id="2303600792989757991">Přepnutí přehledu okna</translation>
 <translation id="2338501278241028356">Chcete-li objevit okolní zařízení, zapněte Bluetooth</translation>
 <translation id="2339073806695260576">Klepnutím na tlačítko dotykového pera na poličce můžete pořídit poznámku nebo snímek obrazovky, případně použít laserové ukazovátko nebo lupu.</translation>
+<translation id="2341729377289034582">Uzamknuto na výšku</translation>
 <translation id="2352467521400612932">Nastavení dotykového pera</translation>
 <translation id="2354174487190027830">Aktivace sítě <ph name="NAME" /></translation>
 <translation id="2359808026110333948">Pokračovat</translation>
 <translation id="2365393535144473978">Zapnutím mobilních dat aktivujete Bluetooth.</translation>
 <translation id="2391579633712104609">180°</translation>
+<translation id="239188844683466770">Přepnout možnost Nerušit</translation>
 <translation id="2412593942846481727">K dispozici je aktualizace</translation>
+<translation id="2416346634399901812">Připojeno k síti <ph name="NETWORK_NAME" /></translation>
 <translation id="2429753432712299108">Zařízení Bluetooth „<ph name="DEVICE_NAME" />“ žádá o povolení ke spárování. Než toto povolení schválíte, zkontrolujte si, zda je na zařízení zobrazen následující přístupový klíč: <ph name="PASSKEY" /></translation>
 <translation id="2482878487686419369">Oznámení</translation>
 <translation id="2484513351006226581">Rozložení klávesnice změníte stisknutím kláves <ph name="KEYBOARD_SHORTCUT" /></translation>
@@ -113,6 +123,7 @@
 <translation id="2946119680249604491">Přidat připojení</translation>
 <translation id="2961963223658824723">Něco se pokazilo. Zkuste to znovu za několik minut.</translation>
 <translation id="2963773877003373896">mod3</translation>
+<translation id="2995447421581609334">Zobrazit zařízení Cast.</translation>
 <translation id="2996462380875591307">Zadokovaná lupa je zapnutá. Vypnete ji opětovným stisknutím kombinace kláves Ctrl+Hledat+D.</translation>
 <translation id="2999742336789313416"><ph name="DISPLAY_NAME" /> je veřejná relace spravovaná doménou <ph name="DOMAIN" />.</translation>
 <translation id="3000461861112256445">Zvuk mono</translation>
@@ -127,6 +138,7 @@
 <translation id="315116470104423982">Mobilní datové přenosy</translation>
 <translation id="3151786313568798007">Orientace</translation>
 <translation id="3153444934357957346">Počet účtů, které lze v rámci vícenásobného přihlášení používat, je omezen na <ph name="MULTI_PROFILE_USER_LIMIT" />.</translation>
+<translation id="3202010236269062730">{NUM_DEVICES,plural, =1{Připojeno k zařízení}few{Připojeno k # zařízením}many{Připojeno k # zařízení}other{Připojeno k # zařízením}}</translation>
 <translation id="3236488194889173876">Není k dispozici žádná mobilní síť</translation>
 <translation id="3238765806255363838"><ph name="USERNAME" /> <ph name="MAIL" /></translation>
 <translation id="3294437725009624529">Host</translation>
@@ -158,6 +170,7 @@
 <translation id="3784455785234192852">Uzamknout</translation>
 <translation id="3798670284305777884">Reproduktor (interní)</translation>
 <translation id="380165613292957338">Jak vám mohu pomoci?</translation>
+<translation id="383629559565718788">Zobrazit nastavení klávesnice</translation>
 <translation id="3846575436967432996">Informace o síti nejsou k dispozici</translation>
 <translation id="385051799172605136">Zpět</translation>
 <translation id="3891340733213178823">Odhlaste se dvojitým stisknutím kombinace kláves Ctrl+Shift+Q.</translation>
@@ -173,8 +186,10 @@
 <translation id="4072264167173457037">Střední signál</translation>
 <translation id="4200057768455216496">Stiskli jste klávesovou zkratku pro zadokovanou lupu. Chcete ji zapnout?</translation>
 <translation id="4217571870635786043">Diktování</translation>
+<translation id="4261870227682513959">Zobrazit nastavení oznámení. Oznámení jsou vypnutá</translation>
 <translation id="4274921305979314545">Propojte svůj Chromebook s telefonem</translation>
 <translation id="4279490309300973883">Zrcadlení</translation>
+<translation id="4292681942966152062">Aktivace sítě <ph name="NETWORK_NAME" /></translation>
 <translation id="4321179778687042513">ctrl</translation>
 <translation id="4331809312908958774">Chrome OS</translation>
 <translation id="4338109981321384717">Lupa</translation>
@@ -238,6 +253,7 @@
 <translation id="5673434351075758678">Po synchronizaci nastavení se oznámení změní z jazyka <ph name="FROM_LOCALE" /> na jazyk <ph name="TO_LOCALE" />.</translation>
 <translation id="574392208103952083">Střední</translation>
 <translation id="5744083938413354016">Přetažení klepnutím</translation>
+<translation id="5750765938512549687">Rozhraní Bluetooth je vypnuté</translation>
 <translation id="5777841717266010279">Ukončit sdílení obrazovky?</translation>
 <translation id="57838592816432529">Ztlumit</translation>
 <translation id="5805697420284793859">Správce oken</translation>
@@ -271,12 +287,14 @@
 <translation id="615957422585914272">Zobrazit softwarovou klávesnici</translation>
 <translation id="6164005077879661055">Po odebrání dozorovaného uživatele budou trvale smazány všechny soubory a místní data, která jsou k němu přiřazena. Navštívené weby a nastavení tohoto dozorovaného uživatele může správce i nadále zobrazit na adrese <ph name="MANAGEMENT_URL" />.</translation>
 <translation id="6165508094623778733">Další informace</translation>
+<translation id="6254629735336163724">Uzamknuto na šířku</translation>
 <translation id="6259254695169772643">K výběru použijte dotykové pero</translation>
 <translation id="6267036997247669271"><ph name="NAME" />: Probíhá aktivace...</translation>
 <translation id="6284232397434400372">Rozlišení se změnilo</translation>
 <translation id="6297287540776456956">Vyberte pomocí dotykového pera oblast.</translation>
 <translation id="6310121235600822547">Displej <ph name="DISPLAY_NAME" /> byl otočen o <ph name="ROTATION" /></translation>
 <translation id="632744581670418035">Překryvná klávesnice</translation>
+<translation id="6376931439017688372">Rozhraní Bluetooth je zapnuté</translation>
 <translation id="639644700271529076">CAPS LOCK je vypnutý</translation>
 <translation id="6406704438230478924">altgr</translation>
 <translation id="643147933154517414">Vše je hotovo</translation>
@@ -320,11 +338,13 @@
 <translation id="7066646422045619941">Tato síť je zakázána vaším administrátorem.</translation>
 <translation id="7067196344162293536">Automatické otáčení</translation>
 <translation id="7076293881109082629">Přihlášení</translation>
+<translation id="7092922358121866860">Zobrazit nastavení Nočního režimu</translation>
 <translation id="7098389117866926363">Zařízení USB Type-C (levý zadní port)</translation>
 <translation id="7131634465328662194">Budete automaticky odhlášeni.</translation>
 <translation id="7143207342074048698">Připojování</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Původní rozlišení bude obnoveno za <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipulovat s nastaveními, která udávají, zda weby mohou používat funkce jako jsou geolokace, mikrofon, fotoaparát apod.</translation>
 <translation id="7256634071279256947">Zadní mikrofon</translation>
 <translation id="726276584504105859">Chcete-li použít rozdělenou obrazovku, přetáhněte okno sem</translation>
 <translation id="7348093485538360975">Softwarová klávesnice</translation>
@@ -341,6 +361,7 @@
 <translation id="7564874036684306347">Přesunutí oken na jinou plochu může vést k neočekávanému chování. Následná oznámení, okna a dialogy mohou být rozděleny mezi plochy.</translation>
 <translation id="7569509451529460200">Jsou zapnuty funkce Braille a ChromeVox</translation>
 <translation id="7593891976182323525">Vyhledávání nebo Shift</translation>
+<translation id="7604942372593434070">Přistupovat k vaší aktivitě prohlížení</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (vlastník)</translation>
 <translation id="7647488630410863958">Chcete-li zobrazit oznámení, odemkněte zařízení</translation>
 <translation id="7649070708921625228">Nápověda</translation>
@@ -353,8 +374,10 @@
 <translation id="7798302898096527229">Vypnete jej stisknutím klávesy Hledat nebo Shift.</translation>
 <translation id="7814236020522506259"><ph name="HOUR" /> a <ph name="MINUTE" /></translation>
 <translation id="7829386189513694949">Silný signál</translation>
+<translation id="7842211907556571265">Připojování k síti <ph name="NETWORK_NAME" /></translation>
 <translation id="7842569679327885685">Upozornění: Experimentální funkce</translation>
 <translation id="7846634333498149051">Klávesnice</translation>
+<translation id="790040513076446191">Manipulovat s nastavením týkajícím se ochrany soukromí</translation>
 <translation id="7904094684485781019">Správce tohoto účtu zakázal vícenásobné přihlášení.</translation>
 <translation id="7933084174919150729">Asistent Google je k dispozici pouze pro primární profil.</translation>
 <translation id="79341161159229895">Správci účtu: <ph name="FIRST_PARENT_EMAIL" /> a <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +396,7 @@
 <translation id="8142699993796781067">Soukromá síť</translation>
 <translation id="8152119955266188852">Stiskli jste klávesovou zkratku pro lupu celé obrazovky. Chcete ji zapnout?</translation>
 <translation id="8190698733819146287">Personalizovat jazyky a zadávání...</translation>
+<translation id="8191230140820435481">Spravovat aplikace, rozšíření a motivy</translation>
 <translation id="8261506727792406068">Smazat</translation>
 <translation id="8297006494302853456">Slabý</translation>
 <translation id="8308637677604853869">Předchozí nabídka</translation>
@@ -391,18 +415,24 @@
 <translation id="8484916590211895857"><ph name="NAME" />: Obnovování připojení...</translation>
 <translation id="8513108775083588393">Automatické otáčení</translation>
 <translation id="8517041960877371778">Je možné, že když bude zařízení <ph name="DEVICE_TYPE" /> zapnuté, nebude se nabíjet.</translation>
+<translation id="8627191004499078455">Připojeno k zařízení <ph name="DEVICE_NAME" /></translation>
 <translation id="8639760480004882931">Zbývá <ph name="PERCENTAGE" /> %</translation>
 <translation id="8649101189709089199">Poslech vybraného textu</translation>
 <translation id="8652175077544655965">Zavřít nastavení</translation>
+<translation id="8653151467777939995">Zobrazit nastavení oznámení. Oznámení jsou zapnutá</translation>
+<translation id="8664753092453405566">Zobrazit seznam sítí. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Byla připojena nabíječka s nízkým napětím</translation>
 <translation id="8683506306463609433">Trasování výkonu je aktivní</translation>
 <translation id="8734991477317290293">Je možné, že se pokouší odcizit vaše stisknutí kláves.</translation>
+<translation id="8735953464173050365">Zobrazit nastavení klávesnice. Je vybrána klávesnice <ph name="KEYBOARD_NAME" /></translation>
+<translation id="875593634123171288">Zobrazit nastavení VPN</translation>
 <translation id="8809737090443522491">Zadejte název aplikace nebo dokumentu</translation>
 <translation id="8814190375133053267">Wi-Fi</translation>
 <translation id="8825534185036233643">Zrcadlení s více než dvěma obrazovkami není podporováno.</translation>
 <translation id="8828714802988429505">90°</translation>
 <translation id="8841375032071747811">Tlačítko Zpět</translation>
+<translation id="8843682306134542540">Přepnout zámek otáčení. <ph name="STATE_TEXT" /></translation>
 <translation id="8850991929411075241">Hledat+Esc</translation>
 <translation id="8870509716567206129">Aplikace nepodporuje režim rozdělené obrazovky.</translation>
 <translation id="8874184842967597500">Nepřipojeno</translation>
diff --git a/ash/strings/ash_strings_da.xtb b/ash/strings/ash_strings_da.xtb
index 195c4f6..e1466732e 100644
--- a/ash/strings/ash_strings_da.xtb
+++ b/ash/strings/ash_strings_da.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Skærmtastaturet er aktiveret</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (indlæser...)</translation>
+<translation id="1841545962859478868">Administratoren af enheden kan overvåge følgende:</translation>
 <translation id="1850504506766569011">Wi-Fi er slået fra.</translation>
 <translation id="1864454756846565995">USB-C-enhed (port bagpå)</translation>
 <translation id="1882897271359938046">Spejler mod <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Opretter forbindelse</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Fortryder og vender tilbage til den gamle opløsning om <ph name="TIMEOUT_SECONDS" /> sekunder.</translation>
+<translation id="7228479291753472782">Manipuler indstillinger, der angiver, om websites må anvende funktioner såsom geoplacering, mikrofon, kamera m.m.</translation>
 <translation id="7256634071279256947">Mikrofon på bagsiden</translation>
 <translation id="726276584504105859">Træk hertil for at bruge delt skærm</translation>
 <translation id="7348093485538360975">Skærmtastatur</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Hvis du flytter vinduer til et andet skrivebord, kan det resultere i uventet adfærd. Efterfølgende underretninger, vinduer og dialogbokse kan blive delt mellem skriveborde.</translation>
 <translation id="7569509451529460200">Braille og ChromeVox er aktiveret</translation>
 <translation id="7593891976182323525">Søg eller Shift</translation>
+<translation id="7604942372593434070">Adgang til din browseraktivitet</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (ejer)</translation>
 <translation id="7647488630410863958">Lås enheden op for at se dine underretninger</translation>
 <translation id="7649070708921625228">Hjælp</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Stærkt signal</translation>
 <translation id="7842569679327885685">Advarsel! Eksperimentel funktion</translation>
 <translation id="7846634333498149051">Tastatur</translation>
+<translation id="790040513076446191">Manipulere indstillinger til beskyttelse af personlige oplysninger</translation>
 <translation id="7904094684485781019">Administratoren for denne konto tillader ikke samlet login fra flere konti.</translation>
 <translation id="7933084174919150729">Google Assistent kan kun bruges via den primære profil.</translation>
 <translation id="79341161159229895">Kontoen administreres af <ph name="FIRST_PARENT_EMAIL" /> og <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Privat netværk</translation>
 <translation id="8152119955266188852">Du trykkede på genvejen for forstørrelse af fuld skærm. Vil du aktivere indstillingen?</translation>
 <translation id="8190698733819146287">Tilpas sprog og indtastning...</translation>
+<translation id="8191230140820435481">Administrere dine apps, udvidelser og temaer</translation>
 <translation id="8261506727792406068">Slet</translation>
 <translation id="8297006494302853456">Svag</translation>
 <translation id="8308637677604853869">Forrige menu</translation>
diff --git a/ash/strings/ash_strings_de.xtb b/ash/strings/ash_strings_de.xtb
index c6ce8e5..69ef3ed 100644
--- a/ash/strings/ash_strings_de.xtb
+++ b/ash/strings/ash_strings_de.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Bildschirmtastatur aktiviert</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (wird geladen…)</translation>
+<translation id="1841545962859478868">Der Geräteadministrator überwacht eventuell Folgendes:</translation>
 <translation id="1850504506766569011">WLAN ist deaktiviert.</translation>
 <translation id="1864454756846565995">USB-C-Gerät (Port hinten)</translation>
 <translation id="1882897271359938046">Wird auf <ph name="DISPLAY_NAME" /> gespiegelt...</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Verbindung wird hergestellt.</translation>
 <translation id="7165278925115064263">Alt + Umschalttaste + K</translation>
 <translation id="7168224885072002358">Alte Auflösung wird in <ph name="TIMEOUT_SECONDS" /> wiederhergestellt.</translation>
+<translation id="7228479291753472782">Einstellungen bearbeiten, die festlegen, ob Websites Funktionen wie die Standortbestimmung, das Mikrofon, die Kamera usw. verwenden dürfen</translation>
 <translation id="7256634071279256947">Mikrofon auf der Rückseite</translation>
 <translation id="726276584504105859">Hierher ziehen, um den Bildschirm zu teilen</translation>
 <translation id="7348093485538360975">Bildschirmtastatur</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Das Verschieben von Fenstern auf einen anderen Desktop kann zu unerwartetem Verhalten führen. Nachfolgende Benachrichtigungen, Fenster und Dialogfelder werden unter Umständen zwischen den Desktops aufgeteilt.</translation>
 <translation id="7569509451529460200">Braille und ChromeVox sind aktiviert</translation>
 <translation id="7593891976182323525">Suchen oder Umschalttaste</translation>
+<translation id="7604942372593434070">Auf Ihre Browseraktivitäten zugreifen</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (Eigentümer)</translation>
 <translation id="7647488630410863958">Gerät entsperren, um Benachrichtigungen zu sehen</translation>
 <translation id="7649070708921625228">Hilfe</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Starkes Signal</translation>
 <translation id="7842569679327885685">Achtung: Experimentelle Funktion</translation>
 <translation id="7846634333498149051">Tastatur</translation>
+<translation id="790040513076446191">Datenschutzeinstellungen bearbeiten</translation>
 <translation id="7904094684485781019">Der Administrator dieses Kontos hat keine Mehrfachanmeldung zugelassen.</translation>
 <translation id="7933084174919150729">Google Assistant ist nur für das Hauptprofil verfügbar.</translation>
 <translation id="79341161159229895">Konto verwaltet von <ph name="FIRST_PARENT_EMAIL" /> und <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privates Netzwerk</translation>
 <translation id="8152119955266188852">Sie haben die Tastenkombination für die Vollbildlupe gedrückt. Möchten Sie sie aktivieren?</translation>
 <translation id="8190698733819146287">Sprache und Eingabe anpassen...</translation>
+<translation id="8191230140820435481">Apps, Erweiterungen und Designs verwalten</translation>
 <translation id="8261506727792406068">Löschen</translation>
 <translation id="8297006494302853456">Schwach</translation>
 <translation id="8308637677604853869">Vorheriges Menü</translation>
diff --git a/ash/strings/ash_strings_el.xtb b/ash/strings/ash_strings_el.xtb
index e306b57..66c317a6 100644
--- a/ash/strings/ash_strings_el.xtb
+++ b/ash/strings/ash_strings_el.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Το πληκτρολόγιο οθόνης είναι ενεργοποιημένο</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Βοηθός (φόρτωση…)</translation>
+<translation id="1841545962859478868">Ο διαχειριστής της συσκευής μπορεί να παρακολουθεί τα παρακάτω:</translation>
 <translation id="1850504506766569011">Το Wi-Fi έχει απενεργοποιηθεί.</translation>
 <translation id="1864454756846565995">Συσκευή USB-C (πίσω θύρα)</translation>
 <translation id="1882897271359938046">Κατοπτρισμός σε <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Σύνδεση</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Επαναφορά στην προηγούμενη ανάλυση σε <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Χειριστείτε ρυθμίσεις οι οποίες καθορίζουν εάν οι ιστότοποι μπορούν να χρησιμοποιούν λειτουργίες, όπως τη γεωτοποθεσία, το μικρόφωνο, την κάμερα, κ.λπ.</translation>
 <translation id="7256634071279256947">Πίσω μικρόφωνο</translation>
 <translation id="726276584504105859">Σύρετε εδώ για να χρησιμοποιήσετε τον διαχωρισμό οθόνης</translation>
 <translation id="7348093485538360975">Πληκτρολόγιο οθόνης</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Η μετακίνηση των παραθύρων σε άλλη επιφάνεια εργασίας ενδέχεται να οδηγήσει σε μη αναμενόμενη συμπεριφορά. Ενδέχεται να γίνει διαχωρισμός μεταγενέστερων ειδοποιήσεων, παραθύρων και παραθύρων διαλόγου μεταξύ των επιφανειών εργασίας.</translation>
 <translation id="7569509451529460200">Το Braille και το ChromeVox ενεργοποιήθηκαν</translation>
 <translation id="7593891976182323525">Search ή Shift</translation>
+<translation id="7604942372593434070">Πρόσβαση στη δραστηριότητα περιήγησής σας</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (κάτοχος)</translation>
 <translation id="7647488630410863958">Ξεκλειδώστε τη συσκευή για να δείτε τις ειδοποιήσεις σας</translation>
 <translation id="7649070708921625228">Βοήθεια</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Ισχυρό σήμα</translation>
 <translation id="7842569679327885685">Προειδοποίηση: Πειραματική λειτουργία</translation>
 <translation id="7846634333498149051">Πληκτρολόγιο</translation>
+<translation id="790040513076446191">Διαχείριση ρυθμίσεων σχετικά με το απόρρητο</translation>
 <translation id="7904094684485781019">Ο διαχειριστής αυτού του λογαριασμού δεν έχει επιτρέψει τις πολλαπλές συνδέσεις.</translation>
 <translation id="7933084174919150729">Ο Βοηθός Google είναι διαθέσιμος μόνο για το κύριο προφίλ.</translation>
 <translation id="79341161159229895">Ο λογαριασμός είναι διαχειριζόμενος από τους χρήστες <ph name="FIRST_PARENT_EMAIL" /> και <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Ιδιωτικό δίκτυο</translation>
 <translation id="8152119955266188852">Πατήσατε τη συντόμευση για τον μεγεθυντικό φακό πλήρους οθόνης. Θέλετε να τον ενεργοποιήσετε;</translation>
 <translation id="8190698733819146287">Προσαρμογή γλωσσών και εισόδου...</translation>
+<translation id="8191230140820435481">Διαχείριση των εφαρμογών, των επεκτάσεων και των θεμάτων σας</translation>
 <translation id="8261506727792406068">Διαγραφή</translation>
 <translation id="8297006494302853456">Αδύναμο</translation>
 <translation id="8308637677604853869">Προηγούμενο μενού</translation>
diff --git a/ash/strings/ash_strings_en-GB.xtb b/ash/strings/ash_strings_en-GB.xtb
index 0e88e703..1d8a1f1 100644
--- a/ash/strings/ash_strings_en-GB.xtb
+++ b/ash/strings/ash_strings_en-GB.xtb
@@ -7,6 +7,7 @@
 <translation id="1037492556044956303"><ph name="DEVICE_NAME" /> added</translation>
 <translation id="1056775291175587022">No networks</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation>
+<translation id="1104084341931202936">Show accessibility settings</translation>
 <translation id="1104621072296271835">Your devices work even better together</translation>
 <translation id="112308213915226829">Autohide shelf</translation>
 <translation id="1153356358378277386">Paired devices</translation>
@@ -20,17 +21,20 @@
 <translation id="1279938420744323401"><ph name="DISPLAY_NAME" /> (<ph name="ANNOTATION" />)</translation>
 <translation id="1290331692326790741">Weak signal</translation>
 <translation id="1293264513303784526">USB-C device (left port)</translation>
+<translation id="1302880136325416935">Show Bluetooth settings. <ph name="STATE_TEXT" /></translation>
 <translation id="1346748346194534595">Right</translation>
 <translation id="1351937230027495976">Collapse menu</translation>
 <translation id="1383876407941801731">Search</translation>
 <translation id="1467432559032391204">Left</translation>
 <translation id="1484102317210609525"><ph name="DEVICE_NAME" /> (HDMI/DP)</translation>
 <translation id="1510238584712386396">Launcher</translation>
+<translation id="1520303207432623762">{NUM_APPS,plural, =1{Show notification settings. Notifications are off for an app}other{Show notification settings. Notifications are off for # apps}}</translation>
 <translation id="1525508553941733066">DISMISS</translation>
 <translation id="1537254971476575106">Full-screen magnifier</translation>
 <translation id="15373452373711364">Large mouse cursor</translation>
 <translation id="1550523713251050646">Click for more options</translation>
 <translation id="1567387640189251553">A different keyboard has been connected since you last entered your password. It may be attempting to steal your keystrokes.</translation>
+<translation id="1570871743947603115">Toggle Bluetooth. <ph name="STATE_TEXT" /></translation>
 <translation id="1608626060424371292">Remove this user</translation>
 <translation id="1621499497873603021">Time left until battery is empty, <ph name="TIME_LEFT" /></translation>
 <translation id="1658406695958299976">Sorry, your password still couldn't be verified. Note: If you changed your password recently, your new password will be applied once you sign out. Please use the old password here.</translation>
@@ -40,8 +44,10 @@
 <translation id="1743570585616704562">Not recognised</translation>
 <translation id="1746730358044914197">Input methods are configured by your administrator.</translation>
 <translation id="1747827819627189109">On-screen keyboard enabled</translation>
+<translation id="1761222317188459878">Toggle network connection. <ph name="STATE_TEXT" /></translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (loading...)</translation>
+<translation id="1841545962859478868">The device admin may monitor the following:</translation>
 <translation id="1850504506766569011">Wi-Fi is turned off.</translation>
 <translation id="1864454756846565995">USB-C device (rear port)</translation>
 <translation id="1882897271359938046">Mirroring to <ph name="DISPLAY_NAME" /></translation>
@@ -62,6 +68,7 @@
 <translation id="2049639323467105390">This device is managed by <ph name="DOMAIN" />.</translation>
 <translation id="2050339315714019657">Portrait</translation>
 <translation id="2067602449040652523">Keyboard brightness</translation>
+<translation id="2075212959500165896">Too many attempts. Try again later.</translation>
 <translation id="2081529251031312395">$1 can still sign in later.</translation>
 <translation id="2127372758936585790">Low-power charger</translation>
 <translation id="2135456203358955318">Docked magnifier</translation>
@@ -75,12 +82,15 @@
 <translation id="2303600792989757991">Toggle window overview</translation>
 <translation id="2338501278241028356">Turn on Bluetooth to discover nearby devices</translation>
 <translation id="2339073806695260576">Tap the stylus button on the shelf to take a note, screenshot, use the laser pointer or magnifying glass.</translation>
+<translation id="2341729377289034582">Locked to vertical</translation>
 <translation id="2352467521400612932">Stylus settings</translation>
 <translation id="2354174487190027830">Activating <ph name="NAME" /></translation>
 <translation id="2359808026110333948">Continue</translation>
 <translation id="2365393535144473978">Enabling mobile data will enable Bluetooth.</translation>
 <translation id="2391579633712104609">180°</translation>
+<translation id="239188844683466770">Toggle Do not disturb</translation>
 <translation id="2412593942846481727">Update available</translation>
+<translation id="2416346634399901812">Connected to <ph name="NETWORK_NAME" /></translation>
 <translation id="2429753432712299108">Bluetooth device "<ph name="DEVICE_NAME" />" would like permission to pair. Before accepting, please confirm that this passkey is shown on that device: <ph name="PASSKEY" /></translation>
 <translation id="2482878487686419369">Notifications</translation>
 <translation id="2484513351006226581">Hit <ph name="KEYBOARD_SHORTCUT" /> to switch keyboard layout.</translation>
@@ -113,6 +123,7 @@
 <translation id="2946119680249604491">Add connection</translation>
 <translation id="2961963223658824723">Something went wrong. Try again in a few seconds.</translation>
 <translation id="2963773877003373896">mod3</translation>
+<translation id="2995447421581609334">Show cast devices.</translation>
 <translation id="2996462380875591307">Docked Magnifier enabled. Press Ctrl+Search+D again to toggle it off.</translation>
 <translation id="2999742336789313416"><ph name="DISPLAY_NAME" /> is a public session managed by <ph name="DOMAIN" /></translation>
 <translation id="3000461861112256445">Mono audio</translation>
@@ -127,6 +138,7 @@
 <translation id="315116470104423982">Mobile data</translation>
 <translation id="3151786313568798007">Orientation</translation>
 <translation id="3153444934357957346">You can only have up to <ph name="MULTI_PROFILE_USER_LIMIT" /> accounts in multiple sign-in.</translation>
+<translation id="3202010236269062730">{NUM_DEVICES,plural, =1{Connected to a device}other{Connected to # devices}}</translation>
 <translation id="3236488194889173876">No mobile network available</translation>
 <translation id="3238765806255363838"><ph name="USERNAME" /> <ph name="MAIL" /></translation>
 <translation id="3294437725009624529">Guest</translation>
@@ -158,6 +170,7 @@
 <translation id="3784455785234192852">Lock</translation>
 <translation id="3798670284305777884">Speaker (internal)</translation>
 <translation id="380165613292957338">Hi, how can I help?</translation>
+<translation id="383629559565718788">Show keyboard settings</translation>
 <translation id="3846575436967432996">No network information available</translation>
 <translation id="385051799172605136">Back</translation>
 <translation id="3891340733213178823">Press Ctrl+Shift+Q twice to sign out.</translation>
@@ -173,8 +186,10 @@
 <translation id="4072264167173457037">Medium signal</translation>
 <translation id="4200057768455216496">You pressed the shortcut for the docked magnifier. Do you want to turn it on?</translation>
 <translation id="4217571870635786043">Dictation</translation>
+<translation id="4261870227682513959">Show notification settings. Notifications are off</translation>
 <translation id="4274921305979314545">Connect your Chromebook with your phone</translation>
 <translation id="4279490309300973883">Mirroring</translation>
+<translation id="4292681942966152062">Activating <ph name="NETWORK_NAME" /></translation>
 <translation id="4321179778687042513">ctrl</translation>
 <translation id="4331809312908958774">Chrome OS</translation>
 <translation id="4338109981321384717">Magnifying glass</translation>
@@ -238,6 +253,7 @@
 <translation id="5673434351075758678">From '<ph name="FROM_LOCALE" />' to '<ph name="TO_LOCALE" />' after syncing your settings.</translation>
 <translation id="574392208103952083">Medium</translation>
 <translation id="5744083938413354016">Tap dragging</translation>
+<translation id="5750765938512549687">Bluetooth is off</translation>
 <translation id="5777841717266010279">Stop screen sharing?</translation>
 <translation id="57838592816432529">Mute</translation>
 <translation id="5805697420284793859">Window manager</translation>
@@ -271,12 +287,14 @@
 <translation id="615957422585914272">Show on-screen keyboard</translation>
 <translation id="6164005077879661055">All files and local data associated with the supervised user will be permanently deleted once this supervised user is removed. Visited websites and settings for this supervised user may still be visible by the manager at <ph name="MANAGEMENT_URL" />.</translation>
 <translation id="6165508094623778733">Learn more</translation>
+<translation id="6254629735336163724">Locked to horizontal</translation>
 <translation id="6259254695169772643">Use your stylus to select</translation>
 <translation id="6267036997247669271"><ph name="NAME" />: Activating...</translation>
 <translation id="6284232397434400372">Resolution changed</translation>
 <translation id="6297287540776456956">Use the stylus to select a region</translation>
 <translation id="6310121235600822547"><ph name="DISPLAY_NAME" /> was rotated to <ph name="ROTATION" /></translation>
 <translation id="632744581670418035">Keyboard overlay</translation>
+<translation id="6376931439017688372">Bluetooth is on</translation>
 <translation id="639644700271529076">CAPS LOCK is off</translation>
 <translation id="6406704438230478924">altgr</translation>
 <translation id="643147933154517414">All finished</translation>
@@ -320,11 +338,13 @@
 <translation id="7066646422045619941">This network is disabled by your administrator.</translation>
 <translation id="7067196344162293536">Auto rotate</translation>
 <translation id="7076293881109082629">Signing in</translation>
+<translation id="7092922358121866860">Show Night Light settings</translation>
 <translation id="7098389117866926363">USB-C device (left port in the back)</translation>
 <translation id="7131634465328662194">You will automatically be signed out.</translation>
 <translation id="7143207342074048698">Connecting ...</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Reverting to old resolution in <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipulate settings that specify whether websites can use features such as geo-location, microphone, camera, etc.</translation>
 <translation id="7256634071279256947">Rear microphone</translation>
 <translation id="726276584504105859">Drag here to use split screen</translation>
 <translation id="7348093485538360975">On-Screen Keyboard</translation>
@@ -341,6 +361,7 @@
 <translation id="7564874036684306347">Moving windows to another desktop may result in unexpected behaviour. Subsequent notifications, windows and dialogues may be split between desktops.</translation>
 <translation id="7569509451529460200">Braille and ChromeVox are enabled</translation>
 <translation id="7593891976182323525">Search or Shift</translation>
+<translation id="7604942372593434070">Access your browsing activity</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (owner)</translation>
 <translation id="7647488630410863958">Unlock device to view your notifications</translation>
 <translation id="7649070708921625228">Help</translation>
@@ -353,8 +374,10 @@
 <translation id="7798302898096527229">Press Search or Shift to cancel.</translation>
 <translation id="7814236020522506259"><ph name="HOUR" /> and <ph name="MINUTE" /></translation>
 <translation id="7829386189513694949">Strong signal</translation>
+<translation id="7842211907556571265">Connecting to <ph name="NETWORK_NAME" /></translation>
 <translation id="7842569679327885685">Warning: Experimental feature</translation>
 <translation id="7846634333498149051">Keyboard</translation>
+<translation id="790040513076446191">Manipulate privacy-related settings</translation>
 <translation id="7904094684485781019">The administrator for this account has disallowed multiple sign-in.</translation>
 <translation id="7933084174919150729">The Google Assistant is only available for primary profile.</translation>
 <translation id="79341161159229895">Account managed by <ph name="FIRST_PARENT_EMAIL" /> and <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +396,7 @@
 <translation id="8142699993796781067">Private networks</translation>
 <translation id="8152119955266188852">You pressed the shortcut for the full-screen magnifier. Do you want to turn it on?</translation>
 <translation id="8190698733819146287">Customise languages and input...</translation>
+<translation id="8191230140820435481">Manage your apps, extensions, and themes</translation>
 <translation id="8261506727792406068">Delete</translation>
 <translation id="8297006494302853456">Weak</translation>
 <translation id="8308637677604853869">Previous menu</translation>
@@ -391,18 +415,24 @@
 <translation id="8484916590211895857"><ph name="NAME" />: Reconnecting...</translation>
 <translation id="8513108775083588393">Auto-rotate</translation>
 <translation id="8517041960877371778">Your <ph name="DEVICE_TYPE" /> may not charge while it is turned on.</translation>
+<translation id="8627191004499078455">Connected to <ph name="DEVICE_NAME" /></translation>
 <translation id="8639760480004882931"><ph name="PERCENTAGE" /> remaining</translation>
 <translation id="8649101189709089199">Select-to-Speak</translation>
 <translation id="8652175077544655965">Close settings</translation>
+<translation id="8653151467777939995">Show notification settings. Notifications are on</translation>
+<translation id="8664753092453405566">Show network list. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Low-power charger connected</translation>
 <translation id="8683506306463609433">Performance tracing active</translation>
 <translation id="8734991477317290293">It may be attempting to steal your keystrokes</translation>
+<translation id="8735953464173050365">Show keyboard settings. <ph name="KEYBOARD_NAME" /> is selected</translation>
+<translation id="875593634123171288">Show VPN settings</translation>
 <translation id="8809737090443522491">Type the name of an app or document</translation>
 <translation id="8814190375133053267">Wi-Fi</translation>
 <translation id="8825534185036233643">Mirroring with more than two displays is not supported.</translation>
 <translation id="8828714802988429505">90°</translation>
 <translation id="8841375032071747811">Back button</translation>
+<translation id="8843682306134542540">Toggle rotation lock. <ph name="STATE_TEXT" /></translation>
 <translation id="8850991929411075241">Search+Esc</translation>
 <translation id="8870509716567206129">App does not support split-screen.</translation>
 <translation id="8874184842967597500">Not connected</translation>
diff --git a/ash/strings/ash_strings_es-419.xtb b/ash/strings/ash_strings_es-419.xtb
index 3142927..2904486e 100644
--- a/ash/strings/ash_strings_es-419.xtb
+++ b/ash/strings/ash_strings_es-419.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Teclado en pantalla habilitado</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistente (cargando…)</translation>
+<translation id="1841545962859478868">Es posible que el administrador de dispositivos supervise lo siguiente:</translation>
 <translation id="1850504506766569011">Wi-Fi desactivada</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (puerto trasero)</translation>
 <translation id="1882897271359938046">Copiando en <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Conectando</translation>
 <translation id="7165278925115064263">Alt+mayúscula+K</translation>
 <translation id="7168224885072002358">Se revertirá a la resolución anterior en <ph name="TIMEOUT_SECONDS" />.</translation>
+<translation id="7228479291753472782">Se manipularán las opciones de configuración que especifican si los sitios web pueden usar funciones, como la ubicación geográfica, el micrófono, la cámara, etc.</translation>
 <translation id="7256634071279256947">Micrófono posterior</translation>
 <translation id="726276584504105859">Arrastra hasta aquí para usar la pantalla dividida</translation>
 <translation id="7348093485538360975">Teclado en pantalla</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Si mueves las ventanas a otro escritorio, puede producirse un comportamiento inesperado. Es posible que las próximas notificaciones, ventanas y cuadros de diálogo se dividan entre los escritorios.</translation>
 <translation id="7569509451529460200">Se habilitaron el braille y ChromeVox</translation>
 <translation id="7593891976182323525">Tecla de búsqueda o Mayús</translation>
+<translation id="7604942372593434070">Se accederá a tu actividad de navegación</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (propietario/a)</translation>
 <translation id="7647488630410863958">Desbloquea el dispositivo para ver tus notificaciones</translation>
 <translation id="7649070708921625228">Ayuda</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Señal fuerte</translation>
 <translation id="7842569679327885685">Advertencia: Función experimental</translation>
 <translation id="7846634333498149051">Teclado</translation>
+<translation id="790040513076446191">Manipular la configuración relacionada con la privacidad</translation>
 <translation id="7904094684485781019">El administrador de esta cuenta inhabilitó el acceso múltiple.</translation>
 <translation id="7933084174919150729">El Asistente de Google solo está disponible para el perfil principal.</translation>
 <translation id="79341161159229895">Cuenta administrada por <ph name="FIRST_PARENT_EMAIL" /> y <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Red privada</translation>
 <translation id="8152119955266188852">Presionaste la combinación de teclas para activar la lupa de pantalla completa. ¿Quieres activarla?</translation>
 <translation id="8190698733819146287">Personalizar idiomas y la entrada de datos</translation>
+<translation id="8191230140820435481">Administrar tus aplicaciones, extensiones y temas</translation>
 <translation id="8261506727792406068">Borrar</translation>
 <translation id="8297006494302853456">Débil</translation>
 <translation id="8308637677604853869">Menú anterior</translation>
diff --git a/ash/strings/ash_strings_es.xtb b/ash/strings/ash_strings_es.xtb
index 4b97d6f7..bdd09587 100644
--- a/ash/strings/ash_strings_es.xtb
+++ b/ash/strings/ash_strings_es.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Teclado en pantalla habilitado</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistente (cargando...)</translation>
+<translation id="1841545962859478868">Es posible que el administrador del dispositivo supervise lo siguiente:</translation>
 <translation id="1850504506766569011">La conexión Wi-Fi está desactivada.</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (puerto trasero)</translation>
 <translation id="1882897271359938046">Copiando en <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Conectando</translation>
 <translation id="7165278925115064263">Alt + Mayús + K</translation>
 <translation id="7168224885072002358">Restableciendo la resolución anterior en <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipula la configuración que especifica si los sitios web pueden usar funciones como la geolocalización, el micrófono, la cámara, etc.</translation>
 <translation id="7256634071279256947">Micrófono trasero</translation>
 <translation id="726276584504105859">Arrastra hasta aquí para utilizar la pantalla dividida</translation>
 <translation id="7348093485538360975">Teclado en pantalla</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Si mueves ventanas a otro escritorio, es posible que se produzca un comportamiento inesperado. Puede que las notificaciones, las ventanas y los cuadros de diálogo que aparezcan después de realizar esta acción se dividan entre los escritorios.</translation>
 <translation id="7569509451529460200">Braille y ChromeVox están habilitados</translation>
 <translation id="7593891976182323525">Tecla de búsqueda o Mayús</translation>
+<translation id="7604942372593434070">Accede a tu actividad de navegación</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (propietario)</translation>
 <translation id="7647488630410863958">Desbloquear el dispositivo para ver las notificaciones</translation>
 <translation id="7649070708921625228">Ayuda</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Señal fuerte</translation>
 <translation id="7842569679327885685">Advertencia: Función experimental</translation>
 <translation id="7846634333498149051">Teclado</translation>
+<translation id="790040513076446191">Modificar la configuración relacionada con la privacidad</translation>
 <translation id="7904094684485781019">El administrador de esta cuenta ha inhabilitado el inicio de sesión múltiple.</translation>
 <translation id="7933084174919150729">El Asistente de Google solo está disponible en el perfil principal.</translation>
 <translation id="79341161159229895">Cuenta gestionada por <ph name="FIRST_PARENT_EMAIL" /> y <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Red privada</translation>
 <translation id="8152119955266188852">Has pulsado la combinación de teclas de la lupa de pantalla completa. ¿Quieres activarla?</translation>
 <translation id="8190698733819146287">Personalizar idiomas...</translation>
+<translation id="8191230140820435481">Administrar tus aplicaciones, extensiones y temas</translation>
 <translation id="8261506727792406068">Eliminar</translation>
 <translation id="8297006494302853456">Débil</translation>
 <translation id="8308637677604853869">Menú anterior</translation>
diff --git a/ash/strings/ash_strings_et.xtb b/ash/strings/ash_strings_et.xtb
index 9dfbc264..ba0c861 100644
--- a/ash/strings/ash_strings_et.xtb
+++ b/ash/strings/ash_strings_et.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Ekraanil kuvatav klaviatuur on lubatud</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (laadimine …)</translation>
+<translation id="1841545962859478868">Seadme administraator võib jälgida järgmist:</translation>
 <translation id="1850504506766569011">WiFi on välja lülitatud.</translation>
 <translation id="1864454756846565995">C-tüüpi USB-seade (tagumine port)</translation>
 <translation id="1882897271359938046">Peegeldamine asukohta <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Ühendamine</translation>
 <translation id="7165278925115064263">Alt + Tõstuklahv + K</translation>
 <translation id="7168224885072002358">Ekraan ennistatakse vanale eraldusvõimele <ph name="TIMEOUT_SECONDS" /> pärast</translation>
+<translation id="7228479291753472782">Muutke seadeid, mis määravad, kas veebisaidid saavad kasutada selliseid funktsioone nagu asukoha määramine, mikrofon, kaamera jne.</translation>
 <translation id="7256634071279256947">Tagumine mikrofon</translation>
 <translation id="726276584504105859">Jagatud ekraani kasutamiseks lohistage siia</translation>
 <translation id="7348093485538360975">Ekraanil kuvatav klaviatuur</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Kui teisaldate aknad teisele töölauale, võib tagajärjeks olla ootamatu käitumine. Järgmised märguanded, aknad ja dialoogid võivad jaguneda eri töölaudade vahel.</translation>
 <translation id="7569509451529460200">Punktkiri ja ChromeVox on lubatud</translation>
 <translation id="7593891976182323525">Otsinguklahv või tõstuklahv</translation>
+<translation id="7604942372593434070">Juurdepääs teie sirvimistegevustele</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (omanik)</translation>
 <translation id="7647488630410863958">Märguannete vaatamiseks avage seade</translation>
 <translation id="7649070708921625228">Abi</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Tugev signaal</translation>
 <translation id="7842569679327885685">Hoiatus: katseline funktsioon</translation>
 <translation id="7846634333498149051">Klaviatuur</translation>
+<translation id="790040513076446191">Privaatsusega seotud seadete muutmine</translation>
 <translation id="7904094684485781019">Selle konto administraator on mitmesse kontosse sisselogimise keelanud.</translation>
 <translation id="7933084174919150729">Google'i assistent on saadaval ainult peamisel profiilil.</translation>
 <translation id="79341161159229895">Kontohaldurid: <ph name="FIRST_PARENT_EMAIL" /> ja <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Privaatvõrk</translation>
 <translation id="8152119955266188852">Vajutasite täisekraani luubi otseteed. Kas soovite luubi sisse lülitada?</translation>
 <translation id="8190698733819146287">Keelte ja sisendi kohandamine...</translation>
+<translation id="8191230140820435481">Teie rakenduste, laienduste ja teemade haldamine</translation>
 <translation id="8261506727792406068">Kustuta</translation>
 <translation id="8297006494302853456">Nõrk</translation>
 <translation id="8308637677604853869">Eelmine menüü</translation>
diff --git a/ash/strings/ash_strings_fa.xtb b/ash/strings/ash_strings_fa.xtb
index de83be2..be51b97 100644
--- a/ash/strings/ash_strings_fa.xtb
+++ b/ash/strings/ash_strings_fa.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">صفحه‌کلید روی صفحه فعال شد</translation>
 <translation id="1823873187264960516">اترنت: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">دستیار (درحال بار کردن…)</translation>
+<translation id="1841545962859478868">سرپرست دستگاه ممکن است موارد زیر را پایش کند</translation>
 <translation id="1850504506766569011">‏Wi-Fi خاموش است.</translation>
 <translation id="1864454756846565995">‏دستگاه USB-C (درگاه عقب)</translation>
 <translation id="1882897271359938046">بازتاب به <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">در حال اتصال</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">برگرداندن به وضوح قدیمی در <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">دستکاری در تنظیماتی که مشخص می‌کند آیا وب‌سایت‌ها می‌توانند از قابلیت‌هایی مثل موقعیت جغرافیایی، میکروفون، دوربین و غیره استفاده کنند.</translation>
 <translation id="7256634071279256947">میکروفون پشت</translation>
 <translation id="726276584504105859">برای استفاده از تقسیم صفحه، به اینجا بکشید</translation>
 <translation id="7348093485538360975">صفحه‌کلید روی صفحه</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">انتقال پنجره‌ها به میز کار دیگر می‌تواند به رفتار غیرمنتظره منجر شود. اعلان‌ها، پنجره‌ها و کادرهای گفتگوی بعدی ممکن است بین میز کارها تقسیم شوند.</translation>
 <translation id="7569509451529460200">‏بریل و ChromeVox فعال هستند</translation>
 <translation id="7593891976182323525">‏جستجو یا Shift</translation>
+<translation id="7604942372593434070">دسترسی به فعالیت مرور شما</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (مالک)</translation>
 <translation id="7647488630410863958">برای مشاهده اعلان‌هایتان، قفل دستگاه را باز کنید</translation>
 <translation id="7649070708921625228">راهنما</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">سیگنال قوی</translation>
 <translation id="7842569679327885685">هشدار: ویژگی آزمایشی</translation>
 <translation id="7846634333498149051">صفحه‌کلید</translation>
+<translation id="790040513076446191">دستکاری تنظیمات مربوط به حریم خصوصی</translation>
 <translation id="7904094684485781019">سرپرست این حساب اجازه ورود چندگانه به سیستم را نمی‌دهد.</translation>
 <translation id="7933084174919150729">‏«دستیار Google» فقط برای نمایه اصلی دردسترس است.</translation>
 <translation id="79341161159229895">حساب تحت مدیریت <ph name="FIRST_PARENT_EMAIL" /> و <ph name="SECOND_PARENT_EMAIL" /> است</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">شبکه‌های خصوصی</translation>
 <translation id="8152119955266188852">میان‌بر مربوط به ذره‌بین تمام‌صفحه را فشار دادید. می‌خواهید آن را روشن کنید؟</translation>
 <translation id="8190698733819146287">سفارشی کردن زبان‌ها و ورودی...</translation>
+<translation id="8191230140820435481">مدیریت برنامه‌ها، افزونه‌ها و طرح‌های زمینه</translation>
 <translation id="8261506727792406068">حذف</translation>
 <translation id="8297006494302853456">ضعیف</translation>
 <translation id="8308637677604853869">منوی قبلی</translation>
diff --git a/ash/strings/ash_strings_fi.xtb b/ash/strings/ash_strings_fi.xtb
index 64772ebc..7d5e8fa6 100644
--- a/ash/strings/ash_strings_fi.xtb
+++ b/ash/strings/ash_strings_fi.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Ruutunäppäimistö otettiin käyttöön</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (ladataan…)</translation>
+<translation id="1841545962859478868">Laitteen järjestelmänvalvoja voi seurata näitä:</translation>
 <translation id="1850504506766569011">Wi-Fi ei ole käytössä.</translation>
 <translation id="1864454756846565995">C-tyypin USB-laite (takaportti)</translation>
 <translation id="1882897271359938046">Peilataan: <ph name="DISPLAY_NAME" /></translation>
@@ -328,6 +329,7 @@
 <translation id="7143207342074048698">Yhdistetään</translation>
 <translation id="7165278925115064263">Alt+Vaihto+K</translation>
 <translation id="7168224885072002358">Palautetaan vanha tarkkuus, aikaa palautukseen <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Muuta asetuksia, jotka määrittävät, voivatko sivustot käyttää tiettyjä ominaisuuksia, esimerkiksi maantieteellistä sijaintia, mikrofonia, kameraa jne.</translation>
 <translation id="7256634071279256947">Takamikrofoni</translation>
 <translation id="726276584504105859">Jaa näyttö vetämällä tähän.</translation>
 <translation id="7348093485538360975">Virtuaalinäppäimistö</translation>
@@ -344,6 +346,7 @@
 <translation id="7564874036684306347">Ikkunan siirtämisellä toiselle työpöydälle voi olla odottamattomia seurauksia. Myöhemmät ilmoitukset, ikkunat ja valintaikkunat voidaan jakaa työpöytien välillä.</translation>
 <translation id="7569509451529460200">Pistekirjoitus ja ChromeVox käytössä</translation>
 <translation id="7593891976182323525">Haku tai Shift</translation>
+<translation id="7604942372593434070">Tarkastele selaustoimintaasi</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (omistaja)</translation>
 <translation id="7647488630410863958">Katso ilmoitukset avaamalla laitteesi lukitus.</translation>
 <translation id="7649070708921625228">Ohje</translation>
@@ -358,6 +361,7 @@
 <translation id="7829386189513694949">Vahva signaali</translation>
 <translation id="7842569679327885685">Varoitus: Kokeellinen ominaisuus</translation>
 <translation id="7846634333498149051">Näppäimistö</translation>
+<translation id="790040513076446191">Muokata tietosuojaan liittyviä asetuksia</translation>
 <translation id="7904094684485781019">Tämän tilin järjestelmänvalvoja on estänyt useisiin tileihin kirjautumisen.</translation>
 <translation id="7933084174919150729">Google Assistant on käytettävissä vain ensisijaisella profiililla.</translation>
 <translation id="79341161159229895"><ph name="FIRST_PARENT_EMAIL" /> ja <ph name="SECOND_PARENT_EMAIL" /> hallinnoivat tiliä</translation>
@@ -376,6 +380,7 @@
 <translation id="8142699993796781067">Yksityinen verkko</translation>
 <translation id="8152119955266188852">Painoit koko näytön suurennuksen pikanäppäintä. Haluatko ottaa sen käyttöön?</translation>
 <translation id="8190698733819146287">Muokkaa kieliä ja syötettä...</translation>
+<translation id="8191230140820435481">Hallita sovelluksia, laajennuksia ja teemoja</translation>
 <translation id="8261506727792406068">Poista</translation>
 <translation id="8297006494302853456">Heikko</translation>
 <translation id="8308637677604853869">Edellinen valikko</translation>
diff --git a/ash/strings/ash_strings_fil.xtb b/ash/strings/ash_strings_fil.xtb
index 07a7b3f7..1a7a033f 100644
--- a/ash/strings/ash_strings_fil.xtb
+++ b/ash/strings/ash_strings_fil.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Naka-enable ang on-screen na keyboard</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (naglo-load...)</translation>
+<translation id="1841545962859478868">Maaaring subaybayan ng admin ng device ang mga sumusunod:</translation>
 <translation id="1850504506766569011">Naka-off ang Wi-Fi.</translation>
 <translation id="1864454756846565995">USB-C device (port sa rear)</translation>
 <translation id="1882897271359938046">Nagmi-mirror sa <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Kumokonekta</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Magre-revert sa lumang resolution sa loob ng <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipulahin ang mga setting na tumutukoy kung makakagamit ang mga website ng mga feature gaya ng geolocation, mikropono, camera, atbp.</translation>
 <translation id="7256634071279256947">Mikropono sa likod</translation>
 <translation id="726276584504105859">I-drag dito upang magamit ang split screen</translation>
 <translation id="7348093485538360975">Nasa screen na keyboard</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Kapag inilipat ang mga window sa isa pang desktop, maaari itong magresulta sa hindi inaasahang gawi. Maaaring hatiin ang mga susunod na notification, window, at dialog sa pagitan ng mga desktop.</translation>
 <translation id="7569509451529460200">Na-enable ang Braille at ChromeVox</translation>
 <translation id="7593891976182323525">Search or Shift</translation>
+<translation id="7604942372593434070">I-access ang iyong aktibidad sa pag-browse</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (may-ari)</translation>
 <translation id="7647488630410863958">I-unlock ang device upang tingnan ang iyong mga notification</translation>
 <translation id="7649070708921625228">Tulong</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Malakas ang signal</translation>
 <translation id="7842569679327885685">Babala: Pang-eskperimentong feature</translation>
 <translation id="7846634333498149051">Keyboard</translation>
+<translation id="790040513076446191">Manipulahin ang mga setting kaugnay ng privacy</translation>
 <translation id="7904094684485781019">Hindi pinayagan ng administrator para sa account na ito ang multiple na pag-sign in.</translation>
 <translation id="7933084174919150729">Available lang ang Google Assistant para sa pangunahing profile.</translation>
 <translation id="79341161159229895">Pinamamahalaan nina <ph name="FIRST_PARENT_EMAIL" /> at <ph name="SECOND_PARENT_EMAIL" /> ang account</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Pribadong network</translation>
 <translation id="8152119955266188852">Napindot mo ang shortcut para sa full-screen magnifier. Gusto mo ba itong i-on?</translation>
 <translation id="8190698733819146287">I-customize ang mga wika at input...</translation>
+<translation id="8191230140820435481">Pamahalaan ang iyong apps, mga extension, at tema</translation>
 <translation id="8261506727792406068">I-delete</translation>
 <translation id="8297006494302853456">Mahina</translation>
 <translation id="8308637677604853869">Nakaraang menu</translation>
diff --git a/ash/strings/ash_strings_fr.xtb b/ash/strings/ash_strings_fr.xtb
index 775c17c8..7d056f7 100644
--- a/ash/strings/ash_strings_fr.xtb
+++ b/ash/strings/ash_strings_fr.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Clavier à l'écran activé</translation>
 <translation id="1823873187264960516">Ethernet : <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (chargement…)</translation>
+<translation id="1841545962859478868">L'administrateur de cet appareil peut contrôler les éléments suivants :</translation>
 <translation id="1850504506766569011">Le Wi-Fi est désactivé.</translation>
 <translation id="1864454756846565995">Appareil USB de type C (port situé sur l'arrière de l'appareil)</translation>
 <translation id="1882897271359938046">Mise en miroir pour <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Connexion en cours</translation>
 <translation id="7165278925115064263">Alt+Maj+K</translation>
 <translation id="7168224885072002358">Rétablissement de la résolution précédente dans <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipule des paramètres qui déterminent si les sites Web peuvent utiliser des fonctionnalités telles que la géolocalisation, le microphone, l'appareil photo, etc.</translation>
 <translation id="7256634071279256947">Micro arrière</translation>
 <translation id="726276584504105859">Faire glisser ici pour utiliser l'écran partagé</translation>
 <translation id="7348093485538360975">Clavier virtuel</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Le déplacement de fenêtres vers un autre écran peut entraîner un comportement inattendu. Par exemple, les notifications, les fenêtres et les boîtes de dialogue peuvent s'afficher sur les deux écrans.</translation>
 <translation id="7569509451529460200">Braille et ChromeVox activés</translation>
 <translation id="7593891976182323525">Recherche ou Maj</translation>
+<translation id="7604942372593434070">Accède à votre activité de navigation</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (propriétaire)</translation>
 <translation id="7647488630410863958">Déverrouiller l'appareil pour consulter vos notifications</translation>
 <translation id="7649070708921625228">Aide</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Signal de forte intensité</translation>
 <translation id="7842569679327885685">Avertissement : Fonctionnalité expérimentale</translation>
 <translation id="7846634333498149051">Clavier</translation>
+<translation id="790040513076446191">Modifier les paramètres de confidentialité</translation>
 <translation id="7904094684485781019">L'administrateur de ce compte a désactivé la connexion multicompte.</translation>
 <translation id="7933084174919150729">L'Assistant Google n'est disponible que pour le profil principal.</translation>
 <translation id="79341161159229895">Compte géré par <ph name="FIRST_PARENT_EMAIL" /> et <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Réseau privé</translation>
 <translation id="8152119955266188852">Vous avez utilisé le raccourci de la loupe plein écran. Voulez-vous l'activer ?</translation>
 <translation id="8190698733819146287">Personnaliser les langues et la saisie...</translation>
+<translation id="8191230140820435481">Gérer vos applications, vos extensions et vos thèmes</translation>
 <translation id="8261506727792406068">Supprimer</translation>
 <translation id="8297006494302853456">Faible</translation>
 <translation id="8308637677604853869">Menu précédent</translation>
diff --git a/ash/strings/ash_strings_gu.xtb b/ash/strings/ash_strings_gu.xtb
index 031380d..513951b 100644
--- a/ash/strings/ash_strings_gu.xtb
+++ b/ash/strings/ash_strings_gu.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">ઓન-સ્ક્રીન કીબોર્ડ સક્ષમ કર્યું</translation>
 <translation id="1823873187264960516">ઇથરનેટ: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">સહાયક (લોડ થઈ રહ્યું છે…)</translation>
+<translation id="1841545962859478868">ઉપકરણ વ્યવસ્થાપક નીચે આપેલનું નિરીક્ષણ કરી શકે છે:</translation>
 <translation id="1850504506766569011">Wi-Fi બંધ છે.</translation>
 <translation id="1864454756846565995">USB-C ઉપકરણ (પાછળનું પોર્ટ)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> પર પ્રતિબિંબિત થઈ રહ્યું છે</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">કનેક્ટ કરી રહ્યું છે</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">જૂના રિઝોલ્યુશન પર પાછા ફરી રહ્યાં છે <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">વેબસાઇટ્સ ભૌગોલિક સ્થાન, માઇક્રોફોન, કૅમેરા, વગેરે જેવી સુવિધાઓને ઉપયોગમાં લઈ શકે છે કે કેમ તેનો ઉલ્લેખ કરતી સેટિંગ્સમાં ફેરફારો કરો.</translation>
 <translation id="7256634071279256947">પાછળનો માઇક્રોફોન</translation>
 <translation id="726276584504105859">વિભાજિત સ્ક્રીનનો ઉપયોગ કરવા માટે અહીં ખેંચો</translation>
 <translation id="7348093485538360975">ઑન-સ્ક્રીન કીબોર્ડ</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">વિંડોને બીજા ડેસ્કટૉપમાં ખસેડવું તે અનપેક્ષિત વર્તણૂકનું કારણ બની શકે છે. અનુગામી નોટિફિકેશન, વિંડો અને સંવાદો ડેસ્કટૉપ વચ્ચે વિભાજિત થઈ શકે છે.</translation>
 <translation id="7569509451529460200">બ્રેઇલ અને ChromeVox સક્ષમ કર્યાં</translation>
 <translation id="7593891976182323525">Search અથવા Shift</translation>
+<translation id="7604942372593434070">તમારી બ્રાઉઝિંગ પ્રવૃત્તિને ઍક્સેસ કરો</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (માલિક)</translation>
 <translation id="7647488630410863958">તમારી સૂચનાઓ જોવા માટે ઉપકરણને અનલૉક કરો</translation>
 <translation id="7649070708921625228">સહાય</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">પ્રબળ સિગ્નલ</translation>
 <translation id="7842569679327885685">ચેતવણી: પ્રાયોગિક સુવિધા</translation>
 <translation id="7846634333498149051">કીબોર્ડ</translation>
+<translation id="790040513076446191">ગોપનીયતા સંબંધિત સેટિંગ્સનો કૂશળતાપૂર્વક ઉપયોગ કરે છે.</translation>
 <translation id="7904094684485781019">આ એકાઉન્ટ માટે વ્યસ્થાપકે બહુવિધ સાઇન-ઇનને નામંજૂર કર્યું છે.</translation>
 <translation id="7933084174919150729">Google સહાયક માત્ર પ્રાથમિક પ્રોફાઇલ માટે જ ઉપલબ્ધ છે.</translation>
 <translation id="79341161159229895"><ph name="FIRST_PARENT_EMAIL" /> અને <ph name="SECOND_PARENT_EMAIL" /> દ્વારા મેનેજ કરાતું એકાઉન્ટ</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">ખાનગી નેટવર્ક</translation>
 <translation id="8152119955266188852">તમે પૂર્ણ-સ્ક્રીન મૅગ્નિફાયર માટેનો શૉર્ટકટ દબાવેલ છે. શું તમે તે ચાલુ કરવા માગો છો?</translation>
 <translation id="8190698733819146287">ભાષાઓ અને ઇનપુટને કસ્ટમાઇઝ કરો...</translation>
+<translation id="8191230140820435481">તમારી ઍપ્લિકેશનો, એક્સ્ટેન્શન અને થીમ્સ મેનેજ કરો</translation>
 <translation id="8261506727792406068">કાઢી નાખો</translation>
 <translation id="8297006494302853456">નબળું</translation>
 <translation id="8308637677604853869">પહેલાનું મેનૂ</translation>
diff --git a/ash/strings/ash_strings_hi.xtb b/ash/strings/ash_strings_hi.xtb
index 740e81bd..1627acb0 100644
--- a/ash/strings/ash_strings_hi.xtb
+++ b/ash/strings/ash_strings_hi.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">ऑन-स्‍क्रीन कीबोर्ड सक्षम है</translation>
 <translation id="1823873187264960516">ईथरनेट: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (सेवा लोड हो रही है...)</translation>
+<translation id="1841545962859478868">डिवाइस व्यवस्थापक इन चीज़ों की निगरानी कर सकता है:</translation>
 <translation id="1850504506766569011">वाई-फ़ाई  बंद है.</translation>
 <translation id="1864454756846565995">USB-C डिवाइस (पिछला पोर्ट)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> पर मिरर कर रहा है</translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">कनेक्‍ट हो रहा है</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> में पुराने रिज़ॉल्यूशन में वापस लौट रहा है</translation>
+<translation id="7228479291753472782">सेटिंग में यह निर्दिष्ट करने वाली हेरफेर करें कि क्‍या वेबसाइट भौगोलिक स्‍थान, माइक्रोफ़ोन, कैमरा आदि जैसी सुविधाओं का उपयोग कर सकती हैं.</translation>
 <translation id="7256634071279256947">पीछे वाला माइक्रोफ़ोन</translation>
 <translation id="726276584504105859">दो स्क्रीन का इस्तेमाल करने के लिए यहां खींचें और छोडें</translation>
 <translation id="7348093485538360975">ऑन-स्‍क्रीन कीबोर्ड</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">विंडो को किसी दूसरे डेस्कटॉप पर ले जाने से अनचाहा बर्ताव हो सकता है. इसके बाद की सूचनाएं, विंडो और संवाद डेस्कटॉप के बीच बंट सकती हैं.</translation>
 <translation id="7569509451529460200">ब्रेल और ChromeVox सक्षम हैं</translation>
 <translation id="7593891976182323525">Search या Shift</translation>
+<translation id="7604942372593434070">अपनी ब्राउज़िंग गतिविधि एक्सेस करें</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (मालिक)</translation>
 <translation id="7647488630410863958">अपने नोटिफ़िकेशन देखने के लिए डिवाइस अनलॉक करें</translation>
 <translation id="7649070708921625228">सहायता</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">मज़बूत सिग्नल</translation>
 <translation id="7842569679327885685">चेतावनी: यह फ़ीचर प्रयोग के लिए है</translation>
 <translation id="7846634333498149051">कीबोर्ड</translation>
+<translation id="790040513076446191">निजता-संबंधी सेटिंग में हेरफेर करें</translation>
 <translation id="7904094684485781019">इस खाते के व्यवस्थापक ने एकाधिक प्रवेश को अस्वीकार कर दिया है.</translation>
 <translation id="7933084174919150729">Google Assistant सिर्फ़ प्राथमिक प्रोफ़ाइल के लिए उपलब्ध है.</translation>
 <translation id="79341161159229895"><ph name="FIRST_PARENT_EMAIL" /> और <ph name="SECOND_PARENT_EMAIL" /> खाता संभालते हैं</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">निजी नेटवर्क</translation>
 <translation id="8152119955266188852">आपने 'सामग्री को फ़ुल-स्क्रीन पर बड़ा दिखाने वाली सेवा' का शॉर्टकट दबाया है. क्या आप इसे चालू करना चाहते हैं?</translation>
 <translation id="8190698733819146287">भाषाएं और इनपुट कस्टमाइज़ करें...</translation>
+<translation id="8191230140820435481">अपने ऐप्स , एक्सटेंशन, और थीम प्रबंधित करें</translation>
 <translation id="8261506727792406068">हटाएं</translation>
 <translation id="8297006494302853456">कमज़ोर</translation>
 <translation id="8308637677604853869">पिछला मेनू</translation>
diff --git a/ash/strings/ash_strings_hr.xtb b/ash/strings/ash_strings_hr.xtb
index a32adb6..9c7186f 100644
--- a/ash/strings/ash_strings_hr.xtb
+++ b/ash/strings/ash_strings_hr.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Tipkovnica na zaslonu omogućena je</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistent (učitavanje...)</translation>
+<translation id="1841545962859478868">Administrator uređaja može nadzirati sljedeće:</translation>
 <translation id="1850504506766569011">Wi-Fi je isključen.</translation>
 <translation id="1864454756846565995">USB-C uređaj (stražnji priključak)</translation>
 <translation id="1882897271359938046">Zrcaljenje na zaslon <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Povezivanje</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Vraćanje na staru razlučivost za <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Upravljajte postavkama koje određuju mogu li web-lokacije upotrebljavati značajke kao što su geolociranje, mikrofon, fotoaparat itd.</translation>
 <translation id="7256634071279256947">Stražnji mikrofon</translation>
 <translation id="726276584504105859">Povucite ovdje da biste upotrebljavali podijeljeni zaslon</translation>
 <translation id="7348093485538360975">Tipkovnica na zaslonu</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Premještanje prozora na drugu radnu površinu može dovesti do neočekivanog ponašanja. Daljnje obavijesti, prozori i dijaloški okviri mogu se podijeliti između radnih površina.</translation>
 <translation id="7569509451529460200">Brajica i ChromeVox omogućeni</translation>
 <translation id="7593891976182323525">Pretraživanje ili Shift</translation>
+<translation id="7604942372593434070">Pristup aktivnosti pregledavanja</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (vlasnik)</translation>
 <translation id="7647488630410863958">Otključajte uređaj da biste vidjeli obavijesti</translation>
 <translation id="7649070708921625228">Pomoć</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Jak signal</translation>
 <translation id="7842569679327885685">Upozorenje: eksperimentalna značajka</translation>
 <translation id="7846634333498149051">Tipkovnica</translation>
+<translation id="790040513076446191">rukovati postavkama koje se odnose na privatnost</translation>
 <translation id="7904094684485781019">Administrator ovog računa onemogućio je višestruku prijavu.</translation>
 <translation id="7933084174919150729">Google asistent dostupan je samo za primarni profil.</translation>
 <translation id="79341161159229895">Računom upravljaju <ph name="FIRST_PARENT_EMAIL" /> i <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privatna mreža</translation>
 <translation id="8152119955266188852">Pritisnuli ste prečac za povećalo za cijeli zaslon. Želite li ga uključiti?</translation>
 <translation id="8190698733819146287">Prilagodi jezike i unos...</translation>
+<translation id="8191230140820435481">upravljati vašim aplikacijama, proširenjima i temama</translation>
 <translation id="8261506727792406068">Izbriši</translation>
 <translation id="8297006494302853456">Slab</translation>
 <translation id="8308637677604853869">Prethodni izbornik</translation>
diff --git a/ash/strings/ash_strings_hu.xtb b/ash/strings/ash_strings_hu.xtb
index f595a3d5..59fbc4e 100644
--- a/ash/strings/ash_strings_hu.xtb
+++ b/ash/strings/ash_strings_hu.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Képernyő-billentyűzet bekapcsolva</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Segéd (betöltés…)</translation>
+<translation id="1841545962859478868">Az eszköz rendszergazdája figyelheti a következőket:</translation>
 <translation id="1850504506766569011">Wi-Fi kikapcsolva.</translation>
 <translation id="1864454756846565995">C típusú USB-vel kompatibilis eszköz (hátsó port)</translation>
 <translation id="1882897271359938046">Tükrözés: <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Csatlakozás</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Visszaállítás a régi felbontásra <ph name="TIMEOUT_SECONDS" /> mp múlva</translation>
+<translation id="7228479291753472782">Azon beállítások módosítása, amelyek meghatározzák, hogy a webhelyek használhatnak-e olyan funkciókat, mint a tartózkodási hely, a mikrofon, a webkamera stb.</translation>
 <translation id="7256634071279256947">Hátulsó mikrofon</translation>
 <translation id="726276584504105859">Húzza ide az osztott képernyő használatához</translation>
 <translation id="7348093485538360975">Képernyő-billentyűzet</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Az ablakok másik asztalra történő áthelyezése váratlan viselkedést eredményezhet. Az ezt követő értesítéseket, ablakokat és párbeszédpaneleket a rendszer megoszthatja az asztalok között.</translation>
 <translation id="7569509451529460200">Braille és ChromeVox bekapcsolva</translation>
 <translation id="7593891976182323525">Keresés vagy Shift</translation>
+<translation id="7604942372593434070">A böngészési tevékenység elérése</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (tulajdonos)</translation>
 <translation id="7647488630410863958">Az eszköz zárolásának feloldása az értesítések megtekintéséhez</translation>
 <translation id="7649070708921625228">Súgó</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Erős jel</translation>
 <translation id="7842569679327885685">Figyelem! Kísérleti funkció</translation>
 <translation id="7846634333498149051">Billentyűzet</translation>
+<translation id="790040513076446191">Adatvédelemmel kapcsolatos beállítások módosítása</translation>
 <translation id="7904094684485781019">A fiók rendszergazdája letiltotta a többfiókos bejelentkezést.</translation>
 <translation id="7933084174919150729">A Google Segéd csak az elsődleges profilhoz áll rendelkezésre.</translation>
 <translation id="79341161159229895">A fiókot a(z) <ph name="FIRST_PARENT_EMAIL" /> és a(z) <ph name="SECOND_PARENT_EMAIL" /> kezeli</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Privát hálózat</translation>
 <translation id="8152119955266188852">Lenyomta a teljes képernyős nagyító billentyűparancsát. Bekapcsolja a funkciót?</translation>
 <translation id="8190698733819146287">Nyelvek és beviteli módok személyre szabása...</translation>
+<translation id="8191230140820435481">Alkalmazások, bővítmények és témák kezelése</translation>
 <translation id="8261506727792406068">Törlés</translation>
 <translation id="8297006494302853456">Gyenge</translation>
 <translation id="8308637677604853869">Előző menü</translation>
diff --git a/ash/strings/ash_strings_id.xtb b/ash/strings/ash_strings_id.xtb
index bd5ddf94..2e88e267 100644
--- a/ash/strings/ash_strings_id.xtb
+++ b/ash/strings/ash_strings_id.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Keyboard di layar diaktifkan</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asisten (sedang dimuat...)</translation>
+<translation id="1841545962859478868">Admin perangkat dapat memantau hal-hal berikut:</translation>
 <translation id="1850504506766569011">Wi-Fi dinonaktifkan.</translation>
 <translation id="1864454756846565995">Perangkat USB-C (port belakang)</translation>
 <translation id="1882897271359938046">Mencerminkan ke <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Menyambungkan</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Mengembalikan ke resolusi lama dalam <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Memanipulasi setelan yang menentukan apakah situs web dapat menggunakan fitur seperti geolokasi, mikrofon, kamera, dll.</translation>
 <translation id="7256634071279256947">Mikrofon belakang</translation>
 <translation id="726276584504105859">Tarik ke sini untuk menggunakan layar terpisah</translation>
 <translation id="7348093485538360975">Keyboard di layar</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Memindahkan jendela ke desktop lain dapat mengakibatkan perilaku tak terduga. Notifikasi, jendela, dan dialog berikutnya dapat terpisah antardesktop.</translation>
 <translation id="7569509451529460200">Braille dan ChromeVox diaktifkan</translation>
 <translation id="7593891976182323525">Telusuri atau Shift</translation>
+<translation id="7604942372593434070">Mengakses aktivitas akses internet Anda</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (pemilik)</translation>
 <translation id="7647488630410863958">Buka kunci perangkat untuk melihat notifikasi</translation>
 <translation id="7649070708921625228">Bantuan</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Sinyal kuat</translation>
 <translation id="7842569679327885685">Peringatan: Fitur eksperimental</translation>
 <translation id="7846634333498149051">Keyboard</translation>
+<translation id="790040513076446191">Memanipulasi setelan yang terkait privasi</translation>
 <translation id="7904094684485781019">Administrator untuk akun ini menonaktifkan fitur masuk banyak akun.</translation>
 <translation id="7933084174919150729">Asisten Google hanya tersedia untuk profil utama.</translation>
 <translation id="79341161159229895">Akun dikelola oleh <ph name="FIRST_PARENT_EMAIL" /> dan <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Jaringan pribadi</translation>
 <translation id="8152119955266188852">Anda menekan pintasan untuk kaca pembesar layar penuh. Ingin mengaktifkannya?</translation>
 <translation id="8190698733819146287">Sesuaikan bahasa dan masukan...</translation>
+<translation id="8191230140820435481">Mengelola aplikasi, ekstensi, dan tema Anda</translation>
 <translation id="8261506727792406068">Hapus</translation>
 <translation id="8297006494302853456">Lemah</translation>
 <translation id="8308637677604853869">Menu sebelumnya</translation>
diff --git a/ash/strings/ash_strings_it.xtb b/ash/strings/ash_strings_it.xtb
index b49d098..adf886c 100644
--- a/ash/strings/ash_strings_it.xtb
+++ b/ash/strings/ash_strings_it.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Tastiera sullo schermo attiva</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistente (caricamento…)</translation>
+<translation id="1841545962859478868">L'amministratore del dispositivo potrebbe monitorare i seguenti elementi:</translation>
 <translation id="1850504506766569011">Wi-Fi non attivo.</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (porta posteriore)</translation>
 <translation id="1882897271359938046">Mirroring su <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Connessione</translation>
 <translation id="7165278925115064263">ALT + MAIUSC + K</translation>
 <translation id="7168224885072002358">Ripristino della risoluzione precedente tra <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipola le impostazioni che specificano se i siti web possono utilizzare funzioni come geolocalizzazione, microfono, fotocamera e così via.</translation>
 <translation id="7256634071279256947">Microfono posteriore</translation>
 <translation id="726276584504105859">Trascina qui per utilizzare la modalità Schermo diviso</translation>
 <translation id="7348093485538360975">Tastiera sullo schermo</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Lo spostamento delle finestre su un altro desktop potrebbe causare comportamenti imprevisti. Le finestre, le notifiche e le finestre di dialogo successive potrebbero essere divise tra i desktop.</translation>
 <translation id="7569509451529460200">Braille e ChromeVox attivati</translation>
 <translation id="7593891976182323525">Tasto per la ricerca o Maiusc</translation>
+<translation id="7604942372593434070">Accedi alla tua attività di navigazione</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (proprietario)</translation>
 <translation id="7647488630410863958">Sblocca il dispositivo per visualizzare le notifiche</translation>
 <translation id="7649070708921625228">Guida</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Segnale forte</translation>
 <translation id="7842569679327885685">Avviso: funzione sperimentale</translation>
 <translation id="7846634333498149051">Tastiera</translation>
+<translation id="790040513076446191">Modifica delle impostazioni relative alla privacy</translation>
 <translation id="7904094684485781019">L'amministratore di questo account ha bloccato l'accesso simultaneo.</translation>
 <translation id="7933084174919150729">L'Assistente Google è disponibile solo per il profilo principale.</translation>
 <translation id="79341161159229895">Account gestito da <ph name="FIRST_PARENT_EMAIL" /> e <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Rete privata</translation>
 <translation id="8152119955266188852">Hai premuto la scorciatoia per la lente d'ingrandimento a schermo intero. Vuoi attivarla?</translation>
 <translation id="8190698733819146287">Personalizza lingue e immissione...</translation>
+<translation id="8191230140820435481">Gestire applicazioni, estensioni e temi</translation>
 <translation id="8261506727792406068">Elimina</translation>
 <translation id="8297006494302853456">Debole</translation>
 <translation id="8308637677604853869">Menu precedente</translation>
diff --git a/ash/strings/ash_strings_iw.xtb b/ash/strings/ash_strings_iw.xtb
index 1e95b12..7a4efea 100644
--- a/ash/strings/ash_strings_iw.xtb
+++ b/ash/strings/ash_strings_iw.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">המקלדת שמופיעה במסך מופעלת</translation>
 <translation id="1823873187264960516">אתרנט: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">‏Assistant (בטעינה...)</translation>
+<translation id="1841545962859478868">ייתכן שמנהל המכשיר עוקב אחרי:</translation>
 <translation id="1850504506766569011">‏Wi-Fi כבוי.</translation>
 <translation id="1864454756846565995">‏מכשיר עם יציאת USB-C (יציאה אחורית)</translation>
 <translation id="1882897271359938046">משקף אל <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">מתחבר</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">חוזר לרזולוציה הקודמת בעוד <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">שינוי הגדרות שמציינות אם אתרים יכולים להשתמש בתכונות כגון מיקום גיאוגרפי, מיקרופון, מצלמה ועוד</translation>
 <translation id="7256634071279256947">מיקרופון אחורי</translation>
 <translation id="726276584504105859">לשימוש במסך מפוצל יש לגרור לכאן</translation>
 <translation id="7348093485538360975">מקלדת על המסך</translation>
@@ -343,6 +345,7 @@
 ייתכן שהודעות, חלונות ותיבות דו-שיח יתפצלו בהמשך בין שולחנות עבודה.</translation>
 <translation id="7569509451529460200">‏כתב ברייל ו-ChromeVox מופעלים</translation>
 <translation id="7593891976182323525">‏חיפוש או Shift</translation>
+<translation id="7604942372593434070">כניסה לפעילות הגלישה שלך</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (בעלים)</translation>
 <translation id="7647488630410863958">בטל את נעילת המכשיר כדי להציג את ההודעות</translation>
 <translation id="7649070708921625228">עזרה</translation>
@@ -357,6 +360,7 @@
 <translation id="7829386189513694949">אות חזק</translation>
 <translation id="7842569679327885685">אזהרה: תכונה ניסיונית</translation>
 <translation id="7846634333498149051">מקלדת</translation>
+<translation id="790040513076446191">לשנות הגדרות הקשורות לפרטיות</translation>
 <translation id="7904094684485781019">מנהל החשבון הזה אסר על כניסה עם מספר חשבונות.</translation>
 <translation id="7933084174919150729">‏Google Assistant זמין רק לפרופיל ראשי.</translation>
 <translation id="79341161159229895">החשבון מנוהל על-ידי <ph name="FIRST_PARENT_EMAIL" /> ועל-ידי <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -375,6 +379,7 @@
 <translation id="8142699993796781067">רשת פרטית</translation>
 <translation id="8152119955266188852">לחצת על מקש הקיצור של זכוכית מגדלת במסך מלא. להפעיל אותה?</translation>
 <translation id="8190698733819146287">התאם אישית שפה וקלט...</translation>
+<translation id="8191230140820435481">לנהל את היישומים, התוספים והעיצובים שלך</translation>
 <translation id="8261506727792406068">מחיקה</translation>
 <translation id="8297006494302853456">חלש</translation>
 <translation id="8308637677604853869">התפריט הקודם</translation>
diff --git a/ash/strings/ash_strings_ja.xtb b/ash/strings/ash_strings_ja.xtb
index cadfb0e..07432fa 100644
--- a/ash/strings/ash_strings_ja.xtb
+++ b/ash/strings/ash_strings_ja.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">画面キーボードが有効です</translation>
 <translation id="1823873187264960516">イーサネット: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">アシスタント(読み込み中...)</translation>
+<translation id="1841545962859478868">デバイス管理者が次の情報を監視している可能性があります。</translation>
 <translation id="1850504506766569011">Wi-Fi が無効になりました。</translation>
 <translation id="1864454756846565995">USB-C デバイス(背面のポート)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> へミラーリング</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">接続中</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> 秒後に元の解像度に戻ります</translation>
+<translation id="7228479291753472782">ウェブサイトが位置情報やマイク、カメラなどの機能を使用できるかどうかの設定の操作</translation>
 <translation id="7256634071279256947">後方のマイク</translation>
 <translation id="726276584504105859">分割画面を使用するにはここにドラッグします</translation>
 <translation id="7348093485538360975">画面キーボード</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">ウィンドウを別のデスクトップに移動すると、予期しない動作が起こることがあります。以降の通知、ウィンドウ、ダイアログは、複数のデスクトップに分かれて表示される可能性があります。</translation>
 <translation id="7569509451529460200">Braille と ChromeVox が有効になっています</translation>
 <translation id="7593891976182323525">検索/Shift</translation>
+<translation id="7604942372593434070">閲覧アクティビティへのアクセス</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" />(所有者)</translation>
 <translation id="7647488630410863958">デバイスのロックを解除して通知を確認してください</translation>
 <translation id="7649070708921625228">ヘルプ</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">電波: 強い</translation>
 <translation id="7842569679327885685">警告: 試験運用版の機能</translation>
 <translation id="7846634333498149051">キーボード</translation>
+<translation id="790040513076446191">プライバシー関連設定を操作する</translation>
 <translation id="7904094684485781019">このアカウントの管理者がマルチ ログインを許可していません。</translation>
 <translation id="7933084174919150729">Google アシスタントはメインのプロフィールでのみご利用いただけます。</translation>
 <translation id="79341161159229895">このアカウントは <ph name="FIRST_PARENT_EMAIL" /> と <ph name="SECOND_PARENT_EMAIL" /> により管理されています</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">プライベート ネットワーク</translation>
 <translation id="8152119955266188852">拡大鏡(全画面)のショートカットを押しました。この機能をオンにしますか?</translation>
 <translation id="8190698733819146287">言語と入力方法をカスタマイズ...</translation>
+<translation id="8191230140820435481">アプリ、拡張機能、テーマを管理する</translation>
 <translation id="8261506727792406068">削除</translation>
 <translation id="8297006494302853456">弱い</translation>
 <translation id="8308637677604853869">前のメニュー</translation>
diff --git a/ash/strings/ash_strings_kn.xtb b/ash/strings/ash_strings_kn.xtb
index a4c330a..c1aea83 100644
--- a/ash/strings/ash_strings_kn.xtb
+++ b/ash/strings/ash_strings_kn.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">ಆನ್ ಸ್ಕ್ರೀನ್ ಕೀಬೋರ್ಡ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ</translation>
 <translation id="1823873187264960516">ಇಥರ್ನೆಟ್: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">ಸಹಾಯಕ (ಲೋಡ್ ಆಗುತ್ತಿದೆ...)</translation>
+<translation id="1841545962859478868">ಸಾಧನದ ನಿರ್ವಾಹಕರು ಕೆಳಗಿನದನ್ನು ಮೇಲ್ವಿಚಾರಣೆ ಮಾಡಬಹುದು:</translation>
 <translation id="1850504506766569011">ವೈ-ಫೈ ಆಫ್ ಮಾಡಲಾಗಿದೆ.</translation>
 <translation id="1864454756846565995">USB-C ಸಾಧನ (ಹಿಂದಿನ ಪೋರ್ಟ್)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> ಗೆ ಪ್ರತಿಬಿಂಬಿಸುತ್ತಿದೆ</translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">ಸಂಪರ್ಕಿಸಲಾಗುತ್ತಿದೆ</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> ನಲ್ಲಿ ಹಳೆಯ ರೆಸಲ್ಯೂಷನ್‌ಗೆ ಹಿಂತಿರುಗಿಸಲಾಗುತ್ತಿದೆ</translation>
+<translation id="7228479291753472782">ಜಿಯೋಲೋಕೇಶನ್, ಮೈಕ್ರೊಫೋನ್, ಕ್ಯಾಮರಾ, ಇತ್ಯಾದಿಯಂತೆ ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ ಹವಾಮಾನ ವೆಬ್‌ಸೈಟ್‌ಗಳ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಬಳಸಬಹುದು.</translation>
 <translation id="7256634071279256947">ಹಿಂಭಾಗದ ಮೈಕ್ರೊಫೋನ್</translation>
 <translation id="726276584504105859">ವಿಭಜಿತ ಪರದೆಯನ್ನು ಬಳಸಲು ಇಲ್ಲಿ ಡ್ರ್ಯಾಗ್‌ ಮಾಡಿ</translation>
 <translation id="7348093485538360975">ಆನ್ ಸ್ಕ್ರೀನ್ ಕೀಬೋರ್ಡ್</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">ವಿಂಡೋಗಳನ್ನು ಬೇರೊಂದು ಡೆಸ್ಕ್‌ಟಾಪ್‌ಗೆ ಸರಿಸಿದರೆ, ಅದು ಅನಿರೀಕ್ಷಿತ ವರ್ತನೆಗೆ ಕಾರಣವಾಗಬಹುದು. ಆನಂತರ ಬರುವ ಅಧಿಸೂಚನೆಗಳು, ವಿಂಡೋಗಳು ಮತ್ತು ಡೈಲಾಗ್‌ಗಳು ಡೆಸ್ಕ್‌ಟಾಪ್‌ಗಳ ನಡುವೆ ವಿಭಜನೆಯಾಗಬಹುದು.</translation>
 <translation id="7569509451529460200">ಬ್ರೈಲ್ ಹಾಗೂ ChromeVox ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ</translation>
 <translation id="7593891976182323525">ಹುಡುಕಾಟ ಅಥವಾ Shift</translation>
+<translation id="7604942372593434070">ನಿಮ್ಮ ಬ್ರೌಸಿಂಗ್ ಚಟುವಟಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಿ</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (ಮಾಲೀಕರು)</translation>
 <translation id="7647488630410863958">ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಿ</translation>
 <translation id="7649070708921625228">ಸಹಾಯ</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">ಪ್ರಬಲ ಸಿಗ್ನಲ್</translation>
 <translation id="7842569679327885685">ಎಚ್ಚರಿಕೆ: ಪ್ರಾಯೋಗಿಕ ವೈಶಿಷ್ಟ್ಯ</translation>
 <translation id="7846634333498149051">ಕೀಬೋರ್ಡ್</translation>
+<translation id="790040513076446191">ಗೌಪ್ಯತೆಗೆ- ಸಂಬಂಧಿಸಿದ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಸ್ವಾಧೀನಪಡಿಸಿಕೊಳ್ಳಿ</translation>
 <translation id="7904094684485781019">ಈ ಖಾತೆಗಾಗಿ ನಿರ್ವಾಹಕರು ಬಹುವಿಧದ ಸೈನ್ ಇನ್ ಅನುಮತಿಸಿಲ್ಲ.</translation>
 <translation id="7933084174919150729">ಪ್ರಾಥಮಿಕ ಪ್ರೊಫೈಲ್‌ಗೆ ಮಾತ್ರ Google ಸಹಾಯಕ ಲಭ್ಯವಿದೆ.</translation>
 <translation id="79341161159229895"><ph name="FIRST_PARENT_EMAIL" /> ಮತ್ತು <ph name="SECOND_PARENT_EMAIL" /> ಮೂಲಕ ಖಾತೆಯನ್ನು ನಿರ್ವಹಿಸಲಾಗಿದೆ</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">ಖಾಸಗಿ ನೆಟ್‌ವರ್ಕ್‌</translation>
 <translation id="8152119955266188852">ನೀವು ಪೂರ್ಣಪರದೆ ವರ್ಧಕದ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಒತ್ತಿದ್ದೀರಿ. ಅದನ್ನು ಆನ್ ಮಾಡಲು ಬಯಸುವಿರಾ?</translation>
 <translation id="8190698733819146287">ಭಾಷೆಗಳು ಮತ್ತು ಇನ್‌ಪುಟ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ...</translation>
+<translation id="8191230140820435481">ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು, ವಿಸ್ತರಣೆಗಳು, ಮತ್ತು ಥೀಮ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ</translation>
 <translation id="8261506727792406068">ಅಳಿಸಿ</translation>
 <translation id="8297006494302853456">ದುರ್ಬಲ</translation>
 <translation id="8308637677604853869">ಹಿಂದಿನ ಮೆನು</translation>
diff --git a/ash/strings/ash_strings_ko.xtb b/ash/strings/ash_strings_ko.xtb
index d8dc688..4668139 100644
--- a/ash/strings/ash_strings_ko.xtb
+++ b/ash/strings/ash_strings_ko.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">터치 키보드 사용 설정됨</translation>
 <translation id="1823873187264960516">이더넷: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">어시스턴트(로드 중...)</translation>
+<translation id="1841545962859478868">기기 관리자가 다음을 모니터링할 수 있습니다.</translation>
 <translation id="1850504506766569011">Wi-Fi가 꺼져 있습니다.</translation>
 <translation id="1864454756846565995">USB-C 기기(후면 포트)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" />에 미러링</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">연결 중</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" />초 후 기존 해상도로 돌아갑니다.</translation>
+<translation id="7228479291753472782">웹사이트에서 위치정보, 마이크, 카메라 등과 같은 기능을 사용할 수 있도록 할지 여부를 지정하는 설정을 조작합니다.</translation>
 <translation id="7256634071279256947">후면 마이크</translation>
 <translation id="726276584504105859">화면 분할을 사용하려면 여기를 드래그하세요.</translation>
 <translation id="7348093485538360975">터치 키보드</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">창을 다른 데스크톱으로 이동하면 예기치 못한 동작이 나타날 수 있습니다. 이후의 알림, 창, 대화상자가 데스크톱 간에 분할될 수 있습니다.</translation>
 <translation id="7569509451529460200">점자와 ChromeVox가 사용 설정되었습니다.</translation>
 <translation id="7593891976182323525">검색 또는 Shift 키</translation>
+<translation id="7604942372593434070">탐색 활동에 액세스</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" />(소유자)</translation>
 <translation id="7647488630410863958">기기를 잠금 해제하여 알림 보기</translation>
 <translation id="7649070708921625228">도움말</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">신호 강함</translation>
 <translation id="7842569679327885685">경고: 실험 기능</translation>
 <translation id="7846634333498149051">키보드</translation>
+<translation id="790040513076446191">개인정보 보호 관련 설정 조정</translation>
 <translation id="7904094684485781019">이 계정의 관리자가 멀티 로그인을 허용하지 않습니다.</translation>
 <translation id="7933084174919150729">Google 어시스턴트는 기본 프로필에서만 사용할 수 있습니다.</translation>
 <translation id="79341161159229895">계정 관리자: <ph name="FIRST_PARENT_EMAIL" /> 및 <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">사설 네트워크</translation>
 <translation id="8152119955266188852">전체 화면 돋보기 단축키를 누르셨습니다. 사용 설정할까요?</translation>
 <translation id="8190698733819146287">언어 및 입력 설정...</translation>
+<translation id="8191230140820435481">앱, 확장 프로그램 및 테마 관리</translation>
 <translation id="8261506727792406068">삭제</translation>
 <translation id="8297006494302853456">약함</translation>
 <translation id="8308637677604853869">이전 메뉴</translation>
diff --git a/ash/strings/ash_strings_lt.xtb b/ash/strings/ash_strings_lt.xtb
index 45e0df0..6c0816b9 100644
--- a/ash/strings/ash_strings_lt.xtb
+++ b/ash/strings/ash_strings_lt.xtb
@@ -7,6 +7,7 @@
 <translation id="1037492556044956303">„<ph name="DEVICE_NAME" />“ pridėtas</translation>
 <translation id="1056775291175587022">Nėra tinklų</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation>
+<translation id="1104084341931202936">Rodyti pritaikymo neįgaliesiems nustatymus</translation>
 <translation id="1104621072296271835">Jūsų įrenginiai kartu veikia dar geriau</translation>
 <translation id="112308213915226829">Automatiškai slėpti lentyną</translation>
 <translation id="1153356358378277386">Susieti įrenginiai</translation>
@@ -20,17 +21,20 @@
 <translation id="1279938420744323401"><ph name="DISPLAY_NAME" /> (<ph name="ANNOTATION" />)</translation>
 <translation id="1290331692326790741">Silpnas signalas</translation>
 <translation id="1293264513303784526">USB-C įrenginys (prievadas kairėje)</translation>
+<translation id="1302880136325416935">Rodyti „Bluetooth“ nustatymus. <ph name="STATE_TEXT" /></translation>
 <translation id="1346748346194534595">Dešinė</translation>
 <translation id="1351937230027495976">Sutraukti meniu</translation>
 <translation id="1383876407941801731">Ieškoti</translation>
 <translation id="1467432559032391204">Kairė</translation>
 <translation id="1484102317210609525"><ph name="DEVICE_NAME" /> (HDMI / DP)</translation>
 <translation id="1510238584712386396">Paleidimo priemonė</translation>
+<translation id="1520303207432623762">{NUM_APPS,plural, =1{Rodyti pranešimų nustatymus. Programos pranešimai išjungti}one{Rodyti pranešimų nustatymus. # programos pranešimai išjungti}few{Rodyti pranešimų nustatymus. # programų pranešimai išjungti}many{Rodyti pranešimų nustatymus. # programos pranešimai išjungti}other{Rodyti pranešimų nustatymus. # programų pranešimai išjungti}}</translation>
 <translation id="1525508553941733066">ATSISAKYTI</translation>
 <translation id="1537254971476575106">Viso ekrano didintuvas</translation>
 <translation id="15373452373711364">Didelis pelės žymeklis</translation>
 <translation id="1550523713251050646">Spustelėkite, jei reikia daugiau parinkčių</translation>
 <translation id="1567387640189251553">Po to, kai paskutinį kartą įvedėte slaptažodį, buvo prijungta kita klaviatūra. Gali būti bandoma pavogti jūsų klavišų paspaudimus.</translation>
+<translation id="1570871743947603115">Perjungti „Bluetooth“. <ph name="STATE_TEXT" /></translation>
 <translation id="1608626060424371292">Pašalinti šį naudotoją</translation>
 <translation id="1621499497873603021">Laikas, likęs iki akumuliatoriaus išsikrovimo: <ph name="TIME_LEFT" /></translation>
 <translation id="1658406695958299976">Deja, jūsų slaptažodžio patvirtinti vis tiek nepavyko. Pastaba: jei neseniai pakeitėte slaptažodį, naujas slaptažodis bus taikomas, kai atsijungsite. Čia naudokite senąjį slaptažodį.</translation>
@@ -40,8 +44,10 @@
 <translation id="1743570585616704562">Neatpažinta</translation>
 <translation id="1746730358044914197">Įvesties metodus konfigūruoja jūsų administratorius.</translation>
 <translation id="1747827819627189109">Ekrano klaviatūra įgalinta</translation>
+<translation id="1761222317188459878">Perjungti tinklo ryšį. <ph name="STATE_TEXT" /></translation>
 <translation id="1823873187264960516">Eternetas: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Padėjėjas (įkeliama...)</translation>
+<translation id="1841545962859478868">Įrenginio administratorius gali stebėti:</translation>
 <translation id="1850504506766569011">„Wi-Fi“ ryšys išjungtas.</translation>
 <translation id="1864454756846565995">USB-C įrenginys (prievadas gale)</translation>
 <translation id="1882897271359938046">Dubliuojama <ph name="DISPLAY_NAME" /></translation>
@@ -62,6 +68,7 @@
 <translation id="2049639323467105390">Įrenginys valdomas „<ph name="DOMAIN" />“.</translation>
 <translation id="2050339315714019657">Stačias</translation>
 <translation id="2067602449040652523">Klaviatūros šviesumas</translation>
+<translation id="2075212959500165896">Per daug bandymų. Vėliau bandykite dar kartą.</translation>
 <translation id="2081529251031312395">$1 vis tiek galės vėliau prisijungti.</translation>
 <translation id="2127372758936585790">Mažos galios įkroviklis</translation>
 <translation id="2135456203358955318">Prie doko prijungtas didintuvas</translation>
@@ -75,12 +82,15 @@
 <translation id="2303600792989757991">Perjungti lango apžvalgą</translation>
 <translation id="2338501278241028356">Įjunkite „Bluetooth“, kad aptiktumėte įrenginius netoliese</translation>
 <translation id="2339073806695260576">Palieskite rašiklio mygtuką lentynoje, kad galėtumėte sukurti užrašą, ekrano kopiją, naudoti lazerinį žymeklį arba didinamąjį stiklą.</translation>
+<translation id="2341729377289034582">Užrakinta vertikali padėtis</translation>
 <translation id="2352467521400612932">Rašiklio nustatymai</translation>
 <translation id="2354174487190027830">Aktyvinamas „<ph name="NAME" />“</translation>
 <translation id="2359808026110333948">Tęsti</translation>
 <translation id="2365393535144473978">Įgalinus mobiliojo ryšio duomenis bus įgalintas „Bluetooth“.</translation>
 <translation id="2391579633712104609">180°</translation>
+<translation id="239188844683466770">Perjungti netrukdymo režimą</translation>
 <translation id="2412593942846481727">Pasiekiamas naujinys</translation>
+<translation id="2416346634399901812">Prisijungta prie „<ph name="NETWORK_NAME" />“</translation>
 <translation id="2429753432712299108">„Bluetooth“ įrenginys „<ph name="DEVICE_NAME" />“ prašo leidimo susieti. Prieš sutikdami, patvirtinkite, kad šiame įrenginyje rodomas šis slaptasis raktas: <ph name="PASSKEY" /></translation>
 <translation id="2482878487686419369">Pranešimai</translation>
 <translation id="2484513351006226581">Paspauskite <ph name="KEYBOARD_SHORTCUT" />, kad perjungtumėte klaviatūros išdėstymą.</translation>
@@ -113,6 +123,7 @@
 <translation id="2946119680249604491">Pridėti ryšį</translation>
 <translation id="2961963223658824723">Deja, įvyko klaida. Bandykite dar kartą po kelių sekundžių.</translation>
 <translation id="2963773877003373896">mod3</translation>
+<translation id="2995447421581609334">Rodyti perdavimo įrenginius.</translation>
 <translation id="2996462380875591307">Prie doko prijungtas didintuvas įgalintas. Dar kartą paspauskite „Ctrl“ + paieškos klavišą + D, kad išjungtumėte režimą.</translation>
 <translation id="2999742336789313416"><ph name="DISPLAY_NAME" /> yra vieša sesija, valdoma <ph name="DOMAIN" /></translation>
 <translation id="3000461861112256445">Monofoninis garsas</translation>
@@ -127,6 +138,7 @@
 <translation id="315116470104423982">Duomenys mobiliesiems</translation>
 <translation id="3151786313568798007">Orientacija</translation>
 <translation id="3153444934357957346">Naudodami kelių paskyrų funkciją, galite turėti iki <ph name="MULTI_PROFILE_USER_LIMIT" /> paskyr.</translation>
+<translation id="3202010236269062730">{NUM_DEVICES,plural, =1{Prisijungta prie įrenginio}one{Prisijungta prie # įrenginio}few{Prisijungta prie # įrenginių}many{Prisijungta prie # įrenginio}other{Prisijungta prie # įrenginių}}</translation>
 <translation id="3236488194889173876">Nėra jokių pasiekiamų mobiliojo ryšio tinklų</translation>
 <translation id="3238765806255363838"><ph name="USERNAME" />, <ph name="MAIL" /></translation>
 <translation id="3294437725009624529">Svečias</translation>
@@ -158,6 +170,7 @@
 <translation id="3784455785234192852">Užrakinti</translation>
 <translation id="3798670284305777884">Garsiakalbis (vidinis)</translation>
 <translation id="380165613292957338">Sveiki, kuo galiu padėti?</translation>
+<translation id="383629559565718788">Rodyti klaviatūros nustatymus</translation>
 <translation id="3846575436967432996">Nėra tinklo informacijos</translation>
 <translation id="385051799172605136">Grįžti</translation>
 <translation id="3891340733213178823">Jei norite atsijungti, du kartus paspauskite „Ctrl“ + „Shift“ + Q.</translation>
@@ -173,8 +186,10 @@
 <translation id="4072264167173457037">Vidutinio stiprumo signalas</translation>
 <translation id="4200057768455216496">Paspaudėte prie doko prijungto didintuvo spartųjį klavišą. Ar norite jį įjungti?</translation>
 <translation id="4217571870635786043">Diktavimas</translation>
+<translation id="4261870227682513959">Rodyti pranešimų nustatymus. Pranešimai išjungti</translation>
 <translation id="4274921305979314545">Susiekite „Chromebook“ su telefonu</translation>
 <translation id="4279490309300973883">Dubliuojama</translation>
+<translation id="4292681942966152062">Suaktyvinamas „<ph name="NETWORK_NAME" />“</translation>
 <translation id="4321179778687042513">ctrl</translation>
 <translation id="4331809312908958774">Chrome OS
 </translation>
@@ -239,6 +254,7 @@
 <translation id="5673434351075758678">Po nustatymų sinchronizavimo kalba pakeista iš „<ph name="FROM_LOCALE" />“ į „<ph name="TO_LOCALE" />“.</translation>
 <translation id="574392208103952083">Vidutinis</translation>
 <translation id="5744083938413354016">Vilkimas palietus</translation>
+<translation id="5750765938512549687">„Bluetooth“ išjungtas</translation>
 <translation id="5777841717266010279">Nutraukti ekrano bendrinimą?</translation>
 <translation id="57838592816432529">Nutildyti</translation>
 <translation id="5805697420284793859">Langų tvarkytuvė</translation>
@@ -272,12 +288,14 @@
 <translation id="615957422585914272">Rodyti ekrano klaviatūrą</translation>
 <translation id="6164005077879661055">Visi failai ir su prižiūrimu naudotoju susiję duomenys bus ištrinti visam laikui, kai bus pašalintas šis prižiūrimas naudotojas. Valdytojas vis tiek gali peržiūrėti šio prižiūrimo naudotojo aplankytas svetaines ir nustatymus adresu <ph name="MANAGEMENT_URL" />.</translation>
 <translation id="6165508094623778733">Sužinokite daugiau</translation>
+<translation id="6254629735336163724">Užrakinta horizontali padėtis</translation>
 <translation id="6259254695169772643">Pasirinkite naudodami rašiklį</translation>
 <translation id="6267036997247669271">„<ph name="NAME" />“: aktyvinama...</translation>
 <translation id="6284232397434400372">Skyra pakeista</translation>
 <translation id="6297287540776456956">Naudodami rašiklį pasirinkite sritį</translation>
 <translation id="6310121235600822547"><ph name="DISPLAY_NAME" /> pakeista į <ph name="ROTATION" /></translation>
 <translation id="632744581670418035">Klaviatūros perdanga</translation>
+<translation id="6376931439017688372">„Bluetooth“ įjungtas</translation>
 <translation id="639644700271529076">DIDŽIŲJŲ RAIDŽIŲ RAŠYMAS išjungtas</translation>
 <translation id="6406704438230478924">altgr</translation>
 <translation id="643147933154517414">Viskas atlikta</translation>
@@ -321,11 +339,13 @@
 <translation id="7066646422045619941">Šį tinklą išjungė jūsų administratorius.</translation>
 <translation id="7067196344162293536">Automatinis kaitaliojimas</translation>
 <translation id="7076293881109082629">Prisijungimas</translation>
+<translation id="7092922358121866860">Rodyti „Nakties šviesos“ nustatymus</translation>
 <translation id="7098389117866926363">USB-C įrenginys (prievadas kairėje, užpakalinėje dalyje)</translation>
 <translation id="7131634465328662194">Būsite automatiškai atjungti.</translation>
 <translation id="7143207342074048698">Jungiama</translation>
 <translation id="7165278925115064263">„Alt“ + „Shift“ + K</translation>
 <translation id="7168224885072002358">Po <ph name="TIMEOUT_SECONDS" /> bus grąžinta sena skyra</translation>
+<translation id="7228479291753472782">Valdyti nustatymus, nurodančius, ar svetainės gali naudoti funkcijas, pvz., geografinės vietovės nustatymą, mikrofoną, fotoaparatą ir kt.</translation>
 <translation id="7256634071279256947">Užpakalinis mikrofonas</translation>
 <translation id="726276584504105859">Vilkite čia, kad naudotumėte skaidytą ekraną</translation>
 <translation id="7348093485538360975">Ekraninė klaviatūra</translation>
@@ -342,6 +362,7 @@
 <translation id="7564874036684306347">Perkėlus langus į kitą darbalaukį gali pasireikšti nenumatyta elgsena. Vėlesni pranešimai, langai ir dialogų langai gali būti padalyti keliuose darbalaukiuose.</translation>
 <translation id="7569509451529460200">Brailio rašmenys ir „ChromeVox“ įgalinti</translation>
 <translation id="7593891976182323525">Paieškos arba antrojo lygio klavišas</translation>
+<translation id="7604942372593434070">Prieiga prie jūsų naršymo veiklos</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (savininkas)</translation>
 <translation id="7647488630410863958">Atrakinkite įrenginį, kad galėtumėte peržiūrėti pranešimus</translation>
 <translation id="7649070708921625228">Žinynas</translation>
@@ -354,8 +375,10 @@
 <translation id="7798302898096527229">Jei norite atšaukti, paspauskite paieškos klavišą arba „Shift“.</translation>
 <translation id="7814236020522506259"><ph name="HOUR" />:<ph name="MINUTE" /></translation>
 <translation id="7829386189513694949">Stiprus signalas</translation>
+<translation id="7842211907556571265">Prisijungiama prie „<ph name="NETWORK_NAME" />“</translation>
 <translation id="7842569679327885685">Įspėjimas: eksperimentinė funkcija</translation>
 <translation id="7846634333498149051">Klaviatūra</translation>
+<translation id="790040513076446191">Keisti privatumo nustatymus</translation>
 <translation id="7904094684485781019">Šios paskyros administratorius neleidžia naudoti kelių paskyrų.</translation>
 <translation id="7933084174919150729">„Google“ padėjėjas pasiekiamas tik pagrindiniame profilyje.</translation>
 <translation id="79341161159229895">Paskyrą tvarko: <ph name="FIRST_PARENT_EMAIL" /> ir <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +397,7 @@
 <translation id="8142699993796781067">Privatus tinklas</translation>
 <translation id="8152119955266188852">Paspaudėte viso ekrano didintuvo spartųjį klavišą. Ar norite jį įjungti?</translation>
 <translation id="8190698733819146287">Tinkinti kalbas ir įvestį...</translation>
+<translation id="8191230140820435481">Valdyti programas, plėtinius ir temas</translation>
 <translation id="8261506727792406068">Ištrinti</translation>
 <translation id="8297006494302853456">Silpnas</translation>
 <translation id="8308637677604853869">Ankstesnis meniu</translation>
@@ -392,18 +416,24 @@
 <translation id="8484916590211895857">„<ph name="NAME" />“: prisijungiama iš naujo...</translation>
 <translation id="8513108775083588393">Automatiškai pasukti</translation>
 <translation id="8517041960877371778">„<ph name="DEVICE_TYPE" />“ gali nebūti kraunamas, kai yra įjungtas.</translation>
+<translation id="8627191004499078455">Prisijungta prie „<ph name="DEVICE_NAME" />“</translation>
 <translation id="8639760480004882931">Liko <ph name="PERCENTAGE" /></translation>
 <translation id="8649101189709089199">Teksto ištarimas</translation>
 <translation id="8652175077544655965">Uždaryti nustatymus</translation>
+<translation id="8653151467777939995">Rodyti pranešimų nustatymus. Pranešimai įjungti</translation>
+<translation id="8664753092453405566">Rodyti tinklų sąrašą. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">Prijungtas mažos galios įkroviklis</translation>
 <translation id="8683506306463609433">Našumo stebėjimas aktyvus</translation>
 <translation id="8734991477317290293">Gali būti bandoma pavogti jūsų klavišų paspaudimus</translation>
+<translation id="8735953464173050365">Rodyti klaviatūros nustatymus. Pasirinkta „<ph name="KEYBOARD_NAME" />“</translation>
+<translation id="875593634123171288">Rodyti VPN nustatymus</translation>
 <translation id="8809737090443522491">Įveskite programos ar dokumento pavadinimą</translation>
 <translation id="8814190375133053267">WI-Fi</translation>
 <translation id="8825534185036233643">Negalima bendrinti daugiau nei dviejų ekranų vaizdų.</translation>
 <translation id="8828714802988429505">90°</translation>
 <translation id="8841375032071747811">Mygtukas „Atgal“</translation>
+<translation id="8843682306134542540">Perjungti pasukimo užrakinimą. <ph name="STATE_TEXT" /></translation>
 <translation id="8850991929411075241">Paieškos klavišas + „Esc“</translation>
 <translation id="8870509716567206129">Programoje nepalaikomas skaidytas ekranas.</translation>
 <translation id="8874184842967597500">Neprisijungta</translation>
diff --git a/ash/strings/ash_strings_lv.xtb b/ash/strings/ash_strings_lv.xtb
index 104f599..7511ead 100644
--- a/ash/strings/ash_strings_lv.xtb
+++ b/ash/strings/ash_strings_lv.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Ekrāna tastatūra iespējota</translation>
 <translation id="1823873187264960516">Ethernet adrese: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistents (notiek ielāde...)</translation>
+<translation id="1841545962859478868">Ierīces administrators var uzraudzīt šo:</translation>
 <translation id="1850504506766569011">Wi-Fi tīkls ir izslēgts.</translation>
 <translation id="1864454756846565995">USB-C ierīce (aizmugurējā pieslēgvieta)</translation>
 <translation id="1882897271359938046">Spoguļo šeit: <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Notiek savienojuma izveide</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Iepriekšējā izšķirtspēja tiks atgriezta pēc <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Mainiet iestatījumus, kas norāda, vai vietnes var izmantot tādas funkcijas kā ģeolokāciju, mikrofonu, kameru u. c.</translation>
 <translation id="7256634071279256947">Aizmugurējais mikrofons</translation>
 <translation id="726276584504105859">Velciet šeit, lai izmantotu ekrāna sadalīšanu.</translation>
 <translation id="7348093485538360975">Ekrāntastatūra</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Logu pārvietošana uz citu darbvirsmu var izraisīt neparedzētu darbību. Turpmākie paziņojumi, logi un dialoglodziņi var tikt sadalīti pa darbvirsmām.</translation>
 <translation id="7569509451529460200">Braila raksts un ChromeVox ir iespējoti</translation>
 <translation id="7593891976182323525">Meklēt vai Shift</translation>
+<translation id="7604942372593434070">Piekļuve jūsu pārlūkošanas darbībām</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (īpašnieks)</translation>
 <translation id="7647488630410863958">Atbloķējiet ierīci, lai skatītu paziņojumus.</translation>
 <translation id="7649070708921625228">Palīdzība</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Spēcīgs signāls</translation>
 <translation id="7842569679327885685">Brīdinājums: šī ir eksperimentāla funkcija!</translation>
 <translation id="7846634333498149051">Tastatūra</translation>
+<translation id="790040513076446191">Manipulēt ar konfidencialitāti saistītus iestatījumus</translation>
 <translation id="7904094684485781019">Šī konta administrators nav atļāvis vairākkārtēju pierakstīšanos.</translation>
 <translation id="7933084174919150729">Google asistents ir pieejams tikai galvenajam profilam.</translation>
 <translation id="79341161159229895">Kontu pārvalda <ph name="FIRST_PARENT_EMAIL" /> un <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privāts tīkls</translation>
 <translation id="8152119955266188852">Jūs nospiedāt pilnekrāna lupas īsinājumtaustiņu. Vai vēlaties to ieslēgt?</translation>
 <translation id="8190698733819146287">Pielāgot valodas un ievadi...</translation>
+<translation id="8191230140820435481">Pārvaldīt jūsu lietotnes, paplašinājumus un motīvus</translation>
 <translation id="8261506727792406068">Dzēst</translation>
 <translation id="8297006494302853456">Vājš</translation>
 <translation id="8308637677604853869">Iepriekšējā izvēlne</translation>
diff --git a/ash/strings/ash_strings_ml.xtb b/ash/strings/ash_strings_ml.xtb
index 0ac6d7c5f9..205a56d 100644
--- a/ash/strings/ash_strings_ml.xtb
+++ b/ash/strings/ash_strings_ml.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">ഓൺ‌ സ്‌ക്രീൻ കീ‌ബോർഡ് പ്രവർത്തനക്ഷമമാക്കി</translation>
 <translation id="1823873187264960516">ഇതർനെറ്റ്: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">സഹായി (ലോഡുചെയ്യുന്നു...)</translation>
+<translation id="1841545962859478868">ഉപകരണ അഡ്‌മിൻ ഇനിപ്പറയുന്നവ നിരീക്ഷിച്ചേക്കാം:</translation>
 <translation id="1850504506766569011">Wi-Fi ഓഫുചെയ്‌തു.</translation>
 <translation id="1864454756846565995">USB-C ഉപകരണം (പുറകിലെ പോർട്ട്)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> എന്നതിലേക്ക് മിറർചെയ്യുന്നു</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">കണക്റ്റുചെയ്യുന്നു</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" />-ൽ പഴയ മിഴിവിലേക്ക് പഴയപടിയാക്കുന്നു</translation>
+<translation id="7228479291753472782">വെബ്‌സൈറ്റിന് ജിയോലൊക്കേഷൻ, മൈക്രോഫോൺ, ക്യാമറ എന്നിവ പോലുള്ള ഫീച്ചറുകൾ ഉപയോഗിക്കാൻ കഴിയുമോ എന്ന് വ്യക്തമാക്കുന്ന ക്രമീകരണം കൈകാര്യം ചെയ്യുക.</translation>
 <translation id="7256634071279256947">പിൻഭാഗത്തെ മൈക്രോഫോൺ</translation>
 <translation id="726276584504105859">സ്പ്ലിറ്റ് സ്ക്രീൻ ഉപയോഗിക്കുന്നതിന് ഇവിടെ വലിച്ചിടുക</translation>
 <translation id="7348093485538360975">ഓൺ-സ്‌ക്രീൻ കീബോർഡ്</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">മറ്റൊരു ഡെസ്‌ക്‌ടോപ്പിലേക്ക് വിൻഡോകൾ നീക്കുന്നത് അപ്രതീക്ഷിതമായ പ്രവർത്തനരീതിയ്‌ക്ക് ഇടയാക്കാം. തുടർന്നുള്ള അറിയിപ്പുകൾ, വിൻഡോകൾ, ഡയലോഗുകൾ എന്നിവ ഡെസ്‌ക്‌‌ടോപ്പുകൾക്കിടയിൽ വിഭജിക്കപ്പെടാം.</translation>
 <translation id="7569509451529460200">Braille, ChromeVox എന്നിവ പ്രവർത്തനക്ഷമമാക്കി</translation>
 <translation id="7593891976182323525">തിരയൽ അല്ലെങ്കിൽ Shift</translation>
+<translation id="7604942372593434070">നിങ്ങളുടെ ബ്രൗസിംഗ് ആക്‌റ്റിവിറ്റി ആക്‌സസ് ചെയ്യുക</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (ഉടമ)</translation>
 <translation id="7647488630410863958">നിങ്ങളുടെ അറിയിപ്പുകൾ കാണാൻ ഉപകരണം അൺലോക്കുചെയ്യുക</translation>
 <translation id="7649070708921625228">സഹായം</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">ശക്തിയുള്ള സിഗ്നൽ</translation>
 <translation id="7842569679327885685">മുന്നറിയിപ്പ്: പരീക്ഷണാത്മക ഫീച്ചർ</translation>
 <translation id="7846634333498149051">കീബോർഡ്</translation>
+<translation id="790040513076446191">സ്വകാര്യത സംബന്ധിയായ ക്രമീകരണങ്ങൾ കൈകാര്യം ചെയ്യുക</translation>
 <translation id="7904094684485781019">ഈ അക്കൗണ്ടിന്റെ അഡ്‌മിനിസ്‌ട്രേറ്റർ ഒന്നിലധികം സൈൻ-ഇൻ അനുവദിക്കുന്നില്ല.</translation>
 <translation id="7933084174919150729">പ്രാഥമിക പ്രൊഫൈലിന് മാത്രമേ Google സഹായി ലഭ്യമാകൂ.</translation>
 <translation id="79341161159229895">അക്കൗണ്ട് മാനേജ് ചെയ്യുന്നത് <ph name="FIRST_PARENT_EMAIL" />, <ph name="SECOND_PARENT_EMAIL" /> എന്നിവരാണ്</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">സ്വകാര്യ നെറ്റ്‌വര്‍ക്ക്</translation>
 <translation id="8152119955266188852">നിങ്ങൾ പൂർണ്ണ സ്‌ക്രീൻ മാഗ്നിഫയറിനുള്ള കുറുക്കുവഴി അമർത്തി. അത് ഓണാക്കണോ?</translation>
 <translation id="8190698733819146287">ഭാഷകൾ‌ ഇച്ഛാനുസൃതമാക്കി നല്‍‌കുക...</translation>
+<translation id="8191230140820435481">നിങ്ങളുടെ അപ്ലിക്കേഷനുകൾ, വിപുലീകരണങ്ങൾ, തീമുകൾ എന്നിവ നിയന്ത്രിക്കുക</translation>
 <translation id="8261506727792406068">ഇല്ലാതാക്കുക</translation>
 <translation id="8297006494302853456">ദുര്‍ബലം</translation>
 <translation id="8308637677604853869">മുൻ മെനു</translation>
diff --git a/ash/strings/ash_strings_mr.xtb b/ash/strings/ash_strings_mr.xtb
index 8be4194..680d4b9 100644
--- a/ash/strings/ash_strings_mr.xtb
+++ b/ash/strings/ash_strings_mr.xtb
@@ -7,6 +7,7 @@
 <translation id="1037492556044956303"><ph name="DEVICE_NAME" /> जोडले</translation>
 <translation id="1056775291175587022">कोणतीही नेटवर्क नाहीत</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation>
+<translation id="1104084341931202936">अॅक्सेसिबिलिटी सेटिंग्ज दाखवा</translation>
 <translation id="1104621072296271835">तुमची डिव्हाइस एकत्र आणखी चांगले काम करतात</translation>
 <translation id="112308213915226829">शेल्फ स्वयं लपवा</translation>
 <translation id="1153356358378277386">जोडलेली डीव्हाइस</translation>
@@ -20,17 +21,20 @@
 <translation id="1279938420744323401"><ph name="DISPLAY_NAME" /> (<ph name="ANNOTATION" />)</translation>
 <translation id="1290331692326790741">खराब सिग्नल</translation>
 <translation id="1293264513303784526">USB-C डिव्‍हाइस (डावे पोर्ट)</translation>
+<translation id="1302880136325416935">ब्लूटूथ सेटिंग्ज दाखवा. <ph name="STATE_TEXT" /></translation>
 <translation id="1346748346194534595">उजवे</translation>
 <translation id="1351937230027495976">मेनू कोलॅप्स करा</translation>
 <translation id="1383876407941801731">शोधा</translation>
 <translation id="1467432559032391204">डावे</translation>
 <translation id="1484102317210609525"><ph name="DEVICE_NAME" /> (HDMI/DP)</translation>
 <translation id="1510238584712386396">लाँचर</translation>
+<translation id="1520303207432623762">{NUM_APPS,plural, =1{सूचना सेटिंग्ज दाखवा. अॅपसाठी सूचना बंद आहेत}one{सूचना सेटिंग्ज दाखवा. # अॅपसाठी सूचना बंद आहेत}other{सूचना सेटिंग्ज दाखवा. # अॅप्ससाठी सूचना बंद आहेत}}</translation>
 <translation id="1525508553941733066">डिसमिस करा</translation>
 <translation id="1537254971476575106">पूर्णस्क्रीन भिंग</translation>
 <translation id="15373452373711364">मोठा माउस कर्सर</translation>
 <translation id="1550523713251050646">अधिक पर्यायांसाठी क्लिक करा</translation>
 <translation id="1567387640189251553">तुम्ही शेवटचा पासवर्ड टाकल्या नंतर वेगळा कीबोर्ड कनेक्ट करण्यात आला आहे. तो कदाचित तुमचे कीस्ट्रोक चोरण्याचा प्रयत्न करत असेल.</translation>
+<translation id="1570871743947603115">ब्लूटूथ टॉगल करा. <ph name="STATE_TEXT" /></translation>
 <translation id="1608626060424371292">हा वापरकर्ता काढा</translation>
 <translation id="1621499497873603021">बॅटरी रिक्त होईपर्यंत शिल्लक वेळ, <ph name="TIME_LEFT" /></translation>
 <translation id="1658406695958299976">सॉरी, तुमच्या पासवर्डची पडताळणी अजूनही झालेली नाही. टीप: तुम्ही तुमचा पासवर्ड अलीकडेच बदलला असल्यास, तुम्ही साइन आउट केल्यानंतर तुमचा नवीन पासवर्ड लागू केला जाईल, कृपया येथे जुना पासवर्ड वापरा.</translation>
@@ -40,8 +44,10 @@
 <translation id="1743570585616704562">ओळखले नाही</translation>
 <translation id="1746730358044914197">तुमच्या प्रशासकाद्वारे कॉन्फिगर केलेल्या इनपुट पद्धती.</translation>
 <translation id="1747827819627189109">ऑन-स्क्रीन कीबोर्ड सक्षम</translation>
+<translation id="1761222317188459878">नेटवर्क कनेक्शन टॉगल करा. <ph name="STATE_TEXT" /></translation>
 <translation id="1823873187264960516">इथरनेट: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">साहाय्यक (लोड होत आहे...)</translation>
+<translation id="1841545962859478868">डिव्हाइस प्रशासक कदाचित खालील गोष्टींचे परीक्षण करू शकतो:</translation>
 <translation id="1850504506766569011">वाय-फाय बंद आहे.</translation>
 <translation id="1864454756846565995">USB-C डिव्‍हाइस (मागील बाजूचे पोर्ट)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> वर मिरर करत आहे</translation>
@@ -62,6 +68,7 @@
 <translation id="2049639323467105390"><ph name="DOMAIN" /> याद्वारे हे डिव्हाइस व्यवस्‍थापित केले जाते.</translation>
 <translation id="2050339315714019657">पोर्ट्रेट</translation>
 <translation id="2067602449040652523">कीबोर्डची चमक</translation>
+<translation id="2075212959500165896">अनेक प्रयत्न केले. नंतर पुन्हा प्रयत्न करा.</translation>
 <translation id="2081529251031312395">$1 नंतरही साइन इन करू शकतो.</translation>
 <translation id="2127372758936585790">निम्न-उर्जेचे चार्जर</translation>
 <translation id="2135456203358955318">डॉक केलेले भिंग</translation>
@@ -75,12 +82,15 @@
 <translation id="2303600792989757991">विंडो विहंगावलोकन टॉगल करा</translation>
 <translation id="2338501278241028356">जवळपासचे डिव्हाइस शोधण्यासाठी ब्लुटूथ चालू करा</translation>
 <translation id="2339073806695260576">नोट व स्क्रीनशॉट घेण्यासाठी, तसेच लेझर पॉइंटर किंवा भिंग वापरण्यासाठी शेल्फवरील स्टायलस बटणावर टॅप करा.</translation>
+<translation id="2341729377289034582">उभ्या दिशेने लॉक केले</translation>
 <translation id="2352467521400612932">लेखणी सेटिंग्ज</translation>
 <translation id="2354174487190027830"><ph name="NAME" /> सक्रिय करत आहे</translation>
 <translation id="2359808026110333948">सुरू ठेवा</translation>
 <translation id="2365393535144473978">मोबाइल डेटा चालू केल्याने ब्लूटूथ चालू होईल.</translation>
 <translation id="2391579633712104609">180°</translation>
+<translation id="239188844683466770">व्यत्यय आणू नका टॉगल करा</translation>
 <translation id="2412593942846481727">अपडेट उपलब्ध आहे</translation>
+<translation id="2416346634399901812"><ph name="NETWORK_NAME" /> शी कनेक्ट केले</translation>
 <translation id="2429753432712299108">"<ph name="DEVICE_NAME" />" Bluetooth डिव्हाइस जोडण्यासाठी परवानगी घेऊ इच्छिते. स्वीकार करण्यापूर्वी, कृपया त्या डिव्हाइसवर ही पासकी दर्शविली असल्याची पुष्टी करा: <ph name="PASSKEY" /></translation>
 <translation id="2482878487686419369">सूचना</translation>
 <translation id="2484513351006226581">कीबोर्डचा लेआउट स्विच करण्यासाठी <ph name="KEYBOARD_SHORTCUT" /> दाबा.</translation>
@@ -113,6 +123,7 @@
 <translation id="2946119680249604491">कनेक्शन जोडा</translation>
 <translation id="2961963223658824723">काहीतरी चूक झाली. काही सेकंदांनी पुन्हा प्रयत्न करा.</translation>
 <translation id="2963773877003373896">mod3</translation>
+<translation id="2995447421581609334">कास्‍ट डिव्‍हाइस दाखवा.</translation>
 <translation id="2996462380875591307">डॉक केलेले मॅग्निफायर सुरू केले. ते टॉगल करून बंद करण्यासाठी पुन्हा Ctrl+Search+D दाबा.</translation>
 <translation id="2999742336789313416"><ph name="DISPLAY_NAME" /> हे <ph name="DOMAIN" /> द्वारे व्यवस्थापित कलेले एक सावर्जनिक सत्र आहे</translation>
 <translation id="3000461861112256445">मोनो ऑडिओ</translation>
@@ -127,6 +138,7 @@
 <translation id="315116470104423982">मोबाइल डेटा</translation>
 <translation id="3151786313568798007">अभिमुखता</translation>
 <translation id="3153444934357957346">मल्टिपल साइन इन मध्ये तुमच्याकडे जास्तीत जास्त फक्त <ph name="MULTI_PROFILE_USER_LIMIT" /> खाती असू शकतात.</translation>
+<translation id="3202010236269062730">{NUM_DEVICES,plural, =1{डिव्हाइसशी कनेक्ट केले}one{# डिव्हाइसशी कनेक्ट केले}other{# डिव्हाइसशी कनेक्ट केले}}</translation>
 <translation id="3236488194889173876">कोणतेही मोबाइल नेटवर्क उपलब्ध नाही</translation>
 <translation id="3238765806255363838"><ph name="USERNAME" /> <ph name="MAIL" /></translation>
 <translation id="3294437725009624529">अतिथी</translation>
@@ -158,6 +170,7 @@
 <translation id="3784455785234192852">लॉक करा</translation>
 <translation id="3798670284305777884">स्पीकर (अंतर्गत)</translation>
 <translation id="380165613292957338">हाय, मला तुमची कशी मदत करता येईल?</translation>
+<translation id="383629559565718788">कीबोर्ड सेटिंग्ज दाखवा</translation>
 <translation id="3846575436967432996">कोणतीही नेटवर्क माहिती उपलब्ध नाही</translation>
 <translation id="385051799172605136">मागील</translation>
 <translation id="3891340733213178823">साइन आउट करण्यासाठी Ctrl+Shift+Q दोनदा दाबा.</translation>
@@ -173,8 +186,10 @@
 <translation id="4072264167173457037">मध्यम सिग्नल</translation>
 <translation id="4200057768455216496">तुम्ही डॉक मॅग्निफायरसाठीचा शॉर्टकट दाबला. तुम्हाला ते सुरू करायचे आहे का?</translation>
 <translation id="4217571870635786043">डिक्टेशन</translation>
+<translation id="4261870227682513959">सूचना सेटिंग्ज दाखवा. सूचना बंद आहेत</translation>
 <translation id="4274921305979314545">तुमचे Chromebook तुमच्या फोनशी कनेक्ट करा</translation>
 <translation id="4279490309300973883">मिररिंग</translation>
+<translation id="4292681942966152062"><ph name="NETWORK_NAME" /> अ‍ॅक्टिव्हेट करत आहे</translation>
 <translation id="4321179778687042513">ctrl</translation>
 <translation id="4331809312908958774">Chrome OS</translation>
 <translation id="4338109981321384717">भिंग</translation>
@@ -238,6 +253,7 @@
 <translation id="5673434351075758678">तुमच्या सेटिंग्ज सिंक केल्यानंतर "<ph name="FROM_LOCALE" />" पासून "<ph name="TO_LOCALE" />" पर्यंत.</translation>
 <translation id="574392208103952083">मध्यम</translation>
 <translation id="5744083938413354016">टॅप ड्रॅगिंग</translation>
+<translation id="5750765938512549687">ब्लूटूथ बंद आहे</translation>
 <translation id="5777841717266010279">स्क्रीन सामायिकरण थांबवायचे?</translation>
 <translation id="57838592816432529">निःशब्द करा</translation>
 <translation id="5805697420284793859">विंडो व्यवस्थापक</translation>
@@ -271,12 +287,14 @@
 <translation id="615957422585914272">ऑन-स्‍क्रीन कीबोर्ड दर्शवा</translation>
 <translation id="6164005077879661055">एकदा हा पर्यवेक्षी वापरकर्ता काढल्यानंतर पर्यवेक्षी वापरकर्त्याशी संबद्ध सर्व फायली आणि स्थानिक डेटा कायमचा हटवला जाईल. या पर्यवेक्षी वापरकर्त्यासाठी भेट दिलेल्या वेबसाइट आणि सेटिंग्ज तरीही <ph name="MANAGEMENT_URL" /> वर व्यवस्थापकाद्वारे दृश्यमान असू शकतात.</translation>
 <translation id="6165508094623778733">अधिक जाणून घ्या</translation>
+<translation id="6254629735336163724">आडव्या दिशेने लॉक केले</translation>
 <translation id="6259254695169772643">निवडण्यासाठी तुमचे स्टायलस वापरा</translation>
 <translation id="6267036997247669271"><ph name="NAME" />: सक्रिय करत आहे...</translation>
 <translation id="6284232397434400372">रीझोल्युशन बदलले</translation>
 <translation id="6297287540776456956">विभाग निवडण्यासाठी लेखणी वापरा</translation>
 <translation id="6310121235600822547"><ph name="DISPLAY_NAME" /> <ph name="ROTATION" /> वर फिरविले होते</translation>
 <translation id="632744581670418035">कीबोर्ड आच्छादन</translation>
+<translation id="6376931439017688372">ब्लूटूथ सुरू आहे</translation>
 <translation id="639644700271529076">CAPS LOCK बंद आहे</translation>
 <translation id="6406704438230478924">altgr</translation>
 <translation id="643147933154517414">सर्व पूर्ण झाले</translation>
@@ -320,11 +338,13 @@
 <translation id="7066646422045619941">हे नेटवर्क आपल्या प्रशासकाने अक्षम केले आहे.</translation>
 <translation id="7067196344162293536">स्वयं फिरवा</translation>
 <translation id="7076293881109082629">साइन इन करीत आहे</translation>
+<translation id="7092922358121866860">रात्रीचा प्रकाश सेटिंग्ज दाखवा</translation>
 <translation id="7098389117866926363">USB-C डिव्‍हाइस (मागील बाजूचे डावे पोर्ट)</translation>
 <translation id="7131634465328662194">तुम्हाला आपोआप साइन आउट केले जाईल.</translation>
 <translation id="7143207342074048698">कनेक्ट करीत आहे</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> मध्ये जुन्या रिजोल्यूशनवर परत करत आहे</translation>
+<translation id="7228479291753472782">वेबसाइट भौगोलिक स्थान, मायक्रोफोन, कॅमेरा, इ. वैशिष्‍ट्ये वापरू शकतात किंवा नाही ते निर्दिष्ट करणार्‍या सेटिंग्ज कुशलतेने हाताळा.</translation>
 <translation id="7256634071279256947">मागील मायक्रोफोन</translation>
 <translation id="726276584504105859">विभाजित स्क्रीन वापरण्यासाठी येथे ड्रॅग करा</translation>
 <translation id="7348093485538360975">ऑन-स्क्रीन कीबोर्ड</translation>
@@ -341,6 +361,7 @@
 <translation id="7564874036684306347">दुसऱ्या डेस्कटॉपवर विंडो हलवण्याने अनपेक्षित वर्तन घडू शकते. नंतर येणाऱ्या सूचना, विंडो आणि डायलॉग हे डेस्कटॉपच्या दरम्यान विभाजित केले जाऊ शकतात.</translation>
 <translation id="7569509451529460200">ब्रेल आणि ChromeVox चालू केलेले आहेत</translation>
 <translation id="7593891976182323525">Search किंवा Shift</translation>
+<translation id="7604942372593434070">आपल्या ब्राउझिंग क्रियाकलापामध्ये प्रवेश करा</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (मालक)</translation>
 <translation id="7647488630410863958">आपल्या सूचना पाहण्यासाठी डिव्हाइस अनलॉक करा</translation>
 <translation id="7649070708921625228">मदत</translation>
@@ -353,8 +374,10 @@
 <translation id="7798302898096527229">रद्द करण्यासाठी Search किंवा Shift दाबा.</translation>
 <translation id="7814236020522506259"><ph name="HOUR" /> आणि <ph name="MINUTE" /></translation>
 <translation id="7829386189513694949">मजबूत सिग्नल</translation>
+<translation id="7842211907556571265"><ph name="NETWORK_NAME" /> शी कनेक्ट करत आहे</translation>
 <translation id="7842569679327885685">चेतावणी: प्रयोगात्मक वैशिष्ट्य</translation>
 <translation id="7846634333498149051">कीबोर्ड</translation>
+<translation id="790040513076446191">गोपनीयतेशी-संबद्ध सेटिंग्ज कुशलतेने हाताळा</translation>
 <translation id="7904094684485781019">या खात्याच्या प्रशासकाने एकाधिक साइन इन ची अनुमती रद्द केली आहे.</translation>
 <translation id="7933084174919150729">Google Assistant फक्त प्राथमिक प्रोफाइलवर उपलब्ध आहे.</translation>
 <translation id="79341161159229895">खाते <ph name="FIRST_PARENT_EMAIL" /> आणि <ph name="SECOND_PARENT_EMAIL" /> ने व्‍यवस्‍थापित केले आहे</translation>
@@ -373,6 +396,7 @@
 <translation id="8142699993796781067">खाजगी नेटवर्क</translation>
 <translation id="8152119955266188852">तुम्ही पूर्ण-स्क्रीन मॅग्निफायर शॉर्टकट दाबला. तुम्हाला ते सुरू करायचे आहे का?</translation>
 <translation id="8190698733819146287">भाषा आणि इनपुट सानुकूलित करा...</translation>
+<translation id="8191230140820435481">आपले अॅप्लिकेशन, विस्तार आणि थीम व्यवस्थापित करा</translation>
 <translation id="8261506727792406068">हटवा</translation>
 <translation id="8297006494302853456">कमकुवत</translation>
 <translation id="8308637677604853869">मागील मेनू</translation>
@@ -391,18 +415,24 @@
 <translation id="8484916590211895857"><ph name="NAME" />: पुन्हा कनेक्ट करीत आहे...</translation>
 <translation id="8513108775083588393">ऑटो-रोटेट</translation>
 <translation id="8517041960877371778">आपले <ph name="DEVICE_TYPE" /> चालू केले असताना कदाचित चार्ज होणार नाही.</translation>
+<translation id="8627191004499078455"><ph name="DEVICE_NAME" /> शी कनेक्ट केले</translation>
 <translation id="8639760480004882931"><ph name="PERCENTAGE" /> शिल्लक</translation>
 <translation id="8649101189709089199">बोलण्यासाठी निवडा</translation>
 <translation id="8652175077544655965">सेटिंग्ज बंद करा</translation>
+<translation id="8653151467777939995">सूचना सेटिंग्ज दाखवा. सूचना सुरू आहेत</translation>
+<translation id="8664753092453405566">नेटवर्क सूची दाखवा. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270°</translation>
 <translation id="8676770494376880701">निम्न-उर्जेचे चार्जर कनेक्ट केले</translation>
 <translation id="8683506306463609433">परफॉर्मंस ट्रेसिंग अॅक्टिव्ह आहे</translation>
 <translation id="8734991477317290293">तो कदाचित तुमचे कीस्ट्रोक चोरण्याचा प्रयत्न करत असेल.</translation>
+<translation id="8735953464173050365">कीबोर्ड सेटिंग्ज दाखवा. <ph name="KEYBOARD_NAME" /> निवडले आहे</translation>
+<translation id="875593634123171288">VPN सेटिंग्ज दाखवा</translation>
 <translation id="8809737090443522491">अ‍ॅप किंवा दस्तऐवजाचे नाव टाइप करा</translation>
 <translation id="8814190375133053267">वाय-फाय</translation>
 <translation id="8825534185036233643">दोन पेक्षा जास्त प्रदर्शनासाठी मि‍ररिंग समर्थित नाही.</translation>
 <translation id="8828714802988429505">90°</translation>
 <translation id="8841375032071747811">मागे जा बटण</translation>
+<translation id="8843682306134542540">रोटेशन लॉक टॉगल करा. <ph name="STATE_TEXT" /></translation>
 <translation id="8850991929411075241">Search+Esc</translation>
 <translation id="8870509716567206129">अॅप विभाजित-स्क्रीनला सपोर्ट करत नाही.</translation>
 <translation id="8874184842967597500">कनेक्ट केलेले नाही</translation>
diff --git a/ash/strings/ash_strings_ms.xtb b/ash/strings/ash_strings_ms.xtb
index 84aa3a22..f50e7ea 100644
--- a/ash/strings/ash_strings_ms.xtb
+++ b/ash/strings/ash_strings_ms.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Papan kekunci pada skrin didayakan</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Pembantu (memuat...)</translation>
+<translation id="1841545962859478868">Pentadbir peranti boleh memantau perkara berikut:</translation>
 <translation id="1850504506766569011">Wi-Fi dimatikan.</translation>
 <translation id="1864454756846565995">Peranti USB-C (port belakang)</translation>
 <translation id="1882897271359938046">Mencerminkan <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Menyambung</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Kembali kepada peleraian lama dalam <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipulasi tetapan yang menentukan sama ada tapak web boleh menggunakan ciri seperti geolokasi, mikrofon, kamera dan sebagainya.</translation>
 <translation id="7256634071279256947">Mikrofon belakang</translation>
 <translation id="726276584504105859">Seret ke sini untuk menggunakan skrin pisah</translation>
 <translation id="7348093485538360975">Papan kekunci pada skrin</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Tindakan memindahkan tetingkap ke desktop lain boleh menyebabkan gelagat yang tidak dijangka. Pemberitahuan, tetingkap dan dialog yang seterusnya mungkin dipisahkan antara desktop.</translation>
 <translation id="7569509451529460200">Braille dan ChromeVox didayakan</translation>
 <translation id="7593891976182323525">Search atau Shift</translation>
+<translation id="7604942372593434070">Akses aktiviti penyemakan imbas anda</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (pemilik)</translation>
 <translation id="7647488630410863958">Buka kunci peranti untuk melihat pemberitahuan anda</translation>
 <translation id="7649070708921625228">Bantuan</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Isyarat kuat</translation>
 <translation id="7842569679327885685">Amaran: Ciri percubaan</translation>
 <translation id="7846634333498149051">Papan kekunci</translation>
+<translation id="790040513076446191">Manipulasi tetapan yang berkaitan privasi</translation>
 <translation id="7904094684485781019">Pentadbir akaun ini tidak membenarkan berbilang log masuk.</translation>
 <translation id="7933084174919150729">Google Assistant hanya tersedia untuk profil utama.</translation>
 <translation id="79341161159229895">Akaun diurus oleh <ph name="FIRST_PARENT_EMAIL" /> dan <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Rangkaian persendirian</translation>
 <translation id="8152119955266188852">Anda menekan pintasan untuk penggadang skrin penuh. Adakah anda ingin menghidupkannya?</translation>
 <translation id="8190698733819146287">Sesuaikan bahasa dan input...</translation>
+<translation id="8191230140820435481">Uruskan apl, sambungan dan tema anda</translation>
 <translation id="8261506727792406068">Padam</translation>
 <translation id="8297006494302853456">Lemah</translation>
 <translation id="8308637677604853869">Menu sebelumnya</translation>
diff --git a/ash/strings/ash_strings_nl.xtb b/ash/strings/ash_strings_nl.xtb
index 6c53591..2519b9a 100644
--- a/ash/strings/ash_strings_nl.xtb
+++ b/ash/strings/ash_strings_nl.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Schermtoetsenbord ingeschakeld</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (laden...)</translation>
+<translation id="1841545962859478868">De apparaatbeheerder kan het volgende controleren:</translation>
 <translation id="1850504506766569011">Wifi is uitgeschakeld.</translation>
 <translation id="1864454756846565995">USB-C-apparaat (poort aan achterkant)</translation>
 <translation id="1882897271359938046">Spiegelen naar <ph name="DISPLAY_NAME" /></translation>
@@ -208,7 +209,7 @@
 <translation id="4905614135390995787">De sneltoets om de modus voor hoog contrast in of uit te schakelen, is gewijzigd. Gebruik <ph name="NEW_SHORTCUT" /> in plaats van <ph name="OLD_SHORTCUT" />.</translation>
 <translation id="4917385247580444890">Sterk</translation>
 <translation id="4918086044614829423">Accepteren</translation>
-<translation id="4924411785043111640">Opnieuw starten en resetten</translation>
+<translation id="4924411785043111640">Opnieuw opstarten en resetten</translation>
 <translation id="4961318399572185831">Scherm casten</translation>
 <translation id="5069971504769299223">De beheerder van het apparaat kan je activiteit mogelijk bekijken.</translation>
 <translation id="5136175204352732067">Ander toetsenbord aangesloten</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Verbinding maken</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Terugzetten naar oude resolutie over <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Instellingen bewerken die bepalen of websites functies (zoals geolocatie, microfoon, camera, enzovoort) kunnen gebruiken</translation>
 <translation id="7256634071279256947">Microfoon aan achterzijde</translation>
 <translation id="726276584504105859">Sleep hier naartoe om het scherm te splitsen</translation>
 <translation id="7348093485538360975">Schermtoetsenbord</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Verplaatsing van vensters naar een ander bureaublad kan leiden tot onverwacht gedrag. Verdere meldingen, vensters en dialoogvensters worden mogelijk verdeeld over de bureaubladen.</translation>
 <translation id="7569509451529460200">Braille en ChromeVox zijn ingeschakeld</translation>
 <translation id="7593891976182323525">Zoeken of Shift</translation>
+<translation id="7604942372593434070">Toegang krijgen tot je browse-activiteit</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (eigenaar)</translation>
 <translation id="7647488630410863958">Ontgrendel het apparaat om je meldingen weer te geven</translation>
 <translation id="7649070708921625228">Help</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Sterk signaal</translation>
 <translation id="7842569679327885685">Waarschuwing: Experimentele functie</translation>
 <translation id="7846634333498149051">Toetsenbord</translation>
+<translation id="790040513076446191">Privacygerelateerde instellingen manipuleren</translation>
 <translation id="7904094684485781019">De beheerder van dit account heeft toegang tot meerdere accounts niet toegestaan.</translation>
 <translation id="7933084174919150729">De Google Assistent is alleen beschikbaar voor het primaire profiel.</translation>
 <translation id="79341161159229895">Account wordt beheerd door <ph name="FIRST_PARENT_EMAIL" /> en <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privénetwerk</translation>
 <translation id="8152119955266188852">Je hebt op de sneltoets voor volledig scherm vergroten gedrukt. Wil je deze functie inschakelen?</translation>
 <translation id="8190698733819146287">Talen en invoer aanpassen...</translation>
+<translation id="8191230140820435481">Je apps, extensies en thema's beheren</translation>
 <translation id="8261506727792406068">Verwijderen</translation>
 <translation id="8297006494302853456">Zwak</translation>
 <translation id="8308637677604853869">Vorig menu</translation>
diff --git a/ash/strings/ash_strings_no.xtb b/ash/strings/ash_strings_no.xtb
index c2c912de..e72146ee 100644
--- a/ash/strings/ash_strings_no.xtb
+++ b/ash/strings/ash_strings_no.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Skjermtastaturet er aktivert</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (laster inn …)</translation>
+<translation id="1841545962859478868">Enhetsadministratoren kan overvåke følgende:</translation>
 <translation id="1850504506766569011">Wi-Fi er slått av.</translation>
 <translation id="1864454756846565995">USB-C-enhet (porten på baksiden)</translation>
 <translation id="1882897271359938046">Speiler <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Kobler til</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Går tilbake til den gamle oppløsningen om <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Håndter innstillinger som angir om nettsteder kan bruke funksjoner som geolokalisering, mikrofonen, kameraet og så videre.</translation>
 <translation id="7256634071279256947">Mikrofon bak</translation>
 <translation id="726276584504105859">Dra hit for å bruke delt skjerm</translation>
 <translation id="7348093485538360975">Skjermtastatur</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Flytting av vinduer til et annet skrivebord kan føre til uventet atferd. Følgende varsler, vinduer og dialogbokser kan bli delt mellom skrivebordene.</translation>
 <translation id="7569509451529460200">Braille og ChromeVox er slått på</translation>
 <translation id="7593891976182323525">Søk eller Shift</translation>
+<translation id="7604942372593434070">Gå til surfeaktiviteten din</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (eier)</translation>
 <translation id="7647488630410863958">Lås opp enheten for å se varslene dine</translation>
 <translation id="7649070708921625228">Hjelp</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Sterkt signal</translation>
 <translation id="7842569679327885685">Advarsel: Funksjon på forsøksstadiet</translation>
 <translation id="7846634333498149051">Tastatur</translation>
+<translation id="790040513076446191">manipulere personvernrelaterte innstillinger</translation>
 <translation id="7904094684485781019">Administratoren for denne kontoen har forbudt multipålogging.</translation>
 <translation id="7933084174919150729">Google-assistenten er bare tilgjengelig for den primære profilen.</translation>
 <translation id="79341161159229895">Kontoen er administrert av <ph name="FIRST_PARENT_EMAIL" /> og <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privat nettverk</translation>
 <translation id="8152119955266188852">Du trykket på snarveien for lupen for hele skjermen. Vil du slå den på?</translation>
 <translation id="8190698733819146287">Tilpass språk og inndata</translation>
+<translation id="8191230140820435481">Administrering av programmer, utvidelser og temaer</translation>
 <translation id="8261506727792406068">Slett</translation>
 <translation id="8297006494302853456">Svak</translation>
 <translation id="8308637677604853869">Forrige meny</translation>
diff --git a/ash/strings/ash_strings_pl.xtb b/ash/strings/ash_strings_pl.xtb
index 2c07c837..b10b60c3 100644
--- a/ash/strings/ash_strings_pl.xtb
+++ b/ash/strings/ash_strings_pl.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Klawiatura ekranowa włączona</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asystent (ładuję…)</translation>
+<translation id="1841545962859478868">Administrator urządzenia może monitorować te zdarzenia:</translation>
 <translation id="1850504506766569011">Wi-Fi wyłączone.</translation>
 <translation id="1864454756846565995">Urządzenie USB-C (tylny port)</translation>
 <translation id="1882897271359938046">Kopia na <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Łączenie</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Powrót do wcześniejszej rozdzielczości za <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Zmień ustawienia określające, czy strony mogą korzystać z takich funkcji jak geolokalizacja, mikrofon, aparat itd.</translation>
 <translation id="7256634071279256947">Tylny mikrofon</translation>
 <translation id="726276584504105859">Przeciągnij tutaj, by podzielić ekran</translation>
 <translation id="7348093485538360975">Klawiatura ekranowa</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Przenoszenie okien na inny pulpit może spowodować nieoczekiwane zachowanie. Kolejne powiadomienia, okna i okna dialogowe mogą pojawiać się na różnych pulpitach.</translation>
 <translation id="7569509451529460200">Włączono Braille'a i ChromeVox</translation>
 <translation id="7593891976182323525">Szukaj lub Shift</translation>
+<translation id="7604942372593434070">Uzyskaj dostęp do swojej aktywności związanej z przeglądaniem</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (właściciel)</translation>
 <translation id="7647488630410863958">Odblokuj urządzenie, by zobaczyć swoje powiadomienia</translation>
 <translation id="7649070708921625228">Pomoc</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">silny sygnał</translation>
 <translation id="7842569679327885685">Ostrzeżenie: funkcja eksperymentalna</translation>
 <translation id="7846634333498149051">Klawiatura</translation>
+<translation id="790040513076446191">Manipulować ustawieniami ochrony prywatności</translation>
 <translation id="7904094684485781019">Administrator tego konta zablokował możliwość wielokrotnego logowania.</translation>
 <translation id="7933084174919150729">Asystent Google jest dostępny tylko na profilu głównym.</translation>
 <translation id="79341161159229895">Kontem zarządzają <ph name="FIRST_PARENT_EMAIL" /> i <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Sieć prywatna</translation>
 <translation id="8152119955266188852">Naciśnięto skrót lupy pełnoekranowej. Czy chcesz ją włączyć?</translation>
 <translation id="8190698733819146287">Dostosuj języki i metody wprowadzania...</translation>
+<translation id="8191230140820435481">Zarządzanie Twoimi aplikacjami, rozszerzeniami i motywami</translation>
 <translation id="8261506727792406068">Usuń</translation>
 <translation id="8297006494302853456">Słaby</translation>
 <translation id="8308637677604853869">Poprzednie menu</translation>
diff --git a/ash/strings/ash_strings_pt-BR.xtb b/ash/strings/ash_strings_pt-BR.xtb
index f2051771..85cce9e 100644
--- a/ash/strings/ash_strings_pt-BR.xtb
+++ b/ash/strings/ash_strings_pt-BR.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Teclado na tela ativado</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistente (carregando…)</translation>
+<translation id="1841545962859478868">O administrador do dispositivo pode monitorar o seguinte:</translation>
 <translation id="1850504506766569011">O Wi-Fi está desligado.</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (porta traseira)</translation>
 <translation id="1882897271359938046">Espelhamento de <ph name="DISPLAY_NAME" /></translation>
@@ -327,6 +328,7 @@
 <translation id="7143207342074048698">Conectando</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Revertendo para resolução anterior em <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipular configurações que determinam se websites podem usar recursos, como geolocalização, microfone, câmera, etc.</translation>
 <translation id="7256634071279256947">Microfone traseiro</translation>
 <translation id="726276584504105859">Arraste aqui para usar a tela dividida</translation>
 <translation id="7348093485538360975">Teclado virtual</translation>
@@ -343,6 +345,7 @@
 <translation id="7564874036684306347">A movimentação de janelas para outra área de trabalho pode resultar em comportamentos inesperados. Notificações posteriores, janelas e caixas de diálogo podem ser divididas entre as áreas de trabalho.</translation>
 <translation id="7569509451529460200">Braille e ChromeVox estão ativados</translation>
 <translation id="7593891976182323525">Pesquisar ou Shift</translation>
+<translation id="7604942372593434070">Acesse sua atividade de navegação</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (proprietário)</translation>
 <translation id="7647488630410863958">Desbloquear dispositivo para ver as notificações</translation>
 <translation id="7649070708921625228">Ajuda</translation>
@@ -357,6 +360,7 @@
 <translation id="7829386189513694949">Sinal forte</translation>
 <translation id="7842569679327885685">Aviso: recurso experimental</translation>
 <translation id="7846634333498149051">Teclado</translation>
+<translation id="790040513076446191">Manipular as configurações relacionadas à privacidade</translation>
 <translation id="7904094684485781019">O administrador desta conta bloqueou o login múltiplo.</translation>
 <translation id="7933084174919150729">O Google Assistente só está disponível para o perfil principal.</translation>
 <translation id="79341161159229895">Conta gerenciada por <ph name="FIRST_PARENT_EMAIL" /> e <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -375,6 +379,7 @@
 <translation id="8142699993796781067">Rede privada</translation>
 <translation id="8152119955266188852">Você pressionou o atalho para a lupa de tela cheia. Gostaria de ativá-la?</translation>
 <translation id="8190698733819146287">Personalizar idiomas e entrada...</translation>
+<translation id="8191230140820435481">Gerenciar seus aplicativos, extensões e temas</translation>
 <translation id="8261506727792406068">Excluir</translation>
 <translation id="8297006494302853456">Fraco</translation>
 <translation id="8308637677604853869">Menu anterior</translation>
diff --git a/ash/strings/ash_strings_pt-PT.xtb b/ash/strings/ash_strings_pt-PT.xtb
index 4d207e5..96d3210 100644
--- a/ash/strings/ash_strings_pt-PT.xtb
+++ b/ash/strings/ash_strings_pt-PT.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Teclado no ecrã ativado</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistente (a carregar...)</translation>
+<translation id="1841545962859478868">O gestor do dispositivo pode monitorizar o seguinte:</translation>
 <translation id="1850504506766569011">A ligação Wi-Fi está desativada.</translation>
 <translation id="1864454756846565995">Dispositivo USB-C (porta traseira)</translation>
 <translation id="1882897271359938046">A espelhar para <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">A ligar</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">A reverter para a resolução antiga dentro de <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Manipular definições que especificam se os Sites podem utilizar funcionalidades como a geolocalização, o microfone, a câmara, etc.</translation>
 <translation id="7256634071279256947">Microfone posterior</translation>
 <translation id="726276584504105859">Arraste para aqui para utilizar o ecrã dividido</translation>
 <translation id="7348093485538360975">Teclado no ecrã</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">A movimentação de janelas para outro ambiente de trabalho pode provocar um comportamento inesperado. É possível dividir as notificações, as janelas e as caixas de diálogo subsequentes entre ambientes de trabalho.</translation>
 <translation id="7569509451529460200">O braille e o ChromeVox estão ativados</translation>
 <translation id="7593891976182323525">Pesquisar ou Shift</translation>
+<translation id="7604942372593434070">Acesso à sua atividade de navegação</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (proprietário)</translation>
 <translation id="7647488630410863958">Desbloquear o dispositivo para ver as notificações</translation>
 <translation id="7649070708921625228">Ajuda</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Sinal forte</translation>
 <translation id="7842569679327885685">Aviso: funcionalidade experimental</translation>
 <translation id="7846634333498149051">Teclado</translation>
+<translation id="790040513076446191">Manipular as definições relacionadas com privacidade</translation>
 <translation id="7904094684485781019">O gestor desta conta removeu a permissão de início de sessão integrado.</translation>
 <translation id="7933084174919150729">O Assistente Google está disponível apenas no perfil principal.</translation>
 <translation id="79341161159229895">Conta gerida por <ph name="FIRST_PARENT_EMAIL" /> e <ph name="SECOND_PARENT_EMAIL" />.</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Rede privada</translation>
 <translation id="8152119955266188852">Premiu o atalho da lupa de ecrã inteiro. Pretende ativá-la?</translation>
 <translation id="8190698733819146287">Personalizar idiomas e introdução...</translation>
+<translation id="8191230140820435481">Gerir as suas aplicações, extensões e temas</translation>
 <translation id="8261506727792406068">Eliminar</translation>
 <translation id="8297006494302853456">Fraca</translation>
 <translation id="8308637677604853869">Menu anterior</translation>
diff --git a/ash/strings/ash_strings_ro.xtb b/ash/strings/ash_strings_ro.xtb
index 5e534d8..c5caf08 100644
--- a/ash/strings/ash_strings_ro.xtb
+++ b/ash/strings/ash_strings_ro.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Tastatură pe ecran activată</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistent (se încarcă...)</translation>
+<translation id="1841545962859478868">Este posibil ca administratorul dispozitivului să monitorizeze următoarele:</translation>
 <translation id="1850504506766569011">Conexiunea Wi-Fi este dezactivată.</translation>
 <translation id="1864454756846565995">Dispozitiv USB-C (portul din spate)</translation>
 <translation id="1882897271359938046">Se oglindește pe <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Se conectează</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Se revine la rezoluția anterioară în <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Controlează setările care precizează dacă site-urile pot utiliza funcții, cum ar fi localizarea geografică, microfonul, camera foto etc.</translation>
 <translation id="7256634071279256947">Microfonul din spate</translation>
 <translation id="726276584504105859">Trage aici pentru a folosi ecranul împărțit</translation>
 <translation id="7348093485538360975">Tastatură pe ecran</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Mutarea ferestrelor pe alt desktop poate duce la un comportament neașteptat. Notificările, ferestrele și casetele de dialog ulterioare ar putea fi împărțite între ecrane.</translation>
 <translation id="7569509451529460200">Braille și ChromeVox sunt activate</translation>
 <translation id="7593891976182323525">Căutare sau Shift</translation>
+<translation id="7604942372593434070">Accesează activitatea de navigare</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (proprietar)</translation>
 <translation id="7647488630410863958">Deblochează dispozitivul pentru a vedea notificările</translation>
 <translation id="7649070708921625228">Ajutor</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Semnal puternic</translation>
 <translation id="7842569679327885685">Avertisment: funcție experimentală</translation>
 <translation id="7846634333498149051">Tastatură</translation>
+<translation id="790040513076446191">Manipulează setările privind confidențialitatea</translation>
 <translation id="7904094684485781019">Administratorul acestui cont a dezactivat conectarea multiplă.</translation>
 <translation id="7933084174919150729">Asistentul Google este disponibil numai pentru profilul principal.</translation>
 <translation id="79341161159229895">Cont gestionat de <ph name="FIRST_PARENT_EMAIL" /> și de <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Rețea privată</translation>
 <translation id="8152119955266188852">Ai accesat comanda rapidă pentru lupa de ecran complet. Vrei să o activezi?</translation>
 <translation id="8190698733819146287">Personalizează limbile și modul de introducere...</translation>
+<translation id="8191230140820435481">Gestionează aplicațiile, extensiile și temele dvs.</translation>
 <translation id="8261506727792406068">Șterge</translation>
 <translation id="8297006494302853456">Slab</translation>
 <translation id="8308637677604853869">Meniul anterior</translation>
diff --git a/ash/strings/ash_strings_ru.xtb b/ash/strings/ash_strings_ru.xtb
index b84162b..a2b5e86 100644
--- a/ash/strings/ash_strings_ru.xtb
+++ b/ash/strings/ash_strings_ru.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Экранная клавиатура включена</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Ассистент (загрузка…)</translation>
+<translation id="1841545962859478868">Администратор устройства может просматривать следующую информацию:</translation>
 <translation id="1850504506766569011">Wi-Fi отключен</translation>
 <translation id="1864454756846565995">Устройство USB-C (порт сзади)</translation>
 <translation id="1882897271359938046">Дублирование экрана в <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Подключение</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Возврат к предыдущему разрешению через <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Изменение настроек, управляющих доступом сайтов к геоданным, микрофону, камере и другим функциям.</translation>
 <translation id="7256634071279256947">Основной микрофон</translation>
 <translation id="726276584504105859">Перетащите сюда, чтобы разделить экран</translation>
 <translation id="7348093485538360975">Экранная клавиатура</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">При переносе окон на другой рабочий стол иногда возникают ошибки. Уведомления и диалоговые окна могут появляться на разных рабочих столах.</translation>
 <translation id="7569509451529460200">Шрифт Брайля и функция ChromeVox включены</translation>
 <translation id="7593891976182323525">Search или Shift</translation>
+<translation id="7604942372593434070">Доступ к данным о посещенных веб-страницах</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (владелец)</translation>
 <translation id="7647488630410863958">Чтобы просмотреть уведомления, разблокируйте устройство</translation>
 <translation id="7649070708921625228">Справка</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">хороший сигнал</translation>
 <translation id="7842569679327885685">Внимание! Экспериментальная функция</translation>
 <translation id="7846634333498149051">Клавиатура</translation>
+<translation id="790040513076446191">Управление настройками конфиденциальности</translation>
 <translation id="7904094684485781019">Администратор этого аккаунта запретил множественный вход</translation>
 <translation id="7933084174919150729">Google Ассистент доступен только в основном профиле</translation>
 <translation id="79341161159229895">Администраторы аккаунта: <ph name="FIRST_PARENT_EMAIL" /> и <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Частные сети</translation>
 <translation id="8152119955266188852">Вы нажали сочетание клавиш для полноэкранной лупы. Включить ее?</translation>
 <translation id="8190698733819146287">Настройки языков и ввода…</translation>
+<translation id="8191230140820435481">Управление приложениями, расширениями и темами</translation>
 <translation id="8261506727792406068">Удалить</translation>
 <translation id="8297006494302853456">Слабый</translation>
 <translation id="8308637677604853869">Предыдущее меню</translation>
diff --git a/ash/strings/ash_strings_sk.xtb b/ash/strings/ash_strings_sk.xtb
index 46ae3ce..8eb5946 100644
--- a/ash/strings/ash_strings_sk.xtb
+++ b/ash/strings/ash_strings_sk.xtb
@@ -7,6 +7,7 @@
 <translation id="1037492556044956303">Pridali ste zariadenie <ph name="DEVICE_NAME" /></translation>
 <translation id="1056775291175587022">Žiadne siete</translation>
 <translation id="1059194134494239015"><ph name="DISPLAY_NAME" />: <ph name="RESOLUTION" /></translation>
+<translation id="1104084341931202936">Zobraziť nastavenia dostupnosti</translation>
 <translation id="1104621072296271835">Vaše zariadenia fungujú spoločne ešte lepšie</translation>
 <translation id="112308213915226829">Automatické skrývanie poličky</translation>
 <translation id="1153356358378277386">Spárované zariadenia</translation>
@@ -20,17 +21,20 @@
 <translation id="1279938420744323401"><ph name="DISPLAY_NAME" /> (<ph name="ANNOTATION" />)</translation>
 <translation id="1290331692326790741">Slabý signál</translation>
 <translation id="1293264513303784526">Zariadenie USB-C (port vľavo)</translation>
+<translation id="1302880136325416935">Zobraziť nastavenia Bluetooth. <ph name="STATE_TEXT" /></translation>
 <translation id="1346748346194534595">Doprava</translation>
 <translation id="1351937230027495976">Zbaliť ponuku</translation>
 <translation id="1383876407941801731">Vyhľadávanie</translation>
 <translation id="1467432559032391204">Doľava</translation>
 <translation id="1484102317210609525"><ph name="DEVICE_NAME" /> (HDMI/DP)</translation>
 <translation id="1510238584712386396">Spúšťač</translation>
+<translation id="1520303207432623762">{NUM_APPS,plural, =1{Zobraziť nastavenia upozornení. Upozornenia sú pre aplikáciu vypnuté}few{Zobraziť nastavenia upozornení. Upozornenia sú vypnuté pre # aplikácie}many{Show notification settings. Notifications are off for # apps}other{Zobraziť nastavenia upozornení. Upozornenia sú vypnuté pre # aplikácií}}</translation>
 <translation id="1525508553941733066">ZATVORIŤ</translation>
 <translation id="1537254971476575106">Lupa celej obrazovky</translation>
 <translation id="15373452373711364">Veľký kurzor myši</translation>
 <translation id="1550523713251050646">Kliknutím zobrazíte ďalšie možnosti</translation>
 <translation id="1567387640189251553">Od posledného zadania hesla bola pripojená iná klávesnica. Je možné, že sa pokúša ukradnúť vaše stlačenia klávesov.</translation>
+<translation id="1570871743947603115">Prepnúť Bluetooth. <ph name="STATE_TEXT" /></translation>
 <translation id="1608626060424371292">Odstrániť tohto používateľa</translation>
 <translation id="1621499497873603021">Čas zostávajúci do vybitia batérie: <ph name="TIME_LEFT" /></translation>
 <translation id="1658406695958299976">Je nám to ľúto, ale vaše heslo sa stále nedarí overiť. Poznámka: Ak ste ho nedávno zmenili, nové heslo sa použije až po odhlásení. Tu použite staré heslo.</translation>
@@ -40,8 +44,10 @@
 <translation id="1743570585616704562">Nerozpoznané</translation>
 <translation id="1746730358044914197">Metódy vstupu sú nakonfigurované vaším správcom.</translation>
 <translation id="1747827819627189109">Klávesnica na obrazovke je povolená</translation>
+<translation id="1761222317188459878">Prepnúť sieťové pripojenie. <ph name="STATE_TEXT" /></translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistent (načítava sa...)</translation>
+<translation id="1841545962859478868">Správca zariadenia môže monitorovať nasledovné:</translation>
 <translation id="1850504506766569011">Pripojenie Wi‑Fi je vypnuté.</translation>
 <translation id="1864454756846565995">Zariadenie USB-C (port vzadu)</translation>
 <translation id="1882897271359938046">Zrkadlenie na displej <ph name="DISPLAY_NAME" /></translation>
@@ -62,6 +68,7 @@
 <translation id="2049639323467105390">Toto zariadenie je spravované doménou <ph name="DOMAIN" />.</translation>
 <translation id="2050339315714019657">Na výšku</translation>
 <translation id="2067602449040652523">Jas klávesnice</translation>
+<translation id="2075212959500165896">Príliš veľa pokusov. Skúste to znova neskôr.</translation>
 <translation id="2081529251031312395">$1 sa bude môcť stále prihlásiť neskôr.</translation>
 <translation id="2127372758936585790">Nabíjačka s nízkym výkonom</translation>
 <translation id="2135456203358955318">Ukotvená lupa</translation>
@@ -75,12 +82,15 @@
 <translation id="2303600792989757991">Prepnúť prehľad okien</translation>
 <translation id="2338501278241028356">Ak chcete nájsť zariadenia v okolí, zapnite Bluetooth</translation>
 <translation id="2339073806695260576">Klepnite na tlačidlo dotykového pera na poličke a vytvorte poznámku, snímku obrazovky alebo použite laserový ukazovateľ či lupu.</translation>
+<translation id="2341729377289034582">Uzamknuté zvisle</translation>
 <translation id="2352467521400612932">Nastavenia dotykového pera</translation>
 <translation id="2354174487190027830">Aktivujte sa sieť <ph name="NAME" /></translation>
 <translation id="2359808026110333948">Pokračovať</translation>
 <translation id="2365393535144473978">Zapnutím mobilných dát aktivujete Bluetooth.</translation>
 <translation id="2391579633712104609">180 °</translation>
+<translation id="239188844683466770">Prepnúť režim Nerušiť</translation>
 <translation id="2412593942846481727">K dispozícii je aktualizácia</translation>
+<translation id="2416346634399901812">Pripojené k sieti <ph name="NETWORK_NAME" /></translation>
 <translation id="2429753432712299108">Zariadenie Bluetooth s názvom <ph name="DEVICE_NAME" /> žiada o povolenie párovania. Skôr ako žiadosti vyhoviete, overte, či sa na danom zariadení zobrazuje nasledujúci prístupový kľúč: <ph name="PASSKEY" /></translation>
 <translation id="2482878487686419369">Upozornenia</translation>
 <translation id="2484513351006226581">Stlačením klávesov <ph name="KEYBOARD_SHORTCUT" /> zmeníte rozloženie klávesnice.</translation>
@@ -113,6 +123,7 @@
 <translation id="2946119680249604491">Pridať pripojenie</translation>
 <translation id="2961963223658824723">Vyskytol sa problém. Skúste to znova o niekoľko minút.</translation>
 <translation id="2963773877003373896">mod3</translation>
+<translation id="2995447421581609334">Zobraziť zariadenia na prenos</translation>
 <translation id="2996462380875591307">Bola zapnutá ukotvená lupa. Vypnete ju opätovným stlačením kombinácie klávesov Ctrl+Hľadať+D.</translation>
 <translation id="2999742336789313416"><ph name="DISPLAY_NAME" /> je verejná relácia spravovaná stránkami <ph name="DOMAIN" /></translation>
 <translation id="3000461861112256445">Zvuk mono</translation>
@@ -127,6 +138,7 @@
 <translation id="315116470104423982">Mobilné dátové prenosy</translation>
 <translation id="3151786313568798007">Orientácia</translation>
 <translation id="3153444934357957346">V rámci viacnásobného prihlásenia môžete mať maximálne nasledujúci počet účtov: <ph name="MULTI_PROFILE_USER_LIMIT" />.</translation>
+<translation id="3202010236269062730">{NUM_DEVICES,plural, =1{Pripojené k zariadeniu}few{Pripojené k # zariadeniam}many{Connected to # devices}other{Pripojené k # zariadeniam}}</translation>
 <translation id="3236488194889173876">Nie je k dispozícii žiadna mobilná sieť</translation>
 <translation id="3238765806255363838"><ph name="USERNAME" /> <ph name="MAIL" /></translation>
 <translation id="3294437725009624529">Hosť</translation>
@@ -158,6 +170,7 @@
 <translation id="3784455785234192852">Uzamknúť</translation>
 <translation id="3798670284305777884">Reproduktor (vnútorný)</translation>
 <translation id="380165613292957338">Ako vám môžem pomôcť?</translation>
+<translation id="383629559565718788">Zobraziť nastavenia klávesnice</translation>
 <translation id="3846575436967432996">Informácie o sieti nie sú k dispozícii</translation>
 <translation id="385051799172605136">Naspäť</translation>
 <translation id="3891340733213178823">Ak sa chcete odhlásiť, stlačte dvakrát kombináciu klávesov Ctrl+Shift+Q.</translation>
@@ -173,8 +186,10 @@
 <translation id="4072264167173457037">Stredne silný signál</translation>
 <translation id="4200057768455216496">Stlačili ste skratku ukotvenej lupy. Chcete ju zapnúť?</translation>
 <translation id="4217571870635786043">Diktovanie</translation>
+<translation id="4261870227682513959">Zobraziť nastavenia upozornení. Upozornenia sú vypnuté</translation>
 <translation id="4274921305979314545">Pripojte si Chromebook k telefónu</translation>
 <translation id="4279490309300973883">Zrkadlenie</translation>
+<translation id="4292681942966152062">Aktivuje sa sieť <ph name="NETWORK_NAME" /></translation>
 <translation id="4321179778687042513">ctrl</translation>
 <translation id="4331809312908958774">Chrome OS</translation>
 <translation id="4338109981321384717">Lupa</translation>
@@ -238,6 +253,7 @@
 <translation id="5673434351075758678">Po synchronizácii nastavení bol zmenený z možnosti <ph name="FROM_LOCALE" /> na <ph name="TO_LOCALE" />.</translation>
 <translation id="574392208103952083">Stredné</translation>
 <translation id="5744083938413354016">Presunutie klepnutím</translation>
+<translation id="5750765938512549687">Rozhranie Bluetooth je vypnuté</translation>
 <translation id="5777841717266010279">Chcete ukončiť zdieľanie obrazovky?</translation>
 <translation id="57838592816432529">Vypnúť zvuk</translation>
 <translation id="5805697420284793859">Správca okien</translation>
@@ -271,12 +287,14 @@
 <translation id="615957422585914272">Zobraziť klávesnicu na obrazovke</translation>
 <translation id="6164005077879661055">Všetky súbory a miestne údaje spojené s týmto kontrolovaným používateľom budú po jeho odstránení natrvalo odstránené. Správca bude môcť naďalej zobraziť navštívené webové stránky a nastavenia tohto používateľa na adrese <ph name="MANAGEMENT_URL" />.</translation>
 <translation id="6165508094623778733">Ďalšie informácie</translation>
+<translation id="6254629735336163724">Uzamknuté vodorovne</translation>
 <translation id="6259254695169772643">Vyberte pomocou dotykového pera</translation>
 <translation id="6267036997247669271"><ph name="NAME" />: Aktivuje sa...</translation>
 <translation id="6284232397434400372">Rozlíšenie sa zmenilo</translation>
 <translation id="6297287540776456956">Pomocou dotykového pera vyberte oblasť</translation>
 <translation id="6310121235600822547">Obrazovka <ph name="DISPLAY_NAME" /> bola otočená o <ph name="ROTATION" /></translation>
 <translation id="632744581670418035">Prekryvná vrstva klávesnice</translation>
+<translation id="6376931439017688372">Rozhranie Bluetooth je zapnuté</translation>
 <translation id="639644700271529076">CAPS LOCK je vypnutý</translation>
 <translation id="6406704438230478924">altgr</translation>
 <translation id="643147933154517414">Všetko je hotové</translation>
@@ -320,11 +338,13 @@
 <translation id="7066646422045619941">Táto sieť je zakázaná správcom.</translation>
 <translation id="7067196344162293536">Automatické otáčanie</translation>
 <translation id="7076293881109082629">Prihlásenie</translation>
+<translation id="7092922358121866860">Zobraziť nastavenia nočného režimu</translation>
 <translation id="7098389117866926363">Zariadenie USB-C (ľavý port vzadu)</translation>
 <translation id="7131634465328662194">Odhlásenie prebehne automaticky.</translation>
 <translation id="7143207342074048698">Pripája sa</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Vrátenie starého rozlíšenia prebehne o <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Upravujte nastavenia, ktoré určujú, či weby môžu používať funkcie, ako sú geolokácia, mikrofón, kamera atď.</translation>
 <translation id="7256634071279256947">Zadný mikrofóm</translation>
 <translation id="726276584504105859">Presuňte okno sem a použite tak rozdelenú obrazovku</translation>
 <translation id="7348093485538360975">Klávesnica na obrazovke</translation>
@@ -341,6 +361,7 @@
 <translation id="7564874036684306347">Presunutie okien na inú obrazovku môže spôsobiť neočakávané správanie. Nasledujúce upozornenia, okná a dialógové okná sa môžu zobraziť rozdelené na viacerých pracovných plochách.</translation>
 <translation id="7569509451529460200">Funkcie Braille a ChromeVox sú povolené</translation>
 <translation id="7593891976182323525">Hľadať alebo Shift</translation>
+<translation id="7604942372593434070">Prístup k vašej aktivite prehliadania</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (vlastník)</translation>
 <translation id="7647488630410863958">Ak si chcete zobraziť upozornenia, odomknite zariadenie</translation>
 <translation id="7649070708921625228">Pomocník</translation>
@@ -353,8 +374,10 @@
 <translation id="7798302898096527229">Ak to chcete zrušiť, stlačte kláves Hľadať alebo Shift.</translation>
 <translation id="7814236020522506259"><ph name="HOUR" /> a <ph name="MINUTE" /></translation>
 <translation id="7829386189513694949">Silný signál</translation>
+<translation id="7842211907556571265">Prebieha pripájanie k sieti <ph name="NETWORK_NAME" /></translation>
 <translation id="7842569679327885685">Upozornenie: Experimentálna funkcia</translation>
 <translation id="7846634333498149051">Klávesnica</translation>
+<translation id="790040513076446191">Manipulovať s nastaveniami týkajúcimi sa ochrany osobných údajov</translation>
 <translation id="7904094684485781019">Správca tohto účtu zakázal viacnásobné prihlásenie.</translation>
 <translation id="7933084174919150729">Asistent Google je k dispozícii iba pre primárny profil.</translation>
 <translation id="79341161159229895">Správcovia účtu: <ph name="FIRST_PARENT_EMAIL" /> a <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +396,7 @@
 <translation id="8142699993796781067">Súkromná sieť</translation>
 <translation id="8152119955266188852">Stlačili ste skratku lupy na celú obrazovku. Chcete ju zapnúť?</translation>
 <translation id="8190698733819146287">Prebieha prispôsobenie jazykov a vstupu...</translation>
+<translation id="8191230140820435481">Spravovať vaše aplikácie, rozšírenia a motívy</translation>
 <translation id="8261506727792406068">Odstrániť</translation>
 <translation id="8297006494302853456">Slabé</translation>
 <translation id="8308637677604853869">Predchádzajúca ponuka</translation>
@@ -391,18 +415,24 @@
 <translation id="8484916590211895857"><ph name="NAME" />: Opätovné pripájanie...</translation>
 <translation id="8513108775083588393">Automatické otáčanie</translation>
 <translation id="8517041960877371778">Vaše zariadenie <ph name="DEVICE_TYPE" /> sa nemusí nabíjať, keď je zapnuté.</translation>
+<translation id="8627191004499078455">Pripojené k zariadeniu <ph name="DEVICE_NAME" /></translation>
 <translation id="8639760480004882931">Zostávajúca kapacita: <ph name="PERCENTAGE" /></translation>
 <translation id="8649101189709089199">Počúvanie vybraného textu</translation>
 <translation id="8652175077544655965">Zatvoriť nastavenia</translation>
+<translation id="8653151467777939995">Zobraziť nastavenia upozornení. Upozornenia sú zapnuté</translation>
+<translation id="8664753092453405566">Zobraziť zoznam sietí. <ph name="STATE_TEXT" /></translation>
 <translation id="8673028979667498656">270 °</translation>
 <translation id="8676770494376880701">Pripojila sa nabíjačka s nízkym výkonom</translation>
 <translation id="8683506306463609433">Trasovanie výkonnosti je aktívne</translation>
 <translation id="8734991477317290293">Je možné, že sa pokúša ukradnúť vaše stlačenia klávesov</translation>
+<translation id="8735953464173050365">Zobraziť nastavenia klávesnice. Vybraná klávesnica: <ph name="KEYBOARD_NAME" /></translation>
+<translation id="875593634123171288">Zobraziť nastavenia VPN</translation>
 <translation id="8809737090443522491">Zadajte názov aplikácie alebo dokumentu</translation>
 <translation id="8814190375133053267">Wi‑Fi</translation>
 <translation id="8825534185036233643">Zrkadlenie s viac ako dvoma obrazovkami nie je podporované.</translation>
 <translation id="8828714802988429505">90 °</translation>
 <translation id="8841375032071747811">Tlačidlo Späť</translation>
+<translation id="8843682306134542540">Prepnúť uzamknutie otočenia. <ph name="STATE_TEXT" /></translation>
 <translation id="8850991929411075241">Hľadať+Esc</translation>
 <translation id="8870509716567206129">Aplikácia nepodporuje rozdelenú obrazovku.</translation>
 <translation id="8874184842967597500">Nepripojené</translation>
diff --git a/ash/strings/ash_strings_sl.xtb b/ash/strings/ash_strings_sl.xtb
index 7a6ea93..e9e1694 100644
--- a/ash/strings/ash_strings_sl.xtb
+++ b/ash/strings/ash_strings_sl.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Zaslonska tipkovnica omogočena</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Pomočnik (nalaganje ...)</translation>
+<translation id="1841545962859478868">Skrbnik naprave morda nadzira naslednje:</translation>
 <translation id="1850504506766569011">Wi-Fi je izklopljen.</translation>
 <translation id="1864454756846565995">Naprava USB-C (vrata zadaj)</translation>
 <translation id="1882897271359938046">Zrcaljenje na <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Vzpostavljanje povezave</translation>
 <translation id="7165278925115064263">Alt + Shift + K</translation>
 <translation id="7168224885072002358">Ponastavitev na prejšnjo ločljivost čez <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Spreminjanje nastavitev, ki določajo, ali smejo spletna mesta uporabljati funkcije, kot so geolokacija, mikrofon, fotoaparat ipd.</translation>
 <translation id="7256634071279256947">Zadnji mikrofon</translation>
 <translation id="726276584504105859">Povlecite sem za razdeljeni zaslon</translation>
 <translation id="7348093485538360975">Zaslonska tipkovnica</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Če okna premaknete na drugo namizje, lahko pride do nepričakovanega delovanja. Nadaljnja obvestila, okna in pogovorna okna so lahko razdeljena med namizji.</translation>
 <translation id="7569509451529460200">Omogočena sta braillova pisava in ChromeVox</translation>
 <translation id="7593891976182323525">Iskanje ali Shift</translation>
+<translation id="7604942372593434070">Dostop do dejavnosti brskanja</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (lastnik)</translation>
 <translation id="7647488630410863958">Če si želite ogledati obvestila, odklenite napravo</translation>
 <translation id="7649070708921625228">Pomoč</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Močan signal</translation>
 <translation id="7842569679327885685">Opozorilo: poskusna funkcija</translation>
 <translation id="7846634333498149051">Tipkovnica</translation>
+<translation id="790040513076446191">Upravljajte nastavitve zasebnosti</translation>
 <translation id="7904094684485781019">Skrbnik tega računa je onemogočil prijavo z več računi.</translation>
 <translation id="7933084174919150729">Pomočnik Google je na voljo samo za primarni profil.</translation>
 <translation id="79341161159229895">Račun upravljata <ph name="FIRST_PARENT_EMAIL" /> in <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Zasebno omrežje</translation>
 <translation id="8152119955266188852">Pritisnili ste bližnjico za celozaslonsko lupo. Ali jo želite vklopiti?</translation>
 <translation id="8190698733819146287">Prilagajanje jezikov in vnosa ...</translation>
+<translation id="8191230140820435481">Upravljajte aplikacije, razširitve in teme</translation>
 <translation id="8261506727792406068">Izbriši</translation>
 <translation id="8297006494302853456">Šibek</translation>
 <translation id="8308637677604853869">Prejšnji meni</translation>
diff --git a/ash/strings/ash_strings_sr.xtb b/ash/strings/ash_strings_sr.xtb
index 4f3f13e..36fc712 100644
--- a/ash/strings/ash_strings_sr.xtb
+++ b/ash/strings/ash_strings_sr.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Тастатура на екрану је омогућена</translation>
 <translation id="1823873187264960516">Етернет: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Помоћник (учитава се...)</translation>
+<translation id="1841545962859478868">Администратор уређаја може да прати следеће:</translation>
 <translation id="1850504506766569011">Wi-Fi је искључен.</translation>
 <translation id="1864454756846565995">Уређај са USB прикључком типа C (задњи порт)</translation>
 <translation id="1882897271359938046">Пресликавање у <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Повезивање</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Враћање на стару резолуцију за <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Управљајте подешавањима која одређују да ли веб-сајтови могу да користе функције као што су геолоцирање, микрофон, камера итд.</translation>
 <translation id="7256634071279256947">Задњи микрофон</translation>
 <translation id="726276584504105859">Превуците овде да бисте користили подељени екран</translation>
 <translation id="7348093485538360975">Тастатура на екрану</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Премештање прозора на други рачунар може да доведе до неочекиваног понашања. Накнадна обавештења, прозори и дијалози ће можда бити подељени између рачунара.</translation>
 <translation id="7569509451529460200">Брајева азбука и ChromeVox су омогућени</translation>
 <translation id="7593891976182323525">Тастер за претрагу или Shift</translation>
+<translation id="7604942372593434070">Приступите активностима прегледања</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (власник)</translation>
 <translation id="7647488630410863958">Откључајте уређај да бисте прегледали обавештења</translation>
 <translation id="7649070708921625228">Помоћ</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">Јак сигнал</translation>
 <translation id="7842569679327885685">Упозорење: Експериментална функција</translation>
 <translation id="7846634333498149051">Тастатура</translation>
+<translation id="790040513076446191">Управљање подешавањима у вези са приватношћу</translation>
 <translation id="7904094684485781019">Администратор за овај налог је забранио вишеструко пријављивање.</translation>
 <translation id="7933084174919150729">Google помоћник је доступан само за примарни профил.</translation>
 <translation id="79341161159229895">Налогом управљају <ph name="FIRST_PARENT_EMAIL" /> и <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Приватна мрежа</translation>
 <translation id="8152119955266188852">Притиснули сте пречицу за лупу за цео екран. Желите ли да је укључите?</translation>
 <translation id="8190698733819146287">Прилагоди језике и унос...</translation>
+<translation id="8191230140820435481">Управљање апликацијама, додацима и темама</translation>
 <translation id="8261506727792406068">Избриши</translation>
 <translation id="8297006494302853456">Слаб</translation>
 <translation id="8308637677604853869">Претходни мени</translation>
diff --git a/ash/strings/ash_strings_sv.xtb b/ash/strings/ash_strings_sv.xtb
index 86717ba..c2cd3dd 100644
--- a/ash/strings/ash_strings_sv.xtb
+++ b/ash/strings/ash_strings_sv.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Skärmen på tangentbordet har aktiverats</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistent (läses in …)</translation>
+<translation id="1841545962859478868">Enhetsadministratören kan övervaka följande:</translation>
 <translation id="1850504506766569011">Wi-Fi är inaktiverat.</translation>
 <translation id="1864454756846565995">USB-C-enhet (bakre port)</translation>
 <translation id="1882897271359938046">Spegling av <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Ansluter</translation>
 <translation id="7165278925115064263">Alt + Skift + K</translation>
 <translation id="7168224885072002358">Återgår till den gamla upplösningen om <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Ändra inställningar som anger om webbplatser kan använda funktioner som geografisk plats, mikrofon, kamera, med mera.</translation>
 <translation id="7256634071279256947">Mikrofonen på baksidan</translation>
 <translation id="726276584504105859">Dra hit för att dela upp skärmen</translation>
 <translation id="7348093485538360975">Skärmtangentbord</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Om du flyttar fönster till ett annat skrivbord kan det leda till oväntat beteende. Efterföljande meddelanden, fönster och dialogrutor kan delas upp mellan skrivborden.</translation>
 <translation id="7569509451529460200">Punktskrift och ChromeVox har aktiverats</translation>
 <translation id="7593891976182323525">Sök eller Skift</translation>
+<translation id="7604942372593434070">Få tillgång till webbaktiviteten</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (ägare)</translation>
 <translation id="7647488630410863958">Lås upp enheten om du vill visa aviseringarna</translation>
 <translation id="7649070708921625228">Hjälp</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">stark signal</translation>
 <translation id="7842569679327885685">Varning: Experimentfunktion</translation>
 <translation id="7846634333498149051">Tangentbord</translation>
+<translation id="790040513076446191">Ändra sekretessrelaterade inställningar</translation>
 <translation id="7904094684485781019">Administratören för kontot tillåter inte multiinloggning.</translation>
 <translation id="7933084174919150729">Endast den primära profilen har tillgång till Google Assistent.</translation>
 <translation id="79341161159229895">Kontot hanteras av <ph name="FIRST_PARENT_EMAIL" /> och <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Privat nätverk</translation>
 <translation id="8152119955266188852">Du tryckte på kortkommandot för helskärmsförstoring. Vill du aktivera funktionen?</translation>
 <translation id="8190698733819146287">Anpassa språk och inmatning...</translation>
+<translation id="8191230140820435481">Hantera dina appar, tillägg och teman</translation>
 <translation id="8261506727792406068">Radera</translation>
 <translation id="8297006494302853456">Svag</translation>
 <translation id="8308637677604853869">Föregående meny</translation>
diff --git a/ash/strings/ash_strings_sw.xtb b/ash/strings/ash_strings_sw.xtb
index 665dc21..1c40dd0 100644
--- a/ash/strings/ash_strings_sw.xtb
+++ b/ash/strings/ash_strings_sw.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Kibodi ya skrini imewashwa</translation>
 <translation id="1823873187264960516">Ethaneti: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Mratibu (inapakia...)</translation>
+<translation id="1841545962859478868">Msimamizi wa kifaa anaweza kufuatilia mambo yafuatayo:</translation>
 <translation id="1850504506766569011">Wi-Fi imezimwa.</translation>
 <translation id="1864454756846565995">Kifaa cha USB-C (mlango wa nyuma)</translation>
 <translation id="1882897271359938046">Inaakisi kwenye <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Inaunganisha</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Inarejesha katika ubora wa zamani baada ya <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Badilisha mipangilio ambayo hubainisha ikiwa tovuti zinaweza kutumia vipengele kama vile kutambulisha mahali, maikrofoni, kamera, nk.</translation>
 <translation id="7256634071279256947">Maikrofoni ya nyuma</translation>
 <translation id="726276584504105859">Buruta hapa ili utumie skrini iliyogawanywa</translation>
 <translation id="7348093485538360975">Kibodi ya skrini</translation>
@@ -342,6 +344,7 @@
 Arifa, madirisha na vidirisha vya baadaye vitagawanywa kati ya maeneo-kazi.</translation>
 <translation id="7569509451529460200">Breli na ChromeVox zimewashwa</translation>
 <translation id="7593891976182323525">Utafutaji au Hama</translation>
+<translation id="7604942372593434070">Fikia shughuli zako za kuvinjari</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (mmiliki)</translation>
 <translation id="7647488630410863958">Fungua kifaa ili uone arifa zako</translation>
 <translation id="7649070708921625228">Usaidizi</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Mtandao ni thabiti</translation>
 <translation id="7842569679327885685">Ilani: Kipengele cha majaribio</translation>
 <translation id="7846634333498149051">Kibodi</translation>
+<translation id="790040513076446191">Weka utakavyo mipangilio husika ya faragha</translation>
 <translation id="7904094684485781019">Msimamizi wa akaunti hii ameondoa uwezo wa kuingia katika akaunti nyingi kwa wakati mmoja</translation>
 <translation id="7933084174919150729">Programu ya Mratibu wa Google inapatikana kwenye wasifu wa msingi pekee.</translation>
 <translation id="79341161159229895">Akaunti inasimamiwa na <ph name="FIRST_PARENT_EMAIL" /> na <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Mtandao binafsi</translation>
 <translation id="8152119955266188852">Umebofya njia ya mkato ya kikuzaji cha skrini nzima. Ungependa kukiwasha?</translation>
 <translation id="8190698733819146287">Geuza lugha na uingizaji kukufaa...</translation>
+<translation id="8191230140820435481">Kudhibiti programu, viendelezi na mandhari yako</translation>
 <translation id="8261506727792406068">Futa</translation>
 <translation id="8297006494302853456">Dhaifu</translation>
 <translation id="8308637677604853869">Menyu ya awali</translation>
diff --git a/ash/strings/ash_strings_ta.xtb b/ash/strings/ash_strings_ta.xtb
index dde1ef25..ac0d1c9c1 100644
--- a/ash/strings/ash_strings_ta.xtb
+++ b/ash/strings/ash_strings_ta.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">ஆன்ஸ்க்ரீன் விசைப்பலகை இயக்கப்பட்டது</translation>
 <translation id="1823873187264960516">ஈதர்நெட்: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">அசிஸ்டண்ட் (ஏற்றுகிறது...)</translation>
+<translation id="1841545962859478868">பின்வருவனவற்றைச் சாதன நிர்வாகி கண்காணிக்கக்கூடும்:</translation>
 <translation id="1850504506766569011">வைஃபை முடக்கத்தில் உள்ளது.</translation>
 <translation id="1864454756846565995">USB-C சாதனம் (பின்பக்கப் போர்ட்)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" /> ஐப் பிரதிபலிக்கிறது</translation>
@@ -295,7 +296,7 @@
 <translation id="6627638273713273709">தேடல்+ஷிஃப்ட்+K</translation>
 <translation id="6650933572246256093">புளூடூத் சாதனம் "<ph name="DEVICE_NAME" />" இணைப்பதற்கான அனுமதியை விரும்புகிறது. அந்தச் சாதனத்தில் இந்தக் கடவுச்சொல்லை உள்ளிடவும்: <ph name="PASSKEY" /></translation>
 <translation id="6657585470893396449">கடவுச்சொல்</translation>
-<translation id="6687966522050791666">மொழி மற்றும் உள்ளீட்டைத் தேர்வுசெய்யவும்</translation>
+<translation id="6687966522050791666">மொழியையும் உள்ளீட்டையும் தேர்வுசெய்யவும்</translation>
 <translation id="6691659475504239918">தேடல்+ஷிஃப்ட்+H</translation>
 <translation id="6696025732084565524">உங்கள் அகற்றத்தக்க விசைப்பலகைக்கு முக்கியப் புதுப்பிப்பு வேண்டும்</translation>
 <translation id="6700713906295497288">IME மெனு பொத்தான்</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">இணைத்தல்</translation>
 <translation id="7165278925115064263">ஆல்ட்+ஷிஃப்ட்+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> வினாடிகளில் பழைய தெளிவுதிறனுக்கு மாற்றியமைக்கப்படும்</translation>
+<translation id="7228479291753472782">புவி இருப்பிடம், மைக்ரோஃபோன், கேமரா போன்ற அம்சங்களை, இணையதளங்கள் பயன்படுத்தலாமா வேண்டாமா என்பதைக் குறிக்கும் அமைப்புகளை மாற்றலாம்.</translation>
 <translation id="7256634071279256947">பின்பக்க மைக்ரோஃபோன்</translation>
 <translation id="726276584504105859">திரைப் பிரிப்பைப் பயன்படுத்த, இங்கே இழுக்கவும்</translation>
 <translation id="7348093485538360975">ஆன்-ஸ்கிரீன் விசைப்பலகை</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">சாளரத்தை வேறு டெஸ்க்டாப்க்கு மாற்றினால், அது எதிர்பாராதவிதமாகச் செயல்படக் கூடும். அடுத்தடுத்து வரும் அறிவிப்புகளும் சாளரங்களும் உரையாடல்களும் டெஸ்க்டாப்களுக்கு இடையே பிரிக்கப்படலாம்.</translation>
 <translation id="7569509451529460200">பிரெய்லி மற்றும் ChromeVox இயக்கப்பட்டன</translation>
 <translation id="7593891976182323525">Search அல்லது Shift</translation>
+<translation id="7604942372593434070">உங்கள் உலாவல் செயல்பாட்டை அணுகலாம்</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (உரிமையாளர்)</translation>
 <translation id="7647488630410863958">அறிவிப்புகளைப் பார்க்க, சாதனத்தைத் திறக்கவும்</translation>
 <translation id="7649070708921625228">உதவி</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">வலுவான சிக்னல்</translation>
 <translation id="7842569679327885685">எச்சரிக்கை: சோதனை அம்சம்</translation>
 <translation id="7846634333498149051">விசைப்பலகை</translation>
+<translation id="790040513076446191">தனியுரிமைத் தொடர்பான அமைப்புகளை கையாளலாம்</translation>
 <translation id="7904094684485781019">இந்தக் கணக்கிற்கான நிர்வாகி பல உள்நுழைவுகளை அனுமதிக்கவில்லை.</translation>
 <translation id="7933084174919150729">முதன்மைச் சுயவிவரத்திற்கு மட்டுமே Google அசிஸ்டண்ட்டைப் பயன்படுத்த முடியும்.</translation>
 <translation id="79341161159229895">கணக்கை நிர்வகிப்பது: <ph name="FIRST_PARENT_EMAIL" /> மற்றும் <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">தனிப்பட்ட நெட்வொர்க்</translation>
 <translation id="8152119955266188852">முழுத்திரைப் பெரிதாக்கியின் ஷார்ட்கட்டை அழுத்தியுள்ளீர்கள். அதை இயக்க விரும்புகிறீர்களா?</translation>
 <translation id="8190698733819146287">மொழிகள் மற்றும்  உள்ளீடைத் தனிப்பயனாக்கு...</translation>
+<translation id="8191230140820435481">உங்கள் பயன்பாடுகள், நீட்டிப்புகள் மற்றும் தீம்களை நிர்வகிக்கலாம்</translation>
 <translation id="8261506727792406068">நீக்கு</translation>
 <translation id="8297006494302853456">வலுவாக இல்லை</translation>
 <translation id="8308637677604853869">முந்தைய மெனு</translation>
diff --git a/ash/strings/ash_strings_te.xtb b/ash/strings/ash_strings_te.xtb
index 7fc64a5..69314f8c 100644
--- a/ash/strings/ash_strings_te.xtb
+++ b/ash/strings/ash_strings_te.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">స్క్రీన్‌పై కనిపించే కీబోర్డ్ ప్రారంభించబడింది</translation>
 <translation id="1823873187264960516">ఈథర్‌నెట్: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">సహాయకం (లోడ్ అవుతోంది...)</translation>
+<translation id="1841545962859478868">పరికర నిర్వాహకుడు క్రింది వాటిని పర్యవేక్షించవచ్చు:</translation>
 <translation id="1850504506766569011">Wi-Fi నిలిపివేయబడింది.</translation>
 <translation id="1864454756846565995">USB-C పరికరం (వెనుకవైపు పోర్ట్)</translation>
 <translation id="1882897271359938046"><ph name="DISPLAY_NAME" />కు దర్పణం చేస్తోంది</translation>
@@ -321,6 +322,7 @@
 <translation id="7143207342074048698">కనెక్ట్ అవుతోంది</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" />లో తిరిగి పాత రిజల్యూషన్‌కి మార్చబడుతోంది</translation>
+<translation id="7228479291753472782">వెబ్‌సైట్‌లు భౌగోళికస్థానం, మైక్రోఫోన్, కెమెరా మొదలైనటువంటి లక్షణాలను ఉపయోగించవచ్చా లేదా అనేది పేర్కొనే సెట్టింగ్‌లను సవరించండి</translation>
 <translation id="7256634071279256947">వెనుకవైపు మైక్రోఫోన్</translation>
 <translation id="726276584504105859">విభజన స్క్రీన్‌ను ఉపయోగించడానికి ఇక్కడికి లాగండి</translation>
 <translation id="7348093485538360975">ఆన్-స్క్రీన్ కీబోర్డ్</translation>
@@ -337,6 +339,7 @@
 <translation id="7564874036684306347">విండోలను మరొక డెస్క్‌టాప్‌కు తరలించడం వలన ఊహించని ప్రవర్తన చోటు చేసుకోవచ్చు. తర్వాత చూపబడే నోటిఫికేషన్‌లు, విండోలు మరియు డైలాగ్‌లు డెస్క్‌టాప్‌ల మధ్య విభజించబడవచ్చు.</translation>
 <translation id="7569509451529460200">బ్రెయిలీ మరియు ChromeVox ప్రారంభించబడ్డాయి</translation>
 <translation id="7593891976182323525">Search లేదా Shift</translation>
+<translation id="7604942372593434070">మీ బ్రౌజింగ్ కార్యాచరణను ప్రాప్యత చేయండి</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (యజమాని)</translation>
 <translation id="7647488630410863958">మీ నోటిఫికేషన్‌లను వీక్షించడానికి పరికరాన్ని అన్‌లాక్ చేయండి</translation>
 <translation id="7649070708921625228">సహాయం</translation>
@@ -351,6 +354,7 @@
 <translation id="7829386189513694949">సిగ్నల్ దృఢంగా ఉంది</translation>
 <translation id="7842569679327885685">హెచ్చరిక: ప్రయోగాత్మక ఫీచర్</translation>
 <translation id="7846634333498149051">కీబోర్డ్</translation>
+<translation id="790040513076446191">గోప్యత సంబంధ సెట్టింగ్‌లను మ్యానిపులేట్ చేయండి</translation>
 <translation id="7904094684485781019">ఈ ఖాతా నిర్వాహకులు బహుళ సైన్-ఇన్‌కు అనుమతించలేదు.</translation>
 <translation id="7933084174919150729">ప్రాథమిక ప్రొఫైల్‌కు మాత్రమే Google సహాయకం అందుబాటులో ఉంటుంది.</translation>
 <translation id="79341161159229895"><ph name="FIRST_PARENT_EMAIL" /> మరియు <ph name="SECOND_PARENT_EMAIL" /> ద్వారా ఖాతా నిర్వహించబడుతోంది</translation>
@@ -369,6 +373,7 @@
 <translation id="8142699993796781067">ప్రైవేట్ నెట్‌వర్క్</translation>
 <translation id="8152119955266188852">పూర్తి స్క్రీన్‌ మాగ్నిఫైయర్‌కు సంబంధించిన షార్ట్‌కట్‌ను మీరు నొక్కారు. మీరు దీనిని ఆన్ చేయాలనుకుంటున్నారా?</translation>
 <translation id="8190698733819146287">భాషలను అనుకూలీకరించి, ఇన్‌పుట్ చెయ్యి...</translation>
+<translation id="8191230140820435481">మీ అనువర్తనాలను, పొడిగింపులను మరియు థీమ్‌లను నిర్వహించండి</translation>
 <translation id="8261506727792406068">తొలగించు</translation>
 <translation id="8297006494302853456">బలహీనం</translation>
 <translation id="8308637677604853869">మునుపటి మెను</translation>
diff --git a/ash/strings/ash_strings_th.xtb b/ash/strings/ash_strings_th.xtb
index afb2085..2a957c6 100644
--- a/ash/strings/ash_strings_th.xtb
+++ b/ash/strings/ash_strings_th.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">เปิดใช้แป้นพิมพ์บนหน้าจออยู่</translation>
 <translation id="1823873187264960516">อีเทอร์เน็ต: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Assistant (กำลังโหลด...)</translation>
+<translation id="1841545962859478868">ผู้ดูแลระบบของอุปกรณ์อาจตรวจสอบรายการต่อไปนี้</translation>
 <translation id="1850504506766569011">WiFi ปิดอยู่</translation>
 <translation id="1864454756846565995">อุปกรณ์ USB-C (พอร์ตด้านหลัง)</translation>
 <translation id="1882897271359938046">กำลังแสดงผลไปที่ <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">กำลังเชื่อมต่อ</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">เปลี่ยนกลับไปเป็นความละเอียดเดิมภายใน <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">แก้ไขการตั้งค่าที่จะกำหนดว่าเว็บไซต์สามารถใช้ฟีเจอร์อย่างตำแหน่งทางภูมิศาสตร์ ไมโครโฟน กล้องถ่ายรูป และอื่นๆ ได้ไหม</translation>
 <translation id="7256634071279256947">ไมโครโฟนด้านหลัง</translation>
 <translation id="726276584504105859">ลากมาที่นี่เพื่อใช้การแยกหน้าจอ</translation>
 <translation id="7348093485538360975">แป้นพิมพ์บนหน้าจอ</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">การย้ายหน้าต่างไปยังเดสก์ท็อปอื่นอาจส่งผลให้ลักษณะการทำงานผิกปกติ การแจ้งเตือน หน้าต่าง และกล่องโต้ตอบที่ตามมาอาจแสดงแยกส่วนในเดสก์ท็อปทั้งสอง</translation>
 <translation id="7569509451529460200">เปิดใช้เบรลล์และ ChromeVox แล้ว</translation>
 <translation id="7593891976182323525">ค้นหาหรือ Shift</translation>
+<translation id="7604942372593434070">เข้าถึงกิจกรรมการท่องเว็บของคุณ</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (เจ้าของ)</translation>
 <translation id="7647488630410863958">ปลดล็อกอุปกรณ์เพื่อดูการแจ้งเตือน</translation>
 <translation id="7649070708921625228">ช่วยเหลือ</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">สัญญาณแรง</translation>
 <translation id="7842569679327885685">คำเตือน: ฟีเจอร์ทดลอง</translation>
 <translation id="7846634333498149051">แป้นพิมพ์</translation>
+<translation id="790040513076446191">จัดการการตั้งค่าที่เกี่ยวข้องกับความเป็นส่วนตัว</translation>
 <translation id="7904094684485781019">ผู้ดูแลระบบของบัญชีนี้ไม่อนุญาตการลงชื่อเข้าสู่ระบบพร้อมกันหลายบัญชี</translation>
 <translation id="7933084174919150729">Google Assistant ใช้งานได้กับโปรไฟล์หลักเท่านั้น</translation>
 <translation id="79341161159229895">บัญชีที่จัดการโดย <ph name="FIRST_PARENT_EMAIL" /> และ <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">เครือข่ายส่วนบุคคล</translation>
 <translation id="8152119955266188852">คุณกดแป้นพิมพ์ลัดสำหรับแว่นขยายทั้งหน้าจอ จะเปิดแว่นขยายไหม</translation>
 <translation id="8190698733819146287">กำหนดค่าภาษาและการป้อนข้อมูล... </translation>
+<translation id="8191230140820435481">จัดการแอปพลิเคชัน ส่วนขยาย และธีมของคุณ</translation>
 <translation id="8261506727792406068">ลบ</translation>
 <translation id="8297006494302853456">อ่อน</translation>
 <translation id="8308637677604853869">เมนูก่อนหน้า</translation>
diff --git a/ash/strings/ash_strings_tr.xtb b/ash/strings/ash_strings_tr.xtb
index 033eb49e..d2b7d8f 100644
--- a/ash/strings/ash_strings_tr.xtb
+++ b/ash/strings/ash_strings_tr.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Dokunmatik klavye etkin</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Asistan (yükleniyor...)</translation>
+<translation id="1841545962859478868">Cihaz yöneticisi aşağıdakileri izleyebilir:</translation>
 <translation id="1850504506766569011">Kablosuz kapalı.</translation>
 <translation id="1864454756846565995">USB-C cihaz (arka bağlantı noktası)</translation>
 <translation id="1882897271359938046">Şuraya yansıtılıyor: <ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Bağlanıyor</translation>
 <translation id="7165278925115064263">Alt+Üst Karakter+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" /> saniye içinde eski çözünürlüğe dönülüyor</translation>
+<translation id="7228479291753472782">Web sitelerinin coğrafi konum, mikrofon, kamera vb. özellikleri kullanıp kullanamayacağını belirten ayarları kendi çıkarları için kullanma</translation>
 <translation id="7256634071279256947">Arka mikrofon</translation>
 <translation id="726276584504105859">Ekranı bölünmüş olarak kullanmak için burayı sürükleyin</translation>
 <translation id="7348093485538360975">Ekran klavyesi</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Pencereleri başka bir masaüstüne taşımak, beklenmeyen davranışlara neden olabilir. Pencereler taşındıktan sonra gelen bildirimler, pencereler ve iletişim kutuları masaüstleri arasında bölünebilir.</translation>
 <translation id="7569509451529460200">Braille ve ChromeVox etkinleştirildi</translation>
 <translation id="7593891976182323525">Arama veya Üst Karakter</translation>
+<translation id="7604942372593434070">Göz atma etkinliğinize erişim sağlama</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (cihaz sahibi)</translation>
 <translation id="7647488630410863958">Bildirimlerinizi görüntülemek için cihazınızın kilidini açın</translation>
 <translation id="7649070708921625228">Yardım</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Güçlü sinyal</translation>
 <translation id="7842569679327885685">Uyarı: Deneysel özellik</translation>
 <translation id="7846634333498149051">Klavye</translation>
+<translation id="790040513076446191">Gizlilikle ilgili ayarları değiştirme</translation>
 <translation id="7904094684485781019">Bu hesabın yöneticisi, çoklu oturum açmayı engelledi.</translation>
 <translation id="7933084174919150729">Google Asistan yalnızca birincil profilde kullanılabilir.</translation>
 <translation id="79341161159229895">Hesap <ph name="FIRST_PARENT_EMAIL" /> ve <ph name="SECOND_PARENT_EMAIL" /> tarafından yönetiliyor</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Özel ağ</translation>
 <translation id="8152119955266188852">Tam ekran büyüteç kısayoluna bastınız. Bu özelliği açmak istiyor musunuz?</translation>
 <translation id="8190698733819146287">Dilleri ve girişi özelleştir...</translation>
+<translation id="8191230140820435481">Uygulamalarınızı, uzantılarınızı ve temalarınızı yönetme</translation>
 <translation id="8261506727792406068">Sil</translation>
 <translation id="8297006494302853456">Zayıf</translation>
 <translation id="8308637677604853869">Önceki menü</translation>
diff --git a/ash/strings/ash_strings_uk.xtb b/ash/strings/ash_strings_uk.xtb
index 44c4264..585650b 100644
--- a/ash/strings/ash_strings_uk.xtb
+++ b/ash/strings/ash_strings_uk.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">Екранну клавіатуру ввімкнено</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Асистент (завантаження…)</translation>
+<translation id="1841545962859478868">Адміністратор пристрою може відстежувати таку інформацію:</translation>
 <translation id="1850504506766569011">Wi-Fi вимкнено.</translation>
 <translation id="1864454756846565995">Пристрій із портом USB типу C (на задній панелі)</translation>
 <translation id="1882897271359938046">Дзеркалювання на <ph name="DISPLAY_NAME" /></translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">Під’єднання</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Попередня роздільна здатність повернеться через <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Керувати налаштуваннями, які вказують, чи можуть веб-сайти використовувати такі функції, як геолокація, мікрофон, камера тощо.</translation>
 <translation id="7256634071279256947">Мікрофон на задній панелі</translation>
 <translation id="726276584504105859">Перетягніть сюди, щоб розділити екран</translation>
 <translation id="7348093485538360975">Екранна клавіатура</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">Переміщення вікон на інший робочий стіл може спричинити неочікувану поведінку. Нові сповіщення та вікна можуть розділятися між робочими столами.</translation>
 <translation id="7569509451529460200">Шрифт Брайля та ChromeVox увімкнено</translation>
 <translation id="7593891976182323525">Клавіша пошуку або Shift</translation>
+<translation id="7604942372593434070">Доступ до даних веб-перегляду</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (власник)</translation>
 <translation id="7647488630410863958">Розблокуйте пристрій, щоб переглянути сповіщення</translation>
 <translation id="7649070708921625228">Довідка</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">потужний сигнал</translation>
 <translation id="7842569679327885685">Попередження. Експериментальна функція</translation>
 <translation id="7846634333498149051">Клавіатура</translation>
+<translation id="790040513076446191">Керувати налаштуваннями, пов’язаними з конфіденційністю</translation>
 <translation id="7904094684485781019">Адміністратор цього облікового запису заборонив паралельний вхід.</translation>
 <translation id="7933084174919150729">Google Асистент доступний лише в основному профілі.</translation>
 <translation id="79341161159229895">Обліковим записом керують <ph name="FIRST_PARENT_EMAIL" /> і <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">Приватна мережа</translation>
 <translation id="8152119955266188852">Ви натиснули комбінацію клавіш для лупи на весь екран. Увімкнути?</translation>
 <translation id="8190698733819146287">Налаштувати мови та введення тексту...</translation>
+<translation id="8191230140820435481">Керувати вашими програмами, розширеннями та темами</translation>
 <translation id="8261506727792406068">Видалити</translation>
 <translation id="8297006494302853456">Слабкий</translation>
 <translation id="8308637677604853869">Попереднє меню</translation>
diff --git a/ash/strings/ash_strings_vi.xtb b/ash/strings/ash_strings_vi.xtb
index 9d47bbb..60216f4 100644
--- a/ash/strings/ash_strings_vi.xtb
+++ b/ash/strings/ash_strings_vi.xtb
@@ -42,12 +42,13 @@
 <translation id="1747827819627189109">Đã bật bàn phím ảo</translation>
 <translation id="1823873187264960516">Ethernet: <ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">Trợ lý (đang tải...)</translation>
+<translation id="1841545962859478868">Quản trị viên thiết bị có thể theo dõi các vấn đề sau đây:</translation>
 <translation id="1850504506766569011">Wi-Fi đang tắt.</translation>
 <translation id="1864454756846565995">Thiết bị USB-C (cổng phía sau)</translation>
 <translation id="1882897271359938046">Đang phản chiếu tới <ph name="DISPLAY_NAME" /></translation>
 <translation id="1885785240814121742">Mở khóa bằng vân tay</translation>
 <translation id="1919743966458266018">Phím tắt để mở trình quản lý tác vụ đã thay đổi. Vui lòng sử dụng <ph name="NEW_SHORTCUT" /> thay vì <ph name="OLD_SHORTCUT" />.</translation>
-<translation id="1923508880661655826">Phiên chạy của bạn sẽ bị xóa khi bạn đăng xuất. <ph name="LEARN_MORE" /></translation>
+<translation id="1923508880661655826">Phiên hoạt động của bạn sẽ bị xóa khi bạn đăng xuất. <ph name="LEARN_MORE" /></translation>
 <translation id="1923539912171292317">Nhấp chuột tự động</translation>
 <translation id="1928739107511554905">Để cập nhật, hãy dùng màn hình cảm ứng để khởi động lại Chromebook bằng bàn phím đã kết nối.</translation>
 <translation id="1942830294380034169">Nút tràn</translation>
@@ -225,7 +226,7 @@
 <translation id="544691375626129091">Tất cả người dùng hiện có mặt đã được thêm vào phiên này.</translation>
 <translation id="5457599981699367932">Duyệt với tư cách Khách</translation>
 <translation id="54609108002486618">Được quản lý</translation>
-<translation id="5496819745535887422">Quản trị viên đang khôi phục thiết bị của bạn. Tất cả dữ liệu sẽ bị xóa khi khởi động lại thiết bị.</translation>
+<translation id="5496819745535887422">Quản trị viên đang hạ cấp hệ điều hành của thiết bị của bạn. Tất cả dữ liệu sẽ bị xóa khi thiết bị khởi động lại.</translation>
 <translation id="553675580533261935">Thoát phiên</translation>
 <translation id="5537725057119320332">Truyền</translation>
 <translation id="5548285847212963613">Tiện ích "<ph name="EXTENSION_NAME" />" có thể giúp kết nối với mạng này.</translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">Đang kết nối</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358">Sẽ hoàn nguyên về độ phân giải cũ sau <ph name="TIMEOUT_SECONDS" /></translation>
+<translation id="7228479291753472782">Thực hiện cài đặt chỉ định liệu trang web có thể sử dụng các tính năng như vị trí địa lý, micrô, máy ảnh, v.v. hay không.</translation>
 <translation id="7256634071279256947">Micrô mặt sau</translation>
 <translation id="726276584504105859">Kéo vào đây để sử dụng chế độ chia đôi màn hình</translation>
 <translation id="7348093485538360975">Bàn phím ảo</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">Di chuyển cửa sổ sang màn hình khác có thể gây ra hoạt động không mong muốn. Các thông báo, cửa sổ và hộp thoại tiếp theo có thể bị phân tách giữa các màn hình.</translation>
 <translation id="7569509451529460200">Đã bật chữ nổi Braille và ChromeVox</translation>
 <translation id="7593891976182323525">Search hoặc Shift</translation>
+<translation id="7604942372593434070">Truy cập hoạt động duyệt web của bạn</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (chủ sở hữu)</translation>
 <translation id="7647488630410863958">Mở khóa thiết bị để xem thông báo của bạn</translation>
 <translation id="7649070708921625228">Trợ giúp</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">Tín hiệu mạnh</translation>
 <translation id="7842569679327885685">Cảnh báo: Tính năng thử nghiệm</translation>
 <translation id="7846634333498149051">Bàn phím</translation>
+<translation id="790040513076446191">Thực hiện cài đặt liên quan đến bảo mật</translation>
 <translation id="7904094684485781019">Quản trị viên của tài khoản này đã không cho phép đăng nhập nhiều tài khoản.</translation>
 <translation id="7933084174919150729">Bạn chỉ có thể sử dụng Trợ lý Google cho hồ sơ chính.</translation>
 <translation id="79341161159229895">Tài khoản được quản lý bởi <ph name="FIRST_PARENT_EMAIL" /> và <ph name="SECOND_PARENT_EMAIL" /></translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">Mạng riêng</translation>
 <translation id="8152119955266188852">Bạn đã nhấn phím tắt để phóng to toàn màn hình. Bạn có muốn bật chế độ này không?</translation>
 <translation id="8190698733819146287">Tùy chỉnh ngôn ngữ và dữ liệu nhập...</translation>
+<translation id="8191230140820435481">Quản lý ứng dụng, tiện ích và chủ đề của bạn</translation>
 <translation id="8261506727792406068">Xóa</translation>
 <translation id="8297006494302853456">Yếu</translation>
 <translation id="8308637677604853869">Menu trước</translation>
@@ -414,7 +419,7 @@
 <translation id="8984179138335769204">Khởi động nhanh</translation>
 <translation id="8995603266996330174">Được quản lý bởi <ph name="DOMAIN" /></translation>
 <translation id="9029474291399787231">Đã có bản cập nhật Adobe Flash Player</translation>
-<translation id="9056839673611986238">Thiết bị sẽ khôi phục</translation>
+<translation id="9056839673611986238">Thiết bị sẽ được hạ cấp hệ điều hành</translation>
 <translation id="9074739597929991885">Bluetooth</translation>
 <translation id="9079731690316798640">Wi-Fi: <ph name="ADDRESS" /></translation>
 <translation id="9080206825613744995">Micrô đang được sử dụng.</translation>
diff --git a/ash/strings/ash_strings_zh-CN.xtb b/ash/strings/ash_strings_zh-CN.xtb
index bea7d6f..5a98646 100644
--- a/ash/strings/ash_strings_zh-CN.xtb
+++ b/ash/strings/ash_strings_zh-CN.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">屏幕键盘已启用</translation>
 <translation id="1823873187264960516">以太网:<ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">智能助理(正在加载中…)</translation>
+<translation id="1841545962859478868">此设备的管理员可能会监控以下各项:</translation>
 <translation id="1850504506766569011">Wi-Fi 已关闭。</translation>
 <translation id="1864454756846565995">USB-C 设备(背面端口)</translation>
 <translation id="1882897271359938046">正在镜像到<ph name="DISPLAY_NAME" /></translation>
@@ -326,6 +327,7 @@
 <translation id="7143207342074048698">正在连接</translation>
 <translation id="7165278925115064263">Alt+Shift+K</translation>
 <translation id="7168224885072002358"><ph name="TIMEOUT_SECONDS" />秒后恢复到原分辨率</translation>
+<translation id="7228479291753472782">管理用于指定网站可否使用地理位置信息、麦克风、摄像头等功能的设置</translation>
 <translation id="7256634071279256947">后置麦克风</translation>
 <translation id="726276584504105859">拖到此处即可使用分屏模式</translation>
 <translation id="7348093485538360975">屏幕键盘</translation>
@@ -342,6 +344,7 @@
 <translation id="7564874036684306347">将窗口移至另一桌面可能会导致出现意外行为 - 后续的通知、窗口和对话框可能会被拆分到不同的桌面中。</translation>
 <translation id="7569509451529460200">已启用盲文和 ChromeVox</translation>
 <translation id="7593891976182323525">搜索键或 Shift</translation>
+<translation id="7604942372593434070">访问您的浏览活动</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" />(所有者)</translation>
 <translation id="7647488630410863958">解锁设备即可查看您的通知</translation>
 <translation id="7649070708921625228">帮助</translation>
@@ -356,6 +359,7 @@
 <translation id="7829386189513694949">信号较强</translation>
 <translation id="7842569679327885685">警告:实验性功能</translation>
 <translation id="7846634333498149051">键盘</translation>
+<translation id="790040513076446191">管理隐私相关设置</translation>
 <translation id="7904094684485781019">此帐号的管理员已停用多帐号登录。</translation>
 <translation id="7933084174919150729">Google 智能助理仅适用于主要个人资料。</translation>
 <translation id="79341161159229895">该帐号由 <ph name="FIRST_PARENT_EMAIL" /> 和 <ph name="SECOND_PARENT_EMAIL" /> 管理</translation>
@@ -374,6 +378,7 @@
 <translation id="8142699993796781067">专用网络</translation>
 <translation id="8152119955266188852">您按下了全屏放大镜的快捷键。要开启这项功能吗?</translation>
 <translation id="8190698733819146287">自定义语言和输入法...</translation>
+<translation id="8191230140820435481">管理您的应用、扩展程序和主题背景</translation>
 <translation id="8261506727792406068">删除</translation>
 <translation id="8297006494302853456">弱</translation>
 <translation id="8308637677604853869">上一菜单</translation>
diff --git a/ash/strings/ash_strings_zh-TW.xtb b/ash/strings/ash_strings_zh-TW.xtb
index 80caa8c..fc765f6 100644
--- a/ash/strings/ash_strings_zh-TW.xtb
+++ b/ash/strings/ash_strings_zh-TW.xtb
@@ -42,6 +42,7 @@
 <translation id="1747827819627189109">螢幕小鍵盤已啟用</translation>
 <translation id="1823873187264960516">乙太網路:<ph name="ADDRESS" /></translation>
 <translation id="1836215606488044471">小幫手 (載入中…)</translation>
+<translation id="1841545962859478868">裝置管理員可能會監控下列項目:</translation>
 <translation id="1850504506766569011">Wi-Fi 已關閉。</translation>
 <translation id="1864454756846565995">USB-C 裝置 (背面連接埠)</translation>
 <translation id="1882897271359938046">正在建立 <ph name="DISPLAY_NAME" /> 鏡像</translation>
@@ -224,7 +225,7 @@
 <translation id="544691375626129091">現有使用者已全部加入這個工作階段。</translation>
 <translation id="5457599981699367932">以訪客身分瀏覽</translation>
 <translation id="54609108002486618">管理</translation>
-<translation id="5496819745535887422">管理員正在復原你的裝置。重新啟動後,裝置上的資料將全數刪除。</translation>
+<translation id="5496819745535887422">你的管理員正在復原你的裝置。重新啟動後,裝置上的資料將全數刪除。</translation>
 <translation id="553675580533261935">結束工作階段</translation>
 <translation id="5537725057119320332">投放</translation>
 <translation id="5548285847212963613">「<ph name="EXTENSION_NAME" />」擴充功能可協助連線至這個網路。</translation>
@@ -325,6 +326,7 @@
 <translation id="7143207342074048698">連線中</translation>
 <translation id="7165278925115064263">Alt + Shift + K 鍵</translation>
 <translation id="7168224885072002358">系統將在 <ph name="TIMEOUT_SECONDS" /> 秒後還原成原來的解析度</translation>
+<translation id="7228479291753472782">控制可指定網站能否使用地理位置、麥克風、攝影機等功能的設定。</translation>
 <translation id="7256634071279256947">後置麥克風</translation>
 <translation id="726276584504105859">拖曳到這裡即可使用分割畫面</translation>
 <translation id="7348093485538360975">螢幕小鍵盤</translation>
@@ -341,6 +343,7 @@
 <translation id="7564874036684306347">將視窗移至其他桌面可能會導致未預期的行為。後續的通知、視窗和對話方塊可能會分開顯示在不同桌面上。</translation>
 <translation id="7569509451529460200">點字模式和 ChromeVox 已啟用</translation>
 <translation id="7593891976182323525">搜尋鍵或 Shift 鍵</translation>
+<translation id="7604942372593434070">存取你的瀏覽活動</translation>
 <translation id="7645176681409127223"><ph name="USER_NAME" /> (擁有者)</translation>
 <translation id="7647488630410863958">解鎖裝置即可查看通知</translation>
 <translation id="7649070708921625228">說明</translation>
@@ -355,6 +358,7 @@
 <translation id="7829386189513694949">訊號穩定</translation>
 <translation id="7842569679327885685">警告:實驗功能</translation>
 <translation id="7846634333498149051">鍵盤</translation>
+<translation id="790040513076446191">操控隱私權相關設定</translation>
 <translation id="7904094684485781019">這個帳戶的管理員已禁止多重登入。</translation>
 <translation id="7933084174919150729">Google 助理僅適用於主要設定檔。</translation>
 <translation id="79341161159229895">這個帳戶是由 <ph name="FIRST_PARENT_EMAIL" /> 和 <ph name="SECOND_PARENT_EMAIL" /> 管理</translation>
@@ -373,6 +377,7 @@
 <translation id="8142699993796781067">私人網路</translation>
 <translation id="8152119955266188852">你按下了全螢幕放大鏡的快速鍵。確定要啟用這項功能嗎?</translation>
 <translation id="8190698733819146287">自訂語言與輸入法...</translation>
+<translation id="8191230140820435481">管理你的應用程式、擴充功能和主題</translation>
 <translation id="8261506727792406068">刪除</translation>
 <translation id="8297006494302853456">弱</translation>
 <translation id="8308637677604853869">前一個選單</translation>
diff --git a/ash/wm/overview/overview_animation_type.h b/ash/wm/overview/overview_animation_type.h
index 0b66f8f..7a039a5 100644
--- a/ash/wm/overview/overview_animation_type.h
+++ b/ash/wm/overview/overview_animation_type.h
@@ -24,6 +24,8 @@
   // Used to restore windows to their original position when exiting overview
   // mode.
   OVERVIEW_ANIMATION_RESTORE_WINDOW,
+  // Same as RESTORE_WINDOW but apply the target at the end of the animation.
+  OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO,
   // Used to animate scaling down of a window that is about to get closed while
   // overview mode is active.
   OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM,
diff --git a/ash/wm/overview/overview_window_animation_observer.cc b/ash/wm/overview/overview_window_animation_observer.cc
deleted file mode 100644
index 03afa83ed..0000000
--- a/ash/wm/overview/overview_window_animation_observer.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 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 "ash/wm/overview/overview_window_animation_observer.h"
-
-#include "ui/compositor/layer.h"
-
-namespace ash {
-
-OverviewWindowAnimationObserver::OverviewWindowAnimationObserver()
-    : weak_ptr_factory_(this) {}
-
-OverviewWindowAnimationObserver::~OverviewWindowAnimationObserver() = default;
-
-void OverviewWindowAnimationObserver::OnImplicitAnimationsCompleted() {
-  for (const auto& layer_transform : layer_transform_map_) {
-    ui::Layer* layer = layer_transform.first;
-    layer->SetTransform(layer_transform.second);
-    layer->RemoveObserver(this);
-  }
-  layer_transform_map_.clear();
-  delete this;
-}
-
-void OverviewWindowAnimationObserver::LayerDestroyed(ui::Layer* layer) {
-  auto iter = layer_transform_map_.find(layer);
-  DCHECK(iter != layer_transform_map_.end());
-  layer_transform_map_.erase(iter);
-  layer->RemoveObserver(this);
-}
-
-void OverviewWindowAnimationObserver::AddLayerTransformPair(
-    ui::Layer* layer,
-    const gfx::Transform& transform) {
-  // Should not have the same |layer|.
-  DCHECK(layer_transform_map_.find(layer) == layer_transform_map_.end());
-  layer_transform_map_.emplace(std::make_pair(layer, transform));
-  layer->AddObserver(this);
-}
-
-base::WeakPtr<OverviewWindowAnimationObserver>
-OverviewWindowAnimationObserver::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
-}  // namespace ash
diff --git a/ash/wm/overview/overview_window_animation_observer.h b/ash/wm/overview/overview_window_animation_observer.h
deleted file mode 100644
index d8b6d19..0000000
--- a/ash/wm/overview/overview_window_animation_observer.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 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 ASH_WM_OVERVIEW_OVERVIEW_WINDOW_ANIMATION_OBSERVER_H_
-#define ASH_WM_OVERVIEW_OVERVIEW_WINDOW_ANIMATION_OBSERVER_H_
-
-#include <map>
-
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "ui/compositor/layer_animation_observer.h"
-#include "ui/compositor/layer_observer.h"
-
-namespace ash {
-
-// An observer sets transforms of a list of overview windows when the observed
-// animation is complete. Currently only the exit animation is observed, because
-// the windows beneath the first window covering the available workspace need to
-// defer SetTransform until the first window completes its animation.
-class OverviewWindowAnimationObserver : public ui::ImplicitAnimationObserver,
-                                        public ui::LayerObserver {
- public:
-  explicit OverviewWindowAnimationObserver();
-  ~OverviewWindowAnimationObserver() override;
-
-  // ui::ImplicitAnimationObserver:
-  void OnImplicitAnimationsCompleted() override;
-
-  // ui::LayerObserver overrides:
-  void LayerDestroyed(ui::Layer* layer) override;
-
-  void AddLayerTransformPair(ui::Layer* layer, const gfx::Transform& transform);
-
-  base::WeakPtr<OverviewWindowAnimationObserver> GetWeakPtr();
-
- private:
-  // Stores the windows' layers and corresponding transforms.
-  std::map<ui::Layer*, gfx::Transform> layer_transform_map_;
-
-  base::WeakPtrFactory<OverviewWindowAnimationObserver> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(OverviewWindowAnimationObserver);
-};
-
-}  // namespace ash
-
-#endif  // ASH_WM_OVERVIEW_OVERVIEW_WINDOW_ANIMATION_OBSERVER_H_
diff --git a/ash/wm/overview/scoped_overview_animation_settings.cc b/ash/wm/overview/scoped_overview_animation_settings.cc
index 4d26978..b18027c 100644
--- a/ash/wm/overview/scoped_overview_animation_settings.cc
+++ b/ash/wm/overview/scoped_overview_animation_settings.cc
@@ -48,6 +48,7 @@
       return base::TimeDelta::FromMilliseconds(kFadeOutMilliseconds);
     case OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS:
     case OVERVIEW_ANIMATION_RESTORE_WINDOW:
+    case OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO:
       return base::TimeDelta::FromMilliseconds(kTransitionMilliseconds);
     case OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM:
       return base::TimeDelta::FromMilliseconds(kCloseScaleMilliseconds);
@@ -118,6 +119,7 @@
       return g_reporter_enter.Pointer();
     case OVERVIEW_ANIMATION_EXIT_OVERVIEW_MODE_FADE_OUT:
     case OVERVIEW_ANIMATION_RESTORE_WINDOW:
+    case OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO:
       return g_reporter_exit.Pointer();
     case OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM:
     case OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM:
@@ -159,6 +161,11 @@
       animation_settings_->SetPreemptionStrategy(
           ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
       break;
+    case OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO:
+      animation_settings_->SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+      animation_settings_->SetTweenType(gfx::Tween::ZERO);
+      break;
     case OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM:
     case OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM:
       animation_settings_->SetTweenType(gfx::Tween::EASE_OUT);
diff --git a/ash/wm/overview/scoped_transform_overview_window.cc b/ash/wm/overview/scoped_transform_overview_window.cc
index 4776323c9..ef55db7 100644
--- a/ash/wm/overview/scoped_transform_overview_window.cc
+++ b/ash/wm/overview/scoped_transform_overview_window.cc
@@ -11,7 +11,6 @@
 #include "ash/shell.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/overview_utils.h"
-#include "ash/wm/overview/overview_window_animation_observer.h"
 #include "ash/wm/overview/scoped_overview_animation_settings.h"
 #include "ash/wm/overview/window_grid.h"
 #include "ash/wm/overview/window_selector.h"
@@ -245,19 +244,6 @@
     auto settings = std::make_unique<ScopedOverviewAnimationSettings>(
         animation_type, window);
     settings->DeferPaint();
-
-    // If current |window_| is the first MRU window covering the available
-    // workspace, add the |window_animation_observer| to its
-    // ScopedOverviewAnimationSettings in order to monitor the complete of its
-    // exiting animation.
-    if (window == GetOverviewWindow() &&
-        selector_item_->should_be_observed_when_exiting()) {
-      auto window_animation_observer_weak_ptr =
-          selector_item_->window_grid()->window_animation_observer();
-      if (window_animation_observer_weak_ptr)
-        settings->AddObserver(window_animation_observer_weak_ptr.get());
-    }
-
     animation_settings->push_back(std::move(settings));
   }
 
@@ -386,9 +372,9 @@
 void ScopedTransformOverviewWindow::SetTransform(
     aura::Window* root_window,
     const gfx::Transform& transform) {
+  // TODO(oshima): Consolidate with SetTransform in
+  // tablet_mode_window_drag_delegate.cc.
   DCHECK(overview_started_);
-  auto window_animation_observer_weak_ptr =
-      selector_item_->window_grid()->window_animation_observer();
 
   gfx::Point target_origin(GetTargetBoundsInScreen().origin());
   for (auto* window : wm::GetTransientTreeIterator(GetOverviewWindow())) {
@@ -399,16 +385,7 @@
         TransformAboutPivot(gfx::Point(target_origin.x() - original_bounds.x(),
                                        target_origin.y() - original_bounds.y()),
                             transform);
-    // If current |window_| should not animate during exiting process, we defer
-    // set transfrom on the window by adding the layer and transform information
-    // to the |window_animation_observer|.
-    if (!selector_item_->should_animate_when_exiting() &&
-        window_animation_observer_weak_ptr) {
-      window_animation_observer_weak_ptr->AddLayerTransformPair(window->layer(),
-                                                                new_transform);
-    } else {
-      window->SetTransform(new_transform);
-    }
+    window->SetTransform(new_transform);
   }
 }
 
diff --git a/ash/wm/overview/window_grid.cc b/ash/wm/overview/window_grid.cc
index df5cfd0..c77b50d 100644
--- a/ash/wm/overview/window_grid.cc
+++ b/ash/wm/overview/window_grid.cc
@@ -25,7 +25,6 @@
 #include "ash/wallpaper/wallpaper_widget_controller.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/overview_utils.h"
-#include "ash/wm/overview/overview_window_animation_observer.h"
 #include "ash/wm/overview/rounded_rect_view.h"
 #include "ash/wm/overview/scoped_overview_animation_settings.h"
 #include "ash/wm/overview/window_selector.h"
@@ -837,10 +836,8 @@
 }
 
 void WindowGrid::SetWindowListNotAnimatedWhenExiting() {
-  for (const auto& item : window_list_) {
+  for (const auto& item : window_list_)
     item->set_should_animate_when_exiting(false);
-    item->set_should_be_observed_when_exiting(false);
-  }
 }
 
 void WindowGrid::StartNudge(WindowSelectorItem* item) {
@@ -1379,14 +1376,8 @@
   if (transition == WindowSelector::OverviewTransition::kExit)
     selector_item->set_should_animate_when_exiting(should_animate);
 
-  if (!(*has_covered_available_workspace) && can_cover_available_workspace) {
-    if (transition == WindowSelector::OverviewTransition::kExit) {
-      selector_item->set_should_be_observed_when_exiting(true);
-      auto* observer = new OverviewWindowAnimationObserver();
-      set_window_animation_observer(observer->GetWeakPtr());
-    }
+  if (!(*has_covered_available_workspace) && can_cover_available_workspace)
     *has_covered_available_workspace = true;
-  }
 }
 
 std::vector<std::unique_ptr<WindowSelectorItem>>::iterator
diff --git a/ash/wm/overview/window_grid.h b/ash/wm/overview/window_grid.h
index be837c34..d5fa321 100644
--- a/ash/wm/overview/window_grid.h
+++ b/ash/wm/overview/window_grid.h
@@ -29,7 +29,6 @@
 
 namespace ash {
 
-class OverviewWindowAnimationObserver;
 class WindowSelectorItem;
 
 // Represents a grid of windows in the Overview Mode in a particular root
@@ -159,12 +158,10 @@
 
   gfx::Rect GetNoItemsIndicatorLabelBoundsForTesting() const;
 
-  // Sets |should_animate_when_entering_| and |should_animate_when_exiting_|
-  // of the selector items of the windows based on where the first MRU window
-  // covering the available workspace is found. Also sets the
-  // |should_be_observed_when_exiting_| of the last should-animate item.
-  // |selector_item| is not nullptr when |selector_item| is the selected item
-  // when exiting overview mode.
+  // Sets |should_animate_when_entering_| and |should_animate_when_exiting_| of
+  // the selector items of the windows based on where the first MRU window
+  // covering the available workspace is found. |selector_item| is not nullptr
+  // when |selector_item| is the selected item when exiting overview mode.
   void SetWindowListAnimationStates(
       WindowSelectorItem* selected_item,
       WindowSelector::OverviewTransition transition);
@@ -173,9 +170,8 @@
   // used when splitview and overview mode are both active, selecting a window
   // will put the window in splitview mode and also end the overview mode. In
   // this case the windows in WindowGrid should not animate when exiting the
-  // overivew mode. Instead, OverviewWindowAnimationObserver will observer the
-  // snapped window animation and reset all windows transform in WindowGrid
-  // directly when the animation is completed.
+  // overivew mode. These windows will use ZERO tween so that transforms will
+  // reset at the end of animation.
   void SetWindowListNotAnimatedWhenExiting();
 
   // Starts a nudge, with |item| being the item that may be deleted. This method
@@ -208,14 +204,6 @@
 
   WindowSelector* window_selector() { return window_selector_; }
 
-  void set_window_animation_observer(
-      base::WeakPtr<OverviewWindowAnimationObserver> observer) {
-    window_animation_observer_ = observer;
-  }
-  base::WeakPtr<OverviewWindowAnimationObserver> window_animation_observer() {
-    return window_animation_observer_;
-  }
-
   const gfx::Rect bounds() const { return bounds_; }
 
   views::Widget* new_selector_item_widget_for_testing() {
@@ -279,7 +267,7 @@
                               int* out_max_right);
 
   // Sets |selector_item|'s |should_animate_when_entering_|,
-  // |should_animate_when_exiting_| and |should_be_observed_when_exiting_|.
+  // |should_animate_when_exiting_|.
   // |selector_item| is not nullptr when |selector_item| is the selected item
   // when exiting overview mode.
   void SetWindowSelectorItemAnimationState(
@@ -339,12 +327,6 @@
   // non-empty if a nudge is in progress.
   std::vector<NudgeData> nudge_data_;
 
-  // Weak ptr to the observer monitoring the exit animation of the first MRU
-  // window which covers the available workspace. The observer will be deleted
-  // by itself when the animation completes.
-  base::WeakPtr<OverviewWindowAnimationObserver> window_animation_observer_ =
-      nullptr;
-
   DISALLOW_COPY_AND_ASSIGN(WindowGrid);
 };
 
diff --git a/ash/wm/overview/window_selector.cc b/ash/wm/overview/window_selector.cc
index 5c22565..2cb62aa 100644
--- a/ash/wm/overview/window_selector.cc
+++ b/ash/wm/overview/window_selector.cc
@@ -369,12 +369,7 @@
     if (split_view_controller->IsSplitViewModeActive() &&
         split_view_controller->GetDefaultSnappedWindow()->GetRootWindow() ==
             window_grid->root_window() &&
-        split_view_controller->snapped_window_animation_observer()) {
-      // OverviewWindowAnimationObserver is used to obseve the snapped window
-      // animation. And the windows in |window_grid| will restore their
-      // transform when the snapped window completes its animation.
-      window_grid->set_window_animation_observer(
-          split_view_controller->snapped_window_animation_observer());
+        split_view_controller->has_animating_window()) {
       window_grid->SetWindowListNotAnimatedWhenExiting();
     } else {
       window_grid->SetWindowListAnimationStates(
diff --git a/ash/wm/overview/window_selector_item.cc b/ash/wm/overview/window_selector_item.cc
index 59aea99..5e15fd43 100644
--- a/ash/wm/overview/window_selector_item.cc
+++ b/ash/wm/overview/window_selector_item.cc
@@ -14,7 +14,6 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/wm/overview/overview_animation_type.h"
 #include "ash/wm/overview/overview_utils.h"
-#include "ash/wm/overview/overview_window_animation_observer.h"
 #include "ash/wm/overview/overview_window_drag_controller.h"
 #include "ash/wm/overview/rounded_rect_view.h"
 #include "ash/wm/overview/scoped_overview_animation_settings.h"
@@ -1144,7 +1143,7 @@
 OverviewAnimationType WindowSelectorItem::GetExitTransformAnimationType() {
   return should_animate_when_exiting_
              ? OverviewAnimationType::OVERVIEW_ANIMATION_RESTORE_WINDOW
-             : OverviewAnimationType::OVERVIEW_ANIMATION_NONE;
+             : OverviewAnimationType::OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO;
 }
 
 float WindowSelectorItem::GetCloseButtonOpacityForTesting() {
diff --git a/ash/wm/overview/window_selector_item.h b/ash/wm/overview/window_selector_item.h
index 9c1f8a3..9c2d1a0 100644
--- a/ash/wm/overview/window_selector_item.h
+++ b/ash/wm/overview/window_selector_item.h
@@ -226,13 +226,6 @@
     return should_animate_when_exiting_;
   }
 
-  void set_should_be_observed_when_exiting(bool should_be_observed) {
-    should_be_observed_when_exiting_ = should_be_observed;
-  }
-  bool should_be_observed_when_exiting() const {
-    return should_be_observed_when_exiting_;
-  }
-
   OverviewAnimationType GetExitOverviewAnimationType();
   OverviewAnimationType GetExitTransformAnimationType();
 
@@ -376,14 +369,6 @@
   // True if the contained window should animate during the exiting animation.
   bool should_animate_when_exiting_ = true;
 
-  // True if the contained window is the first window that covers the available
-  // workspace in the MRU list during the exiting animation. It will create an
-  // OverviewWindowAnimationObserver for |window_grid_| and any other windows
-  // which should not animate will defer SetTranfrom by adding the their
-  // layer-transform pairs to the observer until this contained window completes
-  // its exiting animation.
-  bool should_be_observed_when_exiting_ = false;
-
   // True if after an animation, we need to reorder the stacking order of the
   // widgets.
   bool should_restack_on_animation_end_ = false;
diff --git a/ash/wm/overview/window_selector_unittest.cc b/ash/wm/overview/window_selector_unittest.cc
index cd6b025..ca3a9da 100644
--- a/ash/wm/overview/window_selector_unittest.cc
+++ b/ash/wm/overview/window_selector_unittest.cc
@@ -56,6 +56,7 @@
 #include "ui/aura/test/test_windows.h"
 #include "ui/aura/window.h"
 #include "ui/base/hit_test.h"
+#include "ui/compositor/layer_animation_sequence.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/display_layout.h"
 #include "ui/display/manager/display_manager.h"
@@ -117,6 +118,40 @@
   return static_cast<int>(std::distance(children.begin(), it));
 }
 
+class TweenTester : public ui::LayerAnimationObserver {
+ public:
+  explicit TweenTester(aura::Window* window) : window_(window) {
+    window->layer()->GetAnimator()->AddObserver(this);
+  }
+
+  ~TweenTester() override {
+    window_->layer()->GetAnimator()->RemoveObserver(this);
+    EXPECT_TRUE(will_animate_);
+  }
+
+  // ui::LayerAnimationObserver:
+  void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {}
+  void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
+  void OnLayerAnimationScheduled(
+      ui::LayerAnimationSequence* sequence) override {}
+  void OnAttachedToSequence(ui::LayerAnimationSequence* sequence) override {
+    ui::LayerAnimationObserver::OnAttachedToSequence(sequence);
+    if (!will_animate_) {
+      tween_type_ = sequence->FirstElement()->tween_type();
+      will_animate_ = true;
+    }
+  }
+
+  gfx::Tween::Type tween_type() const { return tween_type_; }
+
+ private:
+  gfx::Tween::Type tween_type_ = gfx::Tween::LINEAR;
+  aura::Window* window_;
+  bool will_animate_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TweenTester);
+};
+
 }  // namespace
 
 // TODO(bruthig): Move all non-simple method definitions out of class
@@ -2280,10 +2315,13 @@
   // Click on |window3| to activate it and exit overview.
   // Should only set |should_animate_when_exiting_| and
   // |should_be_observed_when_exiting_| on window 3.
+  TweenTester tester1(window1.get());
+  TweenTester tester2(window2.get());
+  TweenTester tester3(window3.get());
   ClickWindow(window3.get());
-  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
-  EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating());
-  EXPECT_TRUE(window3->layer()->GetAnimator()->is_animating());
+  EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type());
+  EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type());
+  EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type());
 }
 
 // Tests OverviewWindowAnimationObserver can handle deleted window.
@@ -2316,11 +2354,15 @@
   // Click on |window3| to activate it and exit overview.
   // Should only set |should_animate_when_exiting_| and
   // |should_be_observed_when_exiting_| on window 3.
-  ClickWindow(window3.get());
-  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
-  EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating());
-  EXPECT_TRUE(window3->layer()->GetAnimator()->is_animating());
-
+  {
+    TweenTester tester1(window1.get());
+    TweenTester tester2(window2.get());
+    TweenTester tester3(window3.get());
+    ClickWindow(window3.get());
+    EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type());
+    EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type());
+    EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type());
+  }
   // Destroy |window1| and |window2| before |window3| finishes animation can be
   // handled in OverviewWindowAnimationObserver.
   window1.reset();
@@ -2393,12 +2435,17 @@
   // Create and active a new window should exit overview without error.
   auto widget =
       CreateTestWidget(nullptr, kShellWindowId_StatusContainer, bounds);
+
+  TweenTester tester1(window1.get());
+  TweenTester tester2(window2.get());
+  TweenTester tester3(window3.get());
+
   ClickWindow(widget->GetNativeWindow());
 
   // |window1| and |window2| should animate.
-  EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
-  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
-  EXPECT_FALSE(window3->layer()->GetAnimator()->is_animating());
+  EXPECT_EQ(gfx::Tween::EASE_OUT, tester1.tween_type());
+  EXPECT_EQ(gfx::Tween::EASE_OUT, tester2.tween_type());
+  EXPECT_EQ(gfx::Tween::ZERO, tester3.tween_type());
 }
 
 // Tests that AlwaysOnTopWindow can be handled correctly in new overview
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 78dbe936..dc79cc8 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -19,7 +19,6 @@
 #include "ash/system/toast/toast_data.h"
 #include "ash/system/toast/toast_manager.h"
 #include "ash/wm/mru_window_tracker.h"
-#include "ash/wm/overview/overview_window_animation_observer.h"
 #include "ash/wm/overview/window_grid.h"
 #include "ash/wm/overview/window_selector_controller.h"
 #include "ash/wm/overview/window_selector_item.h"
@@ -1269,7 +1268,9 @@
   overview_window_item_bounds_map_.erase(iter);
 
   // Restore the window's transform first if it's not identity.
-  if (!window->layer()->GetTargetTransform().IsIdentity()) {
+  has_animating_window_ = !window->layer()->GetTargetTransform().IsIdentity();
+
+  if (has_animating_window_) {
     // Calculate the starting transform based on the window's expected snapped
     // bounds and its window item bounds in overview.
     const gfx::Rect snapped_bounds = GetSnappedWindowBoundsInScreen(
@@ -1277,18 +1278,12 @@
     const gfx::Transform starting_transform =
         ScopedTransformOverviewWindow::GetTransformForRect(snapped_bounds,
                                                            item_bounds);
-
-    // Create an observer to observe the window's tranform animation.
-    auto* window_transform_observer = new OverviewWindowAnimationObserver();
-    snapped_window_animation_observer_ =
-        window_transform_observer->GetWeakPtr();
     for (auto* window_iter : wm::GetTransientTreeIterator(window)) {
       if (!starting_transform.IsIdentity())
         window_iter->SetTransform(starting_transform);
-      DoSplitviewTransformAnimation(
-          window_iter->layer(), SPLITVIEW_ANIMATION_RESTORE_OVERVIEW_WINDOW,
-          gfx::Transform(),
-          (window_iter == window) ? window_transform_observer : nullptr);
+      DoSplitviewTransformAnimation(window_iter->layer(),
+                                    SPLITVIEW_ANIMATION_RESTORE_OVERVIEW_WINDOW,
+                                    gfx::Transform());
     }
   }
 }
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 2392284..3002745 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -33,7 +33,6 @@
 class SplitViewControllerTest;
 class SplitViewDivider;
 class SplitViewWindowSelectorTest;
-class OverviewWindowAnimationObserver;
 
 // The controller for the split view. It snaps a window to left/right side of
 // the screen. It also observes the two snapped windows and decides when to exit
@@ -191,9 +190,8 @@
   SnapPosition default_snap_position() const { return default_snap_position_; }
   SplitViewDivider* split_view_divider() { return split_view_divider_.get(); }
   bool is_resizing() const { return is_resizing_; }
-  base::WeakPtr<OverviewWindowAnimationObserver>
-  snapped_window_animation_observer() {
-    return snapped_window_animation_observer_;
+  bool has_animating_window() const {
+    return has_animating_window_;
   }
 
  private:
@@ -406,10 +404,8 @@
   // window comes from the overview.
   base::flat_map<aura::Window*, gfx::Rect> overview_window_item_bounds_map_;
 
-  // Weak ptr to the observer that observes the snapped window's transform
-  // animaiton if it comes from the overview.
-  base::WeakPtr<OverviewWindowAnimationObserver>
-      snapped_window_animation_observer_ = nullptr;
+  // True if there is an animating window.
+  bool has_animating_window_ = false;
 
   base::ObserverList<Observer> observers_;
   mojo::InterfacePtrSet<mojom::SplitViewObserver> mojo_observers_;
diff --git a/ash/wm/splitview/split_view_drag_indicators.cc b/ash/wm/splitview/split_view_drag_indicators.cc
index 1207495b..46ac019 100644
--- a/ash/wm/splitview/split_view_drag_indicators.cc
+++ b/ash/wm/splitview/split_view_drag_indicators.cc
@@ -440,12 +440,10 @@
 
     DoSplitviewTransformAnimation(
         left_rotated_view_->layer(), animation,
-        preview_left ? main_rotated_transform : other_rotated_transform,
-        nullptr);
+        preview_left ? main_rotated_transform : other_rotated_transform);
     DoSplitviewTransformAnimation(
         right_rotated_view_->layer(), animation,
-        preview_left ? other_rotated_transform : main_rotated_transform,
-        nullptr);
+        preview_left ? other_rotated_transform : main_rotated_transform);
   }
 
   std::vector<SplitViewHighlightView*> GetHighlightViews() {
diff --git a/ash/wm/splitview/split_view_highlight_view.cc b/ash/wm/splitview/split_view_highlight_view.cc
index f5cb8ca..f0d8d09 100644
--- a/ash/wm/splitview/split_view_highlight_view.cc
+++ b/ash/wm/splitview/split_view_highlight_view.cc
@@ -142,18 +142,15 @@
     DoSplitviewTransformAnimation(
         middle_->layer(), SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN_OUT,
         CalculateTransformFromRects(middle_->bounds(), middle_bounds,
-                                    landscape),
-        nullptr);
+                                    landscape));
     DoSplitviewTransformAnimation(
         left_top_->layer(), SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN_OUT,
         CalculateTransformFromRects(left_top_->bounds(), left_top_bounds,
-                                    landscape),
-        nullptr);
+                                    landscape));
     DoSplitviewTransformAnimation(
         right_bottom_->layer(), SPLITVIEW_ANIMATION_PREVIEW_AREA_SLIDE_IN_OUT,
         CalculateTransformFromRects(right_bottom_->bounds(),
-                                    right_bottom_bounds, landscape),
-        nullptr);
+                                    right_bottom_bounds, landscape));
   } else {
     left_top_->layer()->SetTransform(gfx::Transform());
     right_bottom_->layer()->SetTransform(gfx::Transform());
@@ -171,4 +168,4 @@
   middle_->layer()->SetColor(color);
 }
 
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index d9db72f..2cef3c9 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -159,8 +159,7 @@
 
 void DoSplitviewTransformAnimation(ui::Layer* layer,
                                    SplitviewAnimationType type,
-                                   const gfx::Transform& target_transform,
-                                   ui::ImplicitAnimationObserver* observer) {
+                                   const gfx::Transform& target_transform) {
   if (layer->GetTargetTransform() == target_transform)
     return;
 
@@ -188,8 +187,6 @@
   ui::ScopedLayerAnimationSettings settings(animator);
   ApplyAnimationSettings(&settings, animator, duration, tween,
                          preemption_strategy, delay);
-  if (observer)
-    settings.AddObserver(observer);
   layer->SetTransform(target_transform);
 }
 
diff --git a/ash/wm/splitview/split_view_utils.h b/ash/wm/splitview/split_view_utils.h
index 0cf04763..534f17d 100644
--- a/ash/wm/splitview/split_view_utils.h
+++ b/ash/wm/splitview/split_view_utils.h
@@ -8,7 +8,6 @@
 #include "ui/gfx/transform.h"
 
 namespace ui {
-class ImplicitAnimationObserver;
 class Layer;
 }  // namespace ui
 
@@ -62,12 +61,10 @@
 // Animates |layer|'s opacity based on |type|.
 void DoSplitviewOpacityAnimation(ui::Layer* layer, SplitviewAnimationType type);
 
-// Animates |layer|'s transform based on |type|. |observer| can be passed if the
-// caller wants to do an action after the animation has ended.
+// Animates |layer|'s transform based on |type|.
 void DoSplitviewTransformAnimation(ui::Layer* layer,
                                    SplitviewAnimationType type,
-                                   const gfx::Transform& target_transform,
-                                   ui::ImplicitAnimationObserver* observer);
+                                   const gfx::Transform& target_transform);
 
 }  // namespace ash
 
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index 3430438..780d13e 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -160,7 +160,7 @@
 void TabletModeWindowState::UpdateWindowPosition(
     wm::WindowState* window_state) {
   gfx::Rect bounds_in_parent = GetBoundsInMaximizedMode(window_state);
-  if (bounds_in_parent == window_state->window()->bounds())
+  if (bounds_in_parent == window_state->window()->GetTargetBounds())
     return;
   window_state->SetBoundsDirect(bounds_in_parent);
 }
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 6244e3e9..6803248a 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -34,11 +34,6 @@
 import("//testing/test.gni")
 
 declare_args() {
-  # Override this value to give a specific build date.
-  # See //base/build_time.cc and //build/write_build_date_header.py for more
-  # details and the expected format.
-  override_build_date = "N/A"
-
   # Indicates if the Location object contains the source code information
   # (file, function, line). False means only the program counter (and currently
   # file name) is saved.
@@ -2751,10 +2746,6 @@
   } else {
     args += [ "default" ]
   }
-
-  if (override_build_date != "N/A") {
-    args += [ override_build_date ]
-  }
 }
 
 if (enable_nocompile_tests) {
diff --git a/base/allocator/partition_allocator/partition_alloc.h b/base/allocator/partition_allocator/partition_alloc.h
index 0294ef92..4e162058 100644
--- a/base/allocator/partition_allocator/partition_alloc.h
+++ b/base/allocator/partition_allocator/partition_alloc.h
@@ -281,17 +281,44 @@
 #endif  // defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
 }
 
-ALWAYS_INLINE void PartitionFree(void* ptr) {
+ALWAYS_INLINE bool PartitionAllocSupportsGetSize() {
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  free(ptr);
+  return false;
 #else
-  // TODO(palmer): Check ptr alignment before continuing. Shall we do the check
-  // inside PartitionCookieFreePointerAdjust?
-  PartitionAllocHooks::FreeHookIfEnabled(ptr);
+  return true;
+#endif
+}
+
+ALWAYS_INLINE size_t PartitionAllocGetSize(void* ptr) {
+  // No need to lock here. Only |ptr| being freed by another thread could
+  // cause trouble, and the caller is responsible for that not happening.
+  DCHECK(PartitionAllocSupportsGetSize());
   ptr = internal::PartitionCookieFreePointerAdjust(ptr);
   internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
   // TODO(palmer): See if we can afford to make this a CHECK.
   DCHECK(internal::PartitionRootBase::IsValidPage(page));
+  size_t size = page->bucket->slot_size;
+  return internal::PartitionCookieSizeAdjustSubtract(size);
+}
+
+ALWAYS_INLINE void PartitionFree(void* ptr) {
+#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
+  free(ptr);
+#else
+  void* original_ptr = ptr;
+  // TODO(palmer): Check ptr alignment before continuing. Shall we do the check
+  // inside PartitionCookieFreePointerAdjust?
+  PartitionAllocHooks::FreeHookIfEnabled(original_ptr);
+  ptr = internal::PartitionCookieFreePointerAdjust(ptr);
+  internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
+  // TODO(palmer): See if we can afford to make this a CHECK.
+  DCHECK(internal::PartitionRootBase::IsValidPage(page));
+
+  // This is somewhat redundant with |PartitionPage::Free|.
+  // TODO(crbug.com/680657): Doing this here might? make it OK to not do it
+  // there.
+  memset(original_ptr, 0xCD, PartitionAllocGetSize(original_ptr));
+
   page->Free(ptr);
 #endif
 }
@@ -386,26 +413,6 @@
 #endif
 }
 
-ALWAYS_INLINE bool PartitionAllocSupportsGetSize() {
-#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  return false;
-#else
-  return true;
-#endif
-}
-
-ALWAYS_INLINE size_t PartitionAllocGetSize(void* ptr) {
-  // No need to lock here. Only |ptr| being freed by another thread could
-  // cause trouble, and the caller is responsible for that not happening.
-  DCHECK(PartitionAllocSupportsGetSize());
-  ptr = internal::PartitionCookieFreePointerAdjust(ptr);
-  internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr);
-  // TODO(palmer): See if we can afford to make this a CHECK.
-  DCHECK(internal::PartitionRootBase::IsValidPage(page));
-  size_t size = page->bucket->slot_size;
-  return internal::PartitionCookieSizeAdjustSubtract(size);
-}
-
 template <size_t N>
 class SizeSpecificPartitionAllocator {
  public:
diff --git a/base/android/java/src/org/chromium/base/PathUtils.java b/base/android/java/src/org/chromium/base/PathUtils.java
index e6fc802..a9eaff6 100644
--- a/base/android/java/src/org/chromium/base/PathUtils.java
+++ b/base/android/java/src/org/chromium/base/PathUtils.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -36,9 +37,9 @@
     private static final int CACHE_DIRECTORY = 2;
     private static final int NUM_DIRECTORIES = 3;
     private static final AtomicBoolean sInitializationStarted = new AtomicBoolean();
-    private static AsyncTask<Void, Void, String[]> sDirPathFetchTask;
+    private static FutureTask<String[]> sDirPathFetchTask;
 
-    // If the AsyncTask started in setPrivateDataDirectorySuffix() fails to complete by the time we
+    // If the FutureTask started in setPrivateDataDirectorySuffix() fails to complete by the time we
     // need the values, we will need the suffix so that we can restart the task synchronously on
     // the UI thread.
     private static String sDataDirectorySuffix;
@@ -102,8 +103,8 @@
 
     /**
      * Fetch the path of the directory where private data is to be stored by the application. This
-     * is meant to be called in an AsyncTask in setPrivateDataDirectorySuffix(), but if we need the
-     * result before the AsyncTask has had a chance to finish, then it's best to cancel the task
+     * is meant to be called in an FutureTask in setPrivateDataDirectorySuffix(), but if we need the
+     * result before the FutureTask has had a chance to finish, then it's best to cancel the task
      * and run it on the UI thread instead, inside getOrComputeDirectoryPaths().
      *
      * @see Context#getDir(String, int)
@@ -148,12 +149,12 @@
             assert ContextUtils.getApplicationContext() != null;
             sDataDirectorySuffix = suffix;
             sCacheSubDirectory = cacheSubDir;
-            sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
-                @Override
-                protected String[] doInBackground(Void... unused) {
-                    return PathUtils.setPrivateDataDirectorySuffixInternal();
-                }
-            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+
+            // We don't use an AsyncTask because this function is called in early Webview startup
+            // and it won't always have a UI thread available. Thus, we can't use
+            // AsyncTask which inherently posts to the UI thread for onPostExecute().
+            sDirPathFetchTask = new FutureTask<>(PathUtils::setPrivateDataDirectorySuffixInternal);
+            AsyncTask.THREAD_POOL_EXECUTOR.execute(sDirPathFetchTask);
         }
     }
 
diff --git a/base/android/java/src/org/chromium/base/StreamUtil.java b/base/android/java/src/org/chromium/base/StreamUtil.java
index f8cbfeeb..aa5e389 100644
--- a/base/android/java/src/org/chromium/base/StreamUtil.java
+++ b/base/android/java/src/org/chromium/base/StreamUtil.java
@@ -6,6 +6,7 @@
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.zip.ZipFile;
 
 /**
  * Helper methods to deal with stream related tasks.
@@ -25,4 +26,19 @@
             // Ignore the exception on close.
         }
     }
+
+    /**
+     * Overload of the above function for {@link ZipFile} which implements Closeable only starting
+     * from api19.
+     * @param zipFile - the ZipFile to be closed.
+     */
+    public static void closeQuietly(ZipFile zipFile) {
+        if (zipFile == null) return;
+
+        try {
+            zipFile.close();
+        } catch (IOException ex) {
+            // Ignore the exception on close.
+        }
+    }
 }
diff --git a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
index 5bc6204..5f340e1 100644
--- a/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
+++ b/base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java
@@ -24,6 +24,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.FileUtils;
 import org.chromium.base.Log;
+import org.chromium.base.StreamUtil;
 import org.chromium.base.SysUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
@@ -35,6 +36,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import javax.annotation.Nullable;
@@ -739,11 +741,13 @@
         File libraryFile = new File(destDir, fileName);
 
         if (!libraryFile.exists()) {
-            try (ZipFile zipFile = new ZipFile(apkPath);
-                    InputStream inputStream =
-                            zipFile.getInputStream(zipFile.getEntry(pathWithinApk))) {
-                if (zipFile.getEntry(pathWithinApk) == null)
+            ZipFile zipFile = null;
+            try {
+                zipFile = new ZipFile(apkPath);
+                ZipEntry zipEntry = zipFile.getEntry(pathWithinApk);
+                if (zipEntry == null)
                     throw new RuntimeException("Cannot find ZipEntry" + pathWithinApk);
+                InputStream inputStream = zipFile.getInputStream(zipEntry);
 
                 FileUtils.copyFileStreamAtomicWithBuffer(
                         inputStream, libraryFile, new byte[16 * 1024]);
@@ -751,6 +755,8 @@
                 libraryFile.setExecutable(true, false);
             } catch (IOException e) {
                 throw new RuntimeException(e);
+            } finally {
+                StreamUtil.closeQuietly(zipFile);
             }
         }
         return libraryFile.getAbsolutePath();
diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc
index aad84fb..598e884 100644
--- a/base/android/jni_android.cc
+++ b/base/android/jni_android.cc
@@ -143,25 +143,20 @@
 jclass LazyGetClass(
     JNIEnv* env,
     const char* class_name,
-    base::subtle::AtomicWord* atomic_class_id) {
-  static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass),
-                "AtomicWord can't be smaller than jclass");
-  subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_class_id);
+    std::atomic<jclass>* atomic_class_id) {
+  const jclass value = std::atomic_load(atomic_class_id);
   if (value)
-    return reinterpret_cast<jclass>(value);
+    return value;
   ScopedJavaGlobalRef<jclass> clazz;
   clazz.Reset(GetClass(env, class_name));
-  subtle::AtomicWord null_aw = reinterpret_cast<subtle::AtomicWord>(NULL);
-  subtle::AtomicWord cas_result = base::subtle::Release_CompareAndSwap(
-      atomic_class_id,
-      null_aw,
-      reinterpret_cast<subtle::AtomicWord>(clazz.obj()));
-  if (cas_result == null_aw) {
+  jclass cas_result = nullptr;
+  if (std::atomic_compare_exchange_strong(atomic_class_id, &cas_result,
+                                          clazz.obj())) {
     // We intentionally leak the global ref since we now storing it as a raw
     // pointer in |atomic_class_id|.
     return clazz.Release();
   } else {
-    return reinterpret_cast<jclass>(cas_result);
+    return cas_result;
   }
 }
 
@@ -170,9 +165,10 @@
                         jclass clazz,
                         const char* method_name,
                         const char* jni_signature) {
-  jmethodID id = type == TYPE_STATIC ?
-      env->GetStaticMethodID(clazz, method_name, jni_signature) :
-      env->GetMethodID(clazz, method_name, jni_signature);
+  auto get_method_ptr = type == MethodID::TYPE_STATIC ?
+      &JNIEnv::GetStaticMethodID :
+      &JNIEnv::GetMethodID;
+  jmethodID id = (env->*get_method_ptr)(clazz, method_name, jni_signature);
   if (base::android::ClearException(env) || !id) {
     LOG(FATAL) << "Failed to find " <<
         (type == TYPE_STATIC ? "static " : "") <<
@@ -189,15 +185,12 @@
                             jclass clazz,
                             const char* method_name,
                             const char* jni_signature,
-                            base::subtle::AtomicWord* atomic_method_id) {
-  static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID),
-                "AtomicWord can't be smaller than jMethodID");
-  subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id);
+                            std::atomic<jmethodID>* atomic_method_id) {
+  const jmethodID value = std::atomic_load(atomic_method_id);
   if (value)
-    return reinterpret_cast<jmethodID>(value);
+    return value;
   jmethodID id = MethodID::Get<type>(env, clazz, method_name, jni_signature);
-  base::subtle::Release_Store(
-      atomic_method_id, reinterpret_cast<subtle::AtomicWord>(id));
+  std::atomic_store(atomic_method_id, id);
   return id;
 }
 
@@ -212,11 +205,11 @@
 
 template jmethodID MethodID::LazyGet<MethodID::TYPE_STATIC>(
     JNIEnv* env, jclass clazz, const char* method_name,
-    const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);
+    const char* jni_signature, std::atomic<jmethodID>* atomic_method_id);
 
 template jmethodID MethodID::LazyGet<MethodID::TYPE_INSTANCE>(
     JNIEnv* env, jclass clazz, const char* method_name,
-    const char* jni_signature, base::subtle::AtomicWord* atomic_method_id);
+    const char* jni_signature, std::atomic<jmethodID>* atomic_method_id);
 
 bool HasException(JNIEnv* env) {
   return env->ExceptionCheck() != JNI_FALSE;
diff --git a/base/android/jni_android.h b/base/android/jni_android.h
index fba6113c..0e8e322 100644
--- a/base/android/jni_android.h
+++ b/base/android/jni_android.h
@@ -8,6 +8,7 @@
 #include <jni.h>
 #include <sys/types.h>
 
+#include <atomic>
 #include <string>
 
 #include "base/android/scoped_java_ref.h"
@@ -106,7 +107,7 @@
 BASE_EXPORT jclass LazyGetClass(
     JNIEnv* env,
     const char* class_name,
-    base::subtle::AtomicWord* atomic_class_id);
+    std::atomic<jclass>* atomic_class_id);
 
 // This class is a wrapper for JNIEnv Get(Static)MethodID.
 class BASE_EXPORT MethodID {
@@ -132,7 +133,7 @@
                            jclass clazz,
                            const char* method_name,
                            const char* jni_signature,
-                           base::subtle::AtomicWord* atomic_method_id);
+                           std::atomic<jmethodID>* atomic_method_id);
 };
 
 // Returns true if an exception is pending in the provided JNIEnv*.
diff --git a/base/android/jni_android_unittest.cc b/base/android/jni_android_unittest.cc
index dabd480..3f25775 100644
--- a/base/android/jni_android_unittest.cc
+++ b/base/android/jni_android_unittest.cc
@@ -13,7 +13,7 @@
 
 namespace {
 
-base::subtle::AtomicWord g_atomic_id = 0;
+std::atomic<jmethodID> g_atomic_id(nullptr);
 int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) {
   jmethodID id = base::android::MethodID::LazyGet<
       base::android::MethodID::TYPE_STATIC>(
@@ -40,7 +40,7 @@
     o += LazyMethodIDCall(env, clazz.obj(), i);
   base::Time end_lazy = base::Time::Now();
 
-  jmethodID id = reinterpret_cast<jmethodID>(g_atomic_id);
+  jmethodID id = g_atomic_id;
   base::Time start = base::Time::Now();
   for (int i = 0; i < 1024; ++i)
     o += MethodIDCall(env, clazz.obj(), id, i);
diff --git a/base/android/jni_generator/SampleForTests_jni.golden b/base/android/jni_generator/SampleForTests_jni.golden
index c3fe968..79092078 100644
--- a/base/android/jni_generator/SampleForTests_jni.golden
+++ b/base/android/jni_generator/SampleForTests_jni.golden
@@ -38,8 +38,8 @@
 const char kClassPath_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB[] =
     "org/chromium/example/jni_generator/SampleForTests$InnerStructB";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_clazz(JNIEnv*
@@ -50,8 +50,8 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerClass_clazz(JNIEnv* env)
@@ -62,8 +62,8 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) {
@@ -73,8 +73,8 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(JNIEnv*
@@ -222,7 +222,8 @@
 }
 
 
-static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_javaMethod = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_javaMethod(nullptr);
 static jint Java_SampleForTests_javaMethod(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper foo,
     JniIntWrapper bar) {
@@ -242,8 +243,8 @@
   return ret;
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_staticJavaMethod(nullptr);
 static jboolean Java_SampleForTests_staticJavaMethod(JNIEnv* env) {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
       org_chromium_example_jni_1generator_SampleForTests_clazz(env), false);
@@ -261,8 +262,8 @@
   return ret;
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_packagePrivateJavaMethod(nullptr);
 static void Java_SampleForTests_packagePrivateJavaMethod(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -279,8 +280,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_methodWithGenericParams(nullptr);
 static void Java_SampleForTests_methodWithGenericParams(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, const base::android::JavaRef<jobject>& foo,
     const base::android::JavaRef<jobject>& bar) {
@@ -298,8 +299,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_Constructor =
-    0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_Constructor(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_Constructor(JNIEnv* env,
     JniIntWrapper foo,
     JniIntWrapper bar) {
@@ -319,8 +320,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_methodThatThrowsException(nullptr);
 static void Java_SampleForTests_methodThatThrowsException(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -336,8 +337,8 @@
           method_id);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_javaMethodWithAnnotatedParam(nullptr);
 static void Java_SampleForTests_javaMethodWithAnnotatedParam(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper foo) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -354,8 +355,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructA_create(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_InnerStructA_create(JNIEnv* env, jlong l,
     JniIntWrapper i,
     const base::android::JavaRef<jstring>& s) {
@@ -375,7 +376,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_addStructA = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_addStructA(nullptr);
 static void Java_SampleForTests_addStructA(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     const base::android::JavaRef<jobject>& a) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -392,8 +394,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_iterateAndDoSomething(nullptr);
 static void Java_SampleForTests_iterateAndDoSomething(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -410,8 +412,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getKey(nullptr);
 static jlong Java_InnerStructB_getKey(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
       org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_clazz(env), 0);
@@ -429,8 +431,8 @@
   return ret;
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024InnerStructB_getValue(nullptr);
 static base::android::ScopedJavaLocalRef<jstring> Java_InnerStructB_getValue(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -449,8 +451,8 @@
   return base::android::ScopedJavaLocalRef<jstring>(env, ret);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_getInnerInterface(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerInterface(JNIEnv* env)
     {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
@@ -469,8 +471,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum =
-    0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_getInnerEnum(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_SampleForTests_getInnerEnum(JNIEnv* env) {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
       org_chromium_example_jni_1generator_SampleForTests_clazz(env), NULL);
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py
index ef676e26..8615649 100755
--- a/base/android/jni_generator/jni_generator.py
+++ b/base/android/jni_generator/jni_generator.py
@@ -874,12 +874,12 @@
 """
     if declare_only:
       template = Template("""\
-extern base::subtle::AtomicWord g_${JAVA_CLASS}_clazz;
+extern std::atomic<jclass> g_${JAVA_CLASS}_clazz;
 """ + class_getter)
     else:
       template = Template("""\
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_${JAVA_CLASS}_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_${JAVA_CLASS}_clazz(nullptr);
 """ + class_getter)
 
     for full_clazz in classes.itervalues():
@@ -1218,7 +1218,7 @@
 ${FUNCTION_SIGNATURE} __attribute__ ((unused));
 ${FUNCTION_SIGNATURE} {""")
     template = Template("""
-static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0;
+static std::atomic<jmethodID> g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(nullptr);
 ${FUNCTION_HEADER}
   CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL},
       ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN});
diff --git a/base/android/jni_generator/testCalledByNatives.golden b/base/android/jni_generator/testCalledByNatives.golden
index f0673f6..09ecb6c 100644
--- a/base/android/jni_generator/testCalledByNatives.golden
+++ b/base/android/jni_generator/testCalledByNatives.golden
@@ -24,7 +24,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni_00024InfoBar[];
 const char kClassPath_org_chromium_TestJni_00024InfoBar[] = "org/chromium/TestJni$InfoBar";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
@@ -33,7 +33,7 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024InfoBar_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024InfoBar_clazz(nullptr);
 #ifndef org_chromium_TestJni_00024InfoBar_clazz_defined
 #define org_chromium_TestJni_00024InfoBar_clazz_defined
 inline jclass org_chromium_TestJni_00024InfoBar_clazz(JNIEnv* env) {
@@ -48,7 +48,7 @@
 
 // Step 3: Method stubs.
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_showConfirmInfoBar = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_showConfirmInfoBar(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showConfirmInfoBar(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar,
     const base::android::JavaRef<jstring>& buttonOk,
@@ -72,7 +72,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_showAutoLoginInfoBar = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_showAutoLoginInfoBar(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_showAutoLoginInfoBar(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj, JniIntWrapper nativeInfoBar,
     const base::android::JavaRef<jstring>& realm,
@@ -94,7 +94,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_00024InfoBar_dismiss = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_00024InfoBar_dismiss(nullptr);
 static void Java_InfoBar_dismiss(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
       org_chromium_TestJni_00024InfoBar_clazz(env));
@@ -110,7 +110,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_shouldShowAutoLogin = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_shouldShowAutoLogin(nullptr);
 static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const base::android::JavaRef<jobject>&
     view,
     const base::android::JavaRef<jstring>& realm,
@@ -132,7 +132,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_openUrl = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_openUrl(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_openUrl(JNIEnv* env, const
     base::android::JavaRef<jstring>& url) {
   CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env),
@@ -151,7 +151,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_activateHardwareAcceleration = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_activateHardwareAcceleration(nullptr);
 static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, jboolean activated,
     JniIntWrapper iPid,
@@ -173,7 +173,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_updateStatus = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_updateStatus(nullptr);
 static jint Java_TestJni_updateStatus(JNIEnv* env, JniIntWrapper status) {
   CHECK_CLAZZ(env, org_chromium_TestJni_clazz(env),
       org_chromium_TestJni_clazz(env), 0);
@@ -191,7 +191,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_uncheckedCall = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_uncheckedCall(nullptr);
 static void Java_TestJni_uncheckedCall(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper iParam) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -207,7 +207,7 @@
           method_id, as_jint(iParam));
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnByteArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnByteArray(nullptr);
 static base::android::ScopedJavaLocalRef<jbyteArray> Java_TestJni_returnByteArray(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -226,7 +226,7 @@
   return base::android::ScopedJavaLocalRef<jbyteArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnBooleanArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnBooleanArray(nullptr);
 static base::android::ScopedJavaLocalRef<jbooleanArray> Java_TestJni_returnBooleanArray(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -245,7 +245,7 @@
   return base::android::ScopedJavaLocalRef<jbooleanArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnCharArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnCharArray(nullptr);
 static base::android::ScopedJavaLocalRef<jcharArray> Java_TestJni_returnCharArray(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -264,7 +264,7 @@
   return base::android::ScopedJavaLocalRef<jcharArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnShortArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnShortArray(nullptr);
 static base::android::ScopedJavaLocalRef<jshortArray> Java_TestJni_returnShortArray(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -283,7 +283,7 @@
   return base::android::ScopedJavaLocalRef<jshortArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnIntArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnIntArray(nullptr);
 static base::android::ScopedJavaLocalRef<jintArray> Java_TestJni_returnIntArray(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -302,7 +302,7 @@
   return base::android::ScopedJavaLocalRef<jintArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnLongArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnLongArray(nullptr);
 static base::android::ScopedJavaLocalRef<jlongArray> Java_TestJni_returnLongArray(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -321,7 +321,7 @@
   return base::android::ScopedJavaLocalRef<jlongArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnDoubleArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnDoubleArray(nullptr);
 static base::android::ScopedJavaLocalRef<jdoubleArray> Java_TestJni_returnDoubleArray(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -340,7 +340,7 @@
   return base::android::ScopedJavaLocalRef<jdoubleArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnObjectArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnObjectArray(nullptr);
 static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnObjectArray(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -359,7 +359,7 @@
   return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_returnArrayOfByteArray = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_returnArrayOfByteArray(nullptr);
 static base::android::ScopedJavaLocalRef<jobjectArray> Java_TestJni_returnArrayOfByteArray(JNIEnv*
     env, const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -378,7 +378,7 @@
   return base::android::ScopedJavaLocalRef<jobjectArray>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormat = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_getCompressFormat(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormat(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -397,7 +397,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_TestJni_getCompressFormatList = 0;
+static std::atomic<jmethodID> g_org_chromium_TestJni_getCompressFormatList(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_TestJni_getCompressFormatList(JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
diff --git a/base/android/jni_generator/testConstantsFromJavaP.golden b/base/android/jni_generator/testConstantsFromJavaP.golden
index 9cb39b7..043685f 100644
--- a/base/android/jni_generator/testConstantsFromJavaP.golden
+++ b/base/android/jni_generator/testConstantsFromJavaP.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_android_view_MotionEvent[];
 const char kClassPath_android_view_MotionEvent[] = "android/view/MotionEvent";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_android_view_MotionEvent_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_android_view_MotionEvent_clazz(nullptr);
 #ifndef android_view_MotionEvent_clazz_defined
 #define android_view_MotionEvent_clazz_defined
 inline jclass android_view_MotionEvent_clazz(JNIEnv* env) {
@@ -124,7 +124,7 @@
 namespace JNI_MotionEvent {
 
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_finalize = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_finalize(nullptr);
 static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static void Java_MotionEvent_finalize(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -142,8 +142,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_android_view_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I = 0;
+static std::atomic<jmethodID>
+    g_android_view_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(nullptr);
 static base::android::ScopedJavaLocalRef<jobject>
     Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* env, jlong p0,
     jlong p1,
@@ -191,8 +191,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord
-    g_android_view_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I = 0;
+static std::atomic<jmethodID>
+    g_android_view_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(nullptr);
 static base::android::ScopedJavaLocalRef<jobject>
     Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, jlong p0,
     jlong p1,
@@ -238,7 +238,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I = 0;
+static std::atomic<jmethodID>
+    g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(nullptr);
 static base::android::ScopedJavaLocalRef<jobject>
     Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0,
     jlong p1,
@@ -282,7 +283,8 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I = 0;
+static std::atomic<jmethodID>
+    g_android_view_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(nullptr);
 static base::android::ScopedJavaLocalRef<jobject>
     Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0,
     jlong p1,
@@ -328,7 +330,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_obtainAVME_J_J_I_F_F_I(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv*
     env, jlong p0,
     jlong p1,
@@ -359,7 +361,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_obtainAVME_AVME = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_obtainAVME_AVME(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env,
     const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainAVME_AVME(JNIEnv* env,
@@ -380,7 +382,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_obtainNoHistory = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_obtainNoHistory(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env,
     const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jobject> Java_MotionEvent_obtainNoHistory(JNIEnv* env,
@@ -401,7 +403,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_recycle = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_recycle(nullptr);
 static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static void Java_MotionEvent_recycle(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -419,7 +421,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getDeviceId = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getDeviceId(nullptr);
 static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -439,7 +441,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getSource = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getSource(nullptr);
 static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -459,7 +461,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_setSource = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_setSource(nullptr);
 static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static void Java_MotionEvent_setSource(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -478,7 +480,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getAction = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getAction(nullptr);
 static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -498,7 +500,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getActionMasked = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getActionMasked(nullptr);
 static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -519,7 +521,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getActionIndex = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getActionIndex(nullptr);
 static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
@@ -540,7 +542,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getFlags = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getFlags(nullptr);
 static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -560,7 +562,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getDownTime = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getDownTime(nullptr);
 static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -580,7 +582,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getEventTime = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getEventTime(nullptr);
 static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
@@ -601,7 +603,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getXF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getXF(nullptr);
 static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getXF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -621,7 +623,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getYF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getYF(nullptr);
 static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getYF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -641,7 +643,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPressureF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPressureF(nullptr);
 static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
@@ -662,7 +664,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getSizeF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getSizeF(nullptr);
 static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -682,7 +684,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMajorF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMajorF(nullptr);
 static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -703,7 +705,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMinorF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMinorF(nullptr);
 static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -724,7 +726,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMajorF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMajorF(nullptr);
 static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -745,7 +747,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMinorF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMinorF(nullptr);
 static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -766,7 +768,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getOrientationF = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getOrientationF(nullptr);
 static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -787,7 +789,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getAxisValueF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getAxisValueF_I(nullptr);
 static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -808,7 +810,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCount = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerCount(nullptr);
 static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -829,7 +831,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerId = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerId(nullptr);
 static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static jint Java_MotionEvent_getPointerId(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -850,7 +852,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getToolType = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getToolType(nullptr);
 static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static jint Java_MotionEvent_getToolType(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -871,7 +873,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_findPointerIndex = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_findPointerIndex(nullptr);
 static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -892,7 +894,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getXF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getXF_I(nullptr);
 static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -913,7 +915,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getYF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getYF_I(nullptr);
 static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -934,7 +936,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPressureF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPressureF_I(nullptr);
 static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -955,7 +957,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getSizeF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getSizeF_I(nullptr);
 static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -976,7 +978,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMajorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMajorF_I(nullptr);
 static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -997,7 +999,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getTouchMinorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getTouchMinorF_I(nullptr);
 static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1018,7 +1020,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMajorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMajorF_I(nullptr);
 static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1039,7 +1041,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getToolMinorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getToolMinorF_I(nullptr);
 static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1060,7 +1062,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getOrientationF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getOrientationF_I(nullptr);
 static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1081,7 +1083,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getAxisValueF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getAxisValueF_I_I(nullptr);
 static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1104,7 +1106,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerCoords = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerCoords(nullptr);
 static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0,
     const base::android::JavaRef<jobject>& p1) __attribute__ ((unused));
@@ -1125,7 +1127,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getPointerProperties = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getPointerProperties(nullptr);
 static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     const base::android::JavaRef<jobject>& p1) __attribute__ ((unused));
@@ -1146,7 +1148,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getMetaState = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getMetaState(nullptr);
 static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getMetaState(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -1166,7 +1168,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getButtonState = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getButtonState(nullptr);
 static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getButtonState(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
@@ -1187,7 +1189,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getRawX = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getRawX(nullptr);
 static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -1207,7 +1209,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getRawY = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getRawY(nullptr);
 static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -1227,7 +1229,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getXPrecision = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getXPrecision(nullptr);
 static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1248,7 +1250,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getYPrecision = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getYPrecision(nullptr);
 static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1269,7 +1271,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistorySize = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistorySize(nullptr);
 static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
@@ -1290,7 +1292,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalEventTime = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalEventTime(nullptr);
 static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const
@@ -1311,7 +1313,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalXF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalXF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1332,7 +1334,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalYF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalYF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -1353,7 +1355,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPressureF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPressureF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const
@@ -1374,7 +1376,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalSizeF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalSizeF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const
@@ -1395,7 +1397,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMajorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMajorF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const
@@ -1416,7 +1418,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMinorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMinorF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const
@@ -1437,7 +1439,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMajorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMajorF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const
@@ -1458,7 +1460,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMinorF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMinorF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const
@@ -1479,7 +1481,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalOrientationF_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalOrientationF_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0) __attribute__ ((unused));
 static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const
@@ -1500,7 +1502,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalAxisValueF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalAxisValueF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1523,7 +1525,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalXF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalXF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1546,7 +1548,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalYF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalYF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1569,7 +1571,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPressureF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPressureF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1592,7 +1594,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalSizeF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalSizeF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1615,7 +1617,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMajorF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1638,7 +1640,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalTouchMinorF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1661,7 +1663,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMajorF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMajorF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1684,7 +1686,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalToolMinorF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalToolMinorF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1707,7 +1709,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalOrientationF_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalOrientationF_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1) __attribute__ ((unused));
@@ -1730,7 +1732,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalAxisValueF_I_I_I(nullptr);
 static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1,
@@ -1755,7 +1757,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getHistoricalPointerCoords = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getHistoricalPointerCoords(nullptr);
 static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper p0,
     JniIntWrapper p1,
@@ -1778,7 +1780,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_getEdgeFlags = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_getEdgeFlags(nullptr);
 static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -1798,7 +1800,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_setEdgeFlags = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_setEdgeFlags(nullptr);
 static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -1817,7 +1819,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_setAction = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_setAction(nullptr);
 static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static void Java_MotionEvent_setAction(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -1836,7 +1838,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_offsetLocation = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_offsetLocation(nullptr);
 static void Java_MotionEvent_offsetLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     jfloat p0,
     jfloat p1) __attribute__ ((unused));
@@ -1857,7 +1859,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_setLocation = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_setLocation(nullptr);
 static void Java_MotionEvent_setLocation(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     jfloat p0,
     jfloat p1) __attribute__ ((unused));
@@ -1878,7 +1880,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_transform = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_transform(nullptr);
 static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     const base::android::JavaRef<jobject>& p0) __attribute__ ((unused));
 static void Java_MotionEvent_transform(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -1897,7 +1899,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_addBatchV_J_F_F_F_F_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_addBatchV_J_F_F_F_F_I(nullptr);
 static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, jlong p0,
     jfloat p1,
@@ -1926,7 +1928,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_addBatchV_J_LAVMEPC_I = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_addBatchV_J_LAVMEPC_I(nullptr);
 static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, jlong p0,
     const base::android::JavaRef<jobjectArray>& p1,
@@ -1949,7 +1951,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_toString = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_toString(nullptr);
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_toString(JNIEnv* env, const
@@ -1970,7 +1972,7 @@
   return base::android::ScopedJavaLocalRef<jstring>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_actionToString = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_actionToString(nullptr);
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env,
     JniIntWrapper p0) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_actionToString(JNIEnv* env,
@@ -1991,7 +1993,7 @@
   return base::android::ScopedJavaLocalRef<jstring>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_axisToString = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_axisToString(nullptr);
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env,
     JniIntWrapper p0) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jstring> Java_MotionEvent_axisToString(JNIEnv* env,
@@ -2012,7 +2014,7 @@
   return base::android::ScopedJavaLocalRef<jstring>(env, ret);
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_axisFromString = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_axisFromString(nullptr);
 static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0)
     __attribute__ ((unused));
 static jint Java_MotionEvent_axisFromString(JNIEnv* env, const base::android::JavaRef<jstring>& p0)
@@ -2033,7 +2035,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_android_view_MotionEvent_writeToParcel = 0;
+static std::atomic<jmethodID> g_android_view_MotionEvent_writeToParcel(nullptr);
 static void Java_MotionEvent_writeToParcel(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     const base::android::JavaRef<jobject>& p0,
     JniIntWrapper p1) __attribute__ ((unused));
diff --git a/base/android/jni_generator/testFromJavaP.golden b/base/android/jni_generator/testFromJavaP.golden
index ca5b050..9225f6e 100644
--- a/base/android/jni_generator/testFromJavaP.golden
+++ b/base/android/jni_generator/testFromJavaP.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_java_io_InputStream[];
 const char kClassPath_java_io_InputStream[] = "java/io/InputStream";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_java_io_InputStream_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_java_io_InputStream_clazz(nullptr);
 #ifndef java_io_InputStream_clazz_defined
 #define java_io_InputStream_clazz_defined
 inline jclass java_io_InputStream_clazz(JNIEnv* env) {
@@ -38,7 +38,7 @@
 namespace JNI_InputStream {
 
 
-static base::subtle::AtomicWord g_java_io_InputStream_available = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_available(nullptr);
 static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_InputStream_available(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -58,7 +58,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_close = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_close(nullptr);
 static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static void Java_InputStream_close(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -76,7 +76,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_mark = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_mark(nullptr);
 static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     JniIntWrapper p0) __attribute__ ((unused));
 static void Java_InputStream_mark(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
@@ -95,7 +95,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_markSupported = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_markSupported(nullptr);
 static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>&
     obj) __attribute__ ((unused));
 static jboolean Java_InputStream_markSupported(JNIEnv* env, const base::android::JavaRef<jobject>&
@@ -116,7 +116,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_readI = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_readI(nullptr);
 static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static jint Java_InputStream_readI(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -136,7 +136,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_readI_AB = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_readI_AB(nullptr);
 static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const
     base::android::JavaRef<jbyteArray>& p0) __attribute__ ((unused));
 static jint Java_InputStream_readI_AB(JNIEnv* env, const base::android::JavaRef<jobject>& obj, const
@@ -157,7 +157,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_readI_AB_I_I = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_readI_AB_I_I(nullptr);
 static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const base::android::JavaRef<jobject>& obj,
     const base::android::JavaRef<jbyteArray>& p0,
     JniIntWrapper p1,
@@ -182,7 +182,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_reset = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_reset(nullptr);
 static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static void Java_InputStream_reset(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -200,7 +200,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_skip = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_skip(nullptr);
 static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong
     p0) __attribute__ ((unused));
 static jlong Java_InputStream_skip(JNIEnv* env, const base::android::JavaRef<jobject>& obj, jlong
@@ -221,7 +221,7 @@
   return ret;
 }
 
-static base::subtle::AtomicWord g_java_io_InputStream_Constructor = 0;
+static std::atomic<jmethodID> g_java_io_InputStream_Constructor(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env)
     __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jobject> Java_InputStream_Constructor(JNIEnv* env) {
diff --git a/base/android/jni_generator/testFromJavaPGenerics.golden b/base/android/jni_generator/testFromJavaPGenerics.golden
index 25c5e0e..cf2646f 100644
--- a/base/android/jni_generator/testFromJavaPGenerics.golden
+++ b/base/android/jni_generator/testFromJavaPGenerics.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_java_util_HashSet[];
 const char kClassPath_java_util_HashSet[] = "java/util/HashSet";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_java_util_HashSet_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_java_util_HashSet_clazz(nullptr);
 #ifndef java_util_HashSet_clazz_defined
 #define java_util_HashSet_clazz_defined
 inline jclass java_util_HashSet_clazz(JNIEnv* env) {
@@ -37,7 +37,7 @@
 namespace JNI_HashSet {
 
 
-static base::subtle::AtomicWord g_java_util_HashSet_dummy = 0;
+static std::atomic<jmethodID> g_java_util_HashSet_dummy(nullptr);
 static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj)
     __attribute__ ((unused));
 static void Java_HashSet_dummy(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
@@ -55,7 +55,7 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord g_java_util_HashSet_getClass = 0;
+static std::atomic<jmethodID> g_java_util_HashSet_getClass(nullptr);
 static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj) __attribute__ ((unused));
 static base::android::ScopedJavaLocalRef<jclass> Java_HashSet_getClass(JNIEnv* env, const
diff --git a/base/android/jni_generator/testInnerClassNatives.golden b/base/android/jni_generator/testInnerClassNatives.golden
index 2c89de9..6cad2b0 100644
--- a/base/android/jni_generator/testInnerClassNatives.golden
+++ b/base/android/jni_generator/testInnerClassNatives.golden
@@ -25,7 +25,7 @@
 const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] =
     "org/chromium/TestJni$MyInnerClass";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
@@ -34,7 +34,7 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyInnerClass_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024MyInnerClass_clazz(nullptr);
 #ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined
 #define org_chromium_TestJni_00024MyInnerClass_clazz_defined
 inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
index 0623b378..ccaf1219 100644
--- a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
+++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden
@@ -25,8 +25,8 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
 const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz
-    = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_TestJni_00024MyOtherInnerClass_clazz(nullptr);
 #ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 #define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
@@ -35,7 +35,7 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
index 3f91cf1..44842c3 100644
--- a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
+++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuterRegistrations.golden
@@ -21,7 +21,7 @@
 extern const char kClassPath_org_chromium_TestJni_00024MyOtherInnerClass[];
 
 extern const char kClassPath_org_chromium_TestJni[];
-extern base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz;
+extern std::atomic<jclass> g_org_chromium_TestJni_00024MyOtherInnerClass_clazz;
 #ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 #define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
@@ -29,7 +29,7 @@
       &g_org_chromium_TestJni_00024MyOtherInnerClass_clazz);
 }
 #endif
-extern base::subtle::AtomicWord g_org_chromium_TestJni_clazz;
+extern std::atomic<jclass> g_org_chromium_TestJni_clazz;
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testInnerClassNativesMultiple.golden b/base/android/jni_generator/testInnerClassNativesMultiple.golden
index a7eff72b..3e53ece 100644
--- a/base/android/jni_generator/testInnerClassNativesMultiple.golden
+++ b/base/android/jni_generator/testInnerClassNativesMultiple.golden
@@ -29,8 +29,8 @@
 const char kClassPath_org_chromium_TestJni_00024MyInnerClass[] =
     "org/chromium/TestJni$MyInnerClass";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyOtherInnerClass_clazz
-    = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_TestJni_00024MyOtherInnerClass_clazz(nullptr);
 #ifndef org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 #define org_chromium_TestJni_00024MyOtherInnerClass_clazz_defined
 inline jclass org_chromium_TestJni_00024MyOtherInnerClass_clazz(JNIEnv* env) {
@@ -39,7 +39,7 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
@@ -48,7 +48,7 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_00024MyInnerClass_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_00024MyInnerClass_clazz(nullptr);
 #ifndef org_chromium_TestJni_00024MyInnerClass_clazz_defined
 #define org_chromium_TestJni_00024MyInnerClass_clazz_defined
 inline jclass org_chromium_TestJni_00024MyInnerClass_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
index 40f3d6be..ab2496ca 100644
--- a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
+++ b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
 const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr);
 #ifndef org_chromium_foo_Foo_clazz_defined
 #define org_chromium_foo_Foo_clazz_defined
 inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
@@ -50,7 +50,7 @@
 }
 
 
-static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0;
+static std::atomic<jmethodID> g_org_chromium_foo_Foo_calledByNative(nullptr);
 static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback1,
     const base::android::JavaRef<jobject>& callback2) {
   CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
diff --git a/base/android/jni_generator/testNativeExportsOnlyOption.golden b/base/android/jni_generator/testNativeExportsOnlyOption.golden
index bdc8d29e..7553007 100644
--- a/base/android/jni_generator/testNativeExportsOnlyOption.golden
+++ b/base/android/jni_generator/testNativeExportsOnlyOption.golden
@@ -33,8 +33,8 @@
 const char kClassPath_org_chromium_example_jni_1generator_SampleForTests[] =
     "org/chromium/example/jni_generator/SampleForTests";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_00024MyOtherInnerClass_clazz_defined
 inline jclass
@@ -45,8 +45,8 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_00024MyInnerClass_clazz(JNIEnv*
@@ -57,8 +57,8 @@
 }
 #endif
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass>
+    g_org_chromium_example_jni_1generator_SampleForTests_clazz(nullptr);
 #ifndef org_chromium_example_jni_1generator_SampleForTests_clazz_defined
 #define org_chromium_example_jni_1generator_SampleForTests_clazz_defined
 inline jclass org_chromium_example_jni_1generator_SampleForTests_clazz(JNIEnv* env) {
@@ -114,8 +114,8 @@
 }
 
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParam(nullptr);
 static void Java_SampleForTests_testMethodWithParam(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) {
   CHECK_CLAZZ(env, obj.obj(),
@@ -132,8 +132,8 @@
   jni_generator::CheckException(env);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithParamAndReturn(nullptr);
 static base::android::ScopedJavaLocalRef<jstring>
     Java_SampleForTests_testMethodWithParamAndReturn(JNIEnv* env, const
     base::android::JavaRef<jobject>& obj, JniIntWrapper iParam) {
@@ -153,8 +153,8 @@
   return base::android::ScopedJavaLocalRef<jstring>(env, ret);
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithParam(nullptr);
 static jint Java_SampleForTests_testStaticMethodWithParam(JNIEnv* env, JniIntWrapper iParam) {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
       org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
@@ -172,8 +172,8 @@
   return ret;
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_testMethodWithNoParam(nullptr);
 static jdouble Java_SampleForTests_testMethodWithNoParam(JNIEnv* env) {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
       org_chromium_example_jni_1generator_SampleForTests_clazz(env), 0);
@@ -191,8 +191,8 @@
   return ret;
 }
 
-static base::subtle::AtomicWord
-    g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam = 0;
+static std::atomic<jmethodID>
+    g_org_chromium_example_jni_1generator_SampleForTests_testStaticMethodWithNoParam(nullptr);
 static base::android::ScopedJavaLocalRef<jstring>
     Java_SampleForTests_testStaticMethodWithNoParam(JNIEnv* env) {
   CHECK_CLAZZ(env, org_chromium_example_jni_1generator_SampleForTests_clazz(env),
diff --git a/base/android/jni_generator/testNatives.golden b/base/android/jni_generator/testNatives.golden
index 1178f19a..23598ff 100644
--- a/base/android/jni_generator/testNatives.golden
+++ b/base/android/jni_generator/testNatives.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
 const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testNativesLong.golden b/base/android/jni_generator/testNativesLong.golden
index b3115ef..52169dcf 100644
--- a/base/android/jni_generator/testNativesLong.golden
+++ b/base/android/jni_generator/testNativesLong.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_TestJni[];
 const char kClassPath_org_chromium_TestJni[] = "org/chromium/TestJni";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_TestJni_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_TestJni_clazz(nullptr);
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testNativesRegistrations.golden b/base/android/jni_generator/testNativesRegistrations.golden
index 1dae786a..8856e05f 100644
--- a/base/android/jni_generator/testNativesRegistrations.golden
+++ b/base/android/jni_generator/testNativesRegistrations.golden
@@ -19,7 +19,7 @@
 // Step 1: Forward declarations (classes).
 
 extern const char kClassPath_org_chromium_TestJni[];
-extern base::subtle::AtomicWord g_org_chromium_TestJni_clazz;
+extern std::atomic<jclass> g_org_chromium_TestJni_clazz;
 #ifndef org_chromium_TestJni_clazz_defined
 #define org_chromium_TestJni_clazz_defined
 inline jclass org_chromium_TestJni_clazz(JNIEnv* env) {
diff --git a/base/android/jni_generator/testSingleJNIAdditionalImport.golden b/base/android/jni_generator/testSingleJNIAdditionalImport.golden
index 4b3eccd..7164c0b 100644
--- a/base/android/jni_generator/testSingleJNIAdditionalImport.golden
+++ b/base/android/jni_generator/testSingleJNIAdditionalImport.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
 const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr);
 #ifndef org_chromium_foo_Foo_clazz_defined
 #define org_chromium_foo_Foo_clazz_defined
 inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
@@ -47,7 +47,7 @@
 }
 
 
-static base::subtle::AtomicWord g_org_chromium_foo_Foo_calledByNative = 0;
+static std::atomic<jmethodID> g_org_chromium_foo_Foo_calledByNative(nullptr);
 static void Java_Foo_calledByNative(JNIEnv* env, const base::android::JavaRef<jobject>& callback) {
   CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
       org_chromium_foo_Foo_clazz(env));
diff --git a/base/android/jni_generator/testTracing.golden b/base/android/jni_generator/testTracing.golden
index 9701027..88dbec9 100644
--- a/base/android/jni_generator/testTracing.golden
+++ b/base/android/jni_generator/testTracing.golden
@@ -21,7 +21,7 @@
 JNI_REGISTRATION_EXPORT extern const char kClassPath_org_chromium_foo_Foo[];
 const char kClassPath_org_chromium_foo_Foo[] = "org/chromium/foo/Foo";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-JNI_REGISTRATION_EXPORT base::subtle::AtomicWord g_org_chromium_foo_Foo_clazz = 0;
+JNI_REGISTRATION_EXPORT std::atomic<jclass> g_org_chromium_foo_Foo_clazz(nullptr);
 #ifndef org_chromium_foo_Foo_clazz_defined
 #define org_chromium_foo_Foo_clazz_defined
 inline jclass org_chromium_foo_Foo_clazz(JNIEnv* env) {
@@ -58,7 +58,7 @@
 }
 
 
-static base::subtle::AtomicWord g_org_chromium_foo_Foo_Constructor = 0;
+static std::atomic<jmethodID> g_org_chromium_foo_Foo_Constructor(nullptr);
 static base::android::ScopedJavaLocalRef<jobject> Java_Foo_Constructor(JNIEnv* env) {
   CHECK_CLAZZ(env, org_chromium_foo_Foo_clazz(env),
       org_chromium_foo_Foo_clazz(env), NULL);
@@ -76,7 +76,7 @@
   return base::android::ScopedJavaLocalRef<jobject>(env, ret);
 }
 
-static base::subtle::AtomicWord g_org_chromium_foo_Foo_callbackFromNative = 0;
+static std::atomic<jmethodID> g_org_chromium_foo_Foo_callbackFromNative(nullptr);
 static void Java_Foo_callbackFromNative(JNIEnv* env, const base::android::JavaRef<jobject>& obj) {
   CHECK_CLAZZ(env, obj.obj(),
       org_chromium_foo_Foo_clazz(env));
diff --git a/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java b/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
index 18fe6ce..4b0c049 100644
--- a/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
+++ b/base/android/junit/src/org/chromium/base/metrics/test/ShadowRecordHistogram.java
@@ -36,6 +36,18 @@
     }
 
     @Implementation
+    public static void recordCount100Histogram(String name, int sample) {
+        Pair<String, Integer> key = Pair.create(name, sample);
+        incrementSampleCount(key);
+    }
+
+    @Implementation
+    public static void recordEnumeratedHistogram(String name, int sample, int boundary) {
+        assert sample < boundary : "Sample " + sample + " is not within boundary " + boundary + "!";
+        incrementSampleCount(Pair.create(name, sample));
+    }
+
+    @Implementation
     public static void recordLongTimesHistogram100(String name, long duration, TimeUnit timeUnit) {
         Pair<String, Integer> key = Pair.create(name, (int) timeUnit.toMillis(duration));
         incrementSampleCount(key);
diff --git a/base/bind.h b/base/bind.h
index 66d5d82d..9915d8d 100644
--- a/base/bind.h
+++ b/base/bind.h
@@ -216,10 +216,9 @@
   PolymorphicInvoke invoke_func = &Invoker::RunOnce;
 
   using InvokeFuncStorage = internal::BindStateBase::InvokeFuncStorage;
-  return CallbackType(new BindState(
+  return CallbackType(BindState::Create(
       reinterpret_cast<InvokeFuncStorage>(invoke_func),
-      std::forward<Functor>(functor),
-      std::forward<Args>(args)...));
+      std::forward<Functor>(functor), std::forward<Args>(args)...));
 }
 
 // Bind as RepeatingCallback.
@@ -257,10 +256,9 @@
   PolymorphicInvoke invoke_func = &Invoker::Run;
 
   using InvokeFuncStorage = internal::BindStateBase::InvokeFuncStorage;
-  return CallbackType(new BindState(
+  return CallbackType(BindState::Create(
       reinterpret_cast<InvokeFuncStorage>(invoke_func),
-      std::forward<Functor>(functor),
-      std::forward<Args>(args)...));
+      std::forward<Functor>(functor), std::forward<Args>(args)...));
 }
 
 // Unannotated Bind.
diff --git a/base/bind_internal.h b/base/bind_internal.h
index 0a90c7db..aff03a6 100644
--- a/base/bind_internal.h
+++ b/base/bind_internal.h
@@ -734,47 +734,85 @@
   return false;
 }
 
-// Used by ApplyCancellationTraits below.
+// Used by QueryCancellationTraits below.
 template <typename Functor, typename BoundArgsTuple, size_t... indices>
-bool ApplyCancellationTraitsIsCancelledImpl(const Functor& functor,
-                                            const BoundArgsTuple& bound_args,
-                                            std::index_sequence<indices...>) {
-  return CallbackCancellationTraits<Functor, BoundArgsTuple>::IsCancelled(
-      functor, std::get<indices>(bound_args)...);
+bool QueryCancellationTraitsImpl(BindStateBase::CancellationQueryMode mode,
+                                 const Functor& functor,
+                                 const BoundArgsTuple& bound_args,
+                                 std::index_sequence<indices...>) {
+  switch (mode) {
+    case BindStateBase::IS_CANCELLED:
+      return CallbackCancellationTraits<Functor, BoundArgsTuple>::IsCancelled(
+          functor, std::get<indices>(bound_args)...);
+    case BindStateBase::MAYBE_VALID:
+      return CallbackCancellationTraits<Functor, BoundArgsTuple>::MaybeValid(
+          functor, std::get<indices>(bound_args)...);
+  }
+  NOTREACHED();
 }
 
 // Relays |base| to corresponding CallbackCancellationTraits<>::Run(). Returns
 // true if the callback |base| represents is canceled.
 template <typename BindStateType>
-bool ApplyCancellationTraitsIsCancelled(const BindStateBase* base) {
+bool QueryCancellationTraits(const BindStateBase* base,
+                             BindStateBase::CancellationQueryMode mode) {
   const BindStateType* storage = static_cast<const BindStateType*>(base);
   static constexpr size_t num_bound_args =
       std::tuple_size<decltype(storage->bound_args_)>::value;
-  return ApplyCancellationTraitsIsCancelledImpl(
-      storage->functor_, storage->bound_args_,
+  return QueryCancellationTraitsImpl(
+      mode, storage->functor_, storage->bound_args_,
       std::make_index_sequence<num_bound_args>());
-};
-
-// Used by ApplyCancellationTraits below.
-template <typename Functor, typename BoundArgsTuple, size_t... indices>
-bool ApplyCancellationTraitsMaybeValidImpl(const Functor& functor,
-                                           const BoundArgsTuple& bound_args,
-                                           std::index_sequence<indices...>) {
-  return CallbackCancellationTraits<Functor, BoundArgsTuple>::MaybeValid(
-      functor, std::get<indices>(bound_args)...);
 }
 
-// Relays |base| to corresponding CallbackCancellationTraits<>::Run(). Returns
-// false if the callback |base| represents is guaranteed to be cancelled.
-template <typename BindStateType>
-bool ApplyCancellationTraitsMaybeValid(const BindStateBase* base) {
-  const BindStateType* storage = static_cast<const BindStateType*>(base);
-  static constexpr size_t num_bound_args =
-      std::tuple_size<decltype(storage->bound_args_)>::value;
-  return ApplyCancellationTraitsMaybeValidImpl(
-      storage->functor_, storage->bound_args_,
-      std::make_index_sequence<num_bound_args>());
-};
+// The base case of BanUnconstructedRefCountedReceiver that checks nothing.
+template <typename Functor, typename Receiver, typename... Unused>
+std::enable_if_t<
+    !(MakeFunctorTraits<Functor>::is_method &&
+      std::is_pointer<std::decay_t<Receiver>>::value &&
+      IsRefCountedType<std::remove_pointer_t<std::decay_t<Receiver>>>::value)>
+BanUnconstructedRefCountedReceiver(const Receiver& receiver, Unused&&...) {}
+
+template <typename Functor>
+void BanUnconstructedRefCountedReceiver() {}
+
+// Asserts that Callback is not the first owner of a ref-counted receiver.
+template <typename Functor, typename Receiver, typename... Unused>
+std::enable_if_t<
+    MakeFunctorTraits<Functor>::is_method &&
+    std::is_pointer<std::decay_t<Receiver>>::value &&
+    IsRefCountedType<std::remove_pointer_t<std::decay_t<Receiver>>>::value>
+BanUnconstructedRefCountedReceiver(const Receiver& receiver, Unused&&...) {
+  DCHECK(receiver);
+
+  // It's error prone to make the implicit first reference to ref-counted types.
+  // In the example below, base::BindOnce() makes the implicit first reference
+  // to the ref-counted Foo. If PostTask() failed or the posted task ran fast
+  // enough, the newly created instance can be destroyed before |oo| makes
+  // another reference.
+  //   Foo::Foo() {
+  //     base::PostTask(FROM_HERE, base::BindOnce(&Foo::Bar, this));
+  //   }
+  //
+  //   scoped_refptr<Foo> oo = new Foo();
+  //
+  // Instead of doing like above, please consider adding a static constructor,
+  // and keep the first reference alive explicitly.
+  //   // static
+  //   scoped_refptr<Foo> Foo::Create() {
+  //     auto foo = base::WrapRefCounted(new Foo());
+  //     base::PostTask(FROM_HERE, base::BindOnce(&Foo::Bar, foo));
+  //     return foo;
+  //   }
+  //
+  //   Foo::Foo() {}
+  //
+  //   scoped_refptr<Foo> oo = Foo::Create();
+  DCHECK(receiver->HasAtLeastOneRef())
+      << "base::Bind() refuses to create the first reference to ref-counted "
+         "objects. That is typically happens around PostTask() in their "
+         "constructor, and such objects can be destroyed before `new` returns "
+         "if the task resolves fast enough.";
+}
 
 // BindState<>
 //
@@ -787,16 +825,20 @@
                                  std::tuple<BoundArgs...>>::is_cancellable>;
 
   template <typename ForwardFunctor, typename... ForwardBoundArgs>
-  explicit BindState(BindStateBase::InvokeFuncStorage invoke_func,
-                     ForwardFunctor&& functor,
-                     ForwardBoundArgs&&... bound_args)
-      // IsCancellable is std::false_type if
-      // CallbackCancellationTraits<>::IsCancelled returns always false.
-      // Otherwise, it's std::true_type.
-      : BindState(IsCancellable{},
-                  invoke_func,
-                  std::forward<ForwardFunctor>(functor),
-                  std::forward<ForwardBoundArgs>(bound_args)...) {}
+  static BindState* Create(BindStateBase::InvokeFuncStorage invoke_func,
+                           ForwardFunctor&& functor,
+                           ForwardBoundArgs&&... bound_args) {
+    // Ban ref counted receivers that were not yet fully constructed to avoid
+    // a common pattern of racy situation.
+    BanUnconstructedRefCountedReceiver<ForwardFunctor>(bound_args...);
+
+    // IsCancellable is std::false_type if
+    // CallbackCancellationTraits<>::IsCancelled returns always false.
+    // Otherwise, it's std::true_type.
+    return new BindState(IsCancellable{}, invoke_func,
+                         std::forward<ForwardFunctor>(functor),
+                         std::forward<ForwardBoundArgs>(bound_args)...);
+  }
 
   Functor functor_;
   std::tuple<BoundArgs...> bound_args_;
@@ -809,8 +851,7 @@
                      ForwardBoundArgs&&... bound_args)
       : BindStateBase(invoke_func,
                       &Destroy,
-                      &ApplyCancellationTraitsIsCancelled<BindState>,
-                      &ApplyCancellationTraitsMaybeValid<BindState>),
+                      &QueryCancellationTraits<BindState>),
         functor_(std::forward<ForwardFunctor>(functor)),
         bound_args_(std::forward<ForwardBoundArgs>(bound_args)...) {
     DCHECK(!IsNull(functor_));
diff --git a/base/bind_unittest.cc b/base/bind_unittest.cc
index f1d19a1..48b23b24 100644
--- a/base/bind_unittest.cc
+++ b/base/bind_unittest.cc
@@ -19,11 +19,12 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using ::testing::_;
-using ::testing::Mock;
+using ::testing::AnyNumber;
 using ::testing::ByMove;
+using ::testing::Mock;
 using ::testing::Return;
 using ::testing::StrictMock;
+using ::testing::_;
 
 namespace base {
 namespace {
@@ -54,6 +55,7 @@
 
   MOCK_CONST_METHOD0(AddRef, void());
   MOCK_CONST_METHOD0(Release, bool());
+  MOCK_CONST_METHOD0(HasAtLeastOneRef, bool());
 
  private:
   // Particularly important in this test to ensure no copies are made.
@@ -72,6 +74,7 @@
  public:
   void AddRef() const {}
   void Release() const {}
+  bool HasAtLeastOneRef() const { return true; }
   virtual void VirtualSet() { value = kParentValue; }
   void NonVirtualSet() { value = kParentValue; }
   int value;
@@ -443,6 +446,7 @@
   EXPECT_CALL(static_func_mock_, IntMethod0()).WillOnce(Return(1337));
   EXPECT_CALL(has_ref_, AddRef()).Times(2);
   EXPECT_CALL(has_ref_, Release()).Times(2);
+  EXPECT_CALL(has_ref_, HasAtLeastOneRef()).WillRepeatedly(Return(true));
   EXPECT_CALL(has_ref_, IntMethod0()).WillOnce(Return(10));
   EXPECT_CALL(has_ref_, IntConstMethod0()).WillOnce(Return(11));
   EXPECT_CALL(no_ref_, IntMethod0()).WillOnce(Return(12));
@@ -481,6 +485,7 @@
   EXPECT_CALL(static_func_mock_, IntMethod0()).WillOnce(Return(1337));
   EXPECT_CALL(has_ref_, AddRef()).Times(2);
   EXPECT_CALL(has_ref_, Release()).Times(2);
+  EXPECT_CALL(has_ref_, HasAtLeastOneRef()).WillRepeatedly(Return(true));
   EXPECT_CALL(has_ref_, IntMethod0()).WillOnce(Return(10));
   EXPECT_CALL(has_ref_, IntConstMethod0()).WillOnce(Return(11));
 
@@ -796,6 +801,7 @@
   EXPECT_CALL(static_func_mock, VoidMethod0());
   EXPECT_CALL(has_ref, AddRef()).Times(4);
   EXPECT_CALL(has_ref, Release()).Times(4);
+  EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillRepeatedly(Return(true));
   EXPECT_CALL(has_ref, VoidMethod0()).Times(2);
   EXPECT_CALL(has_ref, VoidConstMethod0()).Times(2);
 
@@ -844,6 +850,7 @@
   EXPECT_CALL(static_func_mock, IntMethod0()).WillOnce(Return(1337));
   EXPECT_CALL(has_ref, AddRef()).Times(4);
   EXPECT_CALL(has_ref, Release()).Times(4);
+  EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillRepeatedly(Return(true));
   EXPECT_CALL(has_ref, IntMethod0()).WillOnce(Return(31337));
   EXPECT_CALL(has_ref, IntConstMethod0())
       .WillOnce(Return(41337))
@@ -964,6 +971,7 @@
   StrictMock<HasRef> has_ref;
   EXPECT_CALL(has_ref, AddRef()).Times(1);
   EXPECT_CALL(has_ref, Release()).Times(1);
+  EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillRepeatedly(Return(true));
 
   const scoped_refptr<HasRef> refptr(&has_ref);
   CallbackType<TypeParam, int()> scoped_refptr_const_ref_cb =
@@ -1492,5 +1500,13 @@
   EXPECT_DCHECK_DEATH(base::Bind(null_cb, 42));
 }
 
+TEST(BindDeathTest, BanFirstOwnerOfRefCountedType) {
+  StrictMock<HasRef> has_ref;
+  EXPECT_DCHECK_DEATH({
+    EXPECT_CALL(has_ref, HasAtLeastOneRef()).WillOnce(Return(false));
+    base::BindOnce(&HasRef::VoidMethod0, &has_ref);
+  });
+}
+
 }  // namespace
 }  // namespace base
diff --git a/base/callback_internal.cc b/base/callback_internal.cc
index 991ae9f..6a185d9 100644
--- a/base/callback_internal.cc
+++ b/base/callback_internal.cc
@@ -11,12 +11,16 @@
 
 namespace {
 
-bool ReturnFalse(const BindStateBase*) {
-  return false;
-}
-
-bool ReturnTrue(const BindStateBase*) {
-  return true;
+bool QueryCancellationTraitsForNonCancellables(
+    const BindStateBase*,
+    BindStateBase::CancellationQueryMode mode) {
+  switch (mode) {
+    case BindStateBase::IS_CANCELLED:
+      return false;
+    case BindStateBase::MAYBE_VALID:
+      return true;
+  }
+  NOTREACHED();
 }
 
 }  // namespace
@@ -27,17 +31,18 @@
 
 BindStateBase::BindStateBase(InvokeFuncStorage polymorphic_invoke,
                              void (*destructor)(const BindStateBase*))
-    : BindStateBase(polymorphic_invoke, destructor, &ReturnFalse, &ReturnTrue) {
-}
+    : BindStateBase(polymorphic_invoke,
+                    destructor,
+                    &QueryCancellationTraitsForNonCancellables) {}
 
-BindStateBase::BindStateBase(InvokeFuncStorage polymorphic_invoke,
-                             void (*destructor)(const BindStateBase*),
-                             bool (*is_cancelled)(const BindStateBase*),
-                             bool (*maybe_valid)(const BindStateBase*))
+BindStateBase::BindStateBase(
+    InvokeFuncStorage polymorphic_invoke,
+    void (*destructor)(const BindStateBase*),
+    bool (*query_cancellation_traits)(const BindStateBase*,
+                                      CancellationQueryMode))
     : polymorphic_invoke_(polymorphic_invoke),
       destructor_(destructor),
-      is_cancelled_(is_cancelled),
-      maybe_valid_(maybe_valid) {}
+      query_cancellation_traits_(query_cancellation_traits) {}
 
 CallbackBase& CallbackBase::operator=(CallbackBase&& c) noexcept = default;
 CallbackBase::CallbackBase(const CallbackBaseCopyable& c)
diff --git a/base/callback_internal.h b/base/callback_internal.h
index c39d054..07c3dc49 100644
--- a/base/callback_internal.h
+++ b/base/callback_internal.h
@@ -50,6 +50,11 @@
  public:
   REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
 
+  enum CancellationQueryMode {
+    IS_CANCELLED,
+    MAYBE_VALID,
+  };
+
   using InvokeFuncStorage = void(*)();
 
  private:
@@ -57,8 +62,8 @@
                 void (*destructor)(const BindStateBase*));
   BindStateBase(InvokeFuncStorage polymorphic_invoke,
                 void (*destructor)(const BindStateBase*),
-                bool (*is_cancelled)(const BindStateBase*),
-                bool (*maybe_valid)(const BindStateBase*));
+                bool (*query_cancellation_traits)(const BindStateBase*,
+                                                  CancellationQueryMode mode));
 
   ~BindStateBase() = default;
 
@@ -74,10 +79,12 @@
   friend struct ::base::FakeBindState;
 
   bool IsCancelled() const {
-    return is_cancelled_(this);
+    return query_cancellation_traits_(this, IS_CANCELLED);
   }
 
-  bool MaybeValid() const { return maybe_valid_(this); }
+  bool MaybeValid() const {
+    return query_cancellation_traits_(this, MAYBE_VALID);
+  }
 
   // In C++, it is safe to cast function pointers to function pointers of
   // another type. It is not okay to use void*. We create a InvokeFuncStorage
@@ -87,9 +94,8 @@
 
   // Pointer to a function that will properly destroy |this|.
   void (*destructor_)(const BindStateBase*);
-
-  bool (*is_cancelled_)(const BindStateBase*);
-  bool (*maybe_valid_)(const BindStateBase*);
+  bool (*query_cancellation_traits_)(const BindStateBase*,
+                                     CancellationQueryMode mode);
 
   DISALLOW_COPY_AND_ASSIGN(BindStateBase);
 };
diff --git a/base/callback_unittest.cc b/base/callback_unittest.cc
index 130e44f..feb43e99 100644
--- a/base/callback_unittest.cc
+++ b/base/callback_unittest.cc
@@ -24,18 +24,23 @@
 // chance of colliding with another instantiation and breaking the
 // one-definition-rule.
 struct FakeBindState : internal::BindStateBase {
-  FakeBindState()
-      : BindStateBase(&NopInvokeFunc, &Destroy, &IsCancelled, &MaybeValid) {}
+  FakeBindState() : BindStateBase(&NopInvokeFunc, &Destroy, &IsCancelled) {}
 
  private:
   ~FakeBindState() = default;
   static void Destroy(const internal::BindStateBase* self) {
     delete static_cast<const FakeBindState*>(self);
   }
-  static bool IsCancelled(const internal::BindStateBase*) {
-    return false;
+  static bool IsCancelled(const internal::BindStateBase*,
+                          internal::BindStateBase::CancellationQueryMode mode) {
+    switch (mode) {
+      case internal::BindStateBase::IS_CANCELLED:
+        return false;
+      case internal::BindStateBase::MAYBE_VALID:
+        return true;
+    }
+    NOTREACHED();
   }
-  static bool MaybeValid(const internal::BindStateBase*) { return true; }
 };
 
 namespace {
@@ -215,7 +220,10 @@
 class CallbackOwner : public base::RefCounted<CallbackOwner> {
  public:
   explicit CallbackOwner(bool* deleted) {
-    callback_ = Bind(&CallbackOwner::Unused, this);
+    // WrapRefCounted() here is needed to avoid the check failure in the Bind
+    // implementation, that refuses to create the first reference to ref-counted
+    // objects.
+    callback_ = Bind(&CallbackOwner::Unused, WrapRefCounted(this));
     deleted_ = deleted;
   }
   void Reset() {
diff --git a/base/memory/ref_counted.cc b/base/memory/ref_counted.cc
index b9fa15f..e2887045 100644
--- a/base/memory/ref_counted.cc
+++ b/base/memory/ref_counted.cc
@@ -21,6 +21,10 @@
   return ref_count_.IsOne();
 }
 
+bool RefCountedThreadSafeBase::HasAtLeastOneRef() const {
+  return !ref_count_.IsZero();
+}
+
 #if DCHECK_IS_ON()
 RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
   DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without "
diff --git a/base/memory/ref_counted.h b/base/memory/ref_counted.h
index 249f70e0..d826c9581 100644
--- a/base/memory/ref_counted.h
+++ b/base/memory/ref_counted.h
@@ -25,6 +25,7 @@
 class BASE_EXPORT RefCountedBase {
  public:
   bool HasOneRef() const { return ref_count_ == 1; }
+  bool HasAtLeastOneRef() const { return ref_count_ >= 1; }
 
  protected:
   explicit RefCountedBase(StartRefCountFromZeroTag) {
@@ -146,6 +147,7 @@
 class BASE_EXPORT RefCountedThreadSafeBase {
  public:
   bool HasOneRef() const;
+  bool HasAtLeastOneRef() const;
 
  protected:
   explicit constexpr RefCountedThreadSafeBase(StartRefCountFromZeroTag) {}
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index a8b0c744..c1159d47 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -119,7 +119,7 @@
 class ServiceProcessLauncher;
 }
 
-namespace shell_integration {
+namespace shell_integration_linux {
 class LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
 }
 
@@ -329,7 +329,8 @@
   friend class mojo::core::ScopedIPCSupport;
   friend class net::MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
   friend class rlz_lib::FinancialPing;
-  friend class shell_integration::LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
+  friend class shell_integration_linux::
+      LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
   friend class webrtc::DesktopConfigurationMonitor;
   friend class content::ServiceWorkerSubresourceLoader;
   friend class viz::HostGpuMemoryBufferManager;
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index b7c464f..46f3d76 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -325,9 +325,13 @@
 }
 
 void TraceLog::SetAddTraceEventOverride(
-    const AddTraceEventOverrideCallback& override) {
+    const AddTraceEventOverrideCallback& override,
+    const OnFlushCallback& on_flush_callback) {
   subtle::NoBarrier_Store(&trace_event_override_,
                           reinterpret_cast<subtle::AtomicWord>(override));
+  subtle::NoBarrier_Store(
+      &on_flush_callback_,
+      reinterpret_cast<subtle::AtomicWord>(on_flush_callback));
 }
 
 struct TraceLog::RegisteredAsyncObserver {
@@ -371,6 +375,7 @@
       generation_(0),
       use_worker_thread_(false),
       trace_event_override_(0),
+      on_flush_callback_(0),
       filter_factory_for_testing_(nullptr) {
   CategoryRegistry::Initialize();
 
@@ -991,6 +996,12 @@
   // This will flush the thread local buffer.
   delete thread_local_event_buffer_.Get();
 
+  auto on_flush_callback = reinterpret_cast<OnFlushCallback>(
+      subtle::NoBarrier_Load(&on_flush_callback_));
+  if (on_flush_callback) {
+    on_flush_callback();
+  }
+
   // Scheduler uses TRACE_EVENT macros when posting a task, which can lead
   // to acquiring a tracing lock. Given that posting a task requires grabbing
   // a scheduler lock, we need to post this task outside tracing lock to avoid
@@ -1523,65 +1534,86 @@
   return id ^ process_id_hash_;
 }
 
+template <typename T>
+void TraceLog::AddMetadataEventWhileLocked(int thread_id,
+                                           const char* metadata_name,
+                                           const char* arg_name,
+                                           const T& value) {
+  auto trace_event_override = reinterpret_cast<AddTraceEventOverrideCallback>(
+      subtle::NoBarrier_Load(&trace_event_override_));
+  if (trace_event_override) {
+    TraceEvent trace_event;
+    InitializeMetadataEvent(&trace_event, thread_id, metadata_name, arg_name,
+                            value);
+    trace_event_override(trace_event);
+  } else {
+    InitializeMetadataEvent(
+        AddEventToThreadSharedChunkWhileLocked(nullptr, false), thread_id,
+        metadata_name, arg_name, value);
+  }
+}
+
 void TraceLog::AddMetadataEventsWhileLocked() {
   lock_.AssertAcquired();
 
+  auto trace_event_override = reinterpret_cast<AddTraceEventOverrideCallback>(
+      subtle::NoBarrier_Load(&trace_event_override_));
+
   // Move metadata added by |AddMetadataEvent| into the trace log.
-  while (!metadata_events_.empty()) {
-    TraceEvent* event = AddEventToThreadSharedChunkWhileLocked(nullptr, false);
-    event->MoveFrom(std::move(metadata_events_.back()));
-    metadata_events_.pop_back();
+  if (trace_event_override) {
+    while (!metadata_events_.empty()) {
+      trace_event_override(*metadata_events_.back());
+      metadata_events_.pop_back();
+    }
+  } else {
+    while (!metadata_events_.empty()) {
+      TraceEvent* event =
+          AddEventToThreadSharedChunkWhileLocked(nullptr, false);
+      event->MoveFrom(std::move(metadata_events_.back()));
+      metadata_events_.pop_back();
+    }
   }
 
 #if !defined(OS_NACL)  // NaCl shouldn't expose the process id.
-  InitializeMetadataEvent(
-      AddEventToThreadSharedChunkWhileLocked(nullptr, false), 0, "num_cpus",
-      "number", base::SysInfo::NumberOfProcessors());
+  AddMetadataEventWhileLocked(0, "num_cpus", "number",
+                              base::SysInfo::NumberOfProcessors());
 #endif
 
   int current_thread_id = static_cast<int>(base::PlatformThread::CurrentId());
   if (process_sort_index_ != 0) {
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false),
-        current_thread_id, "process_sort_index", "sort_index",
-        process_sort_index_);
+    AddMetadataEventWhileLocked(current_thread_id, "process_sort_index",
+                                "sort_index", process_sort_index_);
   }
 
   if (!process_name_.empty()) {
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false),
-        current_thread_id, "process_name", "name", process_name_);
+    AddMetadataEventWhileLocked(current_thread_id, "process_name", "name",
+                                process_name_);
   }
 
   TimeDelta process_uptime = TRACE_TIME_NOW() - process_creation_time_;
-  InitializeMetadataEvent(
-      AddEventToThreadSharedChunkWhileLocked(nullptr, false), current_thread_id,
-      "process_uptime_seconds", "uptime", process_uptime.InSeconds());
+  AddMetadataEventWhileLocked(current_thread_id, "process_uptime_seconds",
+                              "uptime", process_uptime.InSeconds());
 
 #if defined(OS_ANDROID)
-  InitializeMetadataEvent(
-      AddEventToThreadSharedChunkWhileLocked(nullptr, false), current_thread_id,
-      "chrome_library_address", "start_address",
-      base::StringPrintf("%p", &__executable_start));
+  AddMetadataEventWhileLocked(current_thread_id, "chrome_library_address",
+                              "start_address",
+                              base::StringPrintf("%p", &__executable_start));
 #endif
 
   if (!process_labels_.empty()) {
     std::vector<base::StringPiece> labels;
     for (const auto& it : process_labels_)
       labels.push_back(it.second);
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false),
-        current_thread_id, "process_labels", "labels",
-        base::JoinString(labels, ","));
+    AddMetadataEventWhileLocked(current_thread_id, "process_labels", "labels",
+                                base::JoinString(labels, ","));
   }
 
   // Thread sort indices.
   for (const auto& it : thread_sort_indices_) {
     if (it.second == 0)
       continue;
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false), it.first,
-        "thread_sort_index", "sort_index", it.second);
+    AddMetadataEventWhileLocked(it.first, "thread_sort_index", "sort_index",
+                                it.second);
   }
 
   // Thread names.
@@ -1589,17 +1621,14 @@
   for (const auto& it : thread_names_) {
     if (it.second.empty())
       continue;
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false), it.first,
-        "thread_name", "name", it.second);
+    AddMetadataEventWhileLocked(it.first, "thread_name", "name", it.second);
   }
 
   // If buffer is full, add a metadata record to report this.
   if (!buffer_limit_reached_timestamp_.is_null()) {
-    InitializeMetadataEvent(
-        AddEventToThreadSharedChunkWhileLocked(nullptr, false),
-        current_thread_id, "trace_buffer_overflowed", "overflowed_at_ts",
-        buffer_limit_reached_timestamp_);
+    AddMetadataEventWhileLocked(current_thread_id, "trace_buffer_overflowed",
+                                "overflowed_at_ts",
+                                buffer_limit_reached_timestamp_);
   }
 }
 
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 8fc914d7..6cb4e5c 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -179,10 +179,12 @@
   void CancelTracing(const OutputCallback& cb);
 
   typedef void (*AddTraceEventOverrideCallback)(const TraceEvent&);
+  typedef void (*OnFlushCallback)();
   // The callback will be called up until the point where the flush is
   // finished, i.e. must be callable until OutputCallback is called with
   // has_more_events==false.
-  void SetAddTraceEventOverride(const AddTraceEventOverrideCallback& override);
+  void SetAddTraceEventOverride(const AddTraceEventOverrideCallback& override,
+                                const OnFlushCallback& on_flush_callback);
 
   // Called by TRACE_EVENT* macros, don't call this directly.
   // The name parameter is a category group for example:
@@ -394,6 +396,11 @@
   TraceLog();
   ~TraceLog() override;
   void AddMetadataEventsWhileLocked();
+  template <typename T>
+  void AddMetadataEventWhileLocked(int thread_id,
+                                   const char* metadata_name,
+                                   const char* arg_name,
+                                   const T& value);
 
   InternalTraceOptions trace_options() const {
     return static_cast<InternalTraceOptions>(
@@ -516,6 +523,7 @@
   subtle::AtomicWord generation_;
   bool use_worker_thread_;
   subtle::AtomicWord trace_event_override_;
+  subtle::AtomicWord on_flush_callback_;
 
   FilterFactoryForTesting filter_factory_for_testing_;
 
diff --git a/build/android/docs/android_app_bundles.md b/build/android/docs/android_app_bundles.md
new file mode 100644
index 0000000..e9e8284
--- /dev/null
+++ b/build/android/docs/android_app_bundles.md
@@ -0,0 +1,199 @@
+# Introduction
+
+This document describes how the Chromium build system supports Android app
+bundles.
+
+[TOC]
+
+# Overview of app bundles
+
+An Android app bundle is an alternative application distribution format for
+Android applications on the Google Play Store, that allows reducing the size
+of binaries sent for installation to individual devices that run on Android L
+and beyond. For more information about them, see the official Android
+documentation at:
+
+  https://developer.android.com/guide/app-bundle/
+
+For the context of this document, the most important points are:
+
+  - Unlike a regular APK (e.g. `foo.apk`), the bundle (e.g. `foo.aab`) cannot
+    be installed directly on a device.
+
+  - Instead, it must be processed into a set of installable split APKs, which
+    are stored inside a special zip archive (e.g. `foo.apks`).
+
+  - The splitting can be based on various criteria: e.g. language or screen
+    density for resources, or cpu ABI for native code.
+
+  - The bundle also uses the notion of modules to separate several application
+    features. Each module has its own code, assets and resources, and can be
+    installed separately from the rest of the application if needed.
+
+  - The main application itself is stored in the '`base`' module (this name
+    cannot be changed).
+
+
+# Declaring app bundles with GN templates
+
+Here's an example that shows how to declare a simple bundle that contains
+a single base module, which enables language-based splits:
+
+```gn
+
+  # First declare the first bundle module. The base module is the one
+  # that contains the main application's code, resources and assets.
+  android_app_bundle_module("foo_base_module") {
+    # Declaration are similar to android_apk here.
+    ...
+  }
+
+  # Second, declare the bundle itself.
+  android_app_bundle("foo_bundle") {
+    # Indicate the base module to use for this bundle
+    base_module_target = ":foo_base_module"
+
+    # The name of our bundle file (without any suffix). Default would
+    # be 'foo_bundle' otherwise.
+    bundle_name = "FooBundle"
+
+    # Signing your bundle is required to upload it to the Play Store
+    # but since signing is very slow, avoid doing it for non official
+    # builds. Signing the bundle is not required for local testing.
+    sign_bundle = is_official_build
+
+    # Enable language-based splits for this bundle. Which means that
+    # resources and assets specific to a given language will be placed
+    # into their own split APK in the final .apks archive.
+    enable_language_splits = true
+
+    # Proguard settings must be passed at the bundle, not module, target.
+    proguard_enabled = !is_java_debug
+  }
+```
+
+When generating the `foo_bundle` target with Ninja, you will end up with
+the following:
+
+  - The bundle file under `out/Release/apks/FooBundle.aab`
+
+  - A helper script called `out/Release/bin/foo_bundle`, which can be used
+    to install / launch / uninstall the bundle on local devices.
+
+    This works like an APK wrapper script (e.g. `foo_apk`). Use `--help`
+    to see all possible commands supported by the script.
+
+If you need more modules besides the base one, you will need to list all the
+extra ones using the extra_modules variable which takes a list of GN scopes,
+as in:
+
+```gn
+
+  android_app_bundle_module("foo_base_module") {
+    ...
+  }
+
+  android_app_bundle_module("foo_extra_module") {
+    ...
+  }
+
+  android_app_bundle("foo_bundle") {
+    base_module_target = ":foo_base_module"
+
+    extra_modules = [
+      { # NOTE: Scopes require one field per line, and no comma separators.
+        name = "my_module"
+        module_target = ":foo_extra_module"
+      }
+    ]
+
+    ...
+  }
+```
+
+Note that each extra module is identified by a unique name, which cannot
+be '`base`'.
+
+
+# Bundle signature issues
+
+Signing an app bundle is not necessary, unless you want to upload it to the
+Play Store. Since this process is very slow (it uses `jarsigner` instead of
+the much faster `apkbuilder`), you can control it with the `sign_bundle`
+variable, as described in the example above.
+
+The `.apks` archive however always contains signed split APKs. The keystore
+path/password/alias being used are the default ones, unless you use custom
+values when declaring the bundle itself, as in:
+
+```gn
+  android_app_bundle("foo_bundle") {
+    ...
+    keystore_path = "//path/to/keystore"
+    keystore_password = "K3y$t0Re-Pa$$w0rd"
+    keystore_name = "my-signing-key-name"
+  }
+```
+
+These values are not stored in the bundle itself, but in the wrapper script,
+which will use them to generate the `.apks` archive for you. This allows you
+to properly install updates on top of existing applications on any device.
+
+
+# Proguard and bundles
+
+When using an app bundle that is made of several modules, it is crucial to
+ensure that proguard, if enabled:
+
+- Keeps the obfuscated class names used by each module consistent.
+- Does not remove classes that are not used in one module, but referenced
+  by others.
+
+To achieve this, a special scheme called *synchronized proguarding* is
+performed, which consists of the following steps:
+
+- The list of unoptimized .jar files from all modules are sent to a single
+  proguard command. This generates a new temporary optimized *group* .jar file.
+
+- Each module extracts the optimized class files from the optimized *group*
+  .jar file, to generate its own, module-specific, optimized .jar.
+
+- Each module-specific optimized .jar is then sent to dex generation.
+
+This synchronized proguarding step is added by the `android_app_bundle()` GN
+template. In practice this means the following:
+
+  - If `proguard_enabled` and `proguard_jar_path` must be passed to
+    `android_app_bundle` targets, but not to `android_app_bundle_module` ones.
+
+  - `proguard_configs` can be still passed to individual modules, just
+    like regular APKs. All proguard configs will be merged during the
+    synchronized proguard step.
+
+
+# Manual generation and installation of .apks archives
+
+Note that the `foo_bundle` script knows how to generate the .apks archive
+from the bundle file, and install it to local devices for you. For example,
+to install and launch a bundle, use:
+
+```sh
+  out/Release/bin/foo_bundle run
+```
+
+If you want to manually look or use the `.apks` archive, use the following
+command to generate it:
+
+```sh
+  out/Release/bin/foo_bundle build-bundle-apks \
+      --output-apks=/tmp/BundleFoo.apks
+```
+
+All split APKs within the archive will be properly signed. And you will be
+able to look at its content (with `unzip -l`), or install it manually with:
+
+```sh
+  build/android/gyp/bundletool.py install-apks \
+      --apks=/tmp/BundleFoo.apks \
+      --adb=$(which adb)
+```
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index a4519b2..d4339b0 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -499,12 +499,6 @@
 
 This type corresponds to an Android app bundle (`.aab` file).
 
-* `deps_info['synchronized_proguard_enabled"]`:
-True to indicate that the app modules of this bundle should be proguarded in a
-single synchronized proguarding step. Synchronized proguarding means that all
-un-optimized jars for all modules are sent to a single proguard command. The
-resulting optimized jar is then split into optimized app module jars after.
-
 --------------- END_MARKDOWN ---------------------------------------------------
 """
 
@@ -862,8 +856,6 @@
       'test apk).')
   parser.add_option('--proguard-enabled', action='store_true',
       help='Whether proguard is enabled for this apk or bundle module.')
-  parser.add_option('--synchronized-proguard-enabled', action='store_true',
-      help='Whether synchronized proguarding is enabled for this bundle.')
   parser.add_option('--proguard-configs',
       help='GN-list of proguard flag files to use in final apk.')
   parser.add_option('--proguard-output-jar-path',
@@ -1253,16 +1245,6 @@
         raise Exception('Deps %s have proguard enabled while deps %s have '
                         'proguard disabled' % (deps_proguard_enabled,
                                                deps_proguard_disabled))
-      if (bool(deps_proguard_enabled) !=
-              bool(options.synchronized_proguard_enabled) or
-          bool(deps_proguard_disabled) ==
-              bool(options.synchronized_proguard_enabled)):
-        raise Exception('Modules %s of bundle %s have opposite proguard '
-                        'enabling flags than bundle' % (deps_proguard_enabled +
-                            deps_proguard_disabled, config['deps_info']['name'])
-                        )
-      deps_info['synchronized_proguard_enabled'] = bool(
-          options.synchronized_proguard_enabled)
     else:
       deps_info['proguard_enabled'] = bool(options.proguard_enabled)
       if options.proguard_output_jar_path:
diff --git a/build/android/pylib/utils/maven_downloader.py b/build/android/pylib/utils/maven_downloader.py
index c5b0bad..c60b0140 100755
--- a/build/android/pylib/utils/maven_downloader.py
+++ b/build/android/pylib/utils/maven_downloader.py
@@ -36,7 +36,8 @@
   _REMOTE_REPO = 'https://maven.google.com'
 
   # Default Maven repository.
-  _DEFAULT_REPO_PATH = os.path.join(os.getenv('HOME'), '.m2', 'repository')
+  _DEFAULT_REPO_PATH = os.path.join(
+      os.path.expanduser('~'), '.m2', 'repository')
 
   def __init__(self, debug=False):
     self._repo_path = MavenDownloader._DEFAULT_REPO_PATH
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 379efef..2317a6e1 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -376,10 +376,6 @@
     if (defined(invoker.proguard_enabled) && invoker.proguard_enabled) {
       args += [ "--proguard-enabled" ]
     }
-    if (defined(invoker.synchronized_proguard_enabled) &&
-        invoker.synchronized_proguard_enabled) {
-      args += [ "--synchronized-proguard-enabled" ]
-    }
     if (defined(invoker.proguard_output_jar_path)) {
       _rebased_proguard_output_jar_path =
           rebase_path(invoker.proguard_output_jar_path, root_build_dir)
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index fb174d3a..cfec1fb 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2387,20 +2387,10 @@
       }
     }
 
-    # When using an app bundle that is made of several modules, it is crucial to
-    # ensure that the obfuscated class names used by each module are consistent.
-    #
-    # To achieve this, all modules belonging to the same app bundle must
-    # be grouped into a single .jar, that will be proguarded all at once. This
-    # is called "synchronized proguarding", and ensures that the obfuscated
-    # names of all classes remain consistent across modules.
-    #
-    # Later, this single optimized .jar is processed to generate module-specific
-    # .jar files that only contain the obfuscated classes needed by the module
-    # itself.
-    #
-    # This synchronized proguarding step is added by the bundle target.
-    if (!_is_bundle_module || !_proguard_enabled) {
+    # Dex generation for app bundle modules with proguarding enabled takes
+    # place later due to synchronized proguarding. For more details,
+    # read build/android/docs/android_app_bundles.md
+    if (!(_is_bundle_module && _proguard_enabled)) {
       if (_proguard_enabled) {
         _proguard_target = "${_template_name}__proguard"
         proguard(_proguard_target) {
@@ -2425,6 +2415,10 @@
                 rebase_path(invoker.proguard_config_exclusions, root_build_dir)
             args += [ "--proguard-config-exclusions=$_rebased_proguard_config_exclusions" ]
           }
+          if (defined(invoker.apk_under_test)) {
+            args += [ "--mapping=@FileArg($_rebased_build_config:deps_info:proguard_under_test_mapping)" ]
+            deps += [ "${invoker.apk_under_test}__proguard" ]
+          }
         }
         _dex_sources = [ _proguard_output_jar_path ]
         _dex_deps = [ ":$_proguard_target" ]
@@ -2482,6 +2476,13 @@
         use_pool = true
       }
     } else {
+      # A small sanity check to help developers with a subtle point!
+      assert(
+          !defined(invoker.proguard_jar_path),
+          "proguard_jar_path should not be used for app bundle modules " +
+              "when proguard is enabled. Pass it to the android_app_bundle() " +
+              "target instead!")
+
       _final_deps += [ ":$_java_target" ]
     }
 
@@ -3651,13 +3652,12 @@
   #      APKs use 'chrome-command-line', the WebView one uses
   #      'webview-command-line').
   #
-  #    synchronized_proguard_enabled: True if synchronized proguarding is
-  #      enabled for this bundle. WARNING: All modules that contain Java must
-  #      have proguarding enabled if this is true and disabled if this is false.
+  #    proguard_enabled: Optional. True if proguarding is enabled for this
+  #      bundle. Default is to enable this only for release builds. Note that
+  #      this will always perform synchronized proguarding.
   #
-  #    proguard_jar_path: Path to custom proguard jar used for synchronized
-  #      proguarding. WARNING: Can only be set if
-  #      |synchronized_proguard_enabled| is set to true.
+  #    proguard_jar_path: Optional. Path to custom proguard jar used for
+  #      proguarding.
   #
   # Example:
   #   android_app_bundle("chrome_public_bundle") {
@@ -3693,9 +3693,8 @@
       _all_modules += invoker.extra_modules
     }
 
-    _synchronized_proguard_enabled =
-        defined(invoker.synchronized_proguard_enabled) &&
-        invoker.synchronized_proguard_enabled
+    _proguard_enabled =
+        defined(invoker.proguard_enabled) && invoker.proguard_enabled
 
     # Make build config, which is required for synchronized proguarding.
     _module_targets = []
@@ -3709,16 +3708,16 @@
       type = "android_app_bundle"
       possible_config_deps = _module_targets
       build_config = _build_config
-      synchronized_proguard_enabled = _synchronized_proguard_enabled
     }
 
-    if (_synchronized_proguard_enabled) {
+    if (_proguard_enabled) {
       # Proguard all modules together to keep binary size small while still
       # maintaining compatibility between modules.
       _proguard_output_jar_path =
           "${target_gen_dir}/${target_name}/${target_name}.proguard.jar"
       _sync_proguard_target = "${target_name}__sync_proguard"
       proguard(_sync_proguard_target) {
+        forward_variables_from(invoker, [ "proguard_jar_path" ])
         build_config = _build_config
         deps = _module_targets + [ ":$_build_config_target" ]
 
@@ -3727,10 +3726,6 @@
           "--proguard-configs=@FileArg($_rebased_build_config:deps_info:proguard_all_configs)",
           "--input-paths=@FileArg($_rebased_build_config:deps_info:java_runtime_classpath)",
         ]
-
-        if (defined(invoker.proguard_jar_path)) {
-          proguard_jar_path = invoker.proguard_jar_path
-        }
       }
     }
 
@@ -3745,7 +3740,7 @@
       _module_build_config =
           "$_module_target_gen_dir/${_module_target_name}.build_config"
 
-      if (_synchronized_proguard_enabled) {
+      if (_proguard_enabled) {
         # Extract optimized classes for each module and dex them.
 
         _module_final_dex_target = "${target_name}__${_module.name}__dex"
diff --git a/build/fuchsia/device_target.py b/build/fuchsia/device_target.py
index 34bc99f..acef61b 100644
--- a/build/fuchsia/device_target.py
+++ b/build/fuchsia/device_target.py
@@ -5,9 +5,11 @@
 """Implements commands for running and interacting with Fuchsia on devices."""
 
 import boot_data
+import log_reader
 import logging
 import os
 import subprocess
+import sys
 import target
 import time
 import uuid
@@ -17,6 +19,9 @@
 CONNECT_RETRY_COUNT = 20
 CONNECT_RETRY_WAIT_SECS = 1
 
+# Number of failed connection attempts before redirecting system logs to stdout.
+CONNECT_RETRY_COUNT_BEFORE_LOGGING = 10
+
 class DeviceTarget(target.Target):
   def __init__(self, output_dir, target_cpu, host=None, port=None,
                ssh_config=None):
@@ -93,8 +98,20 @@
       logging.debug(' '.join(bootserver_command))
       subprocess.check_call(bootserver_command)
 
+      # Setup loglistener. Logs will be redirected to stdout if the device takes
+      # longer than expected to boot.
+      loglistener_path = os.path.join(SDK_ROOT, 'tools', 'loglistener')
+      loglistener = subprocess.Popen([loglistener_path, node_name],
+                                     stdout=subprocess.PIPE,
+                                     stderr=subprocess.STDOUT,
+                                     stdin=open(os.devnull))
+      self._SetSystemLogsReader(
+          log_reader.LogReader(loglistener, loglistener.stdout))
+
       logging.debug('Waiting for device to join network.')
-      for _ in xrange(CONNECT_RETRY_COUNT):
+      for retry in xrange(CONNECT_RETRY_COUNT):
+        if retry == CONNECT_RETRY_COUNT_BEFORE_LOGGING:
+          self._system_logs_reader.RedirectTo(sys.stdout);
         self._host = self.__Discover(node_name)
         if self._host:
           break
diff --git a/build/fuchsia/log_reader.py b/build/fuchsia/log_reader.py
new file mode 100644
index 0000000..667da85d
--- /dev/null
+++ b/build/fuchsia/log_reader.py
@@ -0,0 +1,96 @@
+# Copyright 2018 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 multiprocessing
+import os
+import Queue
+import select
+import threading
+
+
+class LogReader(object):
+  """Helper class used to read a debug log stream on a background thread.
+  By default all log messages are stored in an internal buffer. After
+  RedirectTo() is called all logs are redirected to the specified stream.
+  Optionally the caller can give onwnership of the log process to the reader.
+  That process will be killed automatically when the log reader is closed.
+  """
+
+  def __init__(self, process, stream):
+    self._process = process
+    self._stream = stream
+    self._buffer = Queue.Queue()
+
+    # Start a background thread that redirects the stream to self._buffer.
+    self._StartThread(lambda line: self._buffer.put(line))
+
+  def _StartThread(self, on_message_received):
+    self._quit_pipe, thread_quit_pipe = multiprocessing.Pipe()
+
+    self._thread = threading.Thread(target=self._ReadThread,
+                                    args=(thread_quit_pipe,
+                                          on_message_received))
+    self._thread.daemon = True
+    self._thread.start()
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, exception_type, exception_value, traceback):
+    self.Close()
+
+  def __del__(self):
+    self.Close()
+
+  def _ReadThread(self, quit_pipe, on_message_received):
+    """Main function for the background thread that reads |self._stream| and
+    calls |on_message_received| for each line."""
+
+    poll = select.poll()
+    poll.register(self._stream.fileno(), select.POLLIN)
+    poll.register(quit_pipe.fileno(), select.POLLIN)
+
+    quit = False
+    while not quit:
+      events = poll.poll()
+      for fileno, event in events:
+        if fileno == quit_pipe.fileno():
+          quit = True
+          break
+
+        assert fileno == self._stream.fileno()
+        if event & select.POLLIN:
+          on_message_received(self._stream.readline())
+        elif event & select.POLLHUP:
+          quit = True
+
+  def _StopThread(self):
+    if self._thread:
+      try:
+        self._quit_pipe.send("quit")
+      except IOError:
+        # The thread has already quit and closed the other end of the pipe.
+        pass
+      self._thread.join();
+      self._thread = None
+
+  def Close(self):
+    self._StopThread()
+    if self._process:
+      self._process.kill()
+      self._process = None
+
+  def RedirectTo(self, stream):
+    self._StopThread()
+
+    # Drain and reset the buffer.
+    while True:
+      try:
+        line = self._buffer.get_nowait()
+        stream.write(line)
+      except Queue.Empty:
+        break
+
+    # Restart the thread to write stream to stdout.
+    self._StartThread(lambda line: stream.write(line))
diff --git a/build/fuchsia/qemu_target.py b/build/fuchsia/qemu_target.py
index aa356d34..10a8e8d 100644
--- a/build/fuchsia/qemu_target.py
+++ b/build/fuchsia/qemu_target.py
@@ -5,6 +5,7 @@
 """Implements commands for running and interacting with Fuchsia on QEMU."""
 
 import boot_data
+import log_reader
 import logging
 import target
 import os
@@ -61,6 +62,10 @@
     # noisy ANSI spew from the user's terminal emulator.
     kernel_args.append('TERM=dumb')
 
+    # Enable logging to the serial port. This is a temporary fix to investigate
+    # the root cause for https://crbug.com/869753 .
+    kernel_args.append('kernel.serial=legacy')
+
     qemu_command = [qemu_path,
         '-m', str(self._ram_size_mb),
         '-nographic',
@@ -133,10 +138,13 @@
     logging.debug('Launching QEMU.')
     logging.debug(' '.join(qemu_command))
 
-    stdio_flags = {'stdin': open(os.devnull),
-                   'stdout': open(os.devnull),
-                   'stderr': open(os.devnull)}
-    self._qemu_process = subprocess.Popen(qemu_command, **stdio_flags)
+    # QEMU stderr/stdout are redirected to LogReader to debug
+    # https://crbug.com/86975 .
+    self._qemu_process = subprocess.Popen(qemu_command, stdin=open(os.devnull),
+                                          stdout=subprocess.PIPE,
+                                          stderr=subprocess.STDOUT)
+    self._SetSystemLogsReader(
+        log_reader.LogReader(None, self._qemu_process.stdout))
     self._WaitUntilReady();
 
   def Shutdown(self):
diff --git a/build/fuchsia/target.py b/build/fuchsia/target.py
index a9b1f7a..910bdfd 100644
--- a/build/fuchsia/target.py
+++ b/build/fuchsia/target.py
@@ -4,59 +4,18 @@
 
 import boot_data
 import common
+import log_reader
 import logging
-import os
 import remote_cmd
-import subprocess
 import sys
-import tempfile
 import time
-import threading
-import Queue
+
 
 _SHUTDOWN_CMD = ['dm', 'poweroff']
 _ATTACH_MAX_RETRIES = 10
 _ATTACH_RETRY_INTERVAL = 1
 
 
-class LoglistenerReader(object):
-  """Helper class used to read loglistener output."""
-
-  def __init__(self, process):
-    self._process = process
-    self._queue = Queue.Queue()
-    self._thread = threading.Thread(target=self._read_thread)
-    self._thread.daemon = True
-    self._thread.start()
-
-  def __enter__(self):
-    return self
-
-  def __exit__(self, exception_type, exception_value, traceback):
-    self.close()
-
-  def _read_thread(self):
-    try:
-      while True:
-        line = self._process.stdout.readline()
-        if not line:
-            return
-        self._queue.put(line)
-    finally:
-      self._process.stdout.close()
-
-  def close(self):
-    self._process.kill()
-
-  def dump_logs(self):
-    while True:
-      try:
-        line = self._queue.get(block=False)
-        logging.info(line.strip())
-      except Queue.Empty:
-        return
-
-
 class FuchsiaTargetException(Exception):
   def __init__(self, message):
     super(FuchsiaTargetException, self).__init__(message)
@@ -70,6 +29,7 @@
     self._started = False
     self._dry_run = False
     self._target_cpu = target_cpu
+    self._system_logs_reader = None
 
   # Functions used by the Python context manager for teardown.
   def __enter__(self):
@@ -185,19 +145,19 @@
   def _AssertIsStarted(self):
     assert self.IsStarted()
 
+  def _SetSystemLogsReader(self, reader):
+    if self._system_logs_reader:
+      _system_logs_reader.Close()
+    self._system_logs_reader = reader
+
   def _WaitUntilReady(self, retries=_ATTACH_MAX_RETRIES):
     logging.info('Connecting to Fuchsia using SSH.')
-    loglistener_path = os.path.join(common.SDK_ROOT, 'tools', 'loglistener')
-    instance_id = boot_data.GetNodeName(self._output_dir)
-    process = subprocess.Popen([loglistener_path, instance_id],
-                               stdout=subprocess.PIPE,
-                               stderr=subprocess.STDOUT,
-                               stdin=open(os.devnull))
-    with LoglistenerReader(process) as loglistener_reader:
+
+    try:
       for retry in xrange(retries + 1):
-        if retry > 2:
-          # Log loglistener output after 2 failed SSH connection attempt.
-          loglistener_reader.dump_logs();
+        if retry == 2 and self._system_logs_reader:
+          # Log system logs after 2 failed SSH connection attempt.
+          self._system_logs_reader.RedirectTo(sys.stdout);
 
         host, port = self._GetEndpoint()
         if remote_cmd.RunSsh(self._GetSshConfigPath(), host, port, ['true'],
@@ -206,7 +166,12 @@
           self._started = True
           return True
         time.sleep(_ATTACH_RETRY_INTERVAL)
-      logging.error('Timeout limit reached.')
+    finally:
+      if self._system_logs_reader:
+        self._system_logs_reader.Close()
+        self._system_logs_reader = None
+
+    logging.error('Timeout limit reached.')
 
     raise FuchsiaTargetException('Couldn\'t connect using SSH.')
 
diff --git a/build/get_syzygy_binaries.py b/build/get_syzygy_binaries.py
deleted file mode 100755
index 09b1199..0000000
--- a/build/get_syzygy_binaries.py
+++ /dev/null
@@ -1,529 +0,0 @@
-#!/usr/bin/env python
-# 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.
-
-"""A utility script for downloading versioned Syzygy binaries."""
-
-import hashlib
-import errno
-import json
-import logging
-import optparse
-import os
-import re
-import shutil
-import stat
-import sys
-import subprocess
-import tempfile
-import time
-import zipfile
-
-
-_LOGGER = logging.getLogger(os.path.basename(__file__))
-
-# The relative path where official builds are archived in their GS bucket.
-_SYZYGY_ARCHIVE_PATH = ('/builds/official/%(revision)s')
-
-# A JSON file containing the state of the download directory. If this file and
-# directory state do not agree, then the binaries will be downloaded and
-# installed again.
-_STATE = '.state'
-
-# This matches an integer (an SVN revision number) or a SHA1 value (a GIT hash).
-# The archive exclusively uses lowercase GIT hashes.
-_REVISION_RE = re.compile('^(?:\d+|[a-f0-9]{40})$')
-
-# This matches an MD5 hash.
-_MD5_RE = re.compile('^[a-f0-9]{32}$')
-
-# List of reources to be downloaded and installed. These are tuples with the
-# following format:
-# (basename, logging name, relative installation path, extraction filter)
-_RESOURCES = [
-  ('benchmark.zip', 'benchmark', '', None),
-  ('binaries.zip', 'binaries', 'exe', None),
-  ('symbols.zip', 'symbols', 'exe',
-      lambda x: x.filename.endswith('.dll.pdb'))]
-
-
-# Name of the MS DIA dll that we need to copy to the binaries directory.
-_DIA_DLL_NAME = "msdia140.dll"
-
-
-def _LoadState(output_dir):
-  """Loads the contents of the state file for a given |output_dir|, returning
-  None if it doesn't exist.
-  """
-  path = os.path.join(output_dir, _STATE)
-  if not os.path.exists(path):
-    _LOGGER.debug('No state file found.')
-    return None
-  with open(path, 'rb') as f:
-    _LOGGER.debug('Reading state file: %s', path)
-    try:
-      return json.load(f)
-    except ValueError:
-      _LOGGER.debug('Invalid state file.')
-      return None
-
-
-def _SaveState(output_dir, state, dry_run=False):
-  """Saves the |state| dictionary to the given |output_dir| as a JSON file."""
-  path = os.path.join(output_dir, _STATE)
-  _LOGGER.debug('Writing state file: %s', path)
-  if dry_run:
-    return
-  with open(path, 'wb') as f:
-    f.write(json.dumps(state, sort_keys=True, indent=2))
-
-
-def _Md5(path):
-  """Returns the MD5 hash of the file at |path|, which must exist."""
-  return hashlib.md5(open(path, 'rb').read()).hexdigest()
-
-
-def _StateIsValid(state):
-  """Returns true if the given state structure is valid."""
-  if not isinstance(state, dict):
-    _LOGGER.debug('State must be a dict.')
-    return False
-  r = state.get('revision', None)
-  if not isinstance(r, basestring) or not _REVISION_RE.match(r):
-    _LOGGER.debug('State contains an invalid revision.')
-    return False
-  c = state.get('contents', None)
-  if not isinstance(c, dict):
-    _LOGGER.debug('State must contain a contents dict.')
-    return False
-  for (relpath, md5) in c.iteritems():
-    if not isinstance(relpath, basestring) or len(relpath) == 0:
-      _LOGGER.debug('State contents dict contains an invalid path.')
-      return False
-    if not isinstance(md5, basestring) or not _MD5_RE.match(md5):
-      _LOGGER.debug('State contents dict contains an invalid MD5 digest.')
-      return False
-  return True
-
-
-def _BuildActualState(stored, revision, output_dir):
-  """Builds the actual state using the provided |stored| state as a template.
-  Only examines files listed in the stored state, causing the script to ignore
-  files that have been added to the directories locally. |stored| must be a
-  valid state dictionary.
-  """
-  contents = {}
-  state = { 'revision': revision, 'contents': contents }
-  for relpath, md5 in stored['contents'].iteritems():
-    abspath = os.path.abspath(os.path.join(output_dir, relpath))
-    if os.path.isfile(abspath):
-      m = _Md5(abspath)
-      contents[relpath] = m
-
-  return state
-
-
-def _StatesAreConsistent(stored, actual):
-  """Validates whether two state dictionaries are consistent. Both must be valid
-  state dictionaries. Additional entries in |actual| are ignored.
-  """
-  if stored['revision'] != actual['revision']:
-    _LOGGER.debug('Mismatched revision number.')
-    return False
-  cont_stored = stored['contents']
-  cont_actual = actual['contents']
-  for relpath, md5 in cont_stored.iteritems():
-    if relpath not in cont_actual:
-      _LOGGER.debug('Missing content: %s', relpath)
-      return False
-    if md5 != cont_actual[relpath]:
-      _LOGGER.debug('Modified content: %s', relpath)
-      return False
-  return True
-
-
-def _GetCurrentState(revision, output_dir):
-  """Loads the current state and checks to see if it is consistent. Returns
-  a tuple (state, bool). The returned state will always be valid, even if an
-  invalid state is present on disk.
-  """
-  stored = _LoadState(output_dir)
-  if not _StateIsValid(stored):
-    _LOGGER.debug('State is invalid.')
-    # Return a valid but empty state.
-    return ({'revision': '0', 'contents': {}}, False)
-  actual = _BuildActualState(stored, revision, output_dir)
-  # If the script has been modified consider the state invalid.
-  path = os.path.join(output_dir, _STATE)
-  if os.path.getmtime(__file__) > os.path.getmtime(path):
-    return (stored, False)
-  # Otherwise, explicitly validate the state.
-  if not _StatesAreConsistent(stored, actual):
-    return (stored, False)
-  return (stored, True)
-
-
-def _DirIsEmpty(path):
-  """Returns true if the given directory is empty, false otherwise."""
-  for root, dirs, files in os.walk(path):
-    return not dirs and not files
-
-
-def _RmTreeHandleReadOnly(func, path, exc):
-  """An error handling function for use with shutil.rmtree. This will
-  detect failures to remove read-only files, and will change their properties
-  prior to removing them. This is necessary on Windows as os.remove will return
-  an access error for read-only files, and git repos contain read-only
-  pack/index files.
-  """
-  excvalue = exc[1]
-  if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
-    _LOGGER.debug('Removing read-only path: %s', path)
-    os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
-    func(path)
-  else:
-    raise
-
-
-def _RmTree(path):
-  """A wrapper of shutil.rmtree that handles read-only files."""
-  shutil.rmtree(path, ignore_errors=False, onerror=_RmTreeHandleReadOnly)
-
-
-def _CleanState(output_dir, state, dry_run=False):
-  """Cleans up files/directories in |output_dir| that are referenced by
-  the given |state|. Raises an error if there are local changes. Returns a
-  dictionary of files that were deleted.
-  """
-  _LOGGER.debug('Deleting files from previous installation.')
-  deleted = {}
-
-  # Generate a list of files to delete, relative to |output_dir|.
-  contents = state['contents']
-  files = sorted(contents.keys())
-
-  # Try to delete the files. Keep track of directories to delete as well.
-  dirs = {}
-  for relpath in files:
-    fullpath = os.path.join(output_dir, relpath)
-    fulldir = os.path.dirname(fullpath)
-    dirs[fulldir] = True
-    if os.path.exists(fullpath):
-      # If somehow the file has become a directory complain about it.
-      if os.path.isdir(fullpath):
-        raise Exception('Directory exists where file expected: %s' % fullpath)
-
-      # Double check that the file doesn't have local changes. If it does
-      # then refuse to delete it.
-      if relpath in contents:
-        stored_md5 = contents[relpath]
-        actual_md5 = _Md5(fullpath)
-        if actual_md5 != stored_md5:
-          raise Exception('File has local changes: %s' % fullpath)
-
-      # The file is unchanged so it can safely be deleted.
-      _LOGGER.debug('Deleting file "%s".', fullpath)
-      deleted[relpath] = True
-      if not dry_run:
-        os.unlink(fullpath)
-
-  # Sort directories from longest name to shortest. This lets us remove empty
-  # directories from the most nested paths first.
-  dirs = sorted(dirs.keys(), key=lambda x: len(x), reverse=True)
-  for p in dirs:
-    if os.path.exists(p) and _DirIsEmpty(p):
-      _LOGGER.debug('Deleting empty directory "%s".', p)
-      if not dry_run:
-        _RmTree(p)
-
-  return deleted
-
-
-def _FindGsUtil():
-  """Looks for depot_tools and returns the absolute path to gsutil.py."""
-  for path in os.environ['PATH'].split(os.pathsep):
-    path = os.path.abspath(path)
-    git_cl = os.path.join(path, 'git_cl.py')
-    gs_util = os.path.join(path, 'gsutil.py')
-    if os.path.exists(git_cl) and os.path.exists(gs_util):
-      return gs_util
-  return None
-
-
-def _GsUtil(*cmd):
-  """Runs the given command in gsutil with exponential backoff and retries."""
-  gs_util = _FindGsUtil()
-  cmd = [sys.executable, gs_util] + list(cmd)
-
-  retries = 3
-  timeout = 4  # Seconds.
-  while True:
-    _LOGGER.debug('Running %s', cmd)
-    prog = subprocess.Popen(cmd, shell=False)
-    prog.communicate()
-
-    # Stop retrying on success.
-    if prog.returncode == 0:
-      return
-
-    # Raise a permanent failure if retries have been exhausted.
-    if retries == 0:
-      raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
-
-    _LOGGER.debug('Sleeping %d seconds and trying again.', timeout)
-    time.sleep(timeout)
-    retries -= 1
-    timeout *= 2
-
-
-def _Download(resource):
-  """Downloads the given GS resource to a temporary file, returning its path."""
-  tmp = tempfile.mkstemp(suffix='syzygy_archive')
-  os.close(tmp[0])
-  tmp_file = tmp[1]
-  url = 'gs://syzygy-archive' + resource
-  if sys.platform == 'cygwin':
-    # Change temporary path to Windows path for gsutil
-    def winpath(path):
-      return subprocess.check_output(['cygpath', '-w', path]).strip()
-    tmp_file = winpath(tmp_file)
-  _GsUtil('cp', url, tmp_file)
-  return tmp[1]
-
-
-def _MaybeCopyDIABinaries(options, contents):
-  """Try to copy the DIA DLL to the binaries exe directory."""
-  toolchain_data_file = os.path.join(os.path.dirname(__file__),
-                                     'win_toolchain.json')
-  if not os.path.exists(toolchain_data_file):
-    _LOGGER.debug('Toolchain JSON data file doesn\'t exist, skipping.')
-    return
-  with open(toolchain_data_file) as temp_f:
-    toolchain_data = json.load(temp_f)
-  if not os.path.isdir(toolchain_data['path']):
-    _LOGGER.error('The toolchain JSON file is invalid.')
-    return
-  dia_sdk_binaries_dir = os.path.join(toolchain_data['path'], 'DIA SDK', 'bin')
-  dia_dll = os.path.join(dia_sdk_binaries_dir, _DIA_DLL_NAME)
-  if not os.path.exists(dia_dll):
-    _LOGGER.debug('%s is missing, skipping.')
-    return
-  dia_dll_dest = os.path.join(options.output_dir, 'exe', _DIA_DLL_NAME)
-  _LOGGER.debug('Copying %s to %s.' % (dia_dll, dia_dll_dest))
-  if not options.dry_run:
-    shutil.copy(dia_dll, dia_dll_dest)
-    contents[os.path.relpath(dia_dll_dest, options.output_dir)] = (
-        _Md5(dia_dll_dest))
-
-
-def _InstallBinaries(options, deleted={}):
-  """Installs Syzygy binaries. This assumes that the output directory has
-  already been cleaned, as it will refuse to overwrite existing files."""
-  contents = {}
-  state = { 'revision': options.revision, 'contents': contents }
-  archive_path = _SYZYGY_ARCHIVE_PATH % { 'revision': options.revision }
-  if options.resources:
-    resources = [(resource, resource, '', None)
-                 for resource in options.resources]
-  else:
-    resources = _RESOURCES
-  for (base, name, subdir, filt) in resources:
-    # Create the output directory if it doesn't exist.
-    fulldir = os.path.join(options.output_dir, subdir)
-    if os.path.isfile(fulldir):
-      raise Exception('File exists where a directory needs to be created: %s' %
-                      fulldir)
-    if not os.path.exists(fulldir):
-      _LOGGER.debug('Creating directory: %s', fulldir)
-      if not options.dry_run:
-        os.makedirs(fulldir)
-
-    # Download and read the archive.
-    resource = archive_path + '/' + base
-    _LOGGER.debug('Retrieving %s archive at "%s".', name, resource)
-    path = _Download(resource)
-
-    _LOGGER.debug('Unzipping %s archive.', name)
-    with open(path, 'rb') as data:
-      archive = zipfile.ZipFile(data)
-      for entry in archive.infolist():
-        if not filt or filt(entry):
-          fullpath = os.path.normpath(os.path.join(fulldir, entry.filename))
-          relpath = os.path.relpath(fullpath, options.output_dir)
-          if os.path.exists(fullpath):
-            # If in a dry-run take into account the fact that the file *would*
-            # have been deleted.
-            if options.dry_run and relpath in deleted:
-              pass
-            else:
-              raise Exception('Path already exists: %s' % fullpath)
-
-          # Extract the file and update the state dictionary.
-          _LOGGER.debug('Extracting "%s".', fullpath)
-          if not options.dry_run:
-            archive.extract(entry.filename, fulldir)
-            md5 = _Md5(fullpath)
-            contents[relpath] = md5
-            if sys.platform == 'cygwin':
-              os.chmod(fullpath, os.stat(fullpath).st_mode | stat.S_IXUSR)
-
-    _LOGGER.debug('Removing temporary file "%s".', path)
-    os.remove(path)
-
-  if options.copy_dia_binaries:
-    # Try to copy the DIA binaries to the binaries directory.
-    _MaybeCopyDIABinaries(options, contents)
-
-  return state
-
-
-def _ParseCommandLine():
-  """Parses the command-line and returns an options structure."""
-  option_parser = optparse.OptionParser()
-  option_parser.add_option('--dry-run', action='store_true', default=False,
-      help='If true then will simply list actions that would be performed.')
-  option_parser.add_option('--force', action='store_true', default=False,
-      help='Force an installation even if the binaries are up to date.')
-  option_parser.add_option('--no-cleanup', action='store_true', default=False,
-      help='Allow installation on non-Windows platforms, and skip the forced '
-           'cleanup step.')
-  option_parser.add_option('--output-dir', type='string',
-      help='The path where the binaries will be replaced. Existing binaries '
-           'will only be overwritten if not up to date.')
-  option_parser.add_option('--overwrite', action='store_true', default=False,
-      help='If specified then the installation will happily delete and rewrite '
-           'the entire output directory, blasting any local changes.')
-  option_parser.add_option('--revision', type='string',
-      help='The SVN revision or GIT hash associated with the required version.')
-  option_parser.add_option('--revision-file', type='string',
-      help='A text file containing an SVN revision or GIT hash.')
-  option_parser.add_option('--resource', type='string', action='append',
-      dest='resources', help='A resource to be downloaded.')
-  option_parser.add_option('--verbose', dest='log_level', action='store_const',
-      default=logging.INFO, const=logging.DEBUG,
-      help='Enables verbose logging.')
-  option_parser.add_option('--quiet', dest='log_level', action='store_const',
-      default=logging.INFO, const=logging.ERROR,
-      help='Disables all output except for errors.')
-  option_parser.add_option('--copy-dia-binaries', action='store_true',
-      default=False, help='If true then the DIA dll will get copied into the '
-                          'binaries directory if it\'s available.')
-  options, args = option_parser.parse_args()
-  if args:
-    option_parser.error('Unexpected arguments: %s' % args)
-  if not options.output_dir:
-    option_parser.error('Must specify --output-dir.')
-  if not options.revision and not options.revision_file:
-    option_parser.error('Must specify one of --revision or --revision-file.')
-  if options.revision and options.revision_file:
-    option_parser.error('Must not specify both --revision and --revision-file.')
-
-  # Configure logging.
-  logging.basicConfig(level=options.log_level)
-
-  # If a revision file has been specified then read it.
-  if options.revision_file:
-    options.revision = open(options.revision_file, 'rb').read().strip()
-    _LOGGER.debug('Parsed revision "%s" from file "%s".',
-                 options.revision, options.revision_file)
-
-  # Ensure that the specified SVN revision or GIT hash is valid.
-  if not _REVISION_RE.match(options.revision):
-    option_parser.error('Must specify a valid SVN or GIT revision.')
-
-  # This just makes output prettier to read.
-  options.output_dir = os.path.normpath(options.output_dir)
-
-  return options
-
-
-def _RemoveOrphanedFiles(options):
-  """This is run on non-Windows systems to remove orphaned files that may have
-  been downloaded by a previous version of this script.
-  """
-  # Reconfigure logging to output info messages. This will allow inspection of
-  # cleanup status on non-Windows buildbots.
-  _LOGGER.setLevel(logging.INFO)
-
-  output_dir = os.path.abspath(options.output_dir)
-
-  # We only want to clean up the folder in 'src/third_party/syzygy', and we
-  # expect to be called with that as an output directory. This is an attempt to
-  # not start deleting random things if the script is run from an alternate
-  # location, or not called from the gclient hooks.
-  expected_syzygy_dir = os.path.abspath(os.path.join(
-      os.path.dirname(__file__), '..', 'third_party', 'syzygy'))
-  expected_output_dir = os.path.join(expected_syzygy_dir, 'binaries')
-  if expected_output_dir != output_dir:
-    _LOGGER.info('Unexpected output directory, skipping cleanup.')
-    return
-
-  if not os.path.isdir(expected_syzygy_dir):
-    _LOGGER.info('Output directory does not exist, skipping cleanup.')
-    return
-
-  def OnError(function, path, excinfo):
-    """Logs error encountered by shutil.rmtree."""
-    _LOGGER.error('Error when running %s(%s)', function, path, exc_info=excinfo)
-
-  _LOGGER.info('Removing orphaned files from %s', expected_syzygy_dir)
-  if not options.dry_run:
-    shutil.rmtree(expected_syzygy_dir, True, OnError)
-
-
-def main():
-  options = _ParseCommandLine()
-
-  if options.dry_run:
-    _LOGGER.debug('Performing a dry-run.')
-
-  # We only care about Windows platforms, as the Syzygy binaries aren't used
-  # elsewhere. However, there was a short period of time where this script
-  # wasn't gated on OS types, and those OSes downloaded and installed binaries.
-  # This will cleanup orphaned files on those operating systems.
-  if sys.platform not in ('win32', 'cygwin'):
-    if options.no_cleanup:
-      _LOGGER.debug('Skipping usual cleanup for non-Windows platforms.')
-    else:
-      return _RemoveOrphanedFiles(options)
-
-  # Load the current installation state, and validate it against the
-  # requested installation.
-  state, is_consistent = _GetCurrentState(options.revision, options.output_dir)
-
-  # Decide whether or not an install is necessary.
-  if options.force:
-    _LOGGER.debug('Forcing reinstall of binaries.')
-  elif is_consistent:
-    # Avoid doing any work if the contents of the directory are consistent.
-    _LOGGER.debug('State unchanged, no reinstall necessary.')
-    return
-
-  # Under normal logging this is the only only message that will be reported.
-  _LOGGER.info('Installing revision %s Syzygy binaries.',
-               options.revision[0:12])
-
-  # Clean up the old state to begin with.
-  deleted = []
-  if options.overwrite:
-    if os.path.exists(options.output_dir):
-      # If overwrite was specified then take a heavy-handed approach.
-      _LOGGER.debug('Deleting entire installation directory.')
-      if not options.dry_run:
-        _RmTree(options.output_dir)
-  else:
-    # Otherwise only delete things that the previous installation put in place,
-    # and take care to preserve any local changes.
-    deleted = _CleanState(options.output_dir, state, options.dry_run)
-
-  # Install the new binaries. In a dry-run this will actually download the
-  # archives, but it won't write anything to disk.
-  state = _InstallBinaries(options, deleted)
-
-  # Build and save the state for the directory.
-  _SaveState(options.output_dir, state, options.dry_run)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/build/write_build_date_header.py b/build/write_build_date_header.py
index 6fe514f..01c9e276 100755
--- a/build/write_build_date_header.py
+++ b/build/write_build_date_header.py
@@ -78,25 +78,17 @@
   argument_parser.add_argument('output_file', help='The file to write to')
   argument_parser.add_argument(
       'build_type', help='The type of build', choices=('official', 'default'))
-  argument_parser.add_argument(
-      'build_date_override', nargs='?',
-      help='Optional override for the build date. Format must be '
-           '\'Mmm DD YYYY HH:MM:SS\'')
   args = argument_parser.parse_args()
 
-  if args.build_date_override:
-    # Format is expected to be "Mmm DD YYYY HH:MM:SS".
-    build_date = args.build_date_override
-  else:
-    now = datetime.datetime.utcnow()
-    if now.hour < 5:
-      # The time is locked at 5:00 am in UTC to cause the build cache
-      # invalidation to not happen exactly at midnight. Use the same calculation
-      # as the day before.
-      # See //base/build_time.cc.
-      now = now - datetime.timedelta(days=1)
-    now = datetime.datetime(now.year, now.month, now.day, 5, 0, 0)
-    build_date = GetBuildDate(args.build_type, now)
+  now = datetime.datetime.utcnow()
+  if now.hour < 5:
+    # The time is locked at 5:00 am in UTC to cause the build cache
+    # invalidation to not happen exactly at midnight. Use the same calculation
+    # as the day before.
+    # See //base/build_time.cc.
+    now = now - datetime.timedelta(days=1)
+  now = datetime.datetime(now.year, now.month, now.day, 5, 0, 0)
+  build_date = GetBuildDate(args.build_type, now)
 
   output = ('// Generated by //build/write_build_date_header.py\n'
            '#ifndef BUILD_DATE\n'
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index e3a4a2a2..5a45af5 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -287,7 +287,6 @@
     return false;
 
   mutator_->Mutate(std::move(state));
-
   return true;
 }
 
@@ -303,33 +302,54 @@
   return true;
 }
 
+void TickAnimationsIf(AnimationHost::AnimationsList animations,
+                      base::TimeTicks monotonic_time,
+                      bool (*predicate)(const Animation&)) {
+  for (auto& it : animations) {
+    if (predicate(*it))
+      it->Tick(monotonic_time);
+  }
+}
+
 bool AnimationHost::TickAnimations(base::TimeTicks monotonic_time,
                                    const ScrollTree& scroll_tree,
                                    bool is_active_tree) {
   TRACE_EVENT0("cc", "AnimationHost::TickAnimations");
-  // Notes on ordering between a) ticking mutator b) ticking animations: Since
-  // (a) is currently synchronous by doing a, b in that order we ensure worklet
-  // animation output state is up-to-date before having that output actually
-  // take effect in (b). This gaurantees worklet animation output takes effect
+  // We tick animations in the following order:
+  // 1. regular animations 2. mutator 3. worklet animations
+  //
+  // Mutator may depend on scroll offset as its time input e.g., when there is
+  // a worklet animation attached to a scroll timeline.
+  // This ordering ensures we use the latest scroll offset as the input to the
+  // mutator even if there are active scroll animations. Furthermore ticking
+  // worklet animations at the end gaurantees that mutator output takes effect
   // in the same impl frame that it was mutated.
-  // TODO(crbug.com/834452): We will need a different approach when we make (a)
-  // asynchronous but until then this simple ordering is sufficient.
 
   if (!NeedsTickAnimations())
     return false;
 
   TRACE_EVENT_INSTANT0("cc", "NeedsTickAnimations", TRACE_EVENT_SCOPE_THREAD);
 
+  // Worklet animations are ticked at a later stage. See above comment for
+  // details.
+  TickAnimationsIf(ticking_animations_, monotonic_time,
+                   [](const Animation& animation) {
+                     return !animation.IsWorkletAnimation();
+                   });
+
   // TODO(majidvp): At the moment we call this for both active and pending
   // trees similar to other animations. However our final goal is to only call
   // it once, ideally after activation, and only when the input
   // to an active timeline has changed. http://crbug.com/767210
   TickMutator(monotonic_time, scroll_tree, is_active_tree);
 
-  AnimationsList ticking_animations_copy = ticking_animations_;
-  for (auto& it : ticking_animations_copy)
-    it->Tick(monotonic_time);
-
+  // TODO(crbug.com/834452): This works because at the moment the above call is
+  // synchronous. We will need a different approach once it becomes asynchronous
+  // but until then this simple ordering is sufficient.
+  TickAnimationsIf(ticking_animations_, monotonic_time,
+                   [](const Animation& animation) {
+                     return animation.IsWorkletAnimation();
+                   });
   return true;
 }
 
diff --git a/cc/animation/animation_host_unittest.cc b/cc/animation/animation_host_unittest.cc
index 4e635b9..680f7c8a 100644
--- a/cc/animation/animation_host_unittest.cc
+++ b/cc/animation/animation_host_unittest.cc
@@ -12,6 +12,7 @@
 #include "cc/test/animation_test_common.h"
 #include "cc/test/animation_timelines_test_common.h"
 #include "cc/test/mock_layer_tree_mutator.h"
+#include "cc/trees/scroll_node.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -238,5 +239,95 @@
   TickAnimationsTransferEvents(time, 0u);
 }
 
+class MockAnimation : public Animation {
+ public:
+  explicit MockAnimation(int id) : Animation(id) {}
+  MOCK_METHOD1(Tick, void(base::TimeTicks monotonic_time));
+
+ private:
+  ~MockAnimation() {}
+};
+
+bool Animation1TimeEquals20(MutatorInputState* input) {
+  std::unique_ptr<AnimationWorkletInput> in = input->TakeWorkletState(333);
+  return in && in->added_and_updated_animations.size() == 1 &&
+         in->added_and_updated_animations[0]
+                 .worklet_animation_id.animation_id == 22 &&
+         in->added_and_updated_animations[0].current_time == 20;
+}
+
+void CreateScrollingNodeForElement(ElementId element_id,
+                                   PropertyTrees* property_trees) {
+  ScrollNode node;
+  node.scrollable = true;
+  // Setup scroll dimention to be 100x100.
+  node.bounds = gfx::Size(200, 200);
+  node.container_bounds = gfx::Size(100, 100);
+
+  int node_id = property_trees->scroll_tree.Insert(node, 0);
+  property_trees->element_id_to_scroll_node_index[element_id] = node_id;
+}
+
+TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) {
+  ElementId element_id = element_id_;
+  int animation_id1 = 11;
+  int animation_id2 = 12;
+  WorkletAnimationId worklet_animation_id{333, 22};
+
+  client_.RegisterElement(element_id, ElementListType::ACTIVE);
+  client_impl_.RegisterElement(element_id, ElementListType::PENDING);
+  client_impl_.RegisterElement(element_id, ElementListType::ACTIVE);
+  host_impl_->AddAnimationTimeline(timeline_);
+
+  PropertyTrees property_trees;
+  property_trees.is_main_thread = false;
+  CreateScrollingNodeForElement(element_id, &property_trees);
+
+  ScrollTree& scroll_tree = property_trees.scroll_tree;
+  // Set an initial scroll value.
+  scroll_tree.SetScrollOffsetDeltaForTesting(element_id,
+                                             gfx::Vector2dF(10, 10));
+
+  scoped_refptr<MockAnimation> mock_scroll_animation(
+      new MockAnimation(animation_id1));
+  EXPECT_CALL(*mock_scroll_animation, Tick(_))
+      .WillOnce(InvokeWithoutArgs([&]() {
+        // Scroll to 20% of the max value.
+        scroll_tree.SetScrollOffsetDeltaForTesting(element_id,
+                                                   gfx::Vector2dF(20, 20));
+      }));
+
+  // Ensure scroll animation is ticking.
+  timeline_->AttachAnimation(mock_scroll_animation);
+  host_impl_->AddToTicking(mock_scroll_animation);
+
+  // Create scroll timeline that links scroll animation and worklet animation
+  // together. Use timerange so that we have 1:1 time & scroll mapping.
+  auto scroll_timeline = std::make_unique<ScrollTimeline>(
+      element_id, ScrollTimeline::Vertical, 100);
+
+  // Create a worklet animation that is bound to the scroll timeline.
+  scoped_refptr<WorkletAnimation> worklet_animation(
+      new WorkletAnimation(animation_id2, worklet_animation_id, "test_name",
+                           std::move(scroll_timeline), nullptr, true));
+  worklet_animation->AttachElement(element_id);
+  timeline_->AttachAnimation(worklet_animation);
+
+  AddOpacityTransitionToAnimation(worklet_animation.get(), 1, .7f, .3f, true);
+
+  MockLayerTreeMutator* mock_mutator = new NiceMock<MockLayerTreeMutator>();
+  host_impl_->SetLayerTreeMutator(
+      base::WrapUnique<LayerTreeMutator>(mock_mutator));
+  ON_CALL(*mock_mutator, HasAnimators()).WillByDefault(Return(true));
+  EXPECT_CALL(*mock_mutator,
+              MutateRef(::testing::Truly(Animation1TimeEquals20)))
+      .Times(1);
+
+  // Ticking host should cause scroll animation to scroll which should also be
+  // reflected in the input of the layer tree mutator in the same animation
+  // frame.
+  host_impl_->TickAnimations(base::TimeTicks(), scroll_tree, false);
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/animation/animation_target.h b/cc/animation/animation_target.h
index 6efcc53..a815a3c 100644
--- a/cc/animation/animation_target.h
+++ b/cc/animation/animation_target.h
@@ -27,24 +27,24 @@
   virtual ~AnimationTarget() {}
   virtual void NotifyClientFloatAnimated(float opacity,
                                          int target_property_id,
-                                         KeyframeModel* keyframe_model) {}
+                                         KeyframeModel* keyframe_model) = 0;
   virtual void NotifyClientFilterAnimated(const FilterOperations& filter,
                                           int target_property_id,
-                                          KeyframeModel* keyframe_model) {}
+                                          KeyframeModel* keyframe_model) = 0;
   virtual void NotifyClientSizeAnimated(const gfx::SizeF& size,
                                         int target_property_id,
-                                        KeyframeModel* keyframe_model) {}
+                                        KeyframeModel* keyframe_model) = 0;
   virtual void NotifyClientColorAnimated(SkColor color,
                                          int target_property_id,
-                                         KeyframeModel* keyframe_model) {}
+                                         KeyframeModel* keyframe_model) = 0;
   virtual void NotifyClientTransformOperationsAnimated(
       const TransformOperations& operations,
       int target_property_id,
-      KeyframeModel* keyframe_model) {}
+      KeyframeModel* keyframe_model) = 0;
   virtual void NotifyClientScrollOffsetAnimated(
       const gfx::ScrollOffset& scroll_offset,
       int target_property_id,
-      KeyframeModel* keyframe_model) {}
+      KeyframeModel* keyframe_model) = 0;
 };
 
 }  // namespace cc
diff --git a/cc/animation/element_animations.h b/cc/animation/element_animations.h
index 84c506d..7ffb331f 100644
--- a/cc/animation/element_animations.h
+++ b/cc/animation/element_animations.h
@@ -148,6 +148,12 @@
   void NotifyClientFilterAnimated(const FilterOperations& filter,
                                   int target_property_id,
                                   KeyframeModel* keyframe_model) override;
+  void NotifyClientSizeAnimated(const gfx::SizeF& size,
+                                int target_property_id,
+                                KeyframeModel* keyframe_model) override{};
+  void NotifyClientColorAnimated(SkColor color,
+                                 int target_property_id,
+                                 KeyframeModel* keyframe_model) override{};
   void NotifyClientTransformOperationsAnimated(
       const TransformOperations& operations,
       int target_property_id,
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 3da956f..26c8cf7 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -5292,9 +5292,6 @@
       property_trees->element_id_to_scroll_node_index[element_id];
   property_trees->scroll_tree.OnScrollOffsetAnimated(
       element_id, scroll_node_index, scroll_offset, tree);
-  // Run animations which need to respond to updated scroll offset.
-  mutator_host_->TickScrollAnimations(CurrentBeginFrameArgs().frame_time,
-                                      property_trees->scroll_tree);
 }
 
 void LayerTreeHostImpl::SetNeedUpdateGpuRasterizationStatus() {
diff --git a/cc/trees/layer_tree_host_pixeltest_blending.cc b/cc/trees/layer_tree_host_pixeltest_blending.cc
index ba7224c..2682c2a2 100644
--- a/cc/trees/layer_tree_host_pixeltest_blending.cc
+++ b/cc/trees/layer_tree_host_pixeltest_blending.cc
@@ -4,6 +4,7 @@
 
 #include <stdint.h>
 
+#include "build/build_config.h"
 #include "cc/layers/picture_image_layer.h"
 #include "cc/layers/solid_color_layer.h"
 #include "cc/paint/paint_image.h"
@@ -425,8 +426,16 @@
                             kUseMasks | kUseColorMatrix);
 }
 
+// Often times out on Windows 10. See: https://crbug.com/870236.
+#if defined(OS_WIN)
+#define MAYBE_BlendingWithRenderPassWithMaskColorMatrixAA_GL \
+  DISABLED_BlendingWithRenderPassWithMaskColorMatrixAA_GL
+#else
+#define MAYBE_BlendingWithRenderPassWithMaskColorMatrixAA_GL \
+  BlendingWithRenderPassWithMaskColorMatrixAA_GL
+#endif
 TEST_F(LayerTreeHostBlendingPixelTest,
-       BlendingWithRenderPassWithMaskColorMatrixAA_GL) {
+       MAYBE_BlendingWithRenderPassWithMaskColorMatrixAA_GL) {
   RunBlendingWithRenderPass(ZERO_COPY,
                             FILE_PATH_LITERAL("blending_render_pass_mask.png"),
                             kUseMasks | kUseAntialiasing | kUseColorMatrix);
@@ -507,8 +516,16 @@
                             kUseMasks | kUseColorMatrix | kForceShaders);
 }
 
+// Often times out on Windows 10. See: https://crbug.com/870236.
+#if defined(OS_WIN)
+#define MAYBE_BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL \
+  DISABLED_BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL
+#else
+#define MAYBE_BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL \
+  BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL
+#endif
 TEST_F(LayerTreeHostBlendingPixelTest,
-       BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL) {
+       MAYBE_BlendingWithRenderPassShadersWithMaskColorMatrixAA_GL) {
   RunBlendingWithRenderPass(
       ZERO_COPY, FILE_PATH_LITERAL("blending_render_pass_mask.png"),
       kUseMasks | kUseAntialiasing | kUseColorMatrix | kForceShaders);
diff --git a/chrome/VERSION b/chrome/VERSION
index c8e0987a..76141e31 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=70
 MINOR=0
-BUILD=3511
+BUILD=3512
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 92c0da42..026e21a 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -389,6 +389,7 @@
     "//chrome/browser/notifications/notification_channels_provider_android.h",
     "//chrome/browser/notifications/notification_platform_bridge_android.cc",
     "//chrome/browser/ntp_snippets/ntp_snippets_metrics.h",
+    "//chrome/browser/password_manager/password_accessory_metrics_util.h",
     "//chrome/browser/password_manager/password_accessory_view_interface.h",
     "//chrome/browser/profiles/profile_metrics.h",
     "//chrome/browser/translate/android/translate_utils.h",
@@ -1153,24 +1154,16 @@
   }
 }
 
-# Contains rules common to chrome_public_apk and chrome_sync_shell_apk
-template("chrome_public_apk_tmpl_shared") {
-  chrome_public_apk_tmpl(target_name) {
-    forward_variables_from(invoker, "*")
-    if (!defined(deps)) {
-      deps = []
-    }
-
-    deps += [
-      ":app_hooks_java",
-      ":chrome_java",
-      ":chrome_public_apk_resources",
-      ":chrome_public_non_pak_assets",
-      ":chrome_public_pak_assets",
-      "//base:base_java",
-    ]
-  }
-}
+# Dependencies that are common to any chrome_public derivative targets, as well
+# as to chrome_sync_shell_apk.
+_chrome_public_and_sync_shell_shared_deps = [
+  ":app_hooks_java",
+  ":chrome_java",
+  ":chrome_public_apk_resources",
+  ":chrome_public_non_pak_assets",
+  ":chrome_public_pak_assets",
+  "//base:base_java",
+]
 
 generate_jni("test_support_jni_headers") {
   sources = [
@@ -1205,40 +1198,99 @@
 _add_unwind_tables_in_chrome_public_apk =
     can_unwind_with_cfi_table && is_official_build
 
+# Defines a target that derives from the chrome public application. This
+# can be either an APK or an app bundle module. This supports both the
+# chrome_public_xxx targets (for Android J-K) and chrome_modern_public_xxx
+# ones (for Android L-M). For Android N+, see instead
+# monochrome_public_apk_or_module_tmpl() below.
+#
+# Variables:
+#  target_type: Determines the final target type. Should be one of
+#    'android_apk', or 'android_app_bundle_module'.
+#  apk_name: For 'android_apk' target types, name of the final APK without
+#    an .apk suffix (e.g. 'ChromePublic').
+#  module_name: For 'android_app_bundle_module' target types, name of the
+#    module (e.g. 'ChromePublicBase').
+#  is_base_module: For 'android_app_bundle_module' target types only,
+#     set to true to indicate that this is a base application module
+#     (instead of a feature module).
+#  is_modern: Optional. Set to true to indicate that this is a Chrome-modern
+#    target, intended for Android L and M only.
 template("chrome_public_apk_or_module_tmpl") {
-  chrome_public_apk_tmpl_shared(target_name) {
+  _is_modern = defined(invoker.is_modern) && invoker.is_modern
+  chrome_public_common_apk_or_module_tmpl(target_name) {
     forward_variables_from(invoker,
                            [
                              "apk_name",
-                             "version_name",
                              "is_base_module",
+                             "is_modern",
                              "module_name",
                              "target_type",
                            ])
 
-    android_manifest = chrome_public_android_manifest
-    android_manifest_dep = ":chrome_public_android_manifest"
+    deps = _chrome_public_and_sync_shell_shared_deps
+
+    if (_is_modern) {
+      android_manifest = chrome_modern_public_android_manifest
+      android_manifest_dep = ":chrome_modern_public_android_manifest"
+    } else {
+      android_manifest = chrome_public_android_manifest
+      android_manifest_dep = ":chrome_public_android_manifest"
+    }
+
     shared_libraries = [ ":libchrome" ]
     add_unwind_tables_in_apk = _add_unwind_tables_in_chrome_public_apk
     if (_add_unwind_tables_in_chrome_public_apk) {
       shared_library_for_unwind_asset = "chrome"
     }
+
+    # Android supports webp transparent resources properly since API level 18,
+    # so this can only be activated for modern ones (which target API >= 21).
+    # TODO(digit): Turn this on for all builds once JellyBean support is
+    # dropped in the future.
+    png_to_webp = _is_modern && !is_java_debug
+
+    # Native libraries can be loaded directly from the APK using the
+    # Chromium linker. However, we disable this for J-K due to an OEM-specific
+    # platform bug, where overzealous SELinux settings prevent mapping some apk
+    # file segments with PROT_EXEC (see http://crbug.com/398425). This was
+    # fixed for Android L by adding proper CTS tests.
+    load_library_from_apk = _is_modern && chromium_linker_supported
+
+    version_name = chrome_version_name
   }
 }
 
+# The chrome_public and chrome_modern_public APKs and bundle modules
+
 chrome_public_apk_or_module_tmpl("chrome_public_apk") {
-  version_name = chrome_version_name
-  apk_name = "ChromePublic"
   target_type = "android_apk"
+  apk_name = "ChromePublic"
+}
+
+chrome_public_apk_or_module_tmpl("chrome_modern_public_apk") {
+  target_type = "android_apk"
+  apk_name = "ChromeModernPublic"
+  is_modern = true
 }
 
 chrome_public_apk_or_module_tmpl("chrome_public_base_module") {
-  module_name = "ChromePublicBase"
   target_type = "android_app_bundle_module"
   is_base_module = true
+  module_name = "ChromePublicBase"
 }
 
-chrome_public_apk_tmpl_shared("chrome_public_apk_for_test") {
+chrome_public_apk_or_module_tmpl("chrome_modern_public_base_module") {
+  target_type = "android_app_bundle_module"
+  module_name = "ChromeModernPublicBase"
+  is_base_module = true
+  is_modern = true
+}
+
+# Defines the 'chrome_public_apk_for_test' target, which corresponds to APK
+# built to run instrumentation tests. This includes additional code that is
+# not part of the regular 'chrome_public_apk' target.
+chrome_public_common_apk_or_module_tmpl("chrome_public_apk_for_test") {
   testonly = true
   target_type = "android_apk"
   android_manifest = chrome_public_android_manifest
@@ -1249,58 +1301,18 @@
   if (_add_unwind_tables_in_chrome_public_apk) {
     shared_library_for_unwind_asset = "chromefortest"
   }
-  deps = [
-    ":browser_java_test_support",
-    "//chrome/browser/android/metrics:ukm_utils_java",
-    "//components/heap_profiling:heap_profiling_java_test_support",
-    "//content/public/test/android:content_java_test_support",
-    "//third_party/android_tools:android_test_mock_java",
-  ]
+  deps = _chrome_public_and_sync_shell_shared_deps + [
+           ":browser_java_test_support",
+           "//chrome/browser/android/metrics:ukm_utils_java",
+           "//components/heap_profiling:heap_profiling_java_test_support",
+           "//content/public/test/android:content_java_test_support",
+           "//third_party/android_tools:android_test_mock_java",
+         ]
   if (!is_java_debug) {
     proguard_configs = [ "//chrome/android/java/apk_for_test.flags" ]
   }
 }
 
-template("chrome_modern_public_apk_or_module_tmpl") {
-  chrome_public_apk_tmpl_shared(target_name) {
-    forward_variables_from(invoker,
-                           [
-                             "apk_name",
-                             "version_name",
-                             "is_base_module",
-                             "module_name",
-                             "target_type",
-                           ])
-
-    android_manifest = chrome_modern_public_android_manifest
-    android_manifest_dep = ":chrome_modern_public_android_manifest"
-    shared_libraries = [ ":libchrome" ]
-    add_unwind_tables_in_apk = _add_unwind_tables_in_chrome_public_apk
-    if (_add_unwind_tables_in_chrome_public_apk) {
-      shared_library_for_unwind_asset = "chrome"
-    }
-
-    if (!is_java_debug) {
-      png_to_webp = true
-    }
-
-    # Always enable load_library_from_apk.
-    load_library_from_apk = chromium_linker_supported
-  }
-}
-
-chrome_modern_public_apk_or_module_tmpl("chrome_modern_public_apk") {
-  version_name = chrome_version_name
-  apk_name = "ChromeModernPublic"
-  target_type = "android_apk"
-}
-
-chrome_modern_public_apk_or_module_tmpl("chrome_modern_public_base_module") {
-  module_name = "ChromeModernPublicBase"
-  target_type = "android_app_bundle_module"
-  is_base_module = true
-}
-
 android_library("monochrome_java") {
   deps = [
     "//android_webview/glue",
@@ -1313,12 +1325,25 @@
       [ "java/src/org/chromium/chrome/browser/MonochromeApplication.java" ]
 }
 
+# Defines a target that derives from the monochrome public application. This
+# can be either an APK or an app bundle module. Note that these only work
+# on Android N+ devices, see chrome_public_apk_or_module_tmpl() for a template
+# that supports generating targets for older Android releases.
+#
+# Variables:
+#   target_type: Either 'android_apk' or 'android_app_bundle_module'.
+#   apk_name: For APK target types, the final APK name without an .apk
+#     suffix (e.g. "MonochromePublic").
+#   module_name: For module target types, the module's name without a
+#     suffix (e.g. "MonochromePublicBase")
+#   is_base_module: For module target types, a boolean indicating whether
+#     this is a base bundle module (instead of a feature one).
+#
 template("monochrome_public_apk_or_module_tmpl") {
-  monochrome_public_apk_tmpl(target_name) {
+  monochrome_public_common_apk_or_module_tmpl(target_name) {
     forward_variables_from(invoker,
                            [
                              "apk_name",
-                             "version_name",
                              "is_base_module",
                              "module_name",
                              "proguard_jar_path",
@@ -1346,11 +1371,12 @@
     if (_add_unwind_tables_in_chrome_public_apk && can_unwind_with_cfi_table) {
       shared_library_for_unwind_asset = "monochrome"
     }
+
+    version_name = chrome_version_name
   }
 }
 
 monochrome_public_apk_or_module_tmpl("monochrome_public_apk") {
-  version_name = chrome_version_name
   apk_name = "MonochromePublic"
   target_type = "android_apk"
   if (!is_java_debug) {
@@ -1365,7 +1391,7 @@
   is_base_module = true
 }
 
-chrome_public_apk_tmpl_shared("chrome_sync_shell_apk") {
+chrome_public_common_apk_or_module_tmpl("chrome_sync_shell_apk") {
   testonly = true
   target_type = "android_apk"
   android_manifest = chrome_sync_shell_android_manifest
@@ -1376,15 +1402,16 @@
   # This exists here rather than in chrome_sync_shell_test_apk for JNI
   # registration to be able to find the native side functions.
   java_files = [ "sync_shell/javatests/src/org/chromium/chrome/browser/sync/FakeServerHelper.java" ]
-  deps = [
-    ":chrome_sync_shell_apk_template_resources",
+  deps =
+      _chrome_public_and_sync_shell_shared_deps + [
+        ":chrome_sync_shell_apk_template_resources",
 
-    # This exists here because com.google.protobuf.nano is needed in tests,
-    # but that code is stripped out via proguard. Adding this deps adds
-    # usages and prevents removal of the proto code.
-    "//components/sync:test_support_proto_java",
-    "//third_party/android_protobuf:protobuf_nano_javalib",
-  ]
+        # This exists here because com.google.protobuf.nano is needed in tests,
+        # but that code is stripped out via proguard. Adding this deps adds
+        # usages and prevents removal of the proto code.
+        "//components/sync:test_support_proto_java",
+        "//third_party/android_protobuf:protobuf_nano_javalib",
+      ]
 }
 
 chrome_public_test_apk_manifest =
@@ -1611,25 +1638,28 @@
 
 # Generate application bundles if possible.
 android_app_bundle("chrome_public_bundle") {
+  bundle_name = "ChromePublicBundle"
   base_module_target = ":chrome_public_base_module"
+  proguard_enabled = !is_java_debug
 
   # Signing is very slow, only do that for official builds.
   sign_bundle = is_official_build
 
   enable_language_splits = true
-  synchronized_proguard_enabled = !is_java_debug
 }
 
 android_app_bundle("chrome_modern_public_bundle") {
+  bundle_name = "ChromeModernPublicBundle"
   base_module_target = ":chrome_modern_public_base_module"
-  synchronized_proguard_enabled = !is_java_debug
+  proguard_enabled = !is_java_debug
 }
 
 android_app_bundle("monochrome_public_bundle") {
+  bundle_name = "MonochromePublicBundle"
   base_module_target = ":monochrome_public_base_module"
-  synchronized_proguard_enabled = !is_java_debug
   if (!is_java_debug) {
     # TODO(crbug.com/868770): Address proguard version skew.
+    proguard_enabled = true
     proguard_jar_path = "//third_party/proguard/lib/proguard.6.0.3.jar"
   }
 }
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index 7af1b221..44ae5f0 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -25,7 +25,43 @@
   "enable_vr=$enable_vr",
 ]
 
-template("chrome_public_apk_tmpl") {
+# A template used to declare any target that will implement a full Chromium
+# or Chrome application, either as an APK, or an app bundle module.
+#
+# Variables:
+#   target_type: Either 'android_apk' or 'android_app_bundle_module'.
+#   apk_name: For APK target types, the final APK name without a suffix.
+#   module_name: For bundle module target types, the module's name without a
+#     suffix.
+#   is_base_module: For bundle module target types, true iff this is a base
+#     application module, instead of a feature module.
+#   android_manifest: Application manifest path.
+#   android_manifest_dep: Name of target generating the android_manifest.
+#   shared_libraries: List of native shared libraries targets to include in
+#     the final target (e.g. [ ":libchrome" ]).
+#   add_unwind_tables_in_apk: Optional. If true, add the unwind tables to the
+#     final APK or bundle.
+#   is_modern: If true, indicates this corresponds to a chrome_modern_XXX
+#     target that can only run on Android L-M.
+#   png_to_webp: Optional. If true, convert image resources to webp format.
+#     requires Android K+, since these were not supported by Android properly
+#     before 4.3.0.
+#   load_library_from_apk: Optional. If true, native libraries will be loaded
+#     directly from the APK (and stored zipaligned and uncompressed). This
+#     requires either the Chromium linker, or Android M+.
+#   version_name: Application version name (e.g. "Developer Build").
+#   is_modern: Optional. true iff this is a chrome_modern derived build.
+#
+#   Plus all other variables accepted by android_apk() or
+#   android_app_bundle_module(), depending on the target type.
+#
+template("chrome_public_common_apk_or_module_tmpl") {
+  assert(defined(invoker.target_type), "target_type is required!")
+  assert(
+      invoker.target_type == "android_apk" ||
+          invoker.target_type == "android_app_bundle_module",
+      "Invalid target_type definition, should be 'android_apk' or 'android_app_bundle_module'")
+
   # Adds unwind table asset to the chrome apk for the given library target. This
   # is not part of generic apk assets target since it depends on the main shared
   # library of the apk, to extract unwind tables.
@@ -47,6 +83,10 @@
   } else {
     _target_type = invoker.target_type
   }
+
+  _is_modern = defined(invoker.is_modern) && invoker.is_modern
+  assert(_is_modern || !_is_modern)  # Mark as used.
+
   target(_target_type, target_name) {
     forward_variables_from(invoker, "*")
     exclude_xxxhdpi = true
@@ -133,8 +173,10 @@
   }
 }
 
-template("monochrome_public_apk_tmpl") {
-  chrome_public_apk_tmpl(target_name) {
+# The equivalent of chrome_common_apk_or_module_tmpl for all builds of
+# monochrome.
+template("monochrome_public_common_apk_or_module_tmpl") {
+  chrome_public_common_apk_or_module_tmpl(target_name) {
     # Always build 64-bit //android_webview:monochrome because Chrome runs
     # in 32-bit mode.
     if (android_64bit_target_cpu) {
@@ -225,3 +267,21 @@
     }
   }
 }
+
+# These empty templates are still being called from the clank/ BUILD.gn
+# scripts. Remove them when they have been fixed to call the
+# xxx_common_apk_or_module_tmpl templates above.
+
+template("chrome_public_apk_tmpl") {
+  chrome_public_common_apk_or_module_tmpl(target_name) {
+    forward_variables_from(invoker, "*")
+    target_type = "android_apk"
+  }
+}
+
+template("monochrome_public_apk_tmpl") {
+  monochrome_public_common_apk_or_module_tmpl(target_name) {
+    forward_variables_from(invoker, "*")
+    target_type = "android_apk"
+  }
+}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedBasicLogging.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedBasicLogging.java
new file mode 100644
index 0000000..ef2d07d
--- /dev/null
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedBasicLogging.java
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.feed;
+
+import com.google.android.libraries.feed.host.logging.BasicLoggingApi;
+import com.google.android.libraries.feed.host.logging.ContentLoggingData;
+
+/**
+ * Implementation of {@link BasicLoggingApi} that log actions performed on the Feed.
+ */
+public class FeedBasicLogging implements BasicLoggingApi {
+    // TODO(gangwu): implement BasicLoggingApi functionality.
+    @Override
+    public void onContentViewed(ContentLoggingData data) {}
+    @Override
+    public void onContentDismissed(ContentLoggingData data) {}
+    @Override
+    public void onContentClicked(ContentLoggingData data) {}
+    @Override
+    public void onContentContextMenuOpened(ContentLoggingData data) {}
+    @Override
+    public void onMoreButtonViewed(int position) {}
+    @Override
+    public void onMoreButtonClicked(int position) {}
+    @Override
+    public void onOpenedWithContent(int timeToPopulateMs, int contentCount) {}
+    @Override
+    public void onOpenedWithNoImmediateContent() {}
+    @Override
+    public void onOpenedWithNoContent() {}
+}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
index 886eef2..8afcc4c 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNewTabPage.java
@@ -17,8 +17,6 @@
 import com.google.android.libraries.feed.api.scope.FeedStreamScope;
 import com.google.android.libraries.feed.api.stream.Stream;
 import com.google.android.libraries.feed.host.action.ActionApi;
-import com.google.android.libraries.feed.host.logging.BasicLoggingApi;
-import com.google.android.libraries.feed.host.logging.ContentLoggingData;
 import com.google.android.libraries.feed.host.stream.CardConfiguration;
 import com.google.android.libraries.feed.host.stream.SnackbarApi;
 import com.google.android.libraries.feed.host.stream.StreamConfiguration;
@@ -58,28 +56,6 @@
     private SectionHeaderView mSectionHeaderView;
     private FeedImageLoader mImageLoader;
 
-    private static class DummyBasicLoggingApi implements BasicLoggingApi {
-        // TODO(gangwu): implement implement BasicLoggingApi functionality.
-        @Override
-        public void onContentViewed(ContentLoggingData data) {}
-        @Override
-        public void onContentDismissed(ContentLoggingData data) {}
-        @Override
-        public void onContentClicked(ContentLoggingData data) {}
-        @Override
-        public void onContentContextMenuOpened(ContentLoggingData data) {}
-        @Override
-        public void onMoreButtonViewed(int position) {}
-        @Override
-        public void onMoreButtonClicked(int position) {}
-        @Override
-        public void onOpenedWithContent(int timeToPopulateMs, int contentCount) {}
-        @Override
-        public void onOpenedWithNoImmediateContent() {}
-        @Override
-        public void onOpenedWithNoContent() {}
-    }
-
     private class BasicSnackbarApi implements SnackbarApi {
         @Override
         public void show(String message) {
@@ -186,7 +162,7 @@
                         .createFeedStreamScopeBuilder(activity, mImageLoader, actionApi,
                                 new BasicStreamConfiguration(activity.getResources()),
                                 new BasicCardConfiguration(activity.getResources()),
-                                new BasicSnackbarApi(), new DummyBasicLoggingApi())
+                                new BasicSnackbarApi(), new FeedBasicLogging())
                         .build();
 
         mStream = streamScope.getStream();
diff --git a/chrome/android/feed/feed_java_sources.gni b/chrome/android/feed/feed_java_sources.gni
index ebe41f4..3ed1ef5a 100644
--- a/chrome/android/feed/feed_java_sources.gni
+++ b/chrome/android/feed/feed_java_sources.gni
@@ -9,6 +9,7 @@
   feed_deps = [ "//third_party/feed:feed_lib_java" ]
 
   feed_java_sources = [
+    "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedBasicLogging.java",
     "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedContentStorage.java",
     "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedEventReporter.java",
     "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java",
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 99d219d..97d0312 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -193,6 +193,17 @@
             android:taskAffinity=""
             android:excludeFromRecents="true"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density">
+            <!-- TODO(mthiesse, b/72214458): This is a duplication of the icon metadata below.
+                 Daydream will actually ignore the metadata here, and use the metadata on the
+                 activity-alias. However, play store apk validation fails to find the icons on the
+                 alias, so we need to include them here to pass validation. -->
+            {% if enable_vr == "true" %}
+            <meta-data android:name="com.google.android.vr.icon"
+                android:resource="@drawable/daydream_icon_foreground" />
+            <meta-data android:name="com.google.android.vr.icon_background"
+                android:resource="@drawable/daydream_icon_background" />
+            {% endif %}
+            {{ self.supports_vr() }}
         </activity>
         <activity-alias android:name="com.google.android.apps.chrome.IntentDispatcher"
             android:targetActivity="org.chromium.chrome.browser.document.ChromeLauncherActivity"
@@ -200,6 +211,9 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
+                {% if enable_vr == "true" %}
+                <category android:name="com.google.intent.category.DAYDREAM" />
+                {% endif %}
             </intent-filter>
             <!-- Matches the common case of intents with no MIME type.
                  Make sure to keep in sync with the next filter.  -->
@@ -208,6 +222,9 @@
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
+                {% if enable_vr == "true" %}
+                <category android:name="com.google.intent.category.DAYDREAM" />
+                {% endif %}
                 {% if channel in ['stable', 'default'] %}<data android:scheme="googlechrome" />{% endif %}
                 <data android:scheme="http" />
                 <data android:scheme="https" />
@@ -218,13 +235,11 @@
             <!-- Same filter as above but with MIME types.  Intents that
                  do not specify a MIME type won't match. -->
             <intent-filter>
-                {% block common_view_intent_shared_filter_with_mime_body %}
                 {{ self.common_view_intent_shared_filter_body() }}
                 <data android:scheme="content" />
                 <data android:mimeType="text/html"/>
                 <data android:mimeType="text/plain"/>
                 <data android:mimeType="application/xhtml+xml"/>
-                {% endblock %}
             </intent-filter>
             <!-- MHTML support, used for snapshots -->
             <intent-filter>
@@ -246,6 +261,9 @@
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
+                {% if enable_vr == "true" %}
+                <category android:name="com.google.intent.category.DAYDREAM" />
+                {% endif %}
                 <data android:scheme="file"/>
                 <data android:scheme="content"/>
                 <data android:host="*" />
@@ -256,10 +274,8 @@
             <!-- Same filter as above but with mimeType="*/*". Used for
                  handling intent send by ShareIt. -->
             <intent-filter>
-                {% block mhtml_view_intent_shared_filter_with_mime_body %}
                 {{ self.mhtml_view_intent_shared_filter_body() }}
                 <data android:mimeType="*/*"/>
-                {% endblock %}
             </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_SEARCH" />
@@ -283,6 +299,12 @@
             </intent-filter>
             <meta-data android:name="android.app.searchable"
                 android:resource="@xml/searchable" />
+            {% if enable_vr == "true" %}
+            <meta-data android:name="com.google.android.vr.icon"
+                android:resource="@drawable/daydream_icon_foreground" />
+            <meta-data android:name="com.google.android.vr.icon_background"
+                android:resource="@drawable/daydream_icon_background" />
+            {% endif %}
         </activity-alias>
 
         <activity android:name="org.chromium.chrome.browser.media.MediaLauncherActivity"
@@ -391,71 +413,6 @@
             {{ self.extra_web_rendering_activity_definitions() }}
         </activity>
         {% endfor %}
-        {% if enable_vr == "true" %}
-        <!-- Needs to be exported for Daydream deep-links. Also note that we explicitly don't set
-             VR mode here as it actually doesn't matter for VR launches given that we set it
-             within the timeout in onCreate, and it makes launches from 2D much smoother. -->
-        <activity android:name="org.chromium.chrome.browser.vr.VrMainActivity"
-            android:theme="@style/VrSupportThemeLauncher"
-            android:exported="true"
-            android:taskAffinity=""
-            android:relinquishTaskIdentity="true"
-            android:excludeFromRecents="true"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density">
-            <!-- TODO(mthiesse, b/72214458): This is a duplication of the icon metadata below.
-                 Daydream will actually ignore the metadata here, and use the metadata on the
-                 activity-alias. However, play store apk validation fails to find the icons on the
-                 alias, so we need to include them here to pass validation. -->
-            <meta-data android:name="com.google.android.vr.icon"
-                android:resource="@drawable/daydream_icon_foreground" />
-            <meta-data android:name="com.google.android.vr.icon_background"
-                android:resource="@drawable/daydream_icon_background" />
-            {{ self.supports_vr() }}
-        </activity>
-
-        <!-- The VrIntentDispatcher handles many of the same intents as the non-vr
-             IntentDispatcher, and should be kept in sync where applicable. Intents that should
-             not be handled in VR include all intents that don't launch ChromeTabbedActivity.
-             Custom Tabs are a complicated special case, which are kind of supported. -->
-        <activity-alias android:name="com.google.android.apps.chrome.VrIntentDispatcher"
-            android:targetActivity="org.chromium.chrome.browser.vr.VrMainActivity"
-            android:enabled="true"
-            android:exported="true">
-            <meta-data android:name="com.google.android.vr.icon"
-                android:resource="@drawable/daydream_icon_foreground" />
-            <meta-data android:name="com.google.android.vr.icon_background"
-                android:resource="@drawable/daydream_icon_background" />
-
-            <!-- The Intent filters must all have lower priority than the non-VR intent filters
-                 so that intents without the Daydream flag aren't erroneously routed to the
-                 VrIntentDispatcher. -->
-            <intent-filter android:priority="-1">
-                <!-- Show icon in Daydream app launcher. -->
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="com.google.intent.category.DAYDREAM" />
-            </intent-filter>
-
-            <!-- Intent filters below are copied from IntentDispatcher, but with the added
-                 Daydream flag. -->
-
-            <intent-filter android:priority="-1">
-                <category android:name="com.google.intent.category.DAYDREAM" />
-                {{ self.common_view_intent_shared_filter_body() }}
-            </intent-filter>
-            <intent-filter android:priority="-1">
-                <category android:name="com.google.intent.category.DAYDREAM" />
-                {{ self.common_view_intent_shared_filter_with_mime_body() }}
-            </intent-filter>
-            <intent-filter android:priority="-1">
-                <category android:name="com.google.intent.category.DAYDREAM" />
-                {{ self.mhtml_view_intent_shared_filter_body() }}
-            </intent-filter>
-            <intent-filter android:priority="-1">
-                <category android:name="com.google.intent.category.DAYDREAM" />
-                {{ self.mhtml_view_intent_shared_filter_with_mime_body() }}
-            </intent-filter>
-        </activity-alias>
-        {% endif %}
 
         <!-- ChromeTabbedActivity related -->
         <activity android:name="org.chromium.chrome.browser.ChromeTabbedActivity"
diff --git a/chrome/android/java/res/anim/slide_in_right.xml b/chrome/android/java/res/anim/slide_in_right.xml
new file mode 100644
index 0000000..310669a
--- /dev/null
+++ b/chrome/android/java/res/anim/slide_in_right.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="50%p"
+        android:toXDelta="0"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <alpha
+        android:fromAlpha="0.0"
+        android:toAlpha="1.0"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/chrome/android/java/res/anim/slide_out_left.xml b/chrome/android/java/res/anim/slide_out_left.xml
new file mode 100644
index 0000000..5f82365
--- /dev/null
+++ b/chrome/android/java/res/anim/slide_out_left.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <translate
+        android:fromXDelta="0"
+        android:toXDelta="-50%p"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+    <alpha
+        android:fromAlpha="1.0"
+        android:toAlpha="0.0"
+        android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/chrome/android/java/res/layout/download_manager_generic_item.xml b/chrome/android/java/res/layout/download_manager_generic_item.xml
index 38ab921..ceaa410 100644
--- a/chrome/android/java/res/layout/download_manager_generic_item.xml
+++ b/chrome/android/java/res/layout/download_manager_generic_item.xml
@@ -29,6 +29,17 @@
         app:layout_gravity="center_vertical"
         app:chrometint="@color/dark_mode_tint" />
 
+    <org.chromium.chrome.browser.download.home.view.SelectionView
+        android:id="@+id/selection"
+        android:layout_width="@dimen/download_manager_generic_thumbnail_size"
+        android:layout_height="@dimen/download_manager_generic_thumbnail_size"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        app:layout_column="0"
+        app:layout_row="0"
+        app:layout_rowSpan="2"
+        app:layout_gravity="center_vertical" />
+
     <TextView
         android:id="@+id/title"
         style="@style/DownloadItemText"
diff --git a/chrome/android/java/res/layout/download_manager_image_item.xml b/chrome/android/java/res/layout/download_manager_image_item.xml
index 71900cc..0aed867 100644
--- a/chrome/android/java/res/layout/download_manager_image_item.xml
+++ b/chrome/android/java/res/layout/download_manager_image_item.xml
@@ -21,14 +21,12 @@
         android:layout_gravity="center"
         tools:ignore="ContentDescription" />
 
-    <org.chromium.chrome.browser.widget.TintedImageView
+    <org.chromium.chrome.browser.download.home.view.SelectionView
         android:id="@+id/selection"
         android:layout_width="20dp"
         android:layout_height="20dp"
         android:layout_gravity="start|top"
         android:layout_marginStart="6dp"
-        android:layout_marginTop="6dp"
-        android:visibility="gone"
-        app:chrometint="@color/dark_mode_tint"/>
+        android:layout_marginTop="6dp"/>
 
 </FrameLayout>
diff --git a/chrome/android/java/res/layout/list_selection_handle_view.xml b/chrome/android/java/res/layout/list_selection_handle_view.xml
new file mode 100644
index 0000000..3367d7e8
--- /dev/null
+++ b/chrome/android/java/res/layout/list_selection_handle_view.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 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.
+-->
+
+ <merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <org.chromium.chrome.browser.widget.TintedImageView
+        android:id="@+id/check"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="center"
+        android:background="@drawable/list_item_icon_modern_bg"
+        android:visibility="gone"
+        app:chrometint="@color/white_mode_tint"
+        app:layout_gravity="center"
+        tools:ignore="ContentDescription" />
+
+    <org.chromium.chrome.browser.widget.TintedImageView
+        android:id="@+id/circle"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:background="@null"
+        android:src="@drawable/download_circular_selector_transparent"
+        android:visibility="gone"
+        app:chrometint="@null"
+        tools:ignore="ContentDescription"/>
+
+</merge>
diff --git a/chrome/android/java/res/layout/radio_button_with_description.xml b/chrome/android/java/res/layout/radio_button_with_description.xml
index 24823f4..a325065 100644
--- a/chrome/android/java/res/layout/radio_button_with_description.xml
+++ b/chrome/android/java/res/layout/radio_button_with_description.xml
@@ -21,6 +21,7 @@
         android:textColor="#de000000"
         android:textSize="16sp" />
 
+    <!-- This TextView is hidden if it has no text, so the initial visibility should be "gone". -->
     <TextView
         android:id="@+id/description"
         android:layout_width="wrap_content"
@@ -28,5 +29,6 @@
         android:layout_alignStart="@id/title"
         android:layout_below="@id/title"
         android:textColor="#8a000000"
-        android:textSize="14sp" />
+        android:textSize="14sp"
+        android:visibility="gone"/>
 </merge>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 724bdfd..a781ccd 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -57,6 +57,7 @@
          via scrolling. -->
     <dimen name="menu_vertical_fade_distance">15dp</dimen>
     <dimen name="menu_badge_translation_y_distance">6dp</dimen>
+    <dimen name="menu_negative_vertical_offset_not_top_anchored">4dp</dimen>
 
     <!-- Menu button dragging related dimensions -->
     <dimen name="auto_scroll_full_velocity">500dp</dimen>
diff --git a/chrome/android/java/res/values/ids.xml b/chrome/android/java/res/values/ids.xml
index 10bcdcc..0f9cd9c 100644
--- a/chrome/android/java/res/values/ids.xml
+++ b/chrome/android/java/res/values/ids.xml
@@ -129,4 +129,5 @@
     <!-- Tags -->
     <item type="id" name="highlight_state" />
     <item type="id" name="item_animator" />
+    <item type="id" name="highlight_color" />
 </resources>
diff --git a/chrome/android/java/res/xml/accessibility_preferences.xml b/chrome/android/java/res/xml/accessibility_preferences.xml
index 1c4d291d..3a7f83b 100644
--- a/chrome/android/java/res/xml/accessibility_preferences.xml
+++ b/chrome/android/java/res/xml/accessibility_preferences.xml
@@ -21,4 +21,9 @@
         android:summary="@string/reader_for_accessibility_summary"
         android:title="@string/reader_for_accessibility_title" />
 
+    <org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference
+        android:key="accessibility_tab_switcher"
+        android:summary="@string/accessibility_tab_switcher_summary"
+        android:title="@string/accessibility_tab_switcher_title" />
+
 </PreferenceScreen>
diff --git a/chrome/android/java/res_vr/values-v17/vr_styles.xml b/chrome/android/java/res_vr/values-v17/vr_styles.xml
deleted file mode 100644
index dacfdb1..0000000
--- a/chrome/android/java/res_vr/values-v17/vr_styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 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. -->
-
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="VrSupportThemeLauncher" parent="@android:style/Theme.NoDisplay">
-      <item name="android:windowDisablePreview">true</item>
-      <item name="android:windowBackground">@android:color/black</item>
-    </style>
-</resources>
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 0aeef0e..ab1bd471 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -130,6 +130,7 @@
 import org.chromium.chrome.browser.tasks.TasksUma;
 import org.chromium.chrome.browser.toolbar.ToolbarButtonInProductHelpController;
 import org.chromium.chrome.browser.toolbar.ToolbarControlContainer;
+import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
@@ -263,7 +264,7 @@
 
     private Boolean mMergeTabsOnResume;
 
-    private Boolean mIsAccessibilityEnabled;
+    private Boolean mIsAccessibilityTabSwitcherEnabled;
 
     private LocaleManager mLocaleManager;
 
@@ -905,6 +906,7 @@
             }
 
             mUIInitialized = true;
+            onAccessibilityTabSwitcherModeChanged();
         } finally {
             TraceEvent.end("ChromeTabbedActivity.initializeUI");
         }
@@ -1151,14 +1153,30 @@
                 getCompositorViewHolder().onAccessibilityStatusChanged(enabled);
             }
         }
+
+        onAccessibilityTabSwitcherModeChanged();
+    }
+
+    private void onAccessibilityTabSwitcherModeChanged() {
+        if (!mUIInitialized) return;
+
+        boolean accessibilityTabSwitcherEnabled = DeviceClassManager.enableAccessibilityLayout();
         if (mLayoutManager != null && mLayoutManager.overviewVisible()
-                && mIsAccessibilityEnabled != enabled) {
+                && (mIsAccessibilityTabSwitcherEnabled == null
+                           || mIsAccessibilityTabSwitcherEnabled
+                                   != DeviceClassManager.enableAccessibilityLayout())) {
             mLayoutManager.hideOverview(false);
             if (getTabModelSelector().getCurrentModel().getCount() == 0) {
                 getCurrentTabCreator().launchNTP();
             }
         }
-        mIsAccessibilityEnabled = enabled;
+        mIsAccessibilityTabSwitcherEnabled = accessibilityTabSwitcherEnabled;
+
+        if (AccessibilityUtil.isAccessibilityEnabled()) {
+            RecordHistogram.recordBooleanHistogram(
+                    "Accessibility.Android.TabSwitcherPreferenceEnabled",
+                    mIsAccessibilityTabSwitcherEnabled);
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index cbff8d2..cb3876c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -20,6 +20,7 @@
 import android.support.customtabs.CustomTabsSessionToken;
 import android.support.customtabs.TrustedWebUtils;
 
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -51,6 +52,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.UUID;
 
 /**
@@ -384,6 +386,24 @@
      */
     @SuppressLint("InlinedApi")
     private @Action int dispatchToTabbedActivity() {
+        if (mIsVrIntent) {
+            for (WeakReference<Activity> weakActivity : ApplicationStatus.getRunningActivities()) {
+                final Activity activity = weakActivity.get();
+                if (activity == null) continue;
+                if (activity instanceof ChromeTabbedActivity) {
+                    if (VrModuleProvider.getDelegate().willChangeDensityInVr(
+                                (ChromeActivity) activity)) {
+                        // In the rare case that entering VR will trigger a density change (and
+                        // hence an Activity recreation), just return to Daydream home and kill the
+                        // process, as there's no good way to recreate without showing 2D UI
+                        // in-headset.
+                        mActivity.finish();
+                        System.exit(0);
+                    }
+                }
+            }
+        }
+
         maybePrefetchDnsInBackground();
 
         Intent newIntent = new Intent(mIntent);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java
index b58d860..db748a4c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java
@@ -156,7 +156,8 @@
                     ThreadUtils.runOnUiThread(
                             () -> nativeOnSystemRequestCompletion(mNativePtr, null, null));
                 } else {
-                    new CertAsyncTaskKeyChain(mContext, mNativePtr, alias).execute();
+                    new CertAsyncTaskKeyChain(mContext, mNativePtr, alias)
+                            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                 }
             });
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
index 8dfc3337..0bb754c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
@@ -59,6 +59,7 @@
     private final int mItemDividerHeight;
     private final int mVerticalFadeDistance;
     private final int mNegativeSoftwareVerticalOffset;
+    private final int mNegativeVerticalOffsetNotTopAnchored;
     private final int[] mTempLocation;
 
     private PopupWindow mPopup;
@@ -95,6 +96,8 @@
         mNegativeSoftwareVerticalOffset =
                 res.getDimensionPixelSize(R.dimen.menu_negative_software_vertical_offset);
         mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
+        mNegativeVerticalOffsetNotTopAnchored =
+                res.getDimensionPixelSize(R.dimen.menu_negative_vertical_offset_not_top_anchored);
 
         mTempLocation = new int[2];
     }
@@ -256,7 +259,7 @@
         int popupHeight = setMenuHeight(menuItems.size(), visibleDisplayFrame, screenHeight,
                 sizingPadding, footerHeight, headerHeight, anchorView);
         int[] popupPosition = getPopupPosition(mCurrentScreenRotation, visibleDisplayFrame,
-                sizingPadding, anchorView, popupWidth, popupHeight);
+                sizingPadding, anchorView, popupWidth, popupHeight, showFromBottom);
 
         mPopup.setContentView(contentView);
         mPopup.showAtLocation(
@@ -295,7 +298,7 @@
     }
 
     private int[] getPopupPosition(int screenRotation, Rect appRect, Rect padding, View anchorView,
-            int popupWidth, int popupHeight) {
+            int popupWidth, int popupHeight, boolean isAnchorAtBottom) {
         anchorView.getLocationInWindow(mTempLocation);
         int anchorViewX = mTempLocation[0];
         int anchorViewY = mTempLocation[1];
@@ -326,6 +329,18 @@
         } else {
             offsets[1] = -mNegativeSoftwareVerticalOffset;
 
+            // If the anchor is at the bottom of the screen, align the popup with the bottom of the
+            // anchor. The anchor may not be fully visible, so
+            // (appRect.bottom - anchorViewLocationOnScreenY) is used to determine the visible
+            // bottom edge of the anchor view.
+            if (isAnchorAtBottom) {
+                anchorView.getLocationOnScreen(mTempLocation);
+                int anchorViewLocationOnScreenY = mTempLocation[1];
+                offsets[1] += appRect.bottom - anchorViewLocationOnScreenY - popupHeight;
+                offsets[1] -= mNegativeVerticalOffsetNotTopAnchored;
+                if (!mIsByPermanentButton) offsets[1] += padding.bottom;
+            }
+
             if (!ApiCompatibilityUtils.isLayoutRtl(anchorView.getRootView())) {
                 offsets[0] = anchorView.getWidth() - popupWidth;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuButtonHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuButtonHelper.java
index 1881a9c..c3f064e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuButtonHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuButtonHelper.java
@@ -29,6 +29,7 @@
     private boolean mIsTouchEventsBeingProcessed;
     private boolean mShowMenuOnUp;
     private boolean mMenuShowsFromBottom;
+    private Runnable mOnClickRunnable;
 
     /**
      * @param menuHandler MenuHandler implementation that can show and get the app menu.
@@ -109,6 +110,16 @@
         view.setPressed(isActionDown);
     }
 
+    /**
+     * Set a runnable for click events on the menu button. This runnable is triggered with the down
+     * motion event rather than click specifically. This is primarily used to track interaction with
+     * the menu button.
+     * @param clickRunnable The {@link Runnable} to be executed on a down event.
+     */
+    public void setOnClickRunnable(Runnable clickRunnable) {
+        mOnClickRunnable = clickRunnable;
+    }
+
     @SuppressLint("ClickableViewAccessibility")
     @Override
     public boolean onTouch(View view, MotionEvent event) {
@@ -119,6 +130,7 @@
                 if (!mShowMenuOnUp) {
                     isTouchEventConsumed |= true;
                     updateTouchEvent(view, true);
+                    if (mOnClickRunnable != null) mOnClickRunnable.run();
                     showAppMenu(view, true);
                 }
                 break;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java
index 53c41a5..f56acee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java
@@ -40,6 +40,7 @@
         model.addObserver(new PropertyModelChangeProcessor<>(model, stubHolder,
                 new LazyViewBinderAdapter<>(
                         new AccessorySheetViewBinder(), this::onViewInflated)));
+        KeyboardAccessoryMetricsRecorder.recordModelChanges(model);
         mMediator = new AccessorySheetMediator(model);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java
index cb5551b..04ffc26 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java
@@ -9,16 +9,19 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.modelutil.PropertyObservable;
 
 /**
  * Contains the controller logic of the AccessorySheet component.
  * It communicates with data providers and native backends to update a {@link AccessorySheetModel}.
  */
-class AccessorySheetMediator {
+class AccessorySheetMediator
+        implements PropertyObservable.PropertyObserver<AccessorySheetModel.PropertyKey> {
     private final AccessorySheetModel mModel;
 
     AccessorySheetMediator(AccessorySheetModel model) {
         mModel = model;
+        mModel.addObserver(this);
     }
 
     @Nullable
@@ -91,4 +94,19 @@
         int itemCountAfterDeletion = mModel.getTabList().size() - 1;
         return itemCountAfterDeletion > 0 ? 0 : NO_ACTIVE_TAB;
     }
+
+    @Override
+    public void onPropertyChanged(PropertyObservable<AccessorySheetModel.PropertyKey> source,
+            @Nullable AccessorySheetModel.PropertyKey propertyKey) {
+        if (propertyKey == AccessorySheetModel.PropertyKey.VISIBLE) {
+            if (mModel.isVisible() && getTab() != null && getTab().getListener() != null) {
+                getTab().getListener().onTabShown();
+            }
+            return;
+        }
+        if (propertyKey == AccessorySheetModel.PropertyKey.ACTIVE_TAB_INDEX) {
+            return;
+        }
+        assert false : "Every property update needs to be handled explicitly!";
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java
index 839af212d..9cc9297 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java
@@ -78,6 +78,7 @@
         model.addObserver(new PropertyModelChangeProcessor<>(model, mViewHolder,
                 new LazyViewBinderAdapter<>(
                         new KeyboardAccessoryViewBinder(), this::onViewInflated)));
+        KeyboardAccessoryMetricsRecorder.recordModelChanges(model);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java
index a31b40c..e9a24cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java
@@ -61,6 +61,7 @@
         private final Drawable mIcon;
         private final String mContentDescription;
         private final int mTabLayout;
+        private final @AccessoryTabType int mRecordingType;
         private final @Nullable Listener mListener;
 
         /**
@@ -72,14 +73,20 @@
              * @param view The newly created accessory sheet of the tab.
              */
             void onTabCreated(ViewGroup view);
+
+            /**
+             * Triggered when the tab becomes visible to the user.
+             */
+            void onTabShown();
         }
 
         public Tab(Drawable icon, String contentDescription, @LayoutRes int tabLayout,
-                @Nullable Listener listener) {
+                @AccessoryTabType int recordingType, @Nullable Listener listener) {
             mIcon = icon;
             mContentDescription = contentDescription;
             mTabLayout = tabLayout;
             mListener = listener;
+            mRecordingType = recordingType;
         }
 
         /**
@@ -99,6 +106,14 @@
         }
 
         /**
+         * The description for this tab. It will become the content description of the icon.
+         * @return A short string describing the task of this tab.
+         */
+        public @AccessoryTabType int getRecordingType() {
+            return mRecordingType;
+        }
+
+        /**
          * Returns the tab layout which allows to create the tab's view on demand.
          * @return The layout resource that allows to create the view necessary for this tab.
          */
@@ -122,10 +137,12 @@
     public static final class Action {
         private final String mCaption;
         private final Callback<Action> mActionCallback;
+        private @AccessoryAction int mType;
 
-        public Action(String caption, Callback<Action> actionCallback) {
+        public Action(String caption, @AccessoryAction int type, Callback<Action> actionCallback) {
             mCaption = caption;
             mActionCallback = actionCallback;
+            mType = type;
         }
 
         public String getCaption() {
@@ -135,6 +152,10 @@
         public Callback<Action> getCallback() {
             return mActionCallback;
         }
+
+        public @AccessoryAction int getActionType() {
+            return mType;
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java
index 1ca84b4..78010fcd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.autofill.keyboard_accessory;
 
+import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTrigger.MANUAL_CLOSE;
+
 import android.support.annotation.Nullable;
 import android.support.design.widget.TabLayout;
 
@@ -165,6 +167,8 @@
         if (mModel.activeTab() == null) {
             mModel.setActiveTab(tab.getPosition());
         } else {
+            KeyboardAccessoryMetricsRecorder.recordSheetTrigger(
+                    mModel.getTabList().get(mModel.activeTab()).getRecordingType(), MANUAL_CLOSE);
             mModel.setActiveTab(null);
             mVisibilityDelegate.onOpenKeyboard();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java
new file mode 100644
index 0000000..b72750a02
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java
@@ -0,0 +1,175 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.autofill.keyboard_accessory;
+
+import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTrigger.MANUAL_OPEN;
+
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Item;
+import org.chromium.chrome.browser.modelutil.ListObservable;
+import org.chromium.chrome.browser.modelutil.SimpleListObservable;
+
+/**
+ * This class provides helpers to record metrics related to the keyboard accessory and its sheets.
+ * It can set up observers to observe {@link KeyboardAccessoryModel}s, {@link AccessorySheetModel}s
+ * or {@link ListObservable<Item>}s changes and records metrics accordingly.
+ */
+class KeyboardAccessoryMetricsRecorder {
+    static final String UMA_KEYBOARD_ACCESSORY_ACTION_IMPRESSION =
+            "KeyboardAccessory.AccessoryActionImpression";
+    static final String UMA_KEYBOARD_ACCESSORY_ACTION_SELECTED =
+            "KeyboardAccessory.AccessoryActionSelected";
+    static final String UMA_KEYBOARD_ACCESSORY_BAR_SHOWN = "KeyboardAccessory.AccessoryBarShown";
+    static final String UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTIONS =
+            "KeyboardAccessory.AccessorySheetSuggestionCount";
+    static final String UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTION_SELECTED =
+            "KeyboardAccessory.AccessorySheetSuggestionsSelected";
+    static final String UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED =
+            "KeyboardAccessory.AccessorySheetTriggered";
+    static final String UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_PASSWORDS = "Passwords";
+
+    /**
+     * The Recorder itself should be stateless and have no need for an instance.
+     */
+    private KeyboardAccessoryMetricsRecorder() {}
+
+    static void recordModelChanges(KeyboardAccessoryModel keyboardAccessoryModel) {
+        keyboardAccessoryModel.addObserver((source, propertyKey) -> {
+            if (propertyKey == KeyboardAccessoryModel.PropertyKey.VISIBLE) {
+                if (keyboardAccessoryModel.isVisible()) {
+                    recordAccessoryBarImpression(keyboardAccessoryModel);
+                    recordAccessoryActionImpressions(keyboardAccessoryModel);
+                }
+                return;
+            }
+            if (propertyKey == KeyboardAccessoryModel.PropertyKey.ACTIVE_TAB
+                    || propertyKey == KeyboardAccessoryModel.PropertyKey.TAB_SELECTION_CALLBACKS
+                    || propertyKey == KeyboardAccessoryModel.PropertyKey.SUGGESTIONS) {
+                return;
+            }
+            assert false : "Every property update needs to be handled explicitly!";
+        });
+    }
+
+    static void recordModelChanges(AccessorySheetModel accessorySheetModel) {
+        accessorySheetModel.addObserver((source, propertyKey) -> {
+            if (propertyKey == AccessorySheetModel.PropertyKey.VISIBLE) {
+                if (accessorySheetModel.isVisible()) {
+                    int activeTab = accessorySheetModel.getActiveTabIndex();
+                    if (activeTab >= 0 && activeTab < accessorySheetModel.getTabList().size()) {
+                        recordSheetTrigger(
+                                accessorySheetModel.getTabList().get(activeTab).getRecordingType(),
+                                MANUAL_OPEN);
+                    }
+                } else {
+                    recordSheetTrigger(AccessoryTabType.ALL, AccessorySheetTrigger.ANY_CLOSE);
+                }
+                return;
+            }
+            if (propertyKey == AccessorySheetModel.PropertyKey.ACTIVE_TAB_INDEX) {
+                return;
+            }
+            assert false : "Every property update needs to be handled explicitly!";
+        });
+    }
+
+    @VisibleForTesting
+    static String getHistogramForType(String baseHistogram, @AccessoryTabType int tabType) {
+        switch (tabType) {
+            case AccessoryTabType.ALL:
+                return baseHistogram;
+            case AccessoryTabType.PASSWORDS:
+                return baseHistogram + "." + UMA_KEYBOARD_ACCESSORY_SHEET_TYPE_SUFFIX_PASSWORDS;
+        }
+        assert false : "Undefined histogram for tab type " + tabType + " !";
+        return "";
+    }
+
+    static void recordSheetTrigger(
+            @AccessoryTabType int tabType, @AccessorySheetTrigger int bucket) {
+        RecordHistogram.recordEnumeratedHistogram(
+                getHistogramForType(UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED, tabType), bucket,
+                AccessorySheetTrigger.COUNT);
+        if (tabType != AccessoryTabType.ALL) { // Record count for all tab types exactly once!
+            RecordHistogram.recordEnumeratedHistogram(
+                    getHistogramForType(
+                            UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED, AccessoryTabType.ALL),
+                    bucket, AccessorySheetTrigger.COUNT);
+        }
+    }
+
+    static void recordActionImpression(@AccessoryAction int bucket) {
+        RecordHistogram.recordEnumeratedHistogram(
+                UMA_KEYBOARD_ACCESSORY_ACTION_IMPRESSION, bucket, AccessoryAction.COUNT);
+    }
+
+    static void recordActionSelected(@AccessoryAction int bucket) {
+        RecordHistogram.recordEnumeratedHistogram(
+                UMA_KEYBOARD_ACCESSORY_ACTION_SELECTED, bucket, AccessoryAction.COUNT);
+    }
+
+    static void recordSuggestionSelected(@AccessorySuggestionType int bucket) {
+        RecordHistogram.recordEnumeratedHistogram(UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTION_SELECTED,
+                bucket, AccessorySuggestionType.COUNT);
+    }
+
+    static void recordSheetSuggestions(
+            @AccessoryTabType int tabType, SimpleListObservable<Item> suggestionList) {
+        int interactiveSuggestions = 0;
+        for (int i = 0; i < suggestionList.size(); ++i) {
+            if (suggestionList.get(i).getType() == ItemType.SUGGESTION) ++interactiveSuggestions;
+        }
+        RecordHistogram.recordCount100Histogram(
+                getHistogramForType(UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTIONS, tabType),
+                interactiveSuggestions);
+        if (tabType != AccessoryTabType.ALL) { // Record count for all tab types exactly once!
+            RecordHistogram.recordCount100Histogram(
+                    getHistogramForType(
+                            UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTIONS, AccessoryTabType.ALL),
+                    interactiveSuggestions);
+        }
+    }
+
+    private static void recordAccessoryActionImpressions(
+            KeyboardAccessoryModel keyboardAccessoryModel) {
+        for (KeyboardAccessoryData.Action action : keyboardAccessoryModel.getActionList()) {
+            recordActionImpression(action.getActionType());
+        }
+    }
+
+    private static void recordAccessoryBarImpression(
+            KeyboardAccessoryModel keyboardAccessoryModel) {
+        boolean barImpressionRecorded = false;
+        for (@AccessoryBarContents int bucket = 0; bucket < AccessoryBarContents.COUNT; ++bucket) {
+            if (shouldRecordAccessoryImpression(bucket, keyboardAccessoryModel)) {
+                RecordHistogram.recordEnumeratedHistogram(
+                        UMA_KEYBOARD_ACCESSORY_BAR_SHOWN, bucket, AccessoryBarContents.COUNT);
+                barImpressionRecorded = true;
+            }
+        }
+        RecordHistogram.recordEnumeratedHistogram(UMA_KEYBOARD_ACCESSORY_BAR_SHOWN,
+                barImpressionRecorded ? AccessoryBarContents.ANY_CONTENTS
+                                      : AccessoryBarContents.NO_CONTENTS,
+                AccessoryBarContents.COUNT);
+    }
+
+    private static boolean shouldRecordAccessoryImpression(
+            int bucket, KeyboardAccessoryModel keyboardAccessoryModel) {
+        switch (bucket) {
+            case AccessoryBarContents.WITH_TABS:
+                return keyboardAccessoryModel.getTabList().size() > 0;
+            case AccessoryBarContents.WITH_ACTIONS:
+                return keyboardAccessoryModel.getActionList().size() > 0;
+            case AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS:
+                return keyboardAccessoryModel.getAutofillSuggestions() != null;
+            case AccessoryBarContents.ANY_CONTENTS: // Intentional fallthrough.
+            case AccessoryBarContents.NO_CONTENTS:
+                return false; // General impression is logged last.
+        }
+        assert false : "Did not check whether to record an impression bucket " + bucket + ".";
+        return false;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryBridge.java
index f10fe83..56b0c44e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryBridge.java
@@ -53,11 +53,15 @@
             String caption = useLongString
                     ? mActivity.getString(R.string.password_generation_accessory_button)
                     : mActivity.getString(R.string.password_generation_accessory_button_short);
-            generationAction = new Action[] {new Action(caption, (action) -> {
-                assert mNativeView
-                        != 0 : "Controller has been destroyed but the bridge wasn't cleaned up!";
-                nativeOnGenerationRequested(mNativeView);
-            })};
+            generationAction = new Action[] {
+                    new Action(caption, AccessoryAction.GENERATE_PASSWORD_AUTOMATIC, (action) -> {
+                        assert mNativeView
+                                != 0
+                            : "Controller has been destroyed but the bridge wasn't cleaned up!";
+                        KeyboardAccessoryMetricsRecorder.recordActionSelected(
+                                AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
+                        nativeOnGenerationRequested(mNativeView);
+                    })};
         } else {
             generationAction = new Action[0];
         }
@@ -93,6 +97,9 @@
                             text[i], description[i], isPassword[i] == 1, (item) -> {
                                 assert mNativeView
                                         != 0 : "Controller was destroyed but the bridge wasn't!";
+                                KeyboardAccessoryMetricsRecorder.recordSuggestionSelected(
+                                        item.isPassword() ? AccessorySuggestionType.PASSWORD
+                                                          : AccessorySuggestionType.USERNAME);
                                 nativeOnFillingTriggered(
                                         mNativeView, item.isPassword(), item.getCaption());
                             }, this::fetchFavicon);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java
index 0341e215..78937d9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java
@@ -10,7 +10,7 @@
 import android.support.annotation.Nullable;
 import android.support.v7.content.res.AppCompatResources;
 import android.support.v7.widget.RecyclerView;
-import android.view.View;
+import android.view.ViewGroup;
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
@@ -24,7 +24,7 @@
  * This component is a tab that can be added to the {@link ManualFillingCoordinator} which shows it
  * as bottom sheet below the keyboard accessory.
  */
-public class PasswordAccessorySheetCoordinator {
+public class PasswordAccessorySheetCoordinator implements KeyboardAccessoryData.Tab.Listener {
     private final Context mContext;
     private final SimpleListObservable<Item> mModel = new SimpleListObservable<>();
     private final KeyboardAccessoryData.Observer<Item> mMediator = mModel::set;
@@ -70,14 +70,21 @@
     public PasswordAccessorySheetCoordinator(Context context) {
         mContext = context;
         mTab = new KeyboardAccessoryData.Tab(IconProvider.getInstance().getIcon(mContext),
-                null, // TODO(fhorschig): Load from strings or native side.
-                R.layout.password_accessory_sheet, this::onTabCreated);
+                context.getString(R.string.password_accessory_sheet_toggle),
+                R.layout.password_accessory_sheet, AccessoryTabType.PASSWORDS, this);
     }
 
-    private void onTabCreated(View view) {
+    @Override
+    public void onTabCreated(ViewGroup view) {
         PasswordAccessorySheetViewBinder.initializeView((RecyclerView) view, createAdapter(mModel));
     }
 
+    @Override
+    public void onTabShown() {
+        KeyboardAccessoryMetricsRecorder.recordActionImpression(AccessoryAction.MANAGE_PASSWORDS);
+        KeyboardAccessoryMetricsRecorder.recordSheetSuggestions(AccessoryTabType.PASSWORDS, mModel);
+    }
+
     /**
      * Creates an adapter to an {@link PasswordAccessorySheetViewBinder} that is wired
      * up to the model change processor which listens to the {@link SimpleListObservable<Item>}.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
index f212860..7f1e7c6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
@@ -96,6 +96,7 @@
             getTextView().setTransformationMethod(
                     item.isPassword() ? new PasswordTransformationMethod() : null);
             getTextView().setText(item.getCaption());
+            getTextView().setContentDescription(item.getContentDescription());
             if (item.getItemSelectedCallback() != null) {
                 getTextView().setOnClickListener(
                         src -> item.getItemSelectedCallback().onResult(item));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index e9daa29..b2296b8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -66,7 +66,6 @@
 import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
 import org.chromium.chrome.browser.fullscreen.BrowserStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.gsa.GSAState;
-import org.chromium.chrome.browser.metrics.PageLoadMetrics;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
 import org.chromium.chrome.browser.page_info.PageInfoController;
 import org.chromium.chrome.browser.payments.ServiceWorkerPaymentAppBridge;
@@ -78,6 +77,7 @@
 import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -153,6 +153,8 @@
     private boolean mHasSpeculated;
     private CustomTabObserver mTabObserver;
     private CustomTabNavigationEventObserver mTabNavigationEventObserver;
+    /** Adds and removes observers from tabs when needed. */
+    private final TabObserverRegistrar mTabObserverRegistrar = new TabObserverRegistrar();
 
     private String mSpeculatedUrl;
 
@@ -170,6 +172,8 @@
     private WebappCustomTabTimeSpentLogger mWebappTimeSpentLogger;
 
     @Nullable
+    private ModuleEntryPoint mModuleEntryPoint;
+    @Nullable
     private ActivityDelegate mModuleActivityDelegate;
     @Nullable
     private Runnable mLoadModuleCancelRunnable;
@@ -177,81 +181,6 @@
     private boolean mModuleOnResumePending;
     private boolean mHasSetOverlayView;
 
-    private static class PageLoadMetricsObserver implements PageLoadMetrics.Observer {
-        private final CustomTabsConnection mConnection;
-        private final CustomTabsSessionToken mSession;
-        private final Tab mTab;
-        private final CustomTabObserver mCustomTabObserver;
-
-        public PageLoadMetricsObserver(CustomTabsConnection connection,
-                CustomTabsSessionToken session, Tab tab, CustomTabObserver tabObserver) {
-            mConnection = connection;
-            mSession = session;
-            mTab = tab;
-            mCustomTabObserver = tabObserver;
-        }
-
-        @Override
-        public void onNewNavigation(WebContents webContents, long navigationId) {}
-
-        @Override
-        public void onNetworkQualityEstimate(WebContents webContents, long navigationId,
-                int effectiveConnectionType, long httpRttMs, long transportRttMs) {
-            if (webContents != mTab.getWebContents()) return;
-
-            Bundle args = new Bundle();
-            args.putLong(PageLoadMetrics.EFFECTIVE_CONNECTION_TYPE, effectiveConnectionType);
-            args.putLong(PageLoadMetrics.HTTP_RTT, httpRttMs);
-            args.putLong(PageLoadMetrics.TRANSPORT_RTT, transportRttMs);
-            args.putBoolean(CustomTabsConnection.DATA_REDUCTION_ENABLED,
-                    DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
-            mConnection.notifyPageLoadMetrics(mSession, args);
-        }
-
-        @Override
-        public void onFirstContentfulPaint(WebContents webContents, long navigationId,
-                long navigationStartTick, long firstContentfulPaintMs) {
-            if (webContents != mTab.getWebContents()) return;
-
-            mConnection.notifySinglePageLoadMetric(mSession, PageLoadMetrics.FIRST_CONTENTFUL_PAINT,
-                    navigationStartTick, firstContentfulPaintMs);
-        }
-
-        @Override
-        public void onFirstMeaningfulPaint(WebContents webContents, long navigationId,
-                long navigationStartTick, long firstContentfulPaintMs) {
-            if (webContents != mTab.getWebContents()) return;
-
-            mCustomTabObserver.onFirstMeaningfulPaint(mTab);
-        }
-
-        @Override
-        public void onLoadEventStart(WebContents webContents, long navigationId,
-                long navigationStartTick, long loadEventStartMs) {
-            if (webContents != mTab.getWebContents()) return;
-
-            mConnection.notifySinglePageLoadMetric(mSession, PageLoadMetrics.LOAD_EVENT_START,
-                    navigationStartTick, loadEventStartMs);
-        }
-
-        @Override
-        public void onLoadedMainResource(WebContents webContents, long navigationId,
-                long dnsStartMs, long dnsEndMs, long connectStartMs, long connectEndMs,
-                long requestStartMs, long sendStartMs, long sendEndMs) {
-            if (webContents != mTab.getWebContents()) return;
-
-            Bundle args = new Bundle();
-            args.putLong(PageLoadMetrics.DOMAIN_LOOKUP_START, dnsStartMs);
-            args.putLong(PageLoadMetrics.DOMAIN_LOOKUP_END, dnsEndMs);
-            args.putLong(PageLoadMetrics.CONNECT_START, connectStartMs);
-            args.putLong(PageLoadMetrics.CONNECT_END, connectEndMs);
-            args.putLong(PageLoadMetrics.REQUEST_START, requestStartMs);
-            args.putLong(PageLoadMetrics.SEND_START, sendStartMs);
-            args.putLong(PageLoadMetrics.SEND_END, sendEndMs);
-            mConnection.notifyPageLoadMetrics(mSession, args);
-        }
-    }
-
     private static class CustomTabCreator extends ChromeTabCreator {
         private final boolean mSupportsUrlBarHiding;
         private final boolean mIsOpenedByChrome;
@@ -273,34 +202,12 @@
         }
     }
 
-    private PageLoadMetricsObserver mMetricsObserver;
-
-    // Only the normal tab model is observed because there is no incognito tabmodel in Custom Tabs.
-    private TabModelObserver mTabModelObserver = new EmptyTabModelObserver() {
-        @Override
-        public void didAddTab(Tab tab, @TabLaunchType int type) {
-            // Ensure that the PageLoadMetrics observer is attached in all cases, if in
-            // the future we do not go through initializeMainTab. ObserverList.addObserver
-            // will deduplicate additions, so it is safe to add both here as well as in
-            // initializeMainTab().
-            PageLoadMetrics.addObserver(mMetricsObserver);
-            tab.addObserver(mTabObserver);
-            tab.addObserver(mTabNavigationEventObserver);
-        }
-
+    private TabModelObserver mCloseActivityWhenEmptyTabModelObserver = new EmptyTabModelObserver() {
         @Override
         public void didCloseTab(int tabId, boolean incognito) {
-            PageLoadMetrics.removeObserver(mMetricsObserver);
             // Finish the activity after we intent out.
             if (getTabModelSelector().getCurrentModel().getCount() == 0) finishAndClose(false);
         }
-
-        @Override
-        public void tabRemoved(Tab tab) {
-            tab.removeObserver(mTabObserver);
-            tab.removeObserver(mTabNavigationEventObserver);
-            PageLoadMetrics.removeObserver(mMetricsObserver);
-        }
     };
 
     @Override
@@ -417,6 +324,8 @@
         mLoadModuleCancelRunnable = null;
         if (entryPoint == null) return;
 
+        mModuleEntryPoint = entryPoint;
+
         long createActivityDelegateStartTime = ModuleMetrics.now();
         mModuleActivityDelegate = entryPoint.createActivityDelegate(new ActivityHostImpl(this));
         ModuleMetrics.recordCreateActivityDelegateTime(createActivityDelegateStartTime);
@@ -534,10 +443,11 @@
         if (IntentHandler.getExtraHeadersFromIntent(getIntent()) != null) {
             mConnection.cancelSpeculation(mSession);
         }
-
-        getTabModelSelector()
-                .getModel(mIntentDataProvider.isIncognito())
-                .addObserver(mTabModelObserver);
+        // Only the normal tab model is observed because there is no incognito TabModel in Custom
+        // Tabs.
+        TabModel tabModel = getTabModelSelector().getModel(mIntentDataProvider.isIncognito());
+        tabModel.addObserver(mTabObserverRegistrar);
+        tabModel.addObserver(mCloseActivityWhenEmptyTabModelObserver);
 
         boolean successfulStateRestore = false;
         // Attempt to restore the previous tab state if applicable.
@@ -559,9 +469,7 @@
             } else {
                 mMainTab = createMainTab();
             }
-            getTabModelSelector()
-                    .getModel(mIntentDataProvider.isIncognito())
-                    .addTab(mMainTab, 0, mMainTab.getLaunchType());
+            tabModel.addTab(mMainTab, 0, mMainTab.getLaunchType());
         }
 
         // This cannot be done before because we want to do the reparenting only
@@ -783,16 +691,21 @@
     private void initializeMainTab(Tab tab) {
         tab.getTabRedirectHandler().updateIntent(getIntent());
         tab.getView().requestFocus();
+
         mTabObserver = new CustomTabObserver(
                 getApplication(), mSession, mIntentDataProvider.isOpenedByChrome());
         mTabNavigationEventObserver = new CustomTabNavigationEventObserver(mSession);
 
-        mMetricsObserver = new PageLoadMetricsObserver(mConnection, mSession, tab, mTabObserver);
+        mTabObserverRegistrar.registerTabObserver(mTabObserver);
+        mTabObserverRegistrar.registerTabObserver(mTabNavigationEventObserver);
+        mTabObserverRegistrar.registerPageLoadMetricsObserver(
+                new PageLoadMetricsObserver(mConnection, mSession, tab));
+        mTabObserverRegistrar.registerPageLoadMetricsObserver(
+                new FirstMeaningfulPaintObserver(mTabObserver, tab));
+
         // Immediately add the observer to PageLoadMetrics to catch early events that may
         // be generated in the middle of tab initialization.
-        PageLoadMetrics.addObserver(mMetricsObserver);
-        tab.addObserver(mTabObserver);
-        tab.addObserver(mTabNavigationEventObserver);
+        mTabObserverRegistrar.addObserversForTab(tab);
 
         // Let ServiceWorkerPaymentAppBridge observe the opened tab for payment request.
         if (mIntentDataProvider.isForPaymentRequest()) {
@@ -916,6 +829,7 @@
             mModuleActivityDelegate.onDestroy();
             mModuleActivityDelegate = null;
         }
+        mModuleEntryPoint = null;
         ComponentName moduleComponentName = mIntentDataProvider.getModuleComponentName();
         if (moduleComponentName != null) {
             mConnection.getModuleLoader(moduleComponentName).maybeUnloadModule();
@@ -1101,8 +1015,16 @@
 
         if (exitFullscreenIfShowing()) return true;
 
-        if (mModuleActivityDelegate != null && mModuleActivityDelegate.onBackPressed()) return true;
+        if (mModuleActivityDelegate != null && mModuleEntryPoint.getModuleVersion() >= 2) {
+            mModuleActivityDelegate.onBackPressedAsync(this::handleTabBackNavigation);
+            return true;
+        }
 
+        handleTabBackNavigation();
+        return true;
+    }
+
+    private void handleTabBackNavigation() {
         if (!getToolbarManager().back()) {
             if (getCurrentTabModel().getCount() > 1) {
                 getCurrentTabModel().closeTab(getActivityTab(), false, false, false);
@@ -1111,7 +1033,6 @@
                 finishAndClose(false);
             }
         }
-        return true;
     }
 
     @Override
@@ -1124,8 +1045,8 @@
                 (getActivityTab() == null) ? null : getActivityTab().getAppAssociatedWith();
         if (packageName == null) return; // No associated package
 
-        boolean isConnected = packageName.equals(
-                CustomTabsConnection.getInstance().getClientPackageNameForSession(mSession));
+        boolean isConnected =
+                packageName.equals(mConnection.getClientPackageNameForSession(mSession));
         int status = -1;
         if (isConnected) {
             if (mIsKeepAlive) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/FirstMeaningfulPaintObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/FirstMeaningfulPaintObserver.java
new file mode 100644
index 0000000..f49cf37
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/FirstMeaningfulPaintObserver.java
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.customtabs;
+
+import org.chromium.chrome.browser.metrics.PageLoadMetrics;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * Notifies the provided {@link CustomTabObserver} when first meaningful paint occurs.
+ */
+/* package */ class FirstMeaningfulPaintObserver implements PageLoadMetrics.Observer {
+    private final Tab mTab;
+    private final CustomTabObserver mCustomTabObserver;
+
+    /* package */ FirstMeaningfulPaintObserver(CustomTabObserver tabObserver, Tab tab) {
+        mCustomTabObserver = tabObserver;
+        mTab = tab;
+    }
+
+    @Override
+    public void onFirstMeaningfulPaint(WebContents webContents, long navigationId,
+            long navigationStartTick, long firstContentfulPaintMs) {
+        if (webContents != mTab.getWebContents()) return;
+
+        mCustomTabObserver.onFirstMeaningfulPaint(mTab);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PageLoadMetricsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PageLoadMetricsObserver.java
new file mode 100644
index 0000000..4b1c1de
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PageLoadMetricsObserver.java
@@ -0,0 +1,79 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.customtabs;
+
+import android.os.Bundle;
+import android.support.customtabs.CustomTabsSessionToken;
+
+import org.chromium.chrome.browser.metrics.PageLoadMetrics;
+import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * Notifies the provided {@link CustomTabsConnection} of page load metrics, such as time until first
+ * contentful paint.
+ */
+class PageLoadMetricsObserver implements PageLoadMetrics.Observer {
+    private final CustomTabsConnection mConnection;
+    private final CustomTabsSessionToken mSession;
+    private final Tab mTab;
+
+    /* package */ PageLoadMetricsObserver(CustomTabsConnection connection,
+            CustomTabsSessionToken session, Tab tab) {
+        mConnection = connection;
+        mSession = session;
+        mTab = tab;
+    }
+
+    @Override
+    public void onNetworkQualityEstimate(WebContents webContents, long navigationId,
+            int effectiveConnectionType, long httpRttMs, long transportRttMs) {
+        if (webContents != mTab.getWebContents()) return;
+
+        Bundle args = new Bundle();
+        args.putLong(PageLoadMetrics.EFFECTIVE_CONNECTION_TYPE, effectiveConnectionType);
+        args.putLong(PageLoadMetrics.HTTP_RTT, httpRttMs);
+        args.putLong(PageLoadMetrics.TRANSPORT_RTT, transportRttMs);
+        args.putBoolean(CustomTabsConnection.DATA_REDUCTION_ENABLED,
+                DataReductionProxySettings.getInstance().isDataReductionProxyEnabled());
+        mConnection.notifyPageLoadMetrics(mSession, args);
+    }
+
+    @Override
+    public void onFirstContentfulPaint(WebContents webContents, long navigationId,
+            long navigationStartTick, long firstContentfulPaintMs) {
+        if (webContents != mTab.getWebContents()) return;
+
+        mConnection.notifySinglePageLoadMetric(mSession, PageLoadMetrics.FIRST_CONTENTFUL_PAINT,
+                navigationStartTick, firstContentfulPaintMs);
+    }
+
+    @Override
+    public void onLoadEventStart(WebContents webContents, long navigationId,
+            long navigationStartTick, long loadEventStartMs) {
+        if (webContents != mTab.getWebContents()) return;
+
+        mConnection.notifySinglePageLoadMetric(mSession, PageLoadMetrics.LOAD_EVENT_START,
+                navigationStartTick, loadEventStartMs);
+    }
+
+    @Override
+    public void onLoadedMainResource(WebContents webContents, long navigationId,
+            long dnsStartMs, long dnsEndMs, long connectStartMs, long connectEndMs,
+            long requestStartMs, long sendStartMs, long sendEndMs) {
+        if (webContents != mTab.getWebContents()) return;
+
+        Bundle args = new Bundle();
+        args.putLong(PageLoadMetrics.DOMAIN_LOOKUP_START, dnsStartMs);
+        args.putLong(PageLoadMetrics.DOMAIN_LOOKUP_END, dnsEndMs);
+        args.putLong(PageLoadMetrics.CONNECT_START, connectStartMs);
+        args.putLong(PageLoadMetrics.CONNECT_END, connectEndMs);
+        args.putLong(PageLoadMetrics.REQUEST_START, requestStartMs);
+        args.putLong(PageLoadMetrics.SEND_START, sendStartMs);
+        args.putLong(PageLoadMetrics.SEND_END, sendEndMs);
+        mConnection.notifyPageLoadMetrics(mSession, args);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/TabObserverRegistrar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/TabObserverRegistrar.java
new file mode 100644
index 0000000..e10b680f
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/TabObserverRegistrar.java
@@ -0,0 +1,89 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.customtabs;
+
+import org.chromium.chrome.browser.metrics.PageLoadMetrics;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Adds and removes the given {@link PageLoadMetrics.Observer}s and {@link TabObserver}s to Tabs as
+ * they enter/leave the TabModel.
+ *
+ * // TODO(peconn): Get rid of EmptyTabModelObserver now that we have Java 8 default methods.
+ */
+public class TabObserverRegistrar extends EmptyTabModelObserver {
+    private final Set<PageLoadMetrics.Observer> mPageLoadMetricsObservers = new HashSet<>();
+    private final Set<TabObserver> mTabObservers = new HashSet<>();
+
+    /**
+     * Registers a {@link PageLoadMetrics.Observer} to be managed by this Registrar.
+     */
+    public void registerPageLoadMetricsObserver(PageLoadMetrics.Observer observer) {
+        mPageLoadMetricsObservers.add(observer);
+    }
+
+    /**
+     * Registers a {@link TabObserver} to be managed by this Registrar.
+     */
+    public void registerTabObserver(TabObserver observer) {
+        mTabObservers.add(observer);
+    }
+
+    @Override
+    public void didAddTab(Tab tab, int type) {
+        addObserversForTab(tab);
+    }
+
+    @Override
+    public void didCloseTab(int tabId, boolean incognito) {
+        // We don't need to remove the Tab Observers since it's closed.
+        // TODO(peconn): Do we really want to remove the *global* PageLoadMetrics observers here?
+        removePageLoadMetricsObservers();
+    }
+
+    @Override
+    public void tabRemoved(Tab tab) {
+        removePageLoadMetricsObservers();
+        removeTabObservers(tab);
+    }
+
+    /**
+     * Adds all currently registered {@link PageLoadMetrics.Observer}s and {@link TabObserver}s to
+     * the global {@link PageLoadMetrics} object and the given {@link Tab} respectively.
+     */
+    public void addObserversForTab(Tab tab) {
+        addPageLoadMetricsObservers();
+        addTabObservers(tab);
+    }
+
+    private void addPageLoadMetricsObservers() {
+        for (PageLoadMetrics.Observer observer : mPageLoadMetricsObservers) {
+            PageLoadMetrics.addObserver(observer);
+        }
+    }
+
+    private void removePageLoadMetricsObservers() {
+        for (PageLoadMetrics.Observer observer : mPageLoadMetricsObservers) {
+            PageLoadMetrics.removeObserver(observer);
+        }
+    }
+
+    private void addTabObservers(Tab tab) {
+        for (TabObserver observer : mTabObservers) {
+            tab.addObserver(observer);
+        }
+    }
+
+    private void removeTabObservers(Tab tab) {
+        for (TabObserver observer : mTabObservers) {
+            tab.removeObserver(observer);
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java
index de1a26bd87..1f809c2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java
@@ -107,4 +107,12 @@
         }
         return false;
     }
+
+    public void onBackPressedAsync(Runnable notHandledRunnable) {
+        try {
+            mActivityDelegate.onBackPressedAsync(ObjectWrapper.wrap(notHandledRunnable));
+        } catch (RemoteException e) {
+            assert false;
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/IActivityDelegate.aidl b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/IActivityDelegate.aidl
index 693d5f6c..9b8f494 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/IActivityDelegate.aidl
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/IActivityDelegate.aidl
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.customtabs.dynamicmodule;
 
+import org.chromium.chrome.browser.customtabs.dynamicmodule.IObjectWrapper;
+
 interface IActivityDelegate {
   void onCreate(in Bundle savedInstanceState) = 0;
 
@@ -26,4 +28,12 @@
   void onDestroy() = 9;
 
   boolean onBackPressed() = 10;
+
+  /**
+   * Offers an opportunity to handle the back press event. If it is not handled,
+   * the Runnable must be run.
+   *
+   * Introduced in API version 2.
+   */
+  void onBackPressedAsync(in IObjectWrapper /* Runnable */ notHandledRunnable) = 11;
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/device/DeviceClassManager.java b/chrome/android/java/src/org/chromium/chrome/browser/device/DeviceClassManager.java
index 93dbf502..4930d73 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/device/DeviceClassManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/device/DeviceClassManager.java
@@ -5,8 +5,10 @@
 package org.chromium.chrome.browser.device;
 
 import org.chromium.base.CommandLine;
+import org.chromium.base.StrictModeContext;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.ui.base.DeviceFormFactor;
 
@@ -92,8 +94,12 @@
      * @return Whether or not should use the accessibility tab switcher.
      */
     public static boolean enableAccessibilityLayout() {
-        return getInstance().mEnableAccessibilityLayout
-                || AccessibilityUtil.isAccessibilityEnabled();
+        if (getInstance().mEnableAccessibilityLayout) return true;
+        if (!AccessibilityUtil.isAccessibilityEnabled()) return false;
+        try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+            return ChromePreferenceManager.getInstance().readBoolean(
+                    ChromePreferenceManager.ACCESSIBILITY_TAB_SWITCHER, true);
+        }
     }
 
     /**
@@ -107,7 +113,12 @@
      * @return Whether or not we are showing animations.
      */
     public static boolean enableAnimations() {
-        return getInstance().mEnableAnimations && !AccessibilityUtil.isAccessibilityEnabled();
+        if (!getInstance().mEnableAnimations) return false;
+        if (!AccessibilityUtil.isAccessibilityEnabled()) return true;
+        try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+            return !ChromePreferenceManager.getInstance().readBoolean(
+                    ChromePreferenceManager.ACCESSIBILITY_TAB_SWITCHER, true);
+        }
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
index 0262acfd..b795617 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
@@ -26,8 +26,11 @@
         try {
             super.onCreate(savedInstanceState);
 
-            // VR Intents should only ever get routed through the VrMainActivity.
-            assert !VrModuleProvider.getIntentDelegate().isVrIntent(getIntent());
+            if (VrModuleProvider.getIntentDelegate().isVrIntent(getIntent())) {
+                // We need to turn VR mode on as early as possible in the intent handling flow to
+                // avoid brightness flickering when handling VR intents.
+                VrModuleProvider.getDelegate().setVrModeEnabled(this, true);
+            }
 
             @LaunchIntentDispatcher.Action
             int dispatchAction = LaunchIntentDispatcher.dispatch(this, getIntent());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
index 071c0ad..c99dabca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
@@ -46,6 +46,7 @@
     private final OfflineItemSource mSource;
     private final DateOrderedListMutator mListMutator;
     private final ThumbnailProvider mThumbnailProvider;
+    private final MediatorSelectionObserver mSelectionObserver;
 
     private final OffTheRecordOfflineItemFilter mOffTheRecordFilter;
     private final DeleteUndoOfflineItemFilter mDeleteUndoFilter;
@@ -53,6 +54,32 @@
     private final SearchOfflineItemFilter mSearchFilter;
 
     /**
+     * A selection observer that correctly updates the selection state for each item in the list.
+     */
+    private class MediatorSelectionObserver
+            implements SelectionDelegate.SelectionObserver<ListItem> {
+        private final SelectionDelegate<ListItem> mSelectionDelegate;
+
+        public MediatorSelectionObserver(SelectionDelegate<ListItem> delegate) {
+            mSelectionDelegate = delegate;
+            mSelectionDelegate.addObserver(this);
+        }
+
+        @Override
+        public void onSelectionStateChange(List<ListItem> selectedItems) {
+            for (int i = 0; i < mModel.size(); i++) {
+                ListItem item = mModel.get(i);
+                boolean selected = mSelectionDelegate.isItemSelected(item);
+                item.showSelectedAnimation = selected && !item.selected;
+                item.selected = selected;
+                mModel.setItem(i, item);
+            }
+            mModel.dispatchLastEvent();
+            mModel.getProperties().setSelectionModeActive(mSelectionDelegate.isSelectionEnabled());
+        }
+    }
+
+    /**
      * Creates an instance of a DateOrderedListMediator that will push {@code provider} into
      * {@code model}.
      * @param offTheRecord     Whether or not to include off the record items.
@@ -86,6 +113,7 @@
 
         mThumbnailProvider = new ThumbnailProviderImpl(
                 ((ChromeApplication) ContextUtils.getApplicationContext()).getReferencePool());
+        mSelectionObserver = new MediatorSelectionObserver(selectionDelegate);
 
         mModel.getProperties().setEnableItemAnimations(true);
         mModel.getProperties().setOpenCallback(mProvider::openItem);
@@ -95,7 +123,7 @@
         mModel.getProperties().setShareCallback(item -> {});
         mModel.getProperties().setRemoveCallback(this::onDeleteItem);
         mModel.getProperties().setVisualsProvider(this::getVisuals);
-        mModel.getProperties().setSelectionDelegate(selectionDelegate);
+        mModel.getProperties().setSelectionCallback(selectionDelegate::toggleSelectionForItem);
     }
 
     /** Tears down this mediator. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
index 693bd94..ea318f0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
@@ -16,6 +16,12 @@
 public abstract class ListItem {
     public final long stableId;
 
+    /** Indicates that we are in multi-select mode and the item is currently selected. */
+    public boolean selected;
+
+    /** Whether animation should be shown for the recent change in selection state for this item. */
+    public boolean showSelectedAnimation;
+
     /** Creates a {@link ListItem} instance. */
     ListItem(long stableId) {
         this.stableId = stableId;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyModel.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyModel.java
index 2b9f790..f001bab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyModel.java
@@ -6,7 +6,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.chrome.browser.modelutil.PropertyObservable;
-import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
 import org.chromium.components.offline_items_collection.OfflineItem;
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
 import org.chromium.components.offline_items_collection.VisualsCallback;
@@ -41,6 +40,8 @@
         static final PropertyKey CALLBACK_CANCEL = new PropertyKey();
         static final PropertyKey CALLBACK_SHARE = new PropertyKey();
         static final PropertyKey CALLBACK_REMOVE = new PropertyKey();
+        static final PropertyKey CALLBACK_SELECTION = new PropertyKey();
+        static final PropertyKey SELECTION_MODE_ACTIVE = new PropertyKey();
 
         // Utility properties.
         static final PropertyKey PROVIDER_VISUALS = new PropertyKey();
@@ -49,14 +50,15 @@
     }
 
     private boolean mEnableItemAnimations;
+    private boolean mSelectionModeActive;
     private Callback<OfflineItem> mOpenCallback;
     private Callback<OfflineItem> mPauseCallback;
     private Callback<OfflineItem> mResumeCallback;
     private Callback<OfflineItem> mCancelCallback;
     private Callback<OfflineItem> mShareCallback;
     private Callback<OfflineItem> mRemoveCallback;
+    private Callback<ListItem> mSelectionCallback;
     private VisualsProvider mVisualsProvider;
-    private SelectionDelegate<ListItem> mSelectionDelegate;
 
     /** Sets whether or not item animations should be enabled. */
     public void setEnableItemAnimations(boolean enableItemAnimations) {
@@ -70,6 +72,18 @@
         return mEnableItemAnimations;
     }
 
+    /** Sets whether or not selection mode is currently active. */
+    public void setSelectionModeActive(boolean selectionModeActive) {
+        if (mSelectionModeActive == selectionModeActive) return;
+        mSelectionModeActive = selectionModeActive;
+        notifyPropertyChanged(PropertyKey.SELECTION_MODE_ACTIVE);
+    }
+
+    /** @return Whether or not selection mode is currently active. */
+    public boolean getSelectionModeActive() {
+        return mSelectionModeActive;
+    }
+
     /** Sets the callback for when a UI action should open a {@link OfflineItem}. */
     public void setOpenCallback(Callback<OfflineItem> callback) {
         if (mOpenCallback == callback) return;
@@ -154,13 +168,15 @@
         return mVisualsProvider;
     }
 
-    /** Sets the selection delegate to handle selection of list items. */
-    public void setSelectionDelegate(SelectionDelegate<ListItem> selectionDelegate) {
-        mSelectionDelegate = selectionDelegate;
+    /** Sets the callback for when an {@link ListItem} is selected or deselected on the UI. */
+    public void setSelectionCallback(Callback<ListItem> callback) {
+        if (mSelectionCallback == callback) return;
+        mSelectionCallback = callback;
+        notifyPropertyChanged(PropertyKey.CALLBACK_SELECTION);
     }
 
-    /** @return The selection delegate to handle multiple item selections. */
-    public SelectionDelegate<ListItem> getSelectionDelegate() {
-        return mSelectionDelegate;
+    /** @return The callback to trigger when a UI action selects or deselects a {@link ListItem}. */
+    public Callback<ListItem> getSelectionCallback() {
+        return mSelectionCallback;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
index f957fb4..d4ff453f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListPropertyViewBinder.java
@@ -33,7 +33,9 @@
                 || propertyKey == ListPropertyModel.PropertyKey.CALLBACK_CANCEL
                 || propertyKey == ListPropertyModel.PropertyKey.CALLBACK_SHARE
                 || propertyKey == ListPropertyModel.PropertyKey.CALLBACK_REMOVE
-                || propertyKey == ListPropertyModel.PropertyKey.PROVIDER_VISUALS) {
+                || propertyKey == ListPropertyModel.PropertyKey.PROVIDER_VISUALS
+                || propertyKey == ListPropertyModel.PropertyKey.CALLBACK_SELECTION
+                || propertyKey == ListPropertyModel.PropertyKey.SELECTION_MODE_ACTIVE) {
             view.getAdapter().notifyItemChanged(0, view.getAdapter().getItemCount());
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/GenericViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/GenericViewHolder.java
index eb13e2b..274ddbd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/GenericViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/GenericViewHolder.java
@@ -4,11 +4,9 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
-import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.support.annotation.DrawableRes;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 import android.support.v7.content.res.AppCompatResources;
@@ -19,10 +17,10 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.download.home.list.ListItem;
 import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
 import org.chromium.chrome.browser.download.home.list.UiUtils;
+import org.chromium.chrome.browser.download.home.view.SelectionView;
 import org.chromium.chrome.browser.widget.TintedImageView;
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
 
@@ -31,9 +29,6 @@
 public class GenericViewHolder extends ThumbnailAwareViewHolder {
     private static final int INVALID_ID = -1;
 
-    private final ColorStateList mCheckedIconForegroundColorList;
-    private final AnimatedVectorDrawableCompat mCheckDrawable;
-
     private final TextView mTitle;
     private final TextView mCaption;
     private final TintedImageView mThumbnailView;
@@ -61,11 +56,6 @@
         mTitle = (TextView) itemView.findViewById(R.id.title);
         mCaption = (TextView) itemView.findViewById(R.id.caption);
         mThumbnailView = (TintedImageView) itemView.findViewById(R.id.thumbnail);
-
-        mCheckDrawable = AnimatedVectorDrawableCompat.create(
-                itemView.getContext(), R.drawable.ic_check_googblue_24dp_animated);
-        mCheckedIconForegroundColorList =
-                DownloadUtils.getIconForegroundColorList(itemView.getContext());
     }
 
     // ListItemViewHolder implementation.
@@ -89,18 +79,9 @@
 
     private void updateThumbnailView() {
         Resources resources = itemView.getContext().getResources();
-
-        // TODO(shaktisahu): Pass the appropriate value of selection.
-        boolean selected = false;
-        if (selected) {
-            mThumbnailView.setBackgroundResource(R.drawable.list_item_icon_modern_bg);
-            mThumbnailView.getBackground().setLevel(
-                    resources.getInteger(R.integer.list_item_level_selected));
-
-            mThumbnailView.setImageDrawable(mCheckDrawable);
-            mThumbnailView.setTint(mCheckedIconForegroundColorList);
-            mCheckDrawable.start();
-        } else if (mThumbnailBitmap != null) {
+        SelectionView selectionView = itemView.findViewById(R.id.selection);
+        mThumbnailView.setVisibility(selectionView.isSelected() ? View.GONE : View.VISIBLE);
+        if (mThumbnailBitmap != null) {
             assert !mThumbnailBitmap.isRecycled();
 
             mThumbnailView.setBackground(null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
index 9dfedb47..93bad6ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ImageViewHolder.java
@@ -4,27 +4,18 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.download.home.list.ListItem;
 import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
-import org.chromium.chrome.browser.widget.TintedImageView;
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
 
 /** A {@link RecyclerView.ViewHolder} specifically meant to display an image {@code OfflineItem}. */
 public class ImageViewHolder extends ThumbnailAwareViewHolder {
-    private final TintedImageView mSelectionImage;
-    private final ColorStateList mCheckedIconForegroundColorList;
-    private final AnimatedVectorDrawableCompat mCheckDrawable;
-
     public static org.chromium.chrome.browser.download.home.list.holder.ImageViewHolder create(
             ViewGroup parent) {
         View view = LayoutInflater.from(parent.getContext())
@@ -37,11 +28,6 @@
 
     public ImageViewHolder(View view, int thumbnailSizePx) {
         super(view, thumbnailSizePx, thumbnailSizePx);
-        mCheckDrawable = AnimatedVectorDrawableCompat.create(
-                itemView.getContext(), R.drawable.ic_check_googblue_24dp_animated);
-        mCheckedIconForegroundColorList =
-                DownloadUtils.getIconForegroundColorList(itemView.getContext());
-        mSelectionImage = (TintedImageView) itemView.findViewById(R.id.selection);
     }
 
     // ThumbnailAwareViewHolder implementation.
@@ -51,39 +37,10 @@
         ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
         View imageView = itemView.findViewById(R.id.thumbnail);
         imageView.setContentDescription(offlineItem.item.title);
-        updateImageView();
     }
 
     @Override
     void onVisualsChanged(ImageView view, OfflineItemVisuals visuals) {
         view.setImageBitmap(visuals == null ? null : visuals.icon);
-        updateImageView();
-    }
-
-    protected void updateImageView() {
-        Resources resources = itemView.getContext().getResources();
-        // TODO(shaktisahu): Pass the appropriate value of selection.
-        boolean selected = false;
-        boolean selectionModeActive = false;
-        if (selected) {
-            mSelectionImage.setVisibility(View.VISIBLE);
-            mSelectionImage.setBackgroundResource(R.drawable.list_item_icon_modern_bg);
-            mSelectionImage.getBackground().setLevel(
-                    resources.getInteger(R.integer.list_item_level_selected));
-
-            mSelectionImage.setImageDrawable(mCheckDrawable);
-            mSelectionImage.setTint(mCheckedIconForegroundColorList);
-            mCheckDrawable.start();
-        } else if (selectionModeActive) {
-            mSelectionImage.setVisibility(View.VISIBLE);
-            mSelectionImage.setBackground(null);
-            mSelectionImage.setTint(null);
-
-            mSelectionImage.setImageResource(R.drawable.download_circular_selector_transparent);
-        } else {
-            mSelectionImage.setBackground(null);
-            mSelectionImage.setTint(null);
-            mSelectionImage.setVisibility(View.GONE);
-        }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java
index a5b2ee7..9672773 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java
@@ -37,6 +37,7 @@
         ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
         mShareCallback = () -> properties.getShareCallback().onResult(offlineItem.item);
         mDeleteCallback = () -> properties.getRemoveCallback().onResult(offlineItem.item);
+        if (mMore != null) mMore.setClickable(!properties.getSelectionModeActive());
     }
 
     // ListMenuButton.Delegate implementation.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java
index 5b832748..42b04fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java
@@ -12,6 +12,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.download.home.list.ListItem;
 import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
+import org.chromium.chrome.browser.download.home.view.SelectionView;
 import org.chromium.components.offline_items_collection.ContentId;
 import org.chromium.components.offline_items_collection.OfflineItem;
 import org.chromium.components.offline_items_collection.OfflineItemVisuals;
@@ -22,6 +23,7 @@
  */
 abstract class ThumbnailAwareViewHolder extends MoreButtonViewHolder implements VisualsCallback {
     private final ImageView mThumbnail;
+    private final SelectionView mSelectionView;
 
     /**
      * The {@link ContentId} of the associated thumbnail/request if any.
@@ -58,6 +60,7 @@
         super(view);
 
         mThumbnail = (ImageView) view.findViewById(R.id.thumbnail);
+        mSelectionView = itemView.findViewById(R.id.selection);
         mWidthPx = thumbnailWidthPx;
         mHeightPx = thumbnailHeightPx;
     }
@@ -73,9 +76,27 @@
         OfflineItem offlineItem = ((ListItem.OfflineItemListItem) item).item;
 
         // If we're rebinding the same item, ignore the bind.
-        if (offlineItem.id.equals(mId)) return;
+        if (offlineItem.id.equals(mId) && !selectionStateHasChanged(properties, item)) {
+            return;
+        }
 
-        // TODO(shaktisahu): Add callbacks for selection and open.
+        if (mSelectionView != null) {
+            mSelectionView.setSelectionState(
+                    item.selected, properties.getSelectionModeActive(), item.showSelectedAnimation);
+        }
+
+        itemView.setOnLongClickListener(v -> {
+            properties.getSelectionCallback().onResult(item);
+            return true;
+        });
+
+        itemView.setOnClickListener(v -> {
+            if (mSelectionView != null && mSelectionView.isInSelectionMode()) {
+                properties.getSelectionCallback().onResult(item);
+            } else {
+                properties.getOpenCallback().onResult(offlineItem);
+            }
+        });
 
         // Clear any associated bitmap from the thumbnail.
         if (mId != null) onVisualsChanged(mThumbnail, null);
@@ -92,6 +113,13 @@
         if (!mIsRequesting) mCancellable = null;
     }
 
+    private boolean selectionStateHasChanged(ListPropertyModel properties, ListItem item) {
+        if (mSelectionView == null) return false;
+
+        return mSelectionView.isSelected() != item.selected
+                || mSelectionView.isInSelectionMode() != properties.getSelectionModeActive();
+    }
+
     // VisualsCallback implementation.
     @Override
     public void onVisualsAvailable(ContentId id, OfflineItemVisuals visuals) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java
new file mode 100644
index 0000000..de4bfff
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java
@@ -0,0 +1,82 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.download.home.view;
+
+import android.content.Context;
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.widget.TintedImageView;
+
+/**
+ * A helper UI widget that provides visual feedback when the selection state of the underlying view
+ * is changed. The widget represents three distinct states : selected, in selection mode and not
+ * selected. The caller can define the UI behavior at each of these states by subclassing this view.
+ */
+public class SelectionView extends FrameLayout {
+    private final TintedImageView mCheck;
+    private final TintedImageView mCircle;
+    private final AnimatedVectorDrawableCompat mCheckDrawable;
+
+    private boolean mIsSelected;
+    private boolean mInSelectionMode;
+    private boolean mShowSelectedAnimation;
+
+    /** Constructor for inflating from XML. */
+    public SelectionView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        LayoutInflater.from(context).inflate(R.layout.list_selection_handle_view, this, true);
+        mCheck = (TintedImageView) findViewById(R.id.check);
+        mCircle = (TintedImageView) findViewById(R.id.circle);
+        mCheckDrawable = AnimatedVectorDrawableCompat.create(
+                context, R.drawable.ic_check_googblue_24dp_animated);
+    }
+
+    @Override
+    public boolean isSelected() {
+        return mIsSelected;
+    }
+
+    /** @return Whether the selection mode is currently active. */
+    public boolean isInSelectionMode() {
+        return mInSelectionMode;
+    }
+
+    /**
+     * Called to inform the view about its current selection state.
+     * @param selected Whether the item is currently selected.
+     * @param inSelectionMode Whether we are currently in active selection mode.
+     * @param showSelectedAnimation Whether the item was recently selected from an unselected state
+     * and animation should be shown.
+     */
+    public void setSelectionState(
+            boolean selected, boolean inSelectionMode, boolean showSelectedAnimation) {
+        mIsSelected = selected;
+        mInSelectionMode = inSelectionMode;
+        mShowSelectedAnimation = showSelectedAnimation;
+        updateView();
+    }
+
+    private void updateView() {
+        if (mIsSelected) {
+            mCheck.setVisibility(VISIBLE);
+            mCircle.setVisibility(GONE);
+
+            mCheck.setImageDrawable(mCheckDrawable);
+            mCheck.getBackground().setLevel(
+                    getResources().getInteger(R.integer.list_item_level_selected));
+            if (mShowSelectedAnimation) mCheckDrawable.start();
+        } else if (mInSelectionMode) {
+            mCheck.setVisibility(GONE);
+            mCircle.setVisibility(VISIBLE);
+        } else {
+            mCheck.setVisibility(GONE);
+            mCircle.setVisibility(GONE);
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java
index 546c582..064d5164 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/DelayedInvalidationsController.java
@@ -82,7 +82,8 @@
                 }
                 return null;
             }
-        }.execute();
+        }
+                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java
index 03edb62..b8525e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java
@@ -38,12 +38,7 @@
                         ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
         dialogView.setBackgroundResource(R.drawable.popup_bg);
         container.addView(dialogView, params);
-        if (getModalDialog().getCancelOnTouchOutside()) {
-            mDialog.setCanceledOnTouchOutside(true);
-            // The dialog container covers the entire screen. To achieve the cancel on touch outside
-            // behavior cancel the dialog if the container is touched.
-            container.setOnClickListener((v) -> mDialog.cancel());
-        }
+        mDialog.setCanceledOnTouchOutside(getModalDialog().getCancelOnTouchOutside());
         mDialog.show();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modelutil/PropertyListObservable.java b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/PropertyListObservable.java
new file mode 100644
index 0000000..57ee39b
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/PropertyListObservable.java
@@ -0,0 +1,54 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package org.chromium.chrome.browser.modelutil;
+
+import android.support.annotation.Nullable;
+
+import java.util.Collection;
+
+/**
+ * Represents a list of (property-)observable items, and notifies about changes to any of its items.
+ *
+ * @param <T> The type of item in the list.
+ * @param <P> The property key type for {@code T} to be used as payload for partial updates.
+ */
+public class PropertyListObservable<T extends PropertyObservable<P>, P>
+        extends SimpleListObservableBase<T, P> {
+    private final PropertyObservable.PropertyObserver<P> mPropertyObserver =
+            this::onPropertyChanged;
+
+    @Override
+    public void add(T item) {
+        super.add(item);
+        item.addObserver(mPropertyObserver);
+    }
+
+    @Override
+    public void remove(T item) {
+        item.removeObserver(mPropertyObserver);
+        super.remove(item);
+    }
+
+    @Override
+    public void update(int index, T item) {
+        get(index).removeObserver(mPropertyObserver);
+        super.update(index, item);
+        item.addObserver(mPropertyObserver);
+    }
+
+    @Override
+    public void set(Collection<T> newItems) {
+        for (T item : this) {
+            item.removeObserver(mPropertyObserver);
+        }
+        super.set(newItems);
+        for (T item : newItems) {
+            item.addObserver(mPropertyObserver);
+        }
+    }
+
+    private void onPropertyChanged(PropertyObservable<P> source, @Nullable P propertyKey) {
+        notifyItemChanged(indexOf(source), propertyKey);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/modelutil/SimpleListObservableBase.java b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/SimpleListObservableBase.java
index 5e72516..2b539f3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/modelutil/SimpleListObservableBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/modelutil/SimpleListObservableBase.java
@@ -77,10 +77,12 @@
     /**
      * Removes an item by position from the held {@link List}. Notifies observers about the removal.
      * @param position The position of the item to be removed.
+     * @return The item that has been removed.
      */
-    public void removeAt(int position) {
-        mItems.remove(position);
+    public T removeAt(int position) {
+        T item = mItems.remove(position);
         notifyItemRemoved(position);
+        return item;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
index 42bdfb30..8939d903 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
@@ -14,8 +14,6 @@
 
 /**
  * Abstraction over Notification.Builder and NotificationCompat.Builder interfaces.
- *
- * TODO(awdf) Remove this once we've updated to revision 26 of the support library.
  */
 public interface ChromeNotificationBuilder {
     ChromeNotificationBuilder setAutoCancel(boolean autoCancel);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
index 1a112ef..495291eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSection.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.ntp.cards;
 
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 
@@ -12,6 +11,9 @@
 import org.chromium.base.Log;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.modelutil.ListObservable;
+import org.chromium.chrome.browser.modelutil.PropertyListObservable;
+import org.chromium.chrome.browser.modelutil.SimpleListObservableBase;
+import org.chromium.chrome.browser.modelutil.SimpleRecyclerViewMcpBase;
 import org.chromium.chrome.browser.ntp.NewTabPageUma;
 import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.PartialBindCallback;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
@@ -30,11 +32,9 @@
 import org.chromium.chrome.browser.suggestions.SuggestionsRanker;
 import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -50,6 +50,10 @@
     private final SuggestionsCategoryInfo mCategoryInfo;
     private final OfflineModelObserver mOfflineModelObserver;
     private final SuggestionsSource mSuggestionsSource;
+    private final SuggestionsRanker mSuggestionsRanker;
+
+    private final PropertyListObservable<SnippetArticle, PartialBindCallback> mSuggestions =
+            new PropertyListObservable<>();
 
     // Children
     private final SectionHeader mHeader;
@@ -93,6 +97,7 @@
         mDelegate = delegate;
         mCategoryInfo = info;
         mSuggestionsSource = uiDelegate.getSuggestionsSource();
+        mSuggestionsRanker = ranker;
 
         boolean isExpandable = ChromeFeatureList.isEnabled(
                                        ChromeFeatureList.NTP_ARTICLE_SUGGESTIONS_EXPANDABLE_HEADER)
@@ -102,7 +107,8 @@
         mHeader = isExpandable ? new SectionHeader(info.getTitle(), isExpanded,
                                          this::updateSuggestionsVisibilityForExpandableHeader)
                                : new SectionHeader(info.getTitle());
-        mSuggestionsList = new SuggestionsList(mSuggestionsSource, ranker, info);
+        mSuggestionsList =
+                new SuggestionsList(mSuggestionsSource, mSuggestions, this::bindSuggestion);
         mMoreButton = new ActionItem(this, ranker);
 
         mStatus = StatusItem.createNoSuggestionsItem(info);
@@ -111,88 +117,21 @@
 
         mOfflineModelObserver = new OfflineModelObserver(offlinePageBridge);
         uiDelegate.addDestructionObserver(mOfflineModelObserver);
-
     }
 
-    private static class SuggestionsList
-            extends ChildNode<NewTabPageViewHolder, PartialBindCallback>
-            implements Iterable<SnippetArticle>, PartiallyBindable {
-        private final List<SnippetArticle> mSuggestions = new ArrayList<>();
-
+    private static class SuggestionsList extends SimpleRecyclerViewMcpBase<SnippetArticle,
+            NewTabPageViewHolder, PartialBindCallback> {
         private final SuggestionsSource mSuggestionsSource;
-        private final SuggestionsRanker mSuggestionsRanker;
-        private final SuggestionsCategoryInfo mCategoryInfo;
+        private final SimpleListObservableBase<SnippetArticle, PartialBindCallback> mSuggestions;
 
         private boolean mIsDestroyed;
 
-        public SuggestionsList(SuggestionsSource suggestionsSource, SuggestionsRanker ranker,
-                SuggestionsCategoryInfo categoryInfo) {
+        public SuggestionsList(SuggestionsSource suggestionsSource,
+                PropertyListObservable<SnippetArticle, PartialBindCallback> suggestions,
+                ViewBinder<SnippetArticle, NewTabPageViewHolder, PartialBindCallback> viewBinder) {
+            super(ignored -> ItemViewType.SNIPPET, viewBinder, suggestions);
             mSuggestionsSource = suggestionsSource;
-            mSuggestionsRanker = ranker;
-            mCategoryInfo = categoryInfo;
-        }
-
-        @Override
-        protected int getItemCountForDebugging() {
-            return mSuggestions.size();
-        }
-
-        @Override
-        @ItemViewType
-        public int getItemViewType(int position) {
-            checkIndex(position);
-            return ItemViewType.SNIPPET;
-        }
-
-        @Override
-        public void onBindViewHolder(NewTabPageViewHolder holder, int position) {
-            checkIndex(position);
-            SnippetArticle suggestion = getSuggestionAt(position);
-            mSuggestionsRanker.rankSuggestion(suggestion);
-            ((SnippetArticleViewHolder) holder).onBindViewHolder(suggestion, mCategoryInfo);
-        }
-
-        public SnippetArticle getSuggestionAt(int position) {
-            return mSuggestions.get(position);
-        }
-
-        public void clear() {
-            int itemCount = mSuggestions.size();
-            if (itemCount == 0) return;
-
-            mSuggestions.clear();
-            notifyItemRangeRemoved(0, itemCount);
-        }
-
-        /**
-         * Clears all suggestions except for the first {@code n} suggestions.
-         */
-        private void clearAllButFirstN(int n) {
-            int itemCount = mSuggestions.size();
-            if (itemCount > n) {
-                mSuggestions.subList(n, itemCount).clear();
-                notifyItemRangeRemoved(n, itemCount - n);
-            }
-        }
-
-        public void addAll(List<SnippetArticle> suggestions) {
-            if (suggestions.isEmpty()) return;
-
-            int insertionPointIndex = mSuggestions.size();
-            mSuggestions.addAll(suggestions);
-            notifyItemRangeInserted(insertionPointIndex, suggestions.size());
-        }
-
-        public SnippetArticle remove(int position) {
-            SnippetArticle suggestion = mSuggestions.remove(position);
-            notifyItemRemoved(position);
-            return suggestion;
-        }
-
-        @NonNull
-        @Override
-        public Iterator<SnippetArticle> iterator() {
-            return mSuggestions.iterator();
+            mSuggestions = suggestions;
         }
 
         @Override
@@ -208,7 +147,6 @@
 
         @Override
         public void dismissItem(int position, Callback<String> itemRemovedCallback) {
-            checkIndex(position);
             if (mIsDestroyed) {
                 // It is possible for this method to be called after the NewTabPage has had
                 // destroy() called. This can happen when
@@ -218,23 +156,23 @@
                 return;
             }
 
-            SnippetArticle suggestion = remove(position);
+            SnippetArticle suggestion = mSuggestions.removeAt(position);
             mSuggestionsSource.dismissSuggestion(suggestion);
             itemRemovedCallback.onResult(suggestion.mTitle);
         }
 
-        public void updateSuggestionOfflineId(
-                SnippetArticle article, Long newId, boolean isPrefetched) {
-            int index = mSuggestions.indexOf(article);
+        public void updateSuggestionOfflineId(int position, Long newId, boolean isPrefetched) {
+            SnippetArticle article = mSuggestions.get(position);
             // The suggestions could have been removed / replaced in the meantime.
-            if (index == -1) return;
+            if (position == -1) return;
 
             Long oldId = article.getOfflinePageOfflineId();
             article.setOfflinePageOfflineId(newId);
             article.setIsPrefetched(isPrefetched);
 
+            // TODO(bauerb): This notification should be sent by the article itself.
             if ((oldId == null) == (newId == null)) return;
-            notifyItemChanged(index, SnippetArticleViewHolder::refreshOfflineBadgeVisibility);
+            notifyItemChanged(position, SnippetArticleViewHolder::refreshOfflineBadgeVisibility);
         }
 
         public void destroy() {
@@ -316,9 +254,9 @@
      */
     public void removeSuggestionById(String idWithinCategory) {
         int i = 0;
-        for (SnippetArticle suggestion : mSuggestionsList) {
+        for (SnippetArticle suggestion : mSuggestions) {
             if (suggestion.mIdWithinCategory.equals(idWithinCategory)) {
-                mSuggestionsList.remove(i);
+                mSuggestions.removeAt(i);
                 return;
             }
             i++;
@@ -328,7 +266,7 @@
     private int getNumberOfSuggestionsExposed() {
         int exposedCount = 0;
         int suggestionsCount = 0;
-        for (SnippetArticle suggestion : mSuggestionsList) {
+        for (SnippetArticle suggestion : mSuggestions) {
             ++suggestionsCount;
             // We treat all suggestions preceding an exposed suggestion as exposed too.
             if (suggestion.mExposed) exposedCount = suggestionsCount;
@@ -338,15 +276,15 @@
     }
 
     private boolean hasSuggestions() {
-        return mSuggestionsList.getItemCount() != 0;
+        return mSuggestions.size() != 0;
     }
 
     public int getSuggestionsCount() {
-        return mSuggestionsList.getItemCount();
+        return mSuggestions.size();
     }
 
     public SnippetArticle getSuggestionForTesting(int index) {
-        return mSuggestionsList.getSuggestionAt(index);
+        return mSuggestions.get(index);
     }
 
     public boolean isDataStale() {
@@ -367,9 +305,9 @@
     }
 
     private String[] getDisplayedSuggestionIds() {
-        String[] suggestionIds = new String[mSuggestionsList.getItemCount()];
-        for (int i = 0; i < mSuggestionsList.getItemCount(); ++i) {
-            suggestionIds[i] = mSuggestionsList.getSuggestionAt(i).mIdWithinCategory;
+        String[] suggestionIds = new String[mSuggestions.size()];
+        for (int i = 0; i < mSuggestions.size(); ++i) {
+            suggestionIds[i] = mSuggestions.get(i).mIdWithinCategory;
         }
         return suggestionIds;
     }
@@ -396,7 +334,7 @@
         List<SnippetArticle> suggestions =
                 mSuggestionsSource.getSuggestionsForCategory(getCategory());
         Log.d(TAG, "Received %d new suggestions for category %d, had %d previously.",
-                suggestions.size(), getCategory(), mSuggestionsList.getItemCount());
+                suggestions.size(), getCategory(), mSuggestions.size());
 
         // Nothing to append, we can just exit now.
         // TODO(dgn): Distinguish the init case where we have to wait? (https://crbug.com/711457)
@@ -429,13 +367,18 @@
         if (keepSectionSize) {
             Log.d(TAG, "updateSuggestions: keeping the first %d suggestion",
                     numberOfSuggestionsExposed);
-            int numberofSuggestionsToAppend =
+            int numSuggestionsToAppend =
                     Math.max(0, suggestions.size() - numberOfSuggestionsExposed);
-            mSuggestionsList.clearAllButFirstN(numberOfSuggestionsExposed);
-            trimIncomingSuggestions(suggestions,
-                    /* targetSize = */ numberofSuggestionsToAppend);
+            int itemCount = mSuggestions.size();
+            if (itemCount > numberOfSuggestionsExposed) {
+                mSuggestions.removeRange(
+                        numberOfSuggestionsExposed, itemCount - numberOfSuggestionsExposed);
+            }
+            trimIncomingSuggestions(suggestions, /* targetSize = */ numSuggestionsToAppend);
         }
-        mSuggestionsList.addAll(suggestions);
+        if (!suggestions.isEmpty()) {
+            mSuggestions.addAll(suggestions);
+        }
 
         mOfflineModelObserver.updateAllSuggestionsOfflineAvailability(
                 reportPrefetchedSuggestionsCount);
@@ -458,7 +401,7 @@
      * the incoming list.
      */
     private void trimIncomingSuggestions(List<SnippetArticle> suggestions, int targetSize) {
-        for (SnippetArticle suggestion : mSuggestionsList) {
+        for (SnippetArticle suggestion : mSuggestions) {
             suggestions.remove(suggestion);
         }
 
@@ -547,7 +490,7 @@
 
     /** Clears the suggestions and related data, resetting the state of the section. */
     public void clearData() {
-        mSuggestionsList.clear();
+        mSuggestions.set(Collections.emptyList());
         mHasAppended = false;
         mIsDataStale = false;
     }
@@ -653,13 +596,25 @@
             boolean isPrefetched = item != null
                     && TextUtils.equals(item.getClientId().getNamespace(),
                                OfflinePageBridge.SUGGESTED_ARTICLES_NAMESPACE);
-            mSuggestionsList.updateSuggestionOfflineId(
-                    suggestion, item == null ? null : item.getOfflineId(), isPrefetched);
+
+            mSuggestionsList.updateSuggestionOfflineId(mSuggestions.indexOf(suggestion),
+                    item == null ? null : item.getOfflineId(), isPrefetched);
         }
 
         @Override
         public Iterable<SnippetArticle> getOfflinableSuggestions() {
-            return mSuggestionsList;
+            return mSuggestions;
         }
     }
+
+    private void bindSuggestion(NewTabPageViewHolder holder, SnippetArticle suggestion,
+            @Nullable PartialBindCallback callback) {
+        if (callback != null) {
+            callback.onResult(holder);
+            return;
+        }
+
+        mSuggestionsRanker.rankSuggestion(suggestion);
+        ((SnippetArticleViewHolder) holder).onBindViewHolder(suggestion, mCategoryInfo);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
index 92e2fd4a9..818c961 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetArticle.java
@@ -9,6 +9,8 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.DiscardableReferencePool.DiscardableReference;
+import org.chromium.chrome.browser.modelutil.PropertyObservable;
+import org.chromium.chrome.browser.ntp.cards.NewTabPageViewHolder.PartialBindCallback;
 import org.chromium.chrome.browser.suggestions.OfflinableSuggestion;
 
 import java.io.File;
@@ -16,7 +18,8 @@
 /**
  * Represents the data for an article card on the NTP.
  */
-public class SnippetArticle implements OfflinableSuggestion {
+public class SnippetArticle
+        extends PropertyObservable<PartialBindCallback> implements OfflinableSuggestion {
     /** The category of this article. */
     public final int mCategory;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
index 663dea3..c4e72da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
@@ -132,7 +132,8 @@
                 activity.onCheckForUpdate(mUpdateAvailable);
                 recordUpdateHistogram();
             }
-        }.execute();
+        }
+                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditText.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditText.java
index 1fb9e4b..bec71a4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditText.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditText.java
@@ -196,27 +196,13 @@
         if (DEBUG) Log.i(TAG, "setText -- text: %s", text);
         mDisableTextScrollingFromAutocomplete = false;
 
-        // Avoid setting the same text as it will mess up the scroll/cursor position.
-        // Setting the text is also quite expensive, so only do it when the text has changed.
-        if (!isNewTextEquivalentToExistingText(text)) {
-            // Certain OEM implementations of setText trigger disk reads. https://crbug.com/633298
-            try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
-                super.setText(text, type);
-            }
+        // Certain OEM implementations of setText trigger disk reads. https://crbug.com/633298
+        try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+            super.setText(text, type);
         }
         if (mModel != null) mModel.onSetText(text);
     }
 
-    /**
-     * Whether the specified new text is equivalent to what is already being shown in the TextView.
-     *
-     * @param newCharSequence The proposed new text to replace the existing text.
-     * @return Whether the text is the same.
-     */
-    protected boolean isNewTextEquivalentToExistingText(CharSequence newCharSequence) {
-        return TextUtils.equals(getEditableText(), newCharSequence);
-    }
-
     @Override
     public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
         if (shouldIgnoreAccessibilityEvent(event)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index de599ce..f8ae057 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -17,14 +17,11 @@
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.os.SystemClock;
-import android.provider.Settings;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.support.v4.view.MarginLayoutParamsCompat;
 import android.support.v7.content.res.AppCompatResources;
-import android.text.Editable;
-import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.SparseArray;
@@ -62,6 +59,8 @@
 import org.chromium.chrome.browser.omnibox.AutocompleteController.OnSuggestionsReceivedListener;
 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem;
 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggestionDelegate;
+import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
 import org.chromium.chrome.browser.page_info.PageInfoController;
 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager;
@@ -115,13 +114,14 @@
     protected TextView mVerboseStatusTextView;
     protected TintedImageButton mDeleteButton;
     protected TintedImageButton mMicButton;
-    protected UrlBar mUrlBar;
+    protected View mUrlBar;
     private final boolean mIsTablet;
 
     /** A handle to the bottom sheet for chrome home. */
     protected BottomSheet mBottomSheet;
 
     private AutocompleteController mAutocomplete;
+    protected UrlBarCoordinator mUrlCoordinator;
 
     protected ToolbarDataProvider mToolbarDataProvider;
     private ObserverList<UrlFocusChangeListener> mUrlFocusChangeListeners = new ObserverList<>();
@@ -274,15 +274,16 @@
                                 mSuggestionList.getSelectedItemPosition());
                 // Set the UrlBar text to empty, so that it will trigger a text change when we
                 // set the text to the suggestion again.
-                setUrlBarText(UrlBarData.EMPTY);
-                mUrlBar.setText(selectedItem.getSuggestion().getFillIntoEdit());
+                setUrlBarTextEmpty();
+                setUrlBarText(
+                        UrlBarData.forNonUrlText(selectedItem.getSuggestion().getFillIntoEdit()),
+                        UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_END);
                 mSuggestionList.setSelection(0);
-                mUrlBar.setSelection(mUrlBar.getText().length());
                 return true;
             } else if (KeyNavigationUtil.isEnter(event)
                     && LocationBarLayout.this.getVisibility() == VISIBLE) {
                 UiUtils.hideKeyboard(mUrlBar);
-                final String urlText = mUrlBar.getTextWithAutocomplete();
+                final String urlText = mUrlCoordinator.getTextWithAutocomplete();
                 if (mNativeInitialized) {
                     findMatchAndLoadUrl(urlText);
                 } else {
@@ -402,20 +403,9 @@
 
         mDeleteButton = (TintedImageButton) findViewById(R.id.delete_button);
 
-        mUrlBar = (UrlBar) findViewById(R.id.url_bar);
-        // The HTC Sense IME will attempt to autocomplete words in the Omnibox when Prediction is
-        // enabled.  We want to disable this feature and rely on the Omnibox's implementation.
-        // Their IME does not respect ~TYPE_TEXT_FLAG_AUTO_COMPLETE nor any of the other InputType
-        // options I tried, but setting the filter variation prevents it.  Sadly, it also removes
-        // the .com button, but the prediction was buggy as it would autocomplete words even when
-        // typing at the beginning of the omnibox text when other content was present (messing up
-        // what was previously there).  See bug: http://b/issue?id=6200071
-        String defaultIme = Settings.Secure.getString(getContext().getContentResolver(),
-                Settings.Secure.DEFAULT_INPUT_METHOD);
-        if (defaultIme != null && defaultIme.contains("com.htc.android.htcime")) {
-            mUrlBar.setInputType(mUrlBar.getInputType() | InputType.TYPE_TEXT_VARIATION_FILTER);
-        }
-        mUrlBar.setDelegate(this);
+        mUrlBar = findViewById(R.id.url_bar);
+        mUrlCoordinator = new UrlBarCoordinator((UrlBar) mUrlBar);
+        mUrlCoordinator.setDelegate(this);
 
         mSuggestionItems = new ArrayList<OmniboxResultItem>();
         mSuggestionListAdapter = new OmniboxResultsAdapter(getContext(), this, mSuggestionItems);
@@ -436,8 +426,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mUrlBar.setCursorVisible(false);
-
         mLocationBarButtonType = LocationBarButtonType.NONE;
         mNavigationButton.setVisibility(INVISIBLE);
         mSecurityButton.setVisibility(INVISIBLE);
@@ -482,7 +470,7 @@
 
         // mLocationBar's direction is tied to this UrlBar's text direction. Icons inside the
         // location bar, e.g. lock, refresh, X, should be reversed if UrlBar's text is RTL.
-        mUrlBar.setUrlDirectionListener(new UrlBar.UrlDirectionListener() {
+        mUrlCoordinator.setUrlDirectionListener(new UrlBar.UrlDirectionListener() {
             @Override
             public void onUrlDirectionChanged(int layoutDirection) {
                 ApiCompatibilityUtils.setLayoutDirection(LocationBarLayout.this, layoutDirection);
@@ -529,10 +517,7 @@
         mWindowDelegate = windowDelegate;
         mWindowAndroid = windowAndroid;
 
-        mUrlBar.setWindowDelegate(windowDelegate);
-        // If the user focused the omnibox prior to the native libraries being initialized,
-        // autocomplete will not always be enabled, so we force it enabled in that case.
-        mUrlBar.setIgnoreTextChangesForAutocomplete(false);
+        mUrlCoordinator.setWindowDelegate(windowDelegate);
         mAutocomplete = new AutocompleteController(this);
     }
 
@@ -562,9 +547,8 @@
      * @param focusable Whether the url bar should be focusable.
      */
     public void setUrlBarFocusable(boolean focusable) {
-        if (mUrlBar == null) return;
-        mUrlBar.setFocusable(focusable);
-        mUrlBar.setFocusableInTouchMode(focusable);
+        if (mUrlCoordinator == null) return;
+        mUrlCoordinator.setAllowFocus(focusable);
     }
 
     /**
@@ -704,7 +688,7 @@
 
     @Override
     public void selectAll() {
-        mUrlBar.selectAll();
+        mUrlCoordinator.selectAll();
     }
 
     @Override
@@ -714,10 +698,10 @@
         } else {
             String currentUrl = mToolbarDataProvider.getCurrentUrl();
             if (NativePageFactory.isNativePageUrl(currentUrl, mToolbarDataProvider.isIncognito())) {
-                setUrlBarText(UrlBarData.EMPTY);
+                setUrlBarTextEmpty();
             } else {
-                setUrlBarText(mToolbarDataProvider.getUrlBarData());
-                selectAll();
+                setUrlBarText(mToolbarDataProvider.getUrlBarData(), UrlBar.ScrollType.NO_SCROLL,
+                        SelectionState.SELECT_ALL);
             }
             hideSuggestions();
             UiUtils.hideKeyboard(mUrlBar);
@@ -755,7 +739,7 @@
             if (mNativeInitialized) RecordUserAction.record("FocusLocation");
             UrlBarData urlBarData = mToolbarDataProvider.getUrlBarData();
             if (urlBarData.editingText != null) {
-                setUrlBarText(urlBarData);
+                setUrlBarText(urlBarData, UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL);
             }
 
             // Explicitly tell InputMethodManager that the url bar is focused before any callbacks
@@ -791,8 +775,6 @@
         changeLocationBarIcon();
         updateVerboseStatusVisibility();
         updateLocationBarIconContainerVisibility();
-        if (hasFocus) selectAll();
-        mUrlBar.setCursorVisible(hasFocus);
 
         if (!mUrlFocusedWithoutAnimations) handleUrlFocusAnimation(hasFocus);
 
@@ -818,7 +800,7 @@
             mDeferredNativeRunnables.add(new Runnable() {
                 @Override
                 public void run() {
-                    if (TextUtils.isEmpty(mUrlBar.getTextWithAutocomplete())) {
+                    if (TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete())) {
                         startZeroSuggest();
                     }
                 }
@@ -863,7 +845,7 @@
         mNewOmniboxEditSessionTimestamp = -1;
         if (mNativeInitialized && mUrlHasFocus && mToolbarDataProvider.hasTab()) {
             mAutocomplete.startZeroSuggest(mToolbarDataProvider.getProfile(),
-                    mUrlBar.getTextWithAutocomplete(), mToolbarDataProvider.getCurrentUrl(),
+                    mUrlCoordinator.getTextWithAutocomplete(), mToolbarDataProvider.getCurrentUrl(),
                     mToolbarDataProvider.getTitle(), mUrlFocusedFromFakebox);
         }
     }
@@ -888,7 +870,7 @@
         }
 
         stopAutocomplete(false);
-        if (TextUtils.isEmpty(mUrlBar.getTextWithoutAutocomplete())) {
+        if (TextUtils.isEmpty(mUrlCoordinator.getTextWithoutAutocomplete())) {
             // crbug.com/764749
             Log.w(TAG, "onTextChangedForAutocomplete: url is empty");
             hideSuggestions();
@@ -898,8 +880,8 @@
             mRequestSuggestions = new Runnable() {
                 @Override
                 public void run() {
-                    String textWithoutAutocomplete = mUrlBar.getTextWithoutAutocomplete();
-                    boolean preventAutocomplete = !mUrlBar.shouldAutocomplete();
+                    String textWithoutAutocomplete = mUrlCoordinator.getTextWithoutAutocomplete();
+                    boolean preventAutocomplete = !mUrlCoordinator.shouldAutocomplete();
                     mRequestSuggestions = null;
 
                     if (!mToolbarDataProvider.hasTab()) {
@@ -910,11 +892,11 @@
 
                     Profile profile = mToolbarDataProvider.getProfile();
                     int cursorPosition = -1;
-                    if (mUrlBar.getSelectionStart() == mUrlBar.getSelectionEnd()) {
+                    if (mUrlCoordinator.getSelectionStart() == mUrlCoordinator.getSelectionEnd()) {
                         // Conveniently, if there is no selection, those two functions return -1,
                         // exactly the same value needed to pass to start() to indicate no cursor
                         // position.  Hence, there's no need to check for -1 here explicitly.
-                        cursorPosition = mUrlBar.getSelectionStart();
+                        cursorPosition = mUrlCoordinator.getSelectionStart();
                     }
                     mAutocomplete.start(profile, mToolbarDataProvider.getCurrentUrl(),
                             textWithoutAutocomplete, cursorPosition, preventAutocomplete,
@@ -931,7 +913,7 @@
 
     @Override
     public void setDefaultTextEditActionModeCallback(ToolbarActionModeCallback callback) {
-        mUrlBar.setCustomSelectionActionModeCallback(callback);
+        mUrlCoordinator.setActionModeCallback(callback);
     }
 
     @Override
@@ -947,8 +929,8 @@
             ToolbarManager.recordOmniboxFocusReason(
                     ToolbarManager.OmniboxFocusReason.FAKE_BOX_LONG_PRESS);
             // This must be happen after requestUrlFocus(), which changes the selection.
-            mUrlBar.setUrl(UrlBarData.forNonUrlText(pastedText));
-            mUrlBar.setSelection(mUrlBar.getText().length());
+            mUrlCoordinator.setUrlBarData(UrlBarData.forNonUrlText(pastedText),
+                    UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_END);
         } else {
             ToolbarManager.recordOmniboxFocusReason(ToolbarManager.OmniboxFocusReason.FAKE_BOX_TAP);
         }
@@ -977,12 +959,7 @@
 
         updateButtonVisibility();
 
-        mUrlBar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, final boolean hasFocus) {
-                onUrlFocusChange(hasFocus);
-            }
-        });
+        mUrlCoordinator.setOnFocusChangedCallback(this::onUrlFocusChange);
     }
 
     @Override
@@ -1243,7 +1220,7 @@
      */
     protected boolean shouldShowDeleteButton() {
         // Show the delete button at the end when the bar has focus and has some text.
-        boolean hasText = !TextUtils.isEmpty(mUrlBar.getText());
+        boolean hasText = !TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete());
         return hasText && (mUrlBar.hasFocus() || mUrlFocusChangeInProgress);
     }
 
@@ -1363,8 +1340,9 @@
             @Override
             public void onRefineSuggestion(OmniboxSuggestion suggestion) {
                 stopAutocomplete(false);
-                mUrlBar.setUrl(UrlBarData.forNonUrlText(suggestion.getFillIntoEdit()));
-                mUrlBar.setSelection(mUrlBar.getText().length());
+                mUrlCoordinator.setUrlBarData(
+                        UrlBarData.forNonUrlText(suggestion.getFillIntoEdit()),
+                        UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_END);
                 if (suggestion.isUrlSuggestion()) {
                     RecordUserAction.record("MobileOmniboxRefineSuggestion.Url");
                 } else {
@@ -1375,8 +1353,8 @@
             @Override
             public void onSetUrlToSuggestion(OmniboxSuggestion suggestion) {
                 if (mIgnoreOmniboxItemSelection) return;
-                setUrlBarText(UrlBarData.forNonUrlText(suggestion.getFillIntoEdit()));
-                mUrlBar.setSelection(mUrlBar.getText().length());
+                setUrlBarText(UrlBarData.forNonUrlText(suggestion.getFillIntoEdit()),
+                        UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_END);
                 mIgnoreOmniboxItemSelection = true;
             }
 
@@ -1597,9 +1575,9 @@
             return;
         }
 
-        setUrlBarText(UrlBarData.forNonUrlText(query));
+        setUrlBarText(UrlBarData.forNonUrlText(query), UrlBar.ScrollType.NO_SCROLL,
+                SelectionState.SELECT_ALL);
         setUrlBarFocus(true);
-        selectAll();
         stopAutocomplete(false);
         if (mToolbarDataProvider.hasTab()) {
             mAutocomplete.start(mToolbarDataProvider.getProfile(),
@@ -1624,8 +1602,8 @@
     @Override
     public void onClick(View v) {
         if (v == mDeleteButton) {
-            if (!TextUtils.isEmpty(mUrlBar.getTextWithAutocomplete())) {
-                setUrlBarText(UrlBarData.EMPTY);
+            if (!TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete())) {
+                setUrlBarTextEmpty();
                 hideSuggestions();
                 updateButtonVisibility();
             }
@@ -1665,7 +1643,7 @@
             mDeferredOnSelection.run();
             mDeferredOnSelection = null;
         }
-        String userText = mUrlBar.getTextWithoutAutocomplete();
+        String userText = mUrlCoordinator.getTextWithoutAutocomplete();
         mUrlTextAfterSuggestionsReceived = userText + inlineAutocompleteText;
 
         boolean itemsChanged = false;
@@ -1709,8 +1687,8 @@
             return;
         }
 
-        if (mUrlBar.shouldAutocomplete()) {
-            mUrlBar.setAutocompleteText(userText, inlineAutocompleteText);
+        if (mUrlCoordinator.shouldAutocomplete()) {
+            mUrlCoordinator.setAutocompleteText(userText, inlineAutocompleteText);
         }
 
         // Show the suggestion list.
@@ -1774,14 +1752,6 @@
     }
 
     @Override
-    @UrlBar.ScrollType
-    public int getScrollType() {
-        return mToolbarDataProvider.shouldDisplaySearchTerms()
-                ? UrlBar.ScrollType.SCROLL_TO_BEGINNING
-                : UrlBar.ScrollType.SCROLL_TO_TLD;
-    }
-
-    @Override
     public boolean shouldCutCopyVerbatim() {
         // When cutting/copying text in the URL bar, it will try to copy some version of the actual
         // URL to the clipboard, not the currently displayed URL bar contents. We want to avoid this
@@ -1821,7 +1791,11 @@
         }
 
         mOriginalUrl = currentUrl;
-        setUrlBarText(mToolbarDataProvider.getUrlBarData());
+        @ScrollType
+        int scrollType = mToolbarDataProvider.shouldDisplaySearchTerms()
+                ? UrlBar.ScrollType.SCROLL_TO_BEGINNING
+                : UrlBar.ScrollType.SCROLL_TO_TLD;
+        setUrlBarText(mToolbarDataProvider.getUrlBarData(), scrollType, SelectionState.SELECT_ALL);
         if (!mToolbarDataProvider.hasTab()) return;
 
         // Profile may be null if switching to a tab that has not yet been initialized.
@@ -1830,15 +1804,27 @@
     }
 
     /**
-     * Changes the text on the url bar.
+     * Changes the text on the url bar.  The text update will be applied regardless of the current
+     * focus state (comparing to {@link #setUrlToPageUrl()} which only applies text updates when
+     * not focused).
+     *
      * @param urlBarData The contents of the URL bar, both for editing and displaying.
+     * @param scrollType Specifies how the text should be scrolled in the unfocused state.
+     * @param selectionState Specifies how the text should be selected in the focused state.
      * @return Whether the URL was changed as a result of this call.
      */
-    private boolean setUrlBarText(UrlBarData urlBarData) {
-        mUrlBar.setIgnoreTextChangesForAutocomplete(true);
-        boolean urlChanged = mUrlBar.setUrl(urlBarData);
-        mUrlBar.setIgnoreTextChangesForAutocomplete(false);
-        return urlChanged;
+    private boolean setUrlBarText(
+            UrlBarData urlBarData, @ScrollType int scrollType, @SelectionState int selectionState) {
+        return mUrlCoordinator.setUrlBarData(urlBarData, scrollType, selectionState);
+    }
+
+    /**
+     * Clear any text in the URL bar.
+     * @return Whether this changed the existing text.
+     */
+    private boolean setUrlBarTextEmpty() {
+        return mUrlCoordinator.setUrlBarData(
+                UrlBarData.EMPTY, UrlBar.ScrollType.SCROLL_TO_BEGINNING, SelectionState.SELECT_ALL);
     }
 
     private void loadUrlFromOmniboxMatch(
@@ -1857,9 +1843,11 @@
                 && (mDeferredOnSelection != null)
                 && !mDeferredOnSelection.shouldLog();
         if (!shouldSkipNativeLog) {
+            int autocompleteLength = mUrlCoordinator.getTextWithAutocomplete().length()
+                    - mUrlCoordinator.getTextWithoutAutocomplete().length();
             mAutocomplete.onSuggestionSelected(matchPosition, suggestion.hashCode(), type,
                     currentPageUrl, mUrlFocusedFromFakebox, elapsedTimeSinceModified,
-                    mUrlBar.getAutocompleteLength(), webContents);
+                    autocompleteLength, webContents);
         }
         if (((transition & PageTransition.CORE_MASK) == PageTransition.TYPED)
                 && TextUtils.equals(url, mToolbarDataProvider.getCurrentUrl())) {
@@ -1873,7 +1861,8 @@
             // (e.g. manually retyping the same search query), and it seems wrong to
             // treat this as a reload.
             transition = PageTransition.RELOAD;
-        } else if (type == OmniboxSuggestionType.URL_WHAT_YOU_TYPED && mUrlBar.wasLastEditPaste()) {
+        } else if (type == OmniboxSuggestionType.URL_WHAT_YOU_TYPED
+                && mUrlCoordinator.wasLastEditPaste()) {
             // It's important to use the page transition from the suggestion or we might end
             // up saving generated URLs as typed URLs, which would then pollute the subsequent
             // omnibox results. There is one special case where the suggestion text was pasted,
@@ -2056,7 +2045,7 @@
         if (!hasWindowFocus && !mSuggestionModalShown) {
             hideSuggestions();
         } else if (hasWindowFocus && mUrlHasFocus && mNativeInitialized) {
-            Editable currentUrlBarText = mUrlBar.getText();
+            String currentUrlBarText = mUrlCoordinator.getTextWithAutocomplete();
             if (TextUtils.isEmpty(currentUrlBarText)
                     || TextUtils.equals(currentUrlBarText,
                                mToolbarDataProvider.getUrlBarData().getEditingOrDisplayText())) {
@@ -2118,7 +2107,7 @@
 
         // If the URL changed colors and is not focused, update the URL to account for the new
         // color scheme.
-        if (mUrlBar.setUseDarkTextColors(mUseDarkColors) && !mUrlBar.hasFocus()) {
+        if (mUrlCoordinator.setUseDarkTextColors(mUseDarkColors) && !mUrlBar.hasFocus()) {
             setUrlToPageUrl();
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
index 288a4e8..610891a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarPhone.java
@@ -155,9 +155,6 @@
      */
     public void finishUrlFocusChange(boolean hasFocus) {
         if (!hasFocus) {
-            // Scroll to ensure the TLD is visible, if necessary.
-            if (getScrollType() == UrlBar.ScrollType.SCROLL_TO_TLD) mUrlBar.scrollDisplayText();
-
             // The animation rendering may not yet be 100% complete and hiding the keyboard makes
             // the animation quite choppy.
             postDelayed(new Runnable() {
@@ -209,7 +206,8 @@
         ToolbarDataProvider toolbarDataProvider = getToolbarDataProvider();
         if (toolbarDataProvider == null) return;
 
-        if (!getToolbarDataProvider().shouldShowGoogleG(mUrlBar.getEditableText().toString())) {
+        if (!getToolbarDataProvider().shouldShowGoogleG(
+                    mUrlCoordinator.getTextWithAutocomplete())) {
             mGoogleGContainer.setVisibility(View.GONE);
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
index 169b019..ec0b914 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
@@ -9,7 +9,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Rect;
-import android.text.Selection;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.MotionEvent;
@@ -216,7 +215,6 @@
                 mSecurityButton.setVisibility(VISIBLE);
             }
             UiUtils.hideKeyboard(mUrlBar);
-            Selection.setSelection(mUrlBar.getText(), 0);
             // Convert the keyboard back to resize mode (delay the change for an arbitrary
             // amount of time in hopes the keyboard will be completely hidden before making
             // this change).
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index 37fb79e..c22db78c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -4,23 +4,21 @@
 
 package org.chromium.chrome.browser.omnibox;
 
-import android.content.ClipData;
-import android.content.ClipboardManager;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.net.Uri;
 import android.os.Build;
 import android.os.StrictMode;
+import android.provider.Settings;
 import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.text.BidiFormatter;
 import android.text.Editable;
+import android.text.InputType;
 import android.text.Layout;
 import android.text.Selection;
-import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.ReplacementSpan;
 import android.util.AttributeSet;
@@ -37,16 +35,12 @@
 import org.chromium.base.Log;
 import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.WindowDelegate;
-import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer.UrlEmphasisSpan;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.ui.UiUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.net.MalformedURLException;
-import java.net.URL;
 
 /**
  * The URL text entry view for the Omnibox.
@@ -66,12 +60,6 @@
     private static final int MAX_DISPLAYABLE_LENGTH = 4000;
     private static final int MAX_DISPLAYABLE_LENGTH_LOW_END = 1000;
 
-    /** The contents of the URL that precede the path/query after being formatted. */
-    private String mFormattedUrlLocation;
-
-    /** The contents of the URL that precede the path/query before formatting. */
-    private String mOriginalUrlLocation;
-
     private boolean mFirstDrawComplete;
 
     /**
@@ -81,7 +69,7 @@
     private int mUrlDirection;
 
     private UrlBarDelegate mUrlBarDelegate;
-
+    private UrlBarTextContextMenuDelegate mTextContextMenuDelegate;
     private UrlDirectionListener mUrlDirectionListener;
 
     /**
@@ -104,16 +92,6 @@
     private int mPreviousTldScrollResultXPosition;
     private float mPreviousFontSize;
 
-    private final int mDarkHintColor;
-    private final int mDarkDefaultTextColor;
-    private final int mDarkHighlightColor;
-
-    private final int mLightHintColor;
-    private final int mLightDefaultTextColor;
-    private final int mLightHighlightColor;
-
-    private Boolean mUseDarkColors;
-
     // Used as a hint to indicate the text may contain an ellipsize span.  This will be true if an
     // ellispize span was applied the last time the text changed.  A true value here does not
     // guarantee that the text does contain the span currently as newly set text may have cleared
@@ -127,17 +105,14 @@
     private float mDownEventViewTop;
 
     /**
-     * The character index in the displayed text where the origin starts. This is required to
-     * ensure that the end of the origin is not scrolled out of view for long hostnames.
-     */
-    private int mOriginStartIndex;
-
-    /**
      * The character index in the displayed text where the origin ends. This is required to
      * ensure that the end of the origin is not scrolled out of view for long hostnames.
      */
     private int mOriginEndIndex;
 
+    @ScrollType
+    private int mScrollType;
+
     /** What scrolling action should be taken after the URL bar text changes. **/
     @IntDef({ScrollType.NO_SCROLL, ScrollType.SCROLL_TO_TLD, ScrollType.SCROLL_TO_BEGINNING})
     @Retention(RetentionPolicy.SOURCE)
@@ -193,38 +168,33 @@
         boolean shouldForceLTR();
 
         /**
-         * @return What scrolling action should be performed after the URL text is modified.
-         */
-        @ScrollType
-        int getScrollType();
-
-        /**
          * @return Whether or not the copy/cut action should grab the underlying URL or just copy
          *         whatever's in the URL bar verbatim.
          */
         boolean shouldCutCopyVerbatim();
     }
 
+    /** Delegate that provides the additional functionality to the textual context menus. */
+    interface UrlBarTextContextMenuDelegate {
+        /** @return The text to be pasted into the UrlBar. */
+        @NonNull
+        String getTextToPaste();
+
+        /**
+         * Gets potential replacement text to be used instead of the current selected text for
+         * cut/copy actions.  If null is returned, the existing text will be cut or copied.
+         *
+         * @param currentText The current displayed text.
+         * @param selectionStart The selection start in the display text.
+         * @param selectionEnd The selection end in the display text.
+         * @return The text to be cut/copied instead of the currently selected text.
+         */
+        @Nullable
+        String getReplacementCutCopyText(String currentText, int selectionStart, int selectionEnd);
+    }
+
     public UrlBar(Context context, AttributeSet attrs) {
         super(context, attrs);
-
-        Resources resources = getResources();
-
-        mDarkDefaultTextColor =
-                ApiCompatibilityUtils.getColor(resources, R.color.url_emphasis_default_text);
-        mDarkHintColor = ApiCompatibilityUtils.getColor(resources,
-                R.color.locationbar_dark_hint_text);
-        mDarkHighlightColor = getHighlightColor();
-
-        mLightDefaultTextColor =
-                ApiCompatibilityUtils.getColor(resources, R.color.url_emphasis_light_default_text);
-        mLightHintColor =
-                ApiCompatibilityUtils.getColor(resources, R.color.locationbar_light_hint_text);
-        mLightHighlightColor = ApiCompatibilityUtils.getColor(resources,
-                R.color.locationbar_light_selection_color);
-
-        setUseDarkTextColors(true);
-
         mUrlDirection = LAYOUT_DIRECTION_LOCALE;
 
         // The URL Bar is derived from an text edit class, and as such is focusable by
@@ -236,6 +206,19 @@
         setFocusable(false);
         setFocusableInTouchMode(false);
 
+        // The HTC Sense IME will attempt to autocomplete words in the Omnibox when Prediction is
+        // enabled.  We want to disable this feature and rely on the Omnibox's implementation.
+        // Their IME does not respect ~TYPE_TEXT_FLAG_AUTO_COMPLETE nor any of the other InputType
+        // options I tried, but setting the filter variation prevents it.  Sadly, it also removes
+        // the .com button, but the prediction was buggy as it would autocomplete words even when
+        // typing at the beginning of the omnibox text when other content was present (messing up
+        // what was previously there).  See bug: http://b/issue?id=6200071
+        String defaultIme = Settings.Secure.getString(
+                getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        if (defaultIme != null && defaultIme.contains("com.htc.android.htcime")) {
+            setInputType(getInputType() | InputType.TYPE_TEXT_VARIATION_FILTER);
+        }
+
         mGestureDetector =
                 new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
                     @Override
@@ -272,45 +255,10 @@
     }
 
     /**
-     * Specifies whether the URL bar should use dark text colors or light colors.
-     * @param useDarkColors Whether the text colors should be dark (i.e. appropriate for use
-     *                      on a light background).
-     * @return Whether this update resulted in a change from the previous state of text color state.
+     * Set the delegate to be used for text context menu actions.
      */
-    public boolean setUseDarkTextColors(boolean useDarkColors) {
-        if (mUseDarkColors != null && mUseDarkColors.booleanValue() == useDarkColors) return false;
-
-        mUseDarkColors = useDarkColors;
-        if (mUseDarkColors) {
-            setTextColor(mDarkDefaultTextColor);
-            setHighlightColor(mDarkHighlightColor);
-        } else {
-            setTextColor(mLightDefaultTextColor);
-            setHighlightColor(mLightHighlightColor);
-        }
-
-        // Note: Setting the hint text color only takes effect if there is not text in the URL bar.
-        //       To get around this, set the URL to empty before setting the hint color and revert
-        //       back to the previous text after.
-        boolean hasNonEmptyText = false;
-        Editable text = getText();
-        if (!TextUtils.isEmpty(text)) {
-            // Make sure the setText in this block does not affect the suggestions.
-            setIgnoreTextChangesForAutocomplete(true);
-            setText("");
-            hasNonEmptyText = true;
-        }
-        if (useDarkColors) {
-            setHintTextColor(mDarkHintColor);
-        } else {
-            setHintTextColor(mLightHintColor);
-        }
-        if (hasNonEmptyText) {
-            setText(text);
-            setIgnoreTextChangesForAutocomplete(false);
-        }
-
-        return true;
+    public void setTextContextMenuDelegate(UrlBarTextContextMenuDelegate delegate) {
+        mTextContextMenuDelegate = delegate;
     }
 
     @Override
@@ -577,17 +525,11 @@
 
     @Override
     public boolean onTextContextMenuItem(int id) {
-        if (id == android.R.id.paste) {
-            ClipboardManager clipboard = (ClipboardManager) getContext()
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-            ClipData clipData = clipboard.getPrimaryClip();
-            if (clipData != null) {
-                StringBuilder builder = new StringBuilder();
-                for (int i = 0; i < clipData.getItemCount(); i++) {
-                    builder.append(clipData.getItemAt(i).coerceToText(getContext()));
-                }
-                String pasteString = OmniboxViewUtil.sanitizeTextForPaste(builder.toString());
+        if (mTextContextMenuDelegate == null) return super.onTextContextMenuItem(id);
 
+        if (id == android.R.id.paste) {
+            String pasteString = mTextContextMenuDelegate.getTextToPaste();
+            if (pasteString != null) {
                 int min = 0;
                 int max = getText().length();
 
@@ -602,157 +544,68 @@
                 Selection.setSelection(getText(), max);
                 getText().replace(min, max, pasteString);
                 onPaste();
-                return true;
             }
+            return true;
         }
 
-        if (mOriginalUrlLocation == null || mFormattedUrlLocation == null) {
-            return super.onTextContextMenuItem(id);
-        }
+        if ((id == android.R.id.cut || id == android.R.id.copy)
+                && !mUrlBarDelegate.shouldCutCopyVerbatim()) {
+            String currentText = getText().toString();
+            String replacementCutCopyText = mTextContextMenuDelegate.getReplacementCutCopyText(
+                    currentText, getSelectionStart(), getSelectionEnd());
+            if (replacementCutCopyText == null) return super.onTextContextMenuItem(id);
 
-        int selectedStartIndex = getSelectionStart();
-        int selectedEndIndex = getSelectionEnd();
-
-        // If we are copying/cutting the full previously formatted URL, reset the URL
-        // text before initiating the TextViews handling of the context menu.
-        //
-        // Example:
-        //    Original display text: www.example.com
-        //    Original URL:          http://www.example.com
-        //
-        // Editing State:
-        //    www.example.com/blah/foo
-        //    |<--- Selection --->|
-        //
-        // Resulting clipboard text should be:
-        //    http://www.example.com/blah/
-        //
-        // As long as the full original text was selected, it will replace that with the original
-        // URL and keep any further modifications by the user.
-        String currentText = getText().toString();
-        if (selectedStartIndex != 0 || (id != android.R.id.cut && id != android.R.id.copy)
-                || !currentText.startsWith(mFormattedUrlLocation)
-                || selectedEndIndex < mFormattedUrlLocation.length()
-                || mUrlBarDelegate.shouldCutCopyVerbatim()) {
-            return super.onTextContextMenuItem(id);
-        }
-
-        String newText =
-                mOriginalUrlLocation + currentText.substring(mFormattedUrlLocation.length());
-        selectedEndIndex =
-                selectedEndIndex - mFormattedUrlLocation.length() + mOriginalUrlLocation.length();
-
-        setIgnoreTextChangesForAutocomplete(true);
-        setText(newText);
-        setSelection(0, selectedEndIndex);
-        setIgnoreTextChangesForAutocomplete(false);
-
-        boolean retVal = super.onTextContextMenuItem(id);
-
-        if (getText().toString().equals(newText)) {
-            // Restore the old text if the operation didn't modify the text.
             setIgnoreTextChangesForAutocomplete(true);
-            setText(currentText);
-
-            // Move the cursor to the end.
-            setSelection(getText().length());
+            setText(replacementCutCopyText);
+            setSelection(0, replacementCutCopyText.length());
             setIgnoreTextChangesForAutocomplete(false);
+
+            boolean retVal = super.onTextContextMenuItem(id);
+
+            if (TextUtils.equals(getText(), replacementCutCopyText)) {
+                // Restore the old text if the operation did modify the text.
+                setIgnoreTextChangesForAutocomplete(true);
+                setText(currentText);
+
+                // Move the cursor to the end.
+                setSelection(getText().length());
+                setIgnoreTextChangesForAutocomplete(false);
+            }
+
+            return retVal;
+        }
+
+        return super.onTextContextMenuItem(id);
+    }
+
+    /**
+     * Specified how text should be scrolled within the UrlBar.
+     *
+     * @param scrollType What type of scroll should be applied to the text.
+     * @param scrollToIndex The index that should be scrolled to, which only applies to
+     *                      {@link ScrollType#SCROLL_TO_TLD}.
+     */
+    public void setScrollState(@ScrollType int scrollType, int scrollToIndex) {
+        if (scrollType == ScrollType.SCROLL_TO_TLD) {
+            mOriginEndIndex = scrollToIndex;
         } else {
-            // Keep the modified text, but clear the origin span.
-            mOriginStartIndex = 0;
             mOriginEndIndex = 0;
         }
-        return retVal;
+        mScrollType = scrollType;
+        scrollDisplayText();
     }
 
     /**
-     * Sets the text content of the URL bar.
+     * Scrolls the omnibox text to a position determined by the current scroll type.
      *
-     * @param urlBarData The contents of the URL bar, both for editing and displaying.
-     * @return Whether the visible text has changed.
+     * @see #setScrollState(int, int)
      */
-    public boolean setUrl(UrlBarData urlBarData) {
-        mOriginalUrlLocation = null;
-        mFormattedUrlLocation = null;
-        if (urlBarData.url != null) {
-            try {
-                // TODO(bauerb): Use |urlBarData.originEndIndex| for this instead?
-                URL javaUrl = new URL(urlBarData.url);
-                mFormattedUrlLocation =
-                        getUrlContentsPrePath(urlBarData.displayText.toString(), javaUrl.getHost());
-                mOriginalUrlLocation = getUrlContentsPrePath(urlBarData.url, javaUrl.getHost());
-            } catch (MalformedURLException mue) {
-                // Keep |mOriginalUrlLocation| and |mFormattedUrlLocation at null.
-            }
-        }
-
-        mOriginStartIndex = urlBarData.originStartIndex;
-        mOriginEndIndex = urlBarData.originEndIndex;
-
-        Editable previousText = getEditableText();
-        final CharSequence displayText;
-        if (urlBarData.editingText != null && isFocused()) {
-            displayText = urlBarData.editingText;
-        } else {
-            displayText = urlBarData.displayText;
-        }
-        setText(displayText);
-
-        boolean textChanged = !TextUtils.equals(previousText, getEditableText());
-        if (textChanged && !isFocused()) scrollDisplayText();
-        return textChanged;
-    }
-
-    @Override
-    protected boolean isNewTextEquivalentToExistingText(CharSequence newCharSequence) {
-        Spanned currentText = getEditableText();
-        if (currentText == null) return newCharSequence == null;
-
-        // Regardless of focus state, ensure the text content is the same.
-        if (!TextUtils.equals(currentText, newCharSequence)) return false;
-
-        if (isFocused()) {
-            // When focused if the existing text has emphasis spans, then clear that text as well as
-            // those spans should only apply when unfocused.
-            return currentText.getSpans(0, currentText.length(), UrlEmphasisSpan.class).length == 0;
-        }
-
-        // When not focused, compare the emphasis spans applied to the text to determine
-        // equality.  Internally, TextView applies many additional spans that need to be
-        // ignored for this comparison to be useful, so this is scoped to only the span types
-        // applied by our UI.
-        if (!(newCharSequence instanceof Spanned)) return false;
-
-        Spanned newText = (Spanned) newCharSequence;
-        UrlEmphasisSpan[] currentSpans =
-                currentText.getSpans(0, currentText.length(), UrlEmphasisSpan.class);
-        UrlEmphasisSpan[] newSpans = newText.getSpans(0, newText.length(), UrlEmphasisSpan.class);
-        if (currentSpans.length != newSpans.length) return false;
-        for (int i = 0; i < currentSpans.length; i++) {
-            UrlEmphasisSpan currentSpan = currentSpans[i];
-            UrlEmphasisSpan newSpan = newSpans[i];
-            if (!currentSpan.equals(newSpan)
-                    || currentText.getSpanStart(currentSpan) != newText.getSpanStart(newSpan)
-                    || currentText.getSpanEnd(currentSpan) != newText.getSpanEnd(newSpan)
-                    || currentText.getSpanFlags(currentSpan) != newText.getSpanFlags(newSpan)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Scrolls the omnibox text to a position determined by the call to
-     * {@link UrlBarDelegate#getScrollType}.
-     */
-    public void scrollDisplayText() {
-        @ScrollType
-        int scrollType = mUrlBarDelegate.getScrollType();
+    private void scrollDisplayText() {
         if (isLayoutRequested()) {
-            mPendingScroll = scrollType != ScrollType.NO_SCROLL;
+            mPendingScroll = mScrollType != ScrollType.NO_SCROLL;
             return;
         }
-        scrollDisplayTextInternal(scrollType);
+        scrollDisplayTextInternal(mScrollType);
     }
 
     /**
@@ -828,22 +681,6 @@
             return;
         }
 
-        if (mOriginStartIndex == mOriginEndIndex) {
-            scrollTo(0, getScrollY());
-            return;
-        }
-
-        // Do not scroll to the end of the host for URLs such as data:, javascript:, etc...
-        if (mOriginEndIndex == url.length()) {
-            Uri uri = Uri.parse(url.toString());
-            String scheme = uri.getScheme();
-            if (!TextUtils.isEmpty(scheme)
-                    && UrlBarData.UNSUPPORTED_SCHEMES_TO_SPLIT.contains(scheme)) {
-                scrollTo(0, getScrollY());
-                return;
-            }
-        }
-
         int measuredWidth = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
         if (TextUtils.equals(url, previousTldScrollText)
                 && measuredWidth == previousTldScrollViewWidth
@@ -887,9 +724,9 @@
         super.onLayout(changed, left, top, right, bottom);
 
         if (mPendingScroll) {
-            scrollDisplayTextInternal(mUrlBarDelegate.getScrollType());
+            scrollDisplayTextInternal(mScrollType);
         } else if (mPreviousWidth != (right - left)) {
-            scrollDisplayTextInternal(mUrlBarDelegate.getScrollType());
+            scrollDisplayTextInternal(mScrollType);
             mPreviousWidth = right - left;
         }
     }
@@ -953,24 +790,6 @@
                 Editable.SPAN_INCLUSIVE_EXCLUSIVE);
     }
 
-    /**
-     * Returns the portion of the URL that precedes the path/query section of the URL.
-     *
-     * @param url The url to be used to find the preceding portion.
-     * @param host The host to be located in the URL to determine the location of the path.
-     * @return The URL contents that precede the path (or the passed in URL if the host is
-     *         not found).
-     */
-    private static String getUrlContentsPrePath(String url, String host) {
-        int hostIndex = url.indexOf(host);
-        if (hostIndex == -1) return url;
-
-        int pathIndex = url.indexOf('/', hostIndex);
-        if (pathIndex <= 0) return url;
-
-        return url.substring(0, pathIndex);
-    }
-
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         // Certain OEM implementations of onInitializeAccessibilityNodeInfo trigger disk reads
@@ -997,7 +816,7 @@
     @Override
     public void replaceAllTextFromAutocomplete(String text) {
         if (DEBUG) Log.i(TAG, "replaceAllTextFromAutocomplete: " + text);
-        setUrl(UrlBarData.forNonUrlText(text));
+        setText(text);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
new file mode 100644
index 0000000..2829cdc
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
@@ -0,0 +1,132 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox;
+
+import android.support.annotation.IntDef;
+import android.view.ActionMode;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
+import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Coordinates the interactions with the UrlBar text component.
+ */
+public class UrlBarCoordinator {
+    /** Specified how the text should be selected when focused. */
+    @IntDef({SelectionState.SELECT_ALL, SelectionState.SELECT_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SelectionState {
+        /** Select all of the text. */
+        int SELECT_ALL = 0;
+
+        /** Selection (along with the input cursor) will be placed at the end of the text. */
+        int SELECT_END = 1;
+    }
+
+    private final UrlBar mUrlBar;
+    private final UrlBarMediator mMediator;
+
+    /**
+     * Constructs a coordinator for the given UrlBar view.
+     */
+    public UrlBarCoordinator(UrlBar urlBar) {
+        mUrlBar = urlBar;
+
+        PropertyModel model = new PropertyModel(UrlBarProperties.ALL_KEYS);
+        model.addObserver(
+                new PropertyModelChangeProcessor<>(model, urlBar, UrlBarViewBinder::bind));
+
+        mMediator = new UrlBarMediator(model);
+    }
+
+    /** @see UrlBarMediator#setDelegate(UrlBarDelegate) */
+    public void setDelegate(UrlBarDelegate delegate) {
+        mMediator.setDelegate(delegate);
+    }
+
+    /** @see UrlBarMediator#setUrlBarData(UrlBarData, int, int) */
+    public boolean setUrlBarData(
+            UrlBarData data, @ScrollType int scrollType, @SelectionState int state) {
+        return mMediator.setUrlBarData(data, scrollType, state);
+    }
+
+    /** @see UrlBarMediator#setAutocompleteText(String, String) */
+    public void setAutocompleteText(String userText, String autocompleteText) {
+        mMediator.setAutocompleteText(userText, autocompleteText);
+    }
+
+    /** @see UrlBarMediator#setUseDarkTextColors(boolean) */
+    public boolean setUseDarkTextColors(boolean useDarkColors) {
+        return mMediator.setUseDarkTextColors(useDarkColors);
+    }
+
+    /** @see UrlBarMediator#setAllowFocus(boolean) */
+    public void setAllowFocus(boolean allowFocus) {
+        mMediator.setAllowFocus(allowFocus);
+    }
+
+    /** @see UrlBarMediator#setUrlDirectionListener(UrlDirectionListener) */
+    public void setUrlDirectionListener(UrlDirectionListener listener) {
+        mMediator.setUrlDirectionListener(listener);
+    }
+
+    /** @see UrlBarMediator#setOnFocusChangedCallback(Callback) */
+    public void setOnFocusChangedCallback(Callback<Boolean> callback) {
+        mMediator.setOnFocusChangedCallback(callback);
+    }
+
+    /** @see UrlBarMediator#setWindowDelegate(WindowDelegate) */
+    public void setWindowDelegate(WindowDelegate windowDelegate) {
+        mMediator.setWindowDelegate(windowDelegate);
+    }
+
+    /** @see UrlBarMediator#setActionModeCallback(android.view.ActionMode.Callback) */
+    public void setActionModeCallback(ActionMode.Callback callback) {
+        mMediator.setActionModeCallback(callback);
+    }
+
+    /** Selects all of the text of the UrlBar. */
+    public void selectAll() {
+        mUrlBar.selectAll();
+    }
+
+    /** Return the starting selection index for the text. */
+    public int getSelectionStart() {
+        return mUrlBar.getSelectionStart();
+    }
+
+    /** Return the ending selection index for the text. */
+    public int getSelectionEnd() {
+        return mUrlBar.getSelectionEnd();
+    }
+
+    /** Return whether the view can accept autocomplete. */
+    public boolean shouldAutocomplete() {
+        return mUrlBar.shouldAutocomplete();
+    }
+
+    /** Return whether the last edit was the result of a paste operation. */
+    public boolean wasLastEditPaste() {
+        return mUrlBar.wasLastEditPaste();
+    }
+
+    /** Return the full text with any inline autocomplete. */
+    public String getTextWithAutocomplete() {
+        return mUrlBar.getTextWithAutocomplete();
+    }
+
+    /** Return the text excluding any inline autocomplete. */
+    public String getTextWithoutAutocomplete() {
+        return mUrlBar.getTextWithoutAutocomplete();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
new file mode 100644
index 0000000..b2571b2
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
@@ -0,0 +1,317 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.VisibleForTesting;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.view.ActionMode;
+
+import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer.UrlEmphasisSpan;
+import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
+import org.chromium.chrome.browser.omnibox.UrlBarProperties.AutocompleteText;
+import org.chromium.chrome.browser.omnibox.UrlBarProperties.UrlBarTextState;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Handles collecting and pushing state information to the UrlBar model.
+ */
+class UrlBarMediator implements UrlBar.UrlBarTextContextMenuDelegate {
+    private final PropertyModel mModel;
+
+    private Callback<Boolean> mOnFocusChangeCallback;
+    private boolean mHasFocus;
+
+    private UrlBarData mUrlBarData;
+    private @ScrollType int mScrollType = UrlBar.ScrollType.NO_SCROLL;
+    private @SelectionState int mSelectionState = UrlBarCoordinator.SelectionState.SELECT_ALL;
+
+    public UrlBarMediator(PropertyModel model) {
+        mModel = model;
+
+        mModel.setValue(UrlBarProperties.FOCUS_CHANGE_CALLBACK, this::onUrlFocusChange);
+        mModel.setValue(UrlBarProperties.SHOW_CURSOR, false);
+        mModel.setValue(UrlBarProperties.TEXT_CONTEXT_MENU_DELEGATE, this);
+    }
+
+    /**
+     * Set the primary delegate for the UrlBar view.
+     */
+    public void setDelegate(UrlBarDelegate delegate) {
+        mModel.setValue(UrlBarProperties.DELEGATE, delegate);
+    }
+
+    /**
+     * Updates the text content of the UrlBar.
+     *
+     * @param data The new data to be displayed.
+     * @param scrollType The scroll type that should be applied to the data.
+     * @param selectionState Specifies how the text should be selected when focused.
+     * @return Whether this data differs from the previously passed in values.
+     */
+    public boolean setUrlBarData(
+            UrlBarData data, @ScrollType int scrollType, @SelectionState int selectionState) {
+        if (data.originEndIndex == data.originStartIndex) {
+            scrollType = UrlBar.ScrollType.SCROLL_TO_BEGINNING;
+        }
+
+        // Do not scroll to the end of the host for URLs such as data:, javascript:, etc...
+        if (data.url != null && data.originEndIndex == data.url.length()) {
+            Uri uri = Uri.parse(data.url.toString());
+            String scheme = uri.getScheme();
+            if (!TextUtils.isEmpty(scheme)
+                    && UrlBarData.UNSUPPORTED_SCHEMES_TO_SPLIT.contains(scheme)) {
+                scrollType = UrlBar.ScrollType.SCROLL_TO_BEGINNING;
+            }
+        }
+
+        if (isNewTextEquivalentToExistingText(mUrlBarData, data) && mScrollType == scrollType) {
+            return false;
+        }
+        mUrlBarData = data;
+        mScrollType = scrollType;
+        mSelectionState = selectionState;
+
+        pushTextToModel();
+        return true;
+    }
+
+    private void pushTextToModel() {
+        CharSequence text =
+                !mHasFocus ? mUrlBarData.displayText : mUrlBarData.getEditingOrDisplayText();
+        @ScrollType
+        int scrollType = mHasFocus ? UrlBar.ScrollType.NO_SCROLL : mScrollType;
+        if (text == null) text = "";
+
+        UrlBarTextState state =
+                new UrlBarTextState(text, scrollType, mUrlBarData.originEndIndex, mSelectionState);
+        mModel.setValue(UrlBarProperties.TEXT_STATE, state);
+    }
+
+    @VisibleForTesting
+    protected static boolean isNewTextEquivalentToExistingText(
+            UrlBarData existingUrlData, UrlBarData newUrlData) {
+        if (existingUrlData == null) return newUrlData == null;
+        if (newUrlData == null) return false;
+
+        if (!TextUtils.equals(existingUrlData.editingText, newUrlData.editingText)) return false;
+
+        CharSequence existingCharSequence = existingUrlData.displayText;
+        CharSequence newCharSequence = newUrlData.displayText;
+        if (existingCharSequence == null) return newCharSequence == null;
+
+        // Regardless of focus state, ensure the text content is the same.
+        if (!TextUtils.equals(existingCharSequence, newCharSequence)) return false;
+
+        // When not focused, compare the emphasis spans applied to the text to determine
+        // equality.  Internally, TextView applies many additional spans that need to be
+        // ignored for this comparison to be useful, so this is scoped to only the span types
+        // applied by our UI.
+        if (!(newCharSequence instanceof Spanned) || !(existingCharSequence instanceof Spanned)) {
+            return false;
+        }
+
+        Spanned currentText = (Spanned) existingCharSequence;
+        Spanned newText = (Spanned) newCharSequence;
+        UrlEmphasisSpan[] currentSpans =
+                currentText.getSpans(0, currentText.length(), UrlEmphasisSpan.class);
+        UrlEmphasisSpan[] newSpans = newText.getSpans(0, newText.length(), UrlEmphasisSpan.class);
+        if (currentSpans.length != newSpans.length) return false;
+        for (int i = 0; i < currentSpans.length; i++) {
+            UrlEmphasisSpan currentSpan = currentSpans[i];
+            UrlEmphasisSpan newSpan = newSpans[i];
+            if (!currentSpan.equals(newSpan)
+                    || currentText.getSpanStart(currentSpan) != newText.getSpanStart(newSpan)
+                    || currentText.getSpanEnd(currentSpan) != newText.getSpanEnd(newSpan)
+                    || currentText.getSpanFlags(currentSpan) != newText.getSpanFlags(newSpan)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Sets the autocomplete text to be shown.
+     *
+     * @param userText The existing user text.
+     * @param autocompleteText The text to be appended to the user text.
+     */
+    public void setAutocompleteText(String userText, String autocompleteText) {
+        if (!mHasFocus) {
+            assert false : "Should not update autocomplete text when not focused";
+            return;
+        }
+        mModel.setValue(UrlBarProperties.AUTOCOMPLETE_TEXT,
+                new AutocompleteText(userText, autocompleteText));
+    }
+
+    /**
+     * Updates the callback that will be notified when the focus changes on the UrlBar.
+     *
+     * @param callback The callback to be notified on focus changes.
+     */
+    public void setOnFocusChangedCallback(Callback<Boolean> callback) {
+        mOnFocusChangeCallback = callback;
+    }
+
+    private void onUrlFocusChange(boolean focus) {
+        mHasFocus = focus;
+
+        if (mModel.getValue(UrlBarProperties.ALLOW_FOCUS)) {
+            mModel.setValue(UrlBarProperties.SHOW_CURSOR, mHasFocus);
+        }
+
+        UrlBarTextState preCallbackState = mModel.getValue(UrlBarProperties.TEXT_STATE);
+        if (mOnFocusChangeCallback != null) mOnFocusChangeCallback.onResult(focus);
+        boolean textChangedInFocusCallback =
+                mModel.getValue(UrlBarProperties.TEXT_STATE) != preCallbackState;
+        if (mUrlBarData != null && !textChangedInFocusCallback) {
+            pushTextToModel();
+        }
+    }
+
+    /**
+     * Sets whether to use dark text colors.
+     *
+     * @return Whether this resulted in a change from the previous value.
+     */
+    public boolean setUseDarkTextColors(boolean useDarkColors) {
+        boolean previousValue = mModel.getValue(UrlBarProperties.USE_DARK_TEXT_COLORS);
+        mModel.setValue(UrlBarProperties.USE_DARK_TEXT_COLORS, useDarkColors);
+        return previousValue != useDarkColors;
+    }
+
+    /**
+     * Sets whether the view allows user focus.
+     */
+    public void setAllowFocus(boolean allowFocus) {
+        mModel.setValue(UrlBarProperties.ALLOW_FOCUS, allowFocus);
+        if (allowFocus) {
+            mModel.setValue(UrlBarProperties.SHOW_CURSOR, mHasFocus);
+        }
+    }
+
+    /**
+     * Set the listener to be notified for URL direction changes.
+     */
+    public void setUrlDirectionListener(UrlDirectionListener listener) {
+        mModel.setValue(UrlBarProperties.URL_DIRECTION_LISTENER, listener);
+    }
+
+    /**
+     * Set the delegate that provides Window capabilities.
+     */
+    public void setWindowDelegate(WindowDelegate windowDelegate) {
+        mModel.setValue(UrlBarProperties.WINDOW_DELEGATE, windowDelegate);
+    }
+
+    /**
+     * Set the callback to handle contextual Action Modes.
+     */
+    public void setActionModeCallback(ActionMode.Callback callback) {
+        mModel.setValue(UrlBarProperties.ACTION_MODE_CALLBACK, callback);
+    }
+
+    @Override
+    public String getReplacementCutCopyText(
+            String currentText, int selectionStart, int selectionEnd) {
+        if (mUrlBarData == null || mUrlBarData.url == null) return null;
+
+        // Replace the cut/copy text only applies if the user selected from the beginning of the
+        // display text.
+        if (selectionStart != 0) return null;
+
+        // Trim to just the currently selected text as that is the only text we are replacing.
+        currentText = currentText.substring(selectionStart, selectionEnd);
+
+        String formattedUrlLocation;
+        String originalUrlLocation;
+        try {
+            // TODO(bauerb): Use |urlBarData.originEndIndex| for this instead?
+            URL javaUrl = new URL(mUrlBarData.url);
+            formattedUrlLocation = getUrlContentsPrePath(
+                    mUrlBarData.getEditingOrDisplayText().toString(), javaUrl.getHost());
+            originalUrlLocation = getUrlContentsPrePath(mUrlBarData.url, javaUrl.getHost());
+        } catch (MalformedURLException mue) {
+            // Just keep the existing selected text for cut/copy if unable to parse the URL.
+            return null;
+        }
+
+        // If we are copying/cutting the full previously formatted URL, reset the URL
+        // text before initiating the TextViews handling of the context menu.
+        //
+        // Example:
+        //    Original display text: www.example.com
+        //    Original URL:          http://www.example.com
+        //
+        // Editing State:
+        //    www.example.com/blah/foo
+        //    |<--- Selection --->|
+        //
+        // Resulting clipboard text should be:
+        //    http://www.example.com/blah/
+        //
+        // As long as the full original text was selected, it will replace that with the original
+        // URL and keep any further modifications by the user.
+        if (!currentText.startsWith(formattedUrlLocation)
+                || selectionEnd < formattedUrlLocation.length()) {
+            return null;
+        }
+
+        return originalUrlLocation + currentText.substring(formattedUrlLocation.length());
+    }
+
+    @Override
+    public String getTextToPaste() {
+        Context context = ContextUtils.getApplicationContext();
+
+        ClipboardManager clipboard =
+                (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+        ClipData clipData = clipboard.getPrimaryClip();
+        if (clipData == null) return null;
+
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < clipData.getItemCount(); i++) {
+            builder.append(clipData.getItemAt(i).coerceToText(context));
+        }
+        return sanitizeTextForPaste(builder.toString());
+    }
+
+    @VisibleForTesting
+    protected String sanitizeTextForPaste(String text) {
+        return OmniboxViewUtil.sanitizeTextForPaste(text);
+    }
+
+    /**
+     * Returns the portion of the URL that precedes the path/query section of the URL.
+     *
+     * @param url The url to be used to find the preceding portion.
+     * @param host The host to be located in the URL to determine the location of the path.
+     * @return The URL contents that precede the path (or the passed in URL if the host is
+     *         not found).
+     */
+    private static String getUrlContentsPrePath(String url, String host) {
+        int hostIndex = url.indexOf(host);
+        if (hostIndex == -1) return url;
+
+        int pathIndex = url.indexOf('/', hostIndex);
+        if (pathIndex <= 0) return url;
+
+        return url.substring(0, pathIndex);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
new file mode 100644
index 0000000..bdc34ca
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java
@@ -0,0 +1,122 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox;
+
+import android.view.ActionMode;
+
+import org.chromium.base.Callback;
+import org.chromium.chrome.browser.WindowDelegate;
+import org.chromium.chrome.browser.modelutil.PropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey;
+import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarTextContextMenuDelegate;
+import org.chromium.chrome.browser.omnibox.UrlBar.UrlDirectionListener;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
+
+import java.util.Locale;
+
+/**
+ * The model properties for the URL bar text component.
+ */
+class UrlBarProperties {
+    /**
+     * Contains the necessary information to update the text shown in the UrlBar.
+     */
+    static class UrlBarTextState {
+        /** Text to be shown. */
+        public final CharSequence text;
+
+        /** Specifies how the text should be scrolled in the unfocused state. */
+        public final @ScrollType int scrollType;
+
+        /** Specifies the index to scroll to if {@link UrlBar#SCROLL_TO_TLD} is specified. */
+        public int scrollToIndex;
+
+        /** Specifies how the text should be selected in the focused state. */
+        public final @SelectionState int selectionState;
+
+        public UrlBarTextState(CharSequence text, @ScrollType int scrollType, int scrollToIndex,
+                @SelectionState int selectionState) {
+            this.text = text;
+            this.scrollType = scrollType;
+            this.scrollToIndex = scrollToIndex;
+            this.selectionState = selectionState;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.US, "%s: text: %s; scrollType: %d; selectionState: %d",
+                    getClass().getSimpleName(), text, scrollType, selectionState);
+        }
+    }
+
+    /**
+     * Contains the necessary information to display inline autocomplete text.
+     */
+    static class AutocompleteText {
+        /** The text preceding the autocomplete text (typically entered by the user). */
+        public final String userText;
+
+        /** The inline autocomplete text to be appended to the end of the user text. */
+        public final String autocompleteText;
+
+        public AutocompleteText(String userText, String autocompleteText) {
+            this.userText = userText;
+            this.autocompleteText = autocompleteText;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.US, "%s: user text: %s; autocomplete text: %s",
+                    getClass().getSimpleName(), userText, autocompleteText);
+        }
+    }
+
+    /** The callback for contextual action modes (cut, copy, etc...). */
+    public static final ObjectPropertyKey<ActionMode.Callback> ACTION_MODE_CALLBACK =
+            new ObjectPropertyKey<>();
+
+    /** Whether focus should be allowed on the view. */
+    public static final BooleanPropertyKey ALLOW_FOCUS = new BooleanPropertyKey();
+
+    /** Specified the autocomplete text to be shown to the user. */
+    public static final ObjectPropertyKey<AutocompleteText> AUTOCOMPLETE_TEXT =
+            new ObjectPropertyKey<>();
+
+    /** The main delegate that provides additional capabilities to the UrlBar. */
+    public static final ObjectPropertyKey<UrlBarDelegate> DELEGATE = new ObjectPropertyKey<>();
+
+    /** The callback to be notified on focus changes. */
+    public static final ObjectPropertyKey<Callback<Boolean>> FOCUS_CHANGE_CALLBACK =
+            new ObjectPropertyKey<>();
+
+    /** Whether the cursor should be shown in the view. */
+    public static final BooleanPropertyKey SHOW_CURSOR = new BooleanPropertyKey();
+
+    /** Delegate that provides additional functionality to the textual context actions. */
+    public static final ObjectPropertyKey<UrlBarTextContextMenuDelegate>
+            TEXT_CONTEXT_MENU_DELEGATE = new ObjectPropertyKey<>();
+
+    /** The primary text state for what is shown in the view. */
+    public static final ObjectPropertyKey<UrlBarTextState> TEXT_STATE = new ObjectPropertyKey<>();
+
+    /** The listener to be notified of URL direction changes. */
+    public static final ObjectPropertyKey<UrlDirectionListener> URL_DIRECTION_LISTENER =
+            new ObjectPropertyKey<>();
+
+    /** Specifies whether dark text colors should be used in the view. */
+    public static final BooleanPropertyKey USE_DARK_TEXT_COLORS = new BooleanPropertyKey();
+
+    /** The delegate that provides Window capabilities to the view. */
+    public static final ObjectPropertyKey<WindowDelegate> WINDOW_DELEGATE =
+            new ObjectPropertyKey<>();
+
+    public static final PropertyKey[] ALL_KEYS =
+            new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE,
+                    FOCUS_CHANGE_CALLBACK, SHOW_CURSOR, TEXT_CONTEXT_MENU_DELEGATE, TEXT_STATE,
+                    URL_DIRECTION_LISTENER, USE_DARK_TEXT_COLORS, WINDOW_DELEGATE};
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
new file mode 100644
index 0000000..87388b1
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java
@@ -0,0 +1,137 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox;
+
+import android.content.res.Resources;
+import android.support.annotation.ColorRes;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.TextUtils;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.modelutil.PropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.omnibox.UrlBarProperties.AutocompleteText;
+import org.chromium.chrome.browser.omnibox.UrlBarProperties.UrlBarTextState;
+
+/**
+ * Handles translating the UrlBar model data to the view state.
+ */
+class UrlBarViewBinder {
+    /**
+     * @see
+     * org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor.ViewBinder#bind(Object,
+     * Object, Object)
+     */
+    public static void bind(PropertyModel model, UrlBar view, PropertyKey propertyKey) {
+        if (UrlBarProperties.ACTION_MODE_CALLBACK.equals(propertyKey)) {
+            view.setCustomSelectionActionModeCallback(
+                    model.getValue(UrlBarProperties.ACTION_MODE_CALLBACK));
+        } else if (UrlBarProperties.ALLOW_FOCUS.equals(propertyKey)) {
+            boolean allowFocus = model.getValue(UrlBarProperties.ALLOW_FOCUS);
+            view.setAllowFocus(allowFocus);
+        } else if (UrlBarProperties.AUTOCOMPLETE_TEXT.equals(propertyKey)) {
+            AutocompleteText autocomplete = model.getValue(UrlBarProperties.AUTOCOMPLETE_TEXT);
+            if (view.shouldAutocomplete()) {
+                view.setAutocompleteText(autocomplete.userText, autocomplete.autocompleteText);
+            }
+        } else if (UrlBarProperties.DELEGATE.equals(propertyKey)) {
+            view.setDelegate(model.getValue(UrlBarProperties.DELEGATE));
+        } else if (UrlBarProperties.FOCUS_CHANGE_CALLBACK.equals(propertyKey)) {
+            final Callback<Boolean> focusChangeCallback =
+                    model.getValue(UrlBarProperties.FOCUS_CHANGE_CALLBACK);
+            view.setOnFocusChangeListener((v, focused) -> focusChangeCallback.onResult(focused));
+        } else if (UrlBarProperties.SHOW_CURSOR.equals(propertyKey)) {
+            view.setCursorVisible(model.getValue(UrlBarProperties.SHOW_CURSOR));
+        } else if (UrlBarProperties.TEXT_CONTEXT_MENU_DELEGATE.equals(propertyKey)) {
+            view.setTextContextMenuDelegate(
+                    model.getValue(UrlBarProperties.TEXT_CONTEXT_MENU_DELEGATE));
+        } else if (UrlBarProperties.TEXT_STATE.equals(propertyKey)) {
+            UrlBarTextState state = model.getValue(UrlBarProperties.TEXT_STATE);
+            view.setIgnoreTextChangesForAutocomplete(true);
+            view.setText(state.text);
+            view.setScrollState(state.scrollType, state.scrollToIndex);
+            view.setIgnoreTextChangesForAutocomplete(false);
+
+            if (view.hasFocus()) {
+                if (state.selectionState == UrlBarCoordinator.SelectionState.SELECT_ALL) {
+                    view.selectAll();
+                } else if (state.selectionState == UrlBarCoordinator.SelectionState.SELECT_END) {
+                    view.setSelection(view.getText().length());
+                }
+            }
+        } else if (UrlBarProperties.USE_DARK_TEXT_COLORS.equals(propertyKey)) {
+            updateTextColors(view, model.getValue(UrlBarProperties.USE_DARK_TEXT_COLORS));
+        } else if (UrlBarProperties.URL_DIRECTION_LISTENER.equals(propertyKey)) {
+            view.setUrlDirectionListener(model.getValue(UrlBarProperties.URL_DIRECTION_LISTENER));
+        } else if (UrlBarProperties.WINDOW_DELEGATE.equals(propertyKey)) {
+            view.setWindowDelegate(model.getValue(UrlBarProperties.WINDOW_DELEGATE));
+        }
+    }
+
+    private static void updateTextColors(UrlBar view, boolean useDarkTextColors) {
+        int originalHighlightColor;
+        Object highlightColorObj = view.getTag(R.id.highlight_color);
+        if (highlightColorObj == null || !(highlightColorObj instanceof Integer)) {
+            originalHighlightColor = view.getHighlightColor();
+            view.setTag(R.id.highlight_color, originalHighlightColor);
+        } else {
+            originalHighlightColor = (Integer) highlightColorObj;
+        }
+
+        Resources resources = view.getResources();
+        int textColor;
+        int hintColor;
+        int highlightColor;
+        if (useDarkTextColors) {
+            textColor =
+                    ApiCompatibilityUtils.getColor(resources, R.color.url_emphasis_default_text);
+            hintColor =
+                    ApiCompatibilityUtils.getColor(resources, R.color.locationbar_dark_hint_text);
+            highlightColor = originalHighlightColor;
+        } else {
+            textColor = ApiCompatibilityUtils.getColor(
+                    resources, R.color.url_emphasis_light_default_text);
+            hintColor =
+                    ApiCompatibilityUtils.getColor(resources, R.color.locationbar_light_hint_text);
+            highlightColor = ApiCompatibilityUtils.getColor(
+                    resources, R.color.locationbar_light_selection_color);
+        }
+
+        view.setTextColor(textColor);
+        setHintTextColor(view, hintColor);
+        view.setHighlightColor(highlightColor);
+    }
+
+    private static void setHintTextColor(UrlBar view, @ColorRes int textColor) {
+        // Note: Setting the hint text color only takes effect if there is not text in the URL bar.
+        //       To get around this, set the URL to empty before setting the hint color and revert
+        //       back to the previous text after.
+        boolean hasNonEmptyText = false;
+        int selectionStart = 0;
+        int selectionEnd = 0;
+        Editable text = view.getText();
+        if (!TextUtils.isEmpty(text)) {
+            selectionStart = view.getSelectionStart();
+            selectionEnd = view.getSelectionEnd();
+            // Make sure the setText in this block does not affect the suggestions.
+            view.setIgnoreTextChangesForAutocomplete(true);
+            view.setText("");
+            hasNonEmptyText = true;
+        }
+        view.setHintTextColor(textColor);
+        if (hasNonEmptyText) {
+            view.setText(text);
+            if (view.hasFocus()) {
+                Selection.setSelection(view.getText(), selectionStart, selectionEnd);
+            }
+            view.setIgnoreTextChangesForAutocomplete(false);
+        }
+    }
+
+    private UrlBarViewBinder() {}
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/AccessibilityPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/AccessibilityPreferences.java
index 82a0998a..d5102a33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/AccessibilityPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/AccessibilityPreferences.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.accessibility.FontSizePrefs;
 import org.chromium.chrome.browser.accessibility.FontSizePrefs.FontSizePrefsObserver;
+import org.chromium.chrome.browser.util.AccessibilityUtil;
 
 import java.text.NumberFormat;
 
@@ -33,6 +34,7 @@
     private TextScalePreference mTextScalePref;
     private SeekBarLinkedCheckBoxPreference mForceEnableZoomPref;
     private ChromeBaseCheckBoxPreference mReaderForAccessibilityPref;
+    private ChromeBaseCheckBoxPreference mAccessibilityTabSwitcherPref;
 
     private FontSizePrefsObserver mFontSizePrefsObserver = new FontSizePrefsObserver() {
         @Override
@@ -70,7 +72,17 @@
                     Pref.READER_FOR_ACCESSIBILITY_ENABLED));
             mReaderForAccessibilityPref.setOnPreferenceChangeListener(this);
         } else {
-            this.getPreferenceScreen().removePreference(mReaderForAccessibilityPref);
+            getPreferenceScreen().removePreference(mReaderForAccessibilityPref);
+        }
+
+        mAccessibilityTabSwitcherPref = (ChromeBaseCheckBoxPreference) findPreference(
+                ChromePreferenceManager.ACCESSIBILITY_TAB_SWITCHER);
+        if (AccessibilityUtil.isAccessibilityEnabled()) {
+            mAccessibilityTabSwitcherPref.setChecked(
+                    ChromePreferenceManager.getInstance().readBoolean(
+                            ChromePreferenceManager.ACCESSIBILITY_TAB_SWITCHER, true));
+        } else {
+            getPreferenceScreen().removePreference(mAccessibilityTabSwitcherPref);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
index 4f79cd0..b2c25380 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
@@ -193,6 +193,13 @@
     public static final String SHOULD_REGISTER_VR_ASSETS_COMPONENT_ON_STARTUP =
             "should_register_vr_assets_component_on_startup";
 
+    /*
+     * Whether the simplified tab switcher is enabled when accessibility mode is enabled. Keep in
+     * sync with accessibility_preferences.xml.
+     * Default value is true.
+     */
+    public static final String ACCESSIBILITY_TAB_SWITCHER = "accessibility_tab_switcher";
+
     /**
      * Deprecated keys for Chrome Home.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
index ab3a21f7..221ef4e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
@@ -557,8 +557,10 @@
     /** Callback for PassphraseDialogFragment.Listener */
     @Override
     public boolean onPassphraseEntered(String passphrase) {
-        if (!mProfileSyncService.isEngineInitialized()) {
-            // If the engine was shut down since the dialog was opened, do nothing.
+        if (!mProfileSyncService.isEngineInitialized()
+                || !mProfileSyncService.isPassphraseRequiredForDecryption()) {
+            // If the engine was shut down since the dialog was opened, or the passphrase isn't
+            // required anymore, do nothing.
             return false;
         }
         return handleDecryption(passphrase);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
index 3cf64a76..8f604b10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivityLocationBarLayout.java
@@ -12,15 +12,15 @@
 import android.view.View;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.WindowDelegate;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.omnibox.LocationBarLayout;
 import org.chromium.chrome.browser.omnibox.LocationBarVoiceRecognitionHandler;
 import org.chromium.chrome.browser.omnibox.OmniboxSuggestion;
+import org.chromium.chrome.browser.omnibox.UrlBar;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.ui.UiUtils;
-import org.chromium.ui.base.WindowAndroid;
 
 import java.util.List;
 
@@ -72,12 +72,6 @@
     }
 
     @Override
-    public void initializeControls(WindowDelegate windowDelegate, WindowAndroid windowAndroid) {
-        super.initializeControls(windowDelegate, windowAndroid);
-        setShowCachedZeroSuggestResults(true);
-    }
-
-    @Override
     public void onNativeLibraryReady() {
         super.onNativeLibraryReady();
         setAutocompleteProfile(Profile.getLastUsedProfile().getOriginalProfile());
@@ -99,7 +93,9 @@
                     mVoiceRecognitionHandler.isVoiceSearchEnabled());
         }
         if (isVoiceSearchIntent && mUrlBar.isFocused()) onUrlFocusChange(true);
-        if (!TextUtils.isEmpty(mUrlBar.getText())) onTextChangedForAutocomplete();
+        if (!TextUtils.isEmpty(mUrlCoordinator.getTextWithAutocomplete())) {
+            onTextChangedForAutocomplete();
+        }
 
         assert !LocaleManager.getInstance().needToCheckForSearchEnginePromo();
         mPendingSearchPromoDecision = false;
@@ -119,12 +115,9 @@
         // Clear the text regardless of the promo decision.  This allows the user to enter text
         // before native has been initialized and have it not be cleared one the delayed beginQuery
         // logic is performed.
-        mUrlBar.setIgnoreTextChangesForAutocomplete(true);
-        mUrlBar.setUrl(UrlBarData.forNonUrlText(optionalText == null ? "" : optionalText));
-        mUrlBar.setIgnoreTextChangesForAutocomplete(false);
-
-        mUrlBar.setCursorVisible(true);
-        mUrlBar.setSelection(0, mUrlBar.getText().length());
+        mUrlCoordinator.setUrlBarData(
+                UrlBarData.forNonUrlText(optionalText == null ? "" : optionalText),
+                UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL);
 
         if (mPendingSearchPromoDecision) {
             mPendingBeginQuery = true;
@@ -153,8 +146,12 @@
         findViewById(R.id.url_action_container).setVisibility(View.VISIBLE);
     }
 
+    // TODO(tedchoc): Investigate focusing regardless of the search promo state and just ensure
+    //                we don't start processing non-cached suggestion requests until that state
+    //                is finalized after native has been initialized.
     private void focusTextBox() {
         if (!mUrlBar.hasFocus()) mUrlBar.requestFocus();
+        setShowCachedZeroSuggestResults(true);
 
         new Handler().post(new Runnable() {
             @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpFragment.java
index b138caf1..9efca195 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpFragment.java
@@ -5,7 +5,10 @@
 package org.chromium.chrome.browser.signin;
 
 import android.os.Bundle;
+import android.support.annotation.IdRes;
 import android.support.annotation.StringRes;
+import android.support.v4.app.FragmentTransaction;
+import android.view.ViewGroup;
 
 import org.chromium.chrome.R;
 
@@ -29,7 +32,14 @@
 
     @Override
     protected void onSigninRefused() {
-        // TODO(https://crbug.com/869426): Show ConsentBumpMoreOptionsFragment.
+        FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
+                android.R.anim.slide_in_left, android.R.anim.slide_out_right);
+        // Get the id of the view that contains this fragment and replace the fragment.
+        @IdRes int containerId = ((ViewGroup) getView().getParent()).getId();
+        transaction.replace(containerId, new ConsentBumpMoreOptionsFragment());
+        transaction.addToBackStack(null);
+        transaction.commit();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpMoreOptionsFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpMoreOptionsFragment.java
index 24b6465f0..76f33da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpMoreOptionsFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/ConsentBumpMoreOptionsFragment.java
@@ -6,10 +6,13 @@
 
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.widget.RadioButtonWithDescription;
 
@@ -31,6 +34,13 @@
         noChanges.setDescriptionText(getText(R.string.consent_bump_no_changes_description));
         RadioButtonWithDescription turnOn = view.findViewById(R.id.consent_bump_turn_on);
         turnOn.setDescriptionText(getText(R.string.consent_bump_turn_on_description));
+
+        Button button = view.findViewById(R.id.back_button);
+        button.setOnClickListener(btn -> {
+            FragmentManager fragmentManager =
+                    ApiCompatibilityUtils.requireNonNull(getFragmentManager());
+            fragmentManager.popBackStack();
+        });
         return view;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
index bdfc0f94..171312be 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
@@ -436,8 +436,10 @@
      */
     @Override
     public boolean onPassphraseEntered(String passphrase) {
-        if (!mProfileSyncService.isEngineInitialized()) {
-            // If the engine was shut down since the dialog was opened, do nothing.
+        if (!mProfileSyncService.isEngineInitialized()
+                || !mProfileSyncService.isPassphraseRequiredForDecryption()) {
+            // If the engine was shut down since the dialog was opened, or the passphrase isn't
+            // required anymore, do nothing.
             return false;
         }
         return handleDecryption(passphrase);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
index a1dea13..6e5ee9d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/EmptyTabObserver.java
@@ -14,6 +14,9 @@
 
 /**
  * An implementation of the {@link TabObserver} which has empty implementations of all methods.
+ *
+ * Note: Do not replace this with TabObserver with default interface methods as it inadvertently
+ * bloats the number of methods. See https://crbug.com/781359.
  */
 public class EmptyTabObserver implements TabObserver {
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
index 4534f87..f4dd92fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbar.java
@@ -50,6 +50,8 @@
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.omnibox.LocationBar;
 import org.chromium.chrome.browser.omnibox.UrlBar;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator;
+import org.chromium.chrome.browser.omnibox.UrlBarCoordinator.SelectionState;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
 import org.chromium.chrome.browser.page_info.PageInfoController;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -125,7 +127,8 @@
 
     private View mLocationBarFrameLayout;
     private View mTitleUrlContainer;
-    private UrlBar mUrlBar;
+    private TextView mUrlBar;
+    private UrlBarCoordinator mUrlCoordinator;
     private TextView mTitleBar;
     private TintedImageButton mSecurityButton;
     private LinearLayout mCustomActionButtons;
@@ -162,11 +165,12 @@
         super.onFinishInflate();
         setBackground(new ColorDrawable(ColorUtils.getDefaultThemeColor(
                 getResources(), FeatureUtilities.isChromeModernDesignEnabled(), false)));
-        mUrlBar = findViewById(R.id.url_bar);
+        mUrlBar = (TextView) findViewById(R.id.url_bar);
         mUrlBar.setHint("");
-        mUrlBar.setDelegate(this);
         mUrlBar.setEnabled(false);
-        mUrlBar.setAllowFocus(false);
+        mUrlCoordinator = new UrlBarCoordinator((UrlBar) mUrlBar);
+        mUrlCoordinator.setDelegate(this);
+        mUrlCoordinator.setAllowFocus(false);
         mTitleBar = findViewById(R.id.title_bar);
         mLocationBarFrameLayout = findViewById(R.id.location_bar_frame_layout);
         mTitleUrlContainer = findViewById(R.id.title_url_container);
@@ -396,7 +400,8 @@
     @Override
     public void setUrlToPageUrl() {
         if (getCurrentTab() == null) {
-            mUrlBar.setUrl(UrlBarData.EMPTY);
+            mUrlCoordinator.setUrlBarData(
+                    UrlBarData.EMPTY, UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL);
             return;
         }
 
@@ -411,7 +416,8 @@
         // is "about:blank". We should not display it.
         if (NativePageFactory.isNativePageUrl(url, getCurrentTab().isIncognito())
                 || ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL.equals(url)) {
-            mUrlBar.setUrl(UrlBarData.EMPTY);
+            mUrlCoordinator.setUrlBarData(
+                    UrlBarData.EMPTY, UrlBar.ScrollType.NO_SCROLL, SelectionState.SELECT_ALL);
             return;
         }
         final CharSequence displayText;
@@ -437,7 +443,9 @@
             originEnd = displayText.length();
         }
 
-        mUrlBar.setUrl(UrlBarData.create(url, displayText, originStart, originEnd, url));
+        mUrlCoordinator.setUrlBarData(
+                UrlBarData.create(url, displayText, originStart, originEnd, url),
+                UrlBar.ScrollType.SCROLL_TO_TLD, SelectionState.SELECT_ALL);
     }
 
     @Override
@@ -461,7 +469,7 @@
         Resources resources = getResources();
         updateSecurityIcon();
         updateButtonsTint();
-        if (mUrlBar.setUseDarkTextColors(mUseDarkColors)) {
+        if (mUrlCoordinator.setUseDarkTextColors(mUseDarkColors)) {
             setUrlToPageUrl();
         }
 
@@ -599,7 +607,7 @@
 
     @Override
     public void setDefaultTextEditActionModeCallback(ToolbarActionModeCallback callback) {
-        mUrlBar.setCustomSelectionActionModeCallback(callback);
+        mUrlCoordinator.setActionModeCallback(callback);
     }
 
     private void updateLayoutParams() {
@@ -738,12 +746,6 @@
     }
 
     @Override
-    @UrlBar.ScrollType
-    public int getScrollType() {
-        return UrlBar.ScrollType.SCROLL_TO_TLD;
-    }
-
-    @Override
     public void setUrlBarFocus(boolean shouldBeFocused) {}
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbarAnimationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbarAnimationDelegate.java
index 4e13200..15d8ed3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbarAnimationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/CustomTabToolbarAnimationDelegate.java
@@ -14,7 +14,6 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.omnibox.UrlBar;
 import org.chromium.chrome.browser.widget.animation.CancelAwareAnimatorListener;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
@@ -39,7 +38,7 @@
     private final AnimatorSet mSecurityButtonShowAnimator;
     private final AnimatorSet mSecurityButtonHideAnimator;
 
-    private UrlBar mUrlBar;
+    private TextView mUrlBar;
     private TextView mTitleBar;
     private int mSecurityButtonWidth;
     // A flag controlling whether the animation has run before.
@@ -98,7 +97,7 @@
         mShouldRunTitleAnimation = enabled;
     }
 
-    void prepareTitleAnim(UrlBar urlBar, TextView titleBar) {
+    void prepareTitleAnim(TextView urlBar, TextView titleBar) {
         mTitleBar = titleBar;
         mUrlBar = urlBar;
         mUrlBar.setPivotX(0f);
@@ -130,8 +129,6 @@
 
         mUrlBar.requestLayout();
 
-        // Recalculate proportion of shown text in the URL bar and scroll it after font size changed
-        mUrlBar.scrollDisplayText();
         mUrlBar.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
             @Override
             public void onLayoutChange(View v, int left, int top, int right, int bottom,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index fae15f2d..0ccdc8093 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -652,8 +652,31 @@
         }
     }
 
+    /** Record that the bottom toolbar was used for IPH reasons. */
+    private void recordBottomToolbarUseForIPH() {
+        if (mTabModelSelector != null && mTabModelSelector.getCurrentTab() != null) {
+            Tab tab = mTabModelSelector.getCurrentTab();
+            Tracker tracker = TrackerFactory.getTrackerForProfile(tab.getProfile());
+            tracker.notifyEvent(EventConstants.CHROME_DUET_USED_BOTTOM_TOOLBAR);
+        }
+    }
+
+    /**
+     * Add bottom toolbar IPH tracking to an existing click listener.
+     * @param listener The listener to add bottom toolbar tracking to.
+     */
+    private OnClickListener wrapBottomToolbarClickListenerForIPH(OnClickListener listener) {
+        return (v) -> {
+            recordBottomToolbarUseForIPH();
+            listener.onClick(v);
+        };
+    }
+
     private ToolbarButtonData createHomeButton(Context context) {
-        final OnClickListener homeButtonListener = v -> openHomepage();
+        final OnClickListener homeButtonListener = v -> {
+            recordBottomToolbarUseForIPH();
+            openHomepage();
+        };
         return new ToolbarButtonData(R.drawable.btn_toolbar_home,
                 R.string.accessibility_toolbar_btn_home, homeButtonListener, true, context);
     }
@@ -666,6 +689,7 @@
 
     private ToolbarButtonData createSearchAccelerator(Context context) {
         final OnClickListener searchAcceleratorListener = v -> {
+            recordBottomToolbarUseForIPH();
             recordOmniboxFocusReason(OmniboxFocusReason.ACCELERATOR_TAP);
             ACCELERATOR_BUTTON_TAP_ACTION.record();
             setUrlBarFocus(true);
@@ -806,15 +830,23 @@
                                ChromeFeatureList.HORIZONTAL_TAB_SWITCHER_ANDROID)
                     && PrefServiceBridge.getInstance().isIncognitoModeEnabled();
             final ToolbarButtonData secondSlotTabSwitcherButtonData = showIncognitoToggleButton
-                    ? createIncognitoToggleButton(incognitoClickHandler, mActivity)
+                    ? createIncognitoToggleButton(
+                              wrapBottomToolbarClickListenerForIPH(incognitoClickHandler),
+                              mActivity)
                     : null;
+            mAppMenuButtonHelper.setOnClickRunnable(() -> recordBottomToolbarUseForIPH());
             mBottomToolbarCoordinator.initializeWithNative(
                     mActivity.getCompositorViewHolder().getResourceManager(),
-                    mActivity.getCompositorViewHolder().getLayoutManager(), tabSwitcherClickHandler,
+                    mActivity.getCompositorViewHolder().getLayoutManager(),
+                    wrapBottomToolbarClickListenerForIPH(tabSwitcherClickHandler),
                     mAppMenuButtonHelper, mTabModelSelector, mOverviewModeBehavior,
                     mActivity.getContextualSearchManager(), mActivity.getWindowAndroid(),
-                    createNewTabButton(newTabClickHandler, mActivity),
+                    createNewTabButton(
+                            wrapBottomToolbarClickListenerForIPH(newTabClickHandler), mActivity),
                     secondSlotTabSwitcherButtonData);
+
+            Tab currentTab = tabModelSelector.getCurrentTab();
+            maybeShowDuetHelpBubble(currentTab);
         }
 
         onNativeLibraryReady();
@@ -822,6 +854,24 @@
     }
 
     /**
+     * Maybe show the IPH bubble for Chrome Duet.
+     * @param tab The active tab.
+     */
+    private void maybeShowDuetHelpBubble(Tab tab) {
+        if (tab == null) return;
+        final Tracker tracker = TrackerFactory.getTrackerForProfile(tab.getProfile());
+        if (tracker.shouldTriggerHelpUI(FeatureConstants.CHROME_DUET_FEATURE)) {
+            ViewRectProvider provider = new ViewRectProvider(mToolbar);
+            TextBubble bubble = new TextBubble(mToolbar.getContext(), mToolbar,
+                    R.string.iph_duet_icons_moved, R.string.iph_duet_icons_moved, true, provider);
+            bubble.setDismissOnTouchInteraction(true);
+            bubble.addOnDismissListener(
+                    () -> tracker.dismissed(FeatureConstants.CHROME_DUET_FEATURE));
+            bubble.show();
+        }
+    }
+
+    /**
      * Show the update badge in both the top and bottom toolbar.
      * TODO(amaralp): Only the top or bottom menu should be visible.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarTablet.java
index 67afbf7..3800b3e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarTablet.java
@@ -18,6 +18,7 @@
 import android.widget.ImageButton;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.StrictModeContext;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -27,6 +28,7 @@
 import org.chromium.chrome.browser.omnibox.LocationBar;
 import org.chromium.chrome.browser.omnibox.LocationBarTablet;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
+import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.AccessibilityUtil;
 import org.chromium.chrome.browser.util.FeatureUtilities;
@@ -103,7 +105,8 @@
         mForwardButton = (TintedImageButton) findViewById(R.id.forward_button);
         mReloadButton = (TintedImageButton) findViewById(R.id.refresh_button);
         mSecurityButton = (TintedImageButton) findViewById(R.id.security_button);
-        mShowTabStack = AccessibilityUtil.isAccessibilityEnabled();
+        mShowTabStack = AccessibilityUtil.isAccessibilityEnabled()
+                && isAccessibilityTabSwitcherPreferenceEnabled();
 
         mTabSwitcherButtonDrawable =
                 TabSwitcherDrawable.createTabSwitcherDrawable(getContext(), false);
@@ -533,8 +536,8 @@
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_MEMEX)) {
             return;
         }
-        mShowTabStack = enabled;
-        updateSwitcherButtonVisibility(enabled);
+        mShowTabStack = enabled && isAccessibilityTabSwitcherPreferenceEnabled();
+        updateSwitcherButtonVisibility(mShowTabStack);
     }
 
     @Override
@@ -713,4 +716,10 @@
         return set;
     }
 
+    private boolean isAccessibilityTabSwitcherPreferenceEnabled() {
+        try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+            return ChromePreferenceManager.getInstance().readBoolean(
+                    ChromePreferenceManager.ACCESSIBILITY_TAB_SWITCHER, true);
+        }
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
index b81a3723..afe1e6da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
@@ -37,4 +37,5 @@
     boolean bootsToVr();
     boolean isDaydreamReadyDevice();
     boolean isDaydreamCurrentViewer();
+    boolean willChangeDensityInVr(ChromeActivity activity);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
index 9534bb2..efbdb9a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
@@ -143,4 +143,10 @@
         assert false;
         return null;
     }
+
+    @Override
+    public boolean willChangeDensityInVr(ChromeActivity activity) {
+        assert false;
+        return false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
index 65c2b61..830941834 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
@@ -162,4 +162,9 @@
     public Bundle getVrIntentOptions(Context context) {
         return VrIntentUtils.getVrIntentOptions(context);
     }
+
+    @Override
+    public boolean willChangeDensityInVr(ChromeActivity activity) {
+        return VrShellDelegate.willChangeDensityInVr(activity);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrIntentUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrIntentUtils.java
index 026b6a0..f6f2ab9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrIntentUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrIntentUtils.java
@@ -18,18 +18,7 @@
  * Utilities dealing with extracting information about VR intents.
  */
 public class VrIntentUtils {
-    // The Daydream Home app adds this extra to auto-present intents.
-    public static final String AUTOPRESENT_WEVBVR_EXTRA = "browser.vr.AUTOPRESENT_WEBVR";
     public static final String DAYDREAM_CATEGORY = "com.google.intent.category.DAYDREAM";
-    // Tells Chrome not to relaunch itself when receiving a VR intent. This is used by tests since
-    // the relaunch logic does not work properly with the DON flow skipped.
-    public static final String AVOID_RELAUNCH_EXTRA =
-            "org.chromium.chrome.browser.vr.AVOID_RELAUNCH";
-    // Tells Chrome to attempts a relaunch of the intent if it is received outside of VR and doesn't
-    // have the Daydream category set. This is a workaround for https://crbug.com/854327 where
-    // launchInVr can sometimes launch the given intent before entering VR.
-    public static final String ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA =
-            "org.chromium.chrome.browser.vr.ENABLE_TEST_RELUANCH_WORKAROUND";
 
     static final String VR_FRE_INTENT_EXTRA = "org.chromium.chrome.browser.vr.VR_FRE";
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrMainActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrMainActivity.java
deleted file mode 100644
index 2242020..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrMainActivity.java
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.vr;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.os.Bundle;
-
-import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ApplicationStatus;
-import org.chromium.base.Log;
-import org.chromium.base.TraceEvent;
-import org.chromium.chrome.browser.ChromeActivity;
-import org.chromium.chrome.browser.LaunchIntentDispatcher;
-import org.chromium.chrome.browser.document.ChromeLauncherActivity;
-import org.chromium.chrome.browser.util.IntentUtils;
-
-import java.lang.ref.WeakReference;
-
-/**
- * This is the VR equivalent of {@link ChromeLauncherActivity}. It exists because the Android
- * platform doesn't inherently support hybrid VR Activities (like Chrome uses). All VR intents for
- * Chrome are routed through this activity as its manifest entry contains VR specific attributes to
- * ensure a smooth transition into Chrome VR, and allows us to properly handle both implicit and
- * explicit VR intents that may be missing VR categories when they get sent.
- *
- * This Activity doesn't inherit from ChromeLauncherActivity because we need to be able to finish
- * and relaunch this Launcher without calling ChromeLauncherActivity#onCreate which would fire the
- * intent we don't yet want to fire.
- */
-public class VrMainActivity extends Activity {
-    private static final String TAG = "VrMainActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        TraceEvent.begin("VrMainActivity.onCreate");
-        try {
-            super.onCreate(savedInstanceState);
-
-            boolean hasDaydreamCategory = getIntent().hasCategory(VrIntentUtils.DAYDREAM_CATEGORY);
-
-            if (!VrShellDelegate.deviceSupportsVrLaunches()
-                    || (!hasDaydreamCategory && !VrShellDelegate.isInVrSession())) {
-                // TODO(https://crbug.com/854327): Remove this workaround once the issue with
-                // launchInVr sometimes launching the given intent before entering VR is fixed.
-                if (IntentUtils.safeGetBooleanExtra(getIntent(),
-                            VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, false)) {
-                    Log.d(TAG,
-                            "Relaunching given intent due to test workaround boolean being set.");
-                    // Disable the workaround after the first time so we don't get into a loop
-                    getIntent().putExtra(
-                            VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, false);
-                    VrIntentUtils.launchInVr(getIntent(), this);
-                    finish();
-                    return;
-                }
-                StringBuilder error = new StringBuilder("Attempted to launch Chrome into VR ");
-                if (!VrShellDelegate.deviceSupportsVrLaunches()) {
-                    error.append("on a device that doesn't support Chrome in VR.");
-                } else {
-                    error.append("without first being in a VR session or supplying the Daydream "
-                            + "category. Did you mean to use DaydreamApi#launchInVr()?");
-                }
-                Log.e(TAG, error.toString());
-
-                // Remove VR flags and re-target back to the 2D launcher.
-                VrIntentUtils.removeVrExtras(getIntent());
-                getIntent().setClass(this, ChromeLauncherActivity.class);
-
-                // Daydream drops the MAIN action for unknown reasons...
-                if (getIntent().getAction() == null) getIntent().setAction(Intent.ACTION_MAIN);
-                startActivity(getIntent());
-                finish();
-                return;
-            }
-
-            // If the launcher was launched from a 2D context (without calling
-            // DaydreamApi#launchInVr), then we need to relaunch the launcher in VR to allow
-            // downstream Activities to make assumptions about whether they're in VR or not, and
-            // ensure the VR configuration is set before launching Activities as they use the VR
-            // configuration to set style attributes.
-            boolean needsRelaunch;
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-                needsRelaunch = !VrShellDelegate.isInVrSession();
-            } else {
-                Configuration config = getResources().getConfiguration();
-                int uiMode = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
-                needsRelaunch = uiMode != Configuration.UI_MODE_TYPE_VR_HEADSET;
-            }
-            // The check for relaunching does not work properly if the DON flow is skipped, which
-            // is the case during tests. So, allow intents to specify that relaunching is not
-            // necessary.
-            if (IntentUtils.safeGetBooleanExtra(
-                        getIntent(), VrIntentUtils.AVOID_RELAUNCH_EXTRA, false)) {
-                needsRelaunch = false;
-            }
-            if (needsRelaunch) {
-                // Under some situations, like with the skip DON flow developer setting on, we can
-                // get stuck in a relaunch loop as the VR Headset configuration won't get set. Add
-                // an extra to never relaunch more than once.
-                getIntent().putExtra(VrIntentUtils.AVOID_RELAUNCH_EXTRA, true);
-                VrIntentUtils.launchInVr(getIntent(), this);
-                finish();
-                return;
-            }
-
-            // We don't set VrMode for the launcher in the manifest because that causes weird things
-            // to happen when you send a VR intent to Chrome from a non-VR app, so we need to set it
-            // here.
-            VrShellDelegate.setVrModeEnabled(this, true);
-
-            // Daydream likes to remove the Daydream category from explicit intents for some reason.
-            // Since only implicit intents with the Daydream category can be routed here, it's safe
-            // to assume that the intent *should* have the Daydream category, which simplifies our
-            // intent handling.
-            getIntent().addCategory(VrIntentUtils.DAYDREAM_CATEGORY);
-
-            for (WeakReference<Activity> weakActivity : ApplicationStatus.getRunningActivities()) {
-                final Activity activity = weakActivity.get();
-                if (activity == null) continue;
-                if (activity instanceof ChromeActivity) {
-                    if (VrShellDelegate.willChangeDensityInVr((ChromeActivity) activity)) {
-                        // In the rare case that entering VR will trigger a density change (and
-                        // hence an Activity recreation), just return to Daydream home and kill the
-                        // process, as there's no good way to recreate without showing 2D UI
-                        // in-headset.
-                        finish();
-                        System.exit(0);
-                    }
-                }
-            }
-
-            @LaunchIntentDispatcher.Action
-            int dispatchAction = LaunchIntentDispatcher.dispatch(this, getIntent());
-            switch (dispatchAction) {
-                case LaunchIntentDispatcher.Action.FINISH_ACTIVITY:
-                    finish();
-                    break;
-                case LaunchIntentDispatcher.Action.FINISH_ACTIVITY_REMOVE_TASK:
-                    ApiCompatibilityUtils.finishAndRemoveTask(this);
-                    break;
-                default:
-                    assert false : "Intent dispatcher finished with action " + dispatchAction
-                                   + ", finishing anyway";
-                    finish();
-                    break;
-            }
-        } finally {
-            TraceEvent.end("VrMainActivity.onCreate");
-        }
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
index 1731a69..56033a3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
@@ -8,6 +8,7 @@
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -85,6 +86,7 @@
      */
     public void setDescriptionText(CharSequence text) {
         mDescription.setText(text);
+        mDescription.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
     }
 
     /**
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 81ec1cd..b5a67c5c 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -830,11 +830,17 @@
         Override a website’s request to prevent zooming in
       </message>
       <message name="IDS_READER_FOR_ACCESSIBILITY_TITLE" desc="Title of preference that allows the user to use simplified view on any articles, even if the page is mobile-friendly. Simplified view is the new user-facing name for Reader Mode, which extracts the content of an article and removes clutter from a web page and puts the result in a easier-to-read format.">
-        Simplified view
+        Simplified view for web pages
       </message>
       <message name="IDS_READER_FOR_ACCESSIBILITY_SUMMARY" desc="Summary of preference that allows the user to use simplified view on any supported articles, even if the page is mobile-friendly.">
         Offer to show articles in simplified view, when supported
       </message>
+      <message name="IDS_ACCESSIBILITY_TAB_SWITCHER_TITLE" desc="Title of preference that allows the user to use a simplified tab switcher that is accessibility friendly. The words 'Open tabs' should match translation in TC ID 3108541343994525384, provided that 'open' is correctly used as an adjective and not a verb.">
+        Simplified view for open tabs
+      </message>
+      <message name="IDS_ACCESSIBILITY_TAB_SWITCHER_SUMMARY" desc="Summary of the preference that allows the user to use a simplified tab switcher. The simplified tab switcher is recommended for use with TalkBack and Switch Access which are accessibility services provided by Android. TalkBack does not need to be translated. 'Switch Access' translation should match TC ID 382005103867249841.">
+       Recommended when TalkBack or Switch Access are on
+      </message>
 
       <!-- Site settings -->
       <message name="IDS_PREFS_SITE_SETTINGS" desc="Title of the Website Settings screen. [CHAR-LIMIT=32]">
@@ -3635,6 +3641,9 @@
       <message name="IDS_PASSWORD_GENERATION_ACCESSORY_BUTTON_SHORT" desc="Shortened text for the button used to generate a password. This is used for devices with small screen.">
           Suggest password...
       </message>
+      <message name="IDS_PASSWORD_ACCESSORY_SHEET_TOGGLE" desc="Description for the icon button used to open and close the password accessory sheet.">
+          Tap to toggle between password suggestions and keyboard
+      </message>
 
 
       <!-- Launcher Shortcuts -->
@@ -3656,6 +3665,11 @@
         Navigation panel closed
       </message>
 
+      <!-- Chrome Duet -->
+      <message name="IDS_IPH_DUET_ICONS_MOVED" desc="The in-product help message for the split toolbar experiment that explains that the user's icons (menu button, tab switcher button, and home button) have moved to the bottom of the screen.">
+        Your icons have moved to the bottom of the screen
+      </message>
+
       <!-- Photo picker -->
       <message name="IDS_PHOTO_PICKER_SELECT_IMAGES" desc="The label in the title bar of the Photo Picker dialog, suggesting which action the user should take.">
         Select images
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_SUMMARY.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_SUMMARY.png.sha1
new file mode 100644
index 0000000..433f68cd
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_SUMMARY.png.sha1
@@ -0,0 +1 @@
+ef7969ff56c980a729507dafa20ba40b7be0a4d3
\ No newline at end of file
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_TITLE.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_TITLE.png.sha1
new file mode 100644
index 0000000..433f68cd
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_ACCESSIBILITY_TAB_SWITCHER_TITLE.png.sha1
@@ -0,0 +1 @@
+ef7969ff56c980a729507dafa20ba40b7be0a4d3
\ No newline at end of file
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_PASSWORD_ACCESSORY_SHEET_TOGGLE.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_PASSWORD_ACCESSORY_SHEET_TOGGLE.png.sha1
new file mode 100644
index 0000000..28ad997
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_PASSWORD_ACCESSORY_SHEET_TOGGLE.png.sha1
@@ -0,0 +1 @@
+5716c5bb3e618d7064c16d47d34c06437a21276d
\ No newline at end of file
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_READER_FOR_ACCESSIBILITY_TITLE.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_READER_FOR_ACCESSIBILITY_TITLE.png.sha1
new file mode 100644
index 0000000..433f68cd
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_READER_FOR_ACCESSIBILITY_TITLE.png.sha1
@@ -0,0 +1 @@
+ef7969ff56c980a729507dafa20ba40b7be0a4d3
\ No newline at end of file
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_am.xtb b/chrome/android/java/strings/translations/android_chrome_strings_am.xtb
index 6b57f756..f302de8 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_am.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_am.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">ደውል</translation>
 <translation id="4874967477260347223">የማህደረመረጃ ፈቃዶች</translation>
 <translation id="4875775213178255010">የይዘት አስተያየት ጥቆማዎች</translation>
-<translation id="4876188919622883022">የተቃለለ እይታ</translation>
 <translation id="4878404682131129617">በተኪ አገልጋይ በኩል ዋሻን መመስረት አልተሳካም</translation>
 <translation id="4881695831933465202">ክፈት</translation>
 <translation id="488187801263602086">ፋይልን ዳግም ይሰይሙ</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ar.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ar.xtb
index 69183f1..01e4cd4 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ar.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ar.xtb
@@ -463,7 +463,6 @@
 <translation id="4860895144060829044">اتصال</translation>
 <translation id="4874967477260347223">تراخيص وسائل الإعلام</translation>
 <translation id="4875775213178255010">اقتراحات المحتوى</translation>
-<translation id="4876188919622883022">العرض المبسَّط</translation>
 <translation id="4878404682131129617">أخفق إنشاء نفق عبر الخادم الوكيل.</translation>
 <translation id="4881695831933465202">فتح</translation>
 <translation id="488187801263602086">إعادة تسمية الملف</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_bg.xtb b/chrome/android/java/strings/translations/android_chrome_strings_bg.xtb
index edfaac2..7dbab8c 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_bg.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_bg.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Обаждане</translation>
 <translation id="4874967477260347223">Лицензи за мултимедийно съдържание</translation>
 <translation id="4875775213178255010">Предложения за съдържание</translation>
-<translation id="4876188919622883022">Опростен изглед</translation>
 <translation id="4878404682131129617">Създаването на тунел през прокси сървъра не бе успешно</translation>
 <translation id="4881695831933465202">Отваряне</translation>
 <translation id="488187801263602086">Преименуване на файла</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ca.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ca.xtb
index 333d0f0..f224957 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ca.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ca.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Truca</translation>
 <translation id="4874967477260347223">Llicències de contingut multimèdia</translation>
 <translation id="4875775213178255010">Suggeriments de contingut</translation>
-<translation id="4876188919622883022">Visualització simplificada</translation>
 <translation id="4878404682131129617">S'ha produït un error en establir un túnel mitjançant el servidor intermediari</translation>
 <translation id="4881695831933465202">Obre</translation>
 <translation id="488187801263602086">Canvia el nom del fitxer</translation>
@@ -521,7 +520,7 @@
 <translation id="5335288049665977812">Permet que els llocs executin JavaScript (opció recomanada)</translation>
 <translation id="5345040418939504969"><ph name="BOOKMARK_TITLE" /> s'ha suprimit</translation>
 <translation id="5372829067651257087">S'ha copiat l'URL.</translation>
-<translation id="5391532827096253100">La connexió a aquest lloc web no és segura. Informació del lloc web.</translation>
+<translation id="5391532827096253100">La connexió a aquest lloc web no és segura. Informació del lloc web</translation>
 <translation id="5400569084694353794">En fer servir aquesta aplicació, accepteu les <ph name="BEGIN_LINK1" />Condicions del servei<ph name="END_LINK1" /> i l'<ph name="BEGIN_LINK2" />Avís de privadesa<ph name="END_LINK2" /> de Chrome.</translation>
 <translation id="5403644198645076998">Permet només certs llocs</translation>
 <translation id="5414836363063783498">S'està verificant...</translation>
@@ -590,7 +589,7 @@
 <translation id="5809361687334836369">{HOURS,plural, =1{fa # hora}other{fa # hores}}</translation>
 <translation id="5817918615728894473">Vincula</translation>
 <translation id="583281660410589416">Desconegut</translation>
-<translation id="5833397272224757657">Per a la personalització, utilitza el contingut dels llocs web que visites, així com l'activitat i les interaccions del navegador</translation>
+<translation id="5833397272224757657">Utilitza el contingut dels llocs web que visites, així com l'activitat i les interaccions del navegador per oferir una experiència personalitzada.</translation>
 <translation id="5833984609253377421">Comparteix l'enllaç</translation>
 <translation id="5854790677617711513">Anterior a 30 dies</translation>
 <translation id="5858741533101922242">Chrome no pot activar l'adaptador Bluetooth</translation>
@@ -636,7 +635,7 @@
 <translation id="618555311922999635">El tauler de navegació ocupa tota la pantalla</translation>
 <translation id="6192333916571137726">Baixa el fitxer</translation>
 <translation id="6192792657125177640">Excepcions</translation>
-<translation id="6206551242102657620">La connexió és segura. Informació del lloc web.</translation>
+<translation id="6206551242102657620">La connexió és segura. Informació del lloc web</translation>
 <translation id="6210748933810148297">No ets <ph name="EMAIL" />?</translation>
 <translation id="6216432067784365534">Opcions per a <ph name="NAME_OF_LIST_ITEM" /></translation>
 <translation id="6221633008163990886">Desbloqueja per exportar les contrasenyes</translation>
@@ -728,7 +727,7 @@
 <translation id="6914783257214138813">Tothom que pugui veure el fitxer exportat podrà veure també les teves contrasenyes.</translation>
 <translation id="6942665639005891494">Canvia la ubicació predeterminada de baixada en qualsevol moment amb l'opció del menú Configuració</translation>
 <translation id="6945221475159498467">Selecciona</translation>
-<translation id="6963642900430330478">Aquesta pàgina és perillosa. Informació del lloc web.</translation>
+<translation id="6963642900430330478">Aquesta pàgina és perillosa. Informació del lloc web</translation>
 <translation id="6963766334940102469">Suprimeix les adreces d'interès</translation>
 <translation id="6965382102122355670">D'acord</translation>
 <translation id="6978479750597523876">Restableix la configuració del Traductor</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_cs.xtb b/chrome/android/java/strings/translations/android_chrome_strings_cs.xtb
index d507e65..97395e6 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_cs.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_cs.xtb
@@ -5,6 +5,7 @@
 <translation id="1036727731225946849">Přidávání aplikace <ph name="WEBAPK_NAME" />...</translation>
 <translation id="1041308826830691739">Z webů</translation>
 <translation id="1049743911850919806">Anonymní režim</translation>
+<translation id="1054301162707478098">Jste v soukromém režimu.</translation>
 <translation id="10614374240317010">Nikdy se neukládají</translation>
 <translation id="1068672505746868501">Stránky v jazyce <ph name="SOURCE_LANGUAGE" /> nikdy nepřekládat</translation>
 <translation id="10713315585330490"><ph name="FILE_SIZE" /> – <ph name="DESCRIPTION" /></translation>
@@ -35,6 +36,9 @@
 <translation id="124116460088058876">Další jazyky</translation>
 <translation id="124678866338384709">Zavřít aktuální kartu</translation>
 <translation id="1258753120186372309">Sváteční logo Google: <ph name="DOODLE_DESCRIPTION" /></translation>
+<translation id="1259100630977430756">Stránky, které otevřete na soukromých kartách, po zavření všech soukromých karet nezanechají žádné stopy v historii prohlížeče, v úložišti souborů cookie ani v historii vyhledávání. Všechny stažené soubory a vytvořené záložky však zůstanou zachovány.
+
+Nejste však neviditelní. Soukromý režim neskryje vaši aktivitu před zaměstnavatelem, poskytovatelem internetových služeb ani webovými stránkami, které navštívíte.</translation>
 <translation id="127138278192656016">Používat synchronizaci a všechny služby</translation>
 <translation id="1272079795634619415">Zastavit</translation>
 <translation id="1283039547216852943">Klepnutím rozbalíte</translation>
@@ -137,6 +141,7 @@
 <translation id="2021896219286479412">Ovládání webu na celé obrazovce</translation>
 <translation id="2038563949887743358">Zapnout funkci Verze webu pro PC</translation>
 <translation id="2045104531052923016">Ostatní aplikace: <ph name="GIGABYTES" /> GB</translation>
+<translation id="2055670718290744343">Urychlovač vyhledávání</translation>
 <translation id="2063713494490388661">Vyhledání klepnutím</translation>
 <translation id="2079545284768500474" />
 <translation id="2082238445998314030">Výsledek <ph name="RESULT_NUMBER" /> z <ph name="TOTAL_RESULTS" /></translation>
@@ -164,6 +169,7 @@
 <translation id="2234876718134438132">Synchronizace a služby Google</translation>
 <translation id="2259659629660284697">Exportovat hesla…</translation>
 <translation id="2268044343513325586">Upřesnit</translation>
+<translation id="2280910239864711607">Otevřít novou kartu v soukromém režimu</translation>
 <translation id="2286841657746966508">Fakturační adresa</translation>
 <translation id="230115972905494466">Nebyla nalezena žádná kompatibilní zařízení</translation>
 <translation id="2315043854645842844">Volbu certifikátu na straně klienta operační systém nepodporuje.</translation>
@@ -214,6 +220,7 @@
 <translation id="2621115761605608342">Povolit JavaScript pro konkrétní web.</translation>
 <translation id="2625189173221582860">Heslo bylo zkopírováno</translation>
 <translation id="2631006050119455616">Uloženo</translation>
+<translation id="2633278372998075009">Soukromé karty</translation>
 <translation id="2647434099613338025">Přidat jazyk</translation>
 <translation id="2650751991977523696">Stáhnout soubor znovu?</translation>
 <translation id="2653659639078652383">Odeslat</translation>
@@ -273,6 +280,7 @@
 <translation id="321773570071367578">Pokud jste heslovou frázi zapomněli nebo toto nastavení chcete změnit, <ph name="BEGIN_LINK" />resetujte synchronizaci<ph name="END_LINK" />.</translation>
 <translation id="3227137524299004712">Mikrofon</translation>
 <translation id="3232754137068452469">Webová aplikace</translation>
+<translation id="3234355010754616171">Nová soukromá karta</translation>
 <translation id="3236059992281584593">Zbývá: 1 min</translation>
 <translation id="3244271242291266297">MM</translation>
 <translation id="3254409185687681395">Přidat stránku do záložek</translation>
@@ -288,6 +296,7 @@
 <translation id="3350687908700087792">Zavřít všechny anonymní karty</translation>
 <translation id="3365671512111106261">Není k dispozici, když je zapnut Spořič dat</translation>
 <translation id="3367813778245106622">Chcete-li zahájit synchronizaci, znovu se přihlaste.</translation>
+<translation id="3377025655491224618">Soukromá karta</translation>
 <translation id="3384347053049321195">Sdílet obrázek</translation>
 <translation id="3386292677130313581">Pokud web bude chtít znát vaši polohu, zobrazit dotaz (doporučeno)</translation>
 <translation id="3387650086002190359">Stažení souboru <ph name="FILE_NAME" /> se nezdařilo z důvodu chyb systému souborů.</translation>
@@ -385,6 +394,7 @@
 <translation id="4209895695669353772">Chcete-li od Googlu získat personalizované návrhy obsahu, zapněte synchronizaci</translation>
 <translation id="4226663524361240545">Oznámení mohou aktivovat vibraci</translation>
 <translation id="4242533952199664413">Otevřít Nastavení</translation>
+<translation id="4243710787042215766">Otevřít na soukromé kartě</translation>
 <translation id="424864128008805179">Odhlásit se z Chromu?</translation>
 <translation id="4256782883801055595">Licence open source</translation>
 <translation id="4259722352634471385">Navigace je blokována: <ph name="URL" /></translation>
@@ -462,10 +472,10 @@
 <translation id="4860895144060829044">Volat</translation>
 <translation id="4874967477260347223">Licence médií</translation>
 <translation id="4875775213178255010">Návrhy obsahu</translation>
-<translation id="4876188919622883022">Zjednodušené zobrazení</translation>
 <translation id="4878404682131129617">Vytvoření tunelu prostřednictvím proxy serveru se nezdařilo</translation>
 <translation id="4881695831933465202">Otevřít</translation>
 <translation id="488187801263602086">Přejmenovat soubor</translation>
+<translation id="4883379392681899581">Opustit soukromý režim</translation>
 <translation id="4885273946141277891">Nepodporovaný počet instancí Chromu.</translation>
 <translation id="4910889077668685004">Platební aplikace</translation>
 <translation id="4913161338056004800">Obnovit statistiky</translation>
@@ -507,6 +517,7 @@
 <translation id="5224771365102442243">S videem</translation>
 <translation id="5233638681132016545">Nová karta</translation>
 <translation id="5240817131241497236">Nastavení, která v Chromu ovládají synchronizaci, personalizaci a další služby Google, se změnila. Může to mít dopad na vaše aktuální nastavení.</translation>
+<translation id="5264003212305142034"><ph name="BEGIN_LINK1" />Nastavení<ph name="END_LINK1" /> lze kdykoliv upravit. Google může používat obsah na navštívených webech a aktivitu a interakce v prohlížeči k personalizaci prohlížeče Chrome a dalších služeb Google, jako jsou Překladač, Vyhledávání a reklamy.</translation>
 <translation id="5271967389191913893">Zařízení obsah ke stažení nemůže otevřít.</translation>
 <translation id="528192093759286357">Režim celé obrazovky ukončíte přetažením z horního okraje obrazovky a klepnutím na tlačítko Zpět.</translation>
 <translation id="5284584623296338184">Změny záložek, historie, hesel a dalších nastavení se již nebudou synchronizovat do vašeho účtu Google. Vaše existující data však v účtu Google uložena zůstanou.</translation>
@@ -592,6 +603,7 @@
 <translation id="583281660410589416">Neznámé</translation>
 <translation id="5833397272224757657">Používá k personalizaci obsah navštěvovaných webů a aktivitu a interakce v prohlížeči</translation>
 <translation id="5833984609253377421">Sdílet odkaz</translation>
+<translation id="584427517463557805">Vybraná soukromá karta</translation>
 <translation id="5854790677617711513">Starší než 30 dnů</translation>
 <translation id="5858741533101922242">Chrome nemůže zapnout adaptér Bluetooth</translation>
 <translation id="5860033963881614850">Vypnuto</translation>
@@ -621,6 +633,7 @@
 <translation id="6075798973483050474">Upravit domovskou stránku</translation>
 <translation id="60923314841986378">Zbývá: <ph name="HOURS" /> h</translation>
 <translation id="60924377787140961">Brzy se zobrazí další články. Užijte si odpoledne.</translation>
+<translation id="6099151465289169210">Přepnuto na soukromé karty</translation>
 <translation id="6108923351542677676">Probíhá nastavování…</translation>
 <translation id="6111020039983847643">využitá data</translation>
 <translation id="6112702117600201073">Obnovování stránky</translation>
@@ -650,6 +663,7 @@
 <translation id="6320088164292336938">Vibrovat</translation>
 <translation id="6324034347079777476">Synchronizace systému Android je vypnuta.</translation>
 <translation id="6333140779060797560">Sdílet prostřednictvím aplikace <ph name="APPLICATION" /></translation>
+<translation id="6336451774241870485">Nová soukromá karta</translation>
 <translation id="6337234675334993532">Šifrování</translation>
 <translation id="6341580099087024258">Zeptat se, kam se soubory mají uložit</translation>
 <translation id="6343192674172527289">Nebyly nalezeny žádné stažené soubory</translation>
@@ -687,6 +701,7 @@
 <translation id="6593061639179217415">Stránky pro počítač</translation>
 <translation id="6600954340915313787">Zkopírováno do Chromu</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
+<translation id="6610147964972079463">Zavřít soukromé karty</translation>
 <translation id="6612358246767739896">Chráněný obsah</translation>
 <translation id="6627583120233659107">Upravit složku</translation>
 <translation id="6643016212128521049">Vymazat</translation>
@@ -790,6 +805,7 @@
 <translation id="7453467225369441013">Odhlásí vás z většiny webů. Z účtu Google odhlášeni nebudete.</translation>
 <translation id="7454641608352164238">Nedostatek místa</translation>
 <translation id="7455923816558154057">Zobrazíte klepnutím</translation>
+<translation id="7465104139234185284">Zavřít všechny soukromé karty</translation>
 <translation id="7473891865547856676">Ne, děkuji</translation>
 <translation id="7475192538862203634">Pokud se vám tato stránka zobrazuje často, vyzkoušejte tyto <ph name="BEGIN_LINK" />návrhy<ph name="END_LINK" />.</translation>
 <translation id="7475688122056506577">SD karta nebyla nalezena. Některé vaše soubory mohou chybět.</translation>
@@ -885,6 +901,7 @@
 <translation id="8220488350232498290">Staženo: <ph name="GIGABYTES" /> GB</translation>
 <translation id="8249310407154411074">Přesunout na začátek</translation>
 <translation id="8250920743982581267">Dokumenty</translation>
+<translation id="825412236959742607">Tato stránka využívá příliš mnoho paměti, Chrome proto odstranil část obsahu.</translation>
 <translation id="8260126382462817229">Zkuste se přihlásit znovu.</translation>
 <translation id="8261506727792406068">Smazat</translation>
 <translation id="8266862848225348053">Umístění stažených souborů</translation>
@@ -980,6 +997,7 @@
 <translation id="9070377983101773829">Spustit hlasové vyhledávání</translation>
 <translation id="9071742570345586758">Chcete-li zobrazit obsah pro virtuální realitu, nainstalujte si Služby VR Google</translation>
 <translation id="9074336505530349563">Chcete-li od Googlu získat personalizované návrhy obsahu, zapněte synchronizaci</translation>
+<translation id="9080642952018487277">Přejít do soukromého režimu</translation>
 <translation id="9086455579313502267">Nelze získat přístup k síti.</translation>
 <translation id="9099018167121903954">Staženo: <ph name="KILOBYTES" /> kB</translation>
 <translation id="9100505651305367705">Nabízet zjednodušené zobrazení článků (pokud je podporováno)</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_da.xtb b/chrome/android/java/strings/translations/android_chrome_strings_da.xtb
index 925775a..48e1723b 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_da.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_da.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Ring op</translation>
 <translation id="4874967477260347223">Medielicenser</translation>
 <translation id="4875775213178255010">Indholdsforslag</translation>
-<translation id="4876188919622883022">Enkel visning</translation>
 <translation id="4878404682131129617">Der kunne ikke etableres en tunnel via proxyserver</translation>
 <translation id="4881695831933465202">Åbn</translation>
 <translation id="488187801263602086">Omdøb fil</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_de.xtb b/chrome/android/java/strings/translations/android_chrome_strings_de.xtb
index 2b1e7ef..89301544 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_de.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_de.xtb
@@ -463,7 +463,6 @@
 <translation id="4860895144060829044">Anrufen</translation>
 <translation id="4874967477260347223">Medienlizenzen</translation>
 <translation id="4875775213178255010">Inhaltsvorschläge</translation>
-<translation id="4876188919622883022">Vereinfachte Ansicht</translation>
 <translation id="4878404682131129617">Tunnelerstellung via Proxyserver ist fehlgeschlagen</translation>
 <translation id="4881695831933465202">Öffnen</translation>
 <translation id="488187801263602086">Datei umbenennen</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_el.xtb b/chrome/android/java/strings/translations/android_chrome_strings_el.xtb
index ba2e42f..88ce014c 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_el.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_el.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Κλήση</translation>
 <translation id="4874967477260347223">Άδειες πολυμέσων</translation>
 <translation id="4875775213178255010">Προτάσεις περιεχομένου</translation>
-<translation id="4876188919622883022">Απλοποιημένη προβολή</translation>
 <translation id="4878404682131129617">Η δημιουργία διοχέτευσης μέσω διακομιστή μεσολάβησης απέτυχε</translation>
 <translation id="4881695831933465202">Άνοιγμα</translation>
 <translation id="488187801263602086">Μετονομασία αρχείου</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_en-GB.xtb b/chrome/android/java/strings/translations/android_chrome_strings_en-GB.xtb
index 839120f4..de29522c 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_en-GB.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_en-GB.xtb
@@ -5,6 +5,7 @@
 <translation id="1036727731225946849">Adding <ph name="WEBAPK_NAME" />...</translation>
 <translation id="1041308826830691739">From websites</translation>
 <translation id="1049743911850919806">Incognito</translation>
+<translation id="1054301162707478098">You’ve gone private.</translation>
 <translation id="10614374240317010">Never saved</translation>
 <translation id="1068672505746868501">Never translate pages in <ph name="SOURCE_LANGUAGE" /></translation>
 <translation id="10713315585330490"><ph name="FILE_SIZE" /> – <ph name="DESCRIPTION" /></translation>
@@ -35,6 +36,9 @@
 <translation id="124116460088058876">More languages</translation>
 <translation id="124678866338384709">Close current tab</translation>
 <translation id="1258753120186372309">Google doodle: <ph name="DOODLE_DESCRIPTION" /></translation>
+<translation id="1259100630977430756">Pages that you view in private tabs won’t stick around in your browser’s history, cookie store or search history after you’ve closed all of your private tabs. Any files that you download or bookmarks that you create will be kept.
+
+However, you aren’t invisible. Going private doesn’t hide your browsing from your employer, your Internet service provider or the websites that you visit.</translation>
 <translation id="127138278192656016">Use sync and all services</translation>
 <translation id="1272079795634619415">Stop</translation>
 <translation id="1283039547216852943">Tap to expand</translation>
@@ -137,6 +141,7 @@
 <translation id="2021896219286479412">Full screen site controls</translation>
 <translation id="2038563949887743358">Turn on Request desktop site</translation>
 <translation id="2045104531052923016"><ph name="GIGABYTES" /> GB other apps</translation>
+<translation id="2055670718290744343">Search accelerator</translation>
 <translation id="2063713494490388661">Tap to Search</translation>
 <translation id="2079545284768500474">Undo</translation>
 <translation id="2082238445998314030">Result <ph name="RESULT_NUMBER" /> of <ph name="TOTAL_RESULTS" /></translation>
@@ -164,6 +169,7 @@
 <translation id="2234876718134438132">Sync and Google services</translation>
 <translation id="2259659629660284697">Export passwords…</translation>
 <translation id="2268044343513325586">Refine</translation>
+<translation id="2280910239864711607">Open a new tab in private mode</translation>
 <translation id="2286841657746966508">Billing address</translation>
 <translation id="230115972905494466">No compatible devices found</translation>
 <translation id="2315043854645842844">Client side certificate selection is not supported by the operating system.</translation>
@@ -214,6 +220,7 @@
 <translation id="2621115761605608342">Allow JavaScript for a specific site.</translation>
 <translation id="2625189173221582860">Password copied</translation>
 <translation id="2631006050119455616">Saved</translation>
+<translation id="2633278372998075009">Private tabs</translation>
 <translation id="2647434099613338025">Add language</translation>
 <translation id="2650751991977523696">Download file again?</translation>
 <translation id="2653659639078652383">Submit</translation>
@@ -273,6 +280,7 @@
 <translation id="321773570071367578">If you forgot your passphrase or want to change this setting, <ph name="BEGIN_LINK" />reset sync<ph name="END_LINK" /></translation>
 <translation id="3227137524299004712">Microphone</translation>
 <translation id="3232754137068452469">Web App</translation>
+<translation id="3234355010754616171">New private tab</translation>
 <translation id="3236059992281584593">1 min left</translation>
 <translation id="3244271242291266297">MM</translation>
 <translation id="3254409185687681395">Bookmark this page</translation>
@@ -288,6 +296,7 @@
 <translation id="3350687908700087792">Close all incognito tabs</translation>
 <translation id="3365671512111106261">Unavailable when Data Saver is turned on</translation>
 <translation id="3367813778245106622">Sign in again to start sync</translation>
+<translation id="3377025655491224618">Private tab</translation>
 <translation id="3384347053049321195">Share image</translation>
 <translation id="3386292677130313581">Ask before allowing sites to know your location (recommended)</translation>
 <translation id="3387650086002190359"><ph name="FILE_NAME" /> download failed due to file system errors.</translation>
@@ -385,6 +394,7 @@
 <translation id="4209895695669353772">To get personalised content suggested by Google, turn on sync</translation>
 <translation id="4226663524361240545">Notifications may vibrate the device</translation>
 <translation id="4242533952199664413">Open settings</translation>
+<translation id="4243710787042215766">Open in private tab</translation>
 <translation id="424864128008805179">Sign out of Chrome?</translation>
 <translation id="4256782883801055595">Open-source licences</translation>
 <translation id="4259722352634471385">Navigation is blocked: <ph name="URL" /></translation>
@@ -462,10 +472,10 @@
 <translation id="4860895144060829044">Call</translation>
 <translation id="4874967477260347223">Media Licenses</translation>
 <translation id="4875775213178255010">Content Suggestions</translation>
-<translation id="4876188919622883022">Simplified view</translation>
 <translation id="4878404682131129617">Establishing a tunnel via proxy server failed</translation>
 <translation id="4881695831933465202">Open</translation>
 <translation id="488187801263602086">Rename file</translation>
+<translation id="4883379392681899581">Leave private mode</translation>
 <translation id="4885273946141277891">Unsupported number of Chrome instances.</translation>
 <translation id="4910889077668685004">Payment apps</translation>
 <translation id="4913161338056004800">Reset statistics</translation>
@@ -507,6 +517,7 @@
 <translation id="5224771365102442243">With video</translation>
 <translation id="5233638681132016545">New tab</translation>
 <translation id="5240817131241497236">The settings that control sync, personalisation and other Google services in Chrome have changed. This may affect your current settings.</translation>
+<translation id="5264003212305142034"><ph name="BEGIN_LINK1" />Settings<ph name="END_LINK1" /> can be customised at any time. Google may use content on sites that you visit, plus browser activity and interactions, to personalise Chrome and other Google services such as Translate, Search and ads.</translation>
 <translation id="5271967389191913893">Device cannot open the content to be downloaded.</translation>
 <translation id="528192093759286357">Drag from top and touch the back button to exit full screen.</translation>
 <translation id="5284584623296338184">Changes to your bookmarks, history, passwords and other settings will no longer be synced to your Google Account. However, your existing data will remain stored in your Google account.</translation>
@@ -592,6 +603,7 @@
 <translation id="583281660410589416">Unknown</translation>
 <translation id="5833397272224757657">Uses content on sites that you visit, plus browser activity and interactions, for personalisation</translation>
 <translation id="5833984609253377421">Share link</translation>
+<translation id="584427517463557805">Selected private tab</translation>
 <translation id="5854790677617711513">Older than 30 days</translation>
 <translation id="5858741533101922242">Chrome is unable to turn on Bluetooth adaptor</translation>
 <translation id="5860033963881614850">Off</translation>
@@ -621,6 +633,7 @@
 <translation id="6075798973483050474">Edit home page</translation>
 <translation id="60923314841986378"><ph name="HOURS" /> hours left</translation>
 <translation id="60924377787140961">More articles will appear soon. Enjoy your afternoon!</translation>
+<translation id="6099151465289169210">Switched to private tabs</translation>
 <translation id="6108923351542677676">Setup in progress…</translation>
 <translation id="6111020039983847643">data used</translation>
 <translation id="6112702117600201073">Refreshing page</translation>
@@ -650,6 +663,7 @@
 <translation id="6320088164292336938">Vibrate</translation>
 <translation id="6324034347079777476">Android system sync disabled</translation>
 <translation id="6333140779060797560">Share via <ph name="APPLICATION" /></translation>
+<translation id="6336451774241870485">New private tab</translation>
 <translation id="6337234675334993532">Encryption</translation>
 <translation id="6341580099087024258">Ask where to save files</translation>
 <translation id="6343192674172527289">No downloads found</translation>
@@ -687,6 +701,7 @@
 <translation id="6593061639179217415">Desktop site</translation>
 <translation id="6600954340915313787">Copied to Chrome</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
+<translation id="6610147964972079463">Close private tabs</translation>
 <translation id="6612358246767739896">Protected content</translation>
 <translation id="6627583120233659107">Edit folder</translation>
 <translation id="6643016212128521049">Clear</translation>
@@ -790,6 +805,7 @@
 <translation id="7453467225369441013">Signs you out of most sites. You won't be signed out of your Google Account.</translation>
 <translation id="7454641608352164238">Not enough space</translation>
 <translation id="7455923816558154057">Tap to view</translation>
+<translation id="7465104139234185284">Close all private tabs</translation>
 <translation id="7473891865547856676">No Thanks</translation>
 <translation id="7475192538862203634">If you’re seeing this frequently, try these <ph name="BEGIN_LINK" />suggestions<ph name="END_LINK" />.</translation>
 <translation id="7475688122056506577">SD card not found. Some of your files may be missing.</translation>
@@ -885,6 +901,7 @@
 <translation id="8220488350232498290"><ph name="GIGABYTES" /> GB downloaded</translation>
 <translation id="8249310407154411074">Move to top</translation>
 <translation id="8250920743982581267">Documents</translation>
+<translation id="825412236959742607">This page uses too much memory, so Chrome removed some content.</translation>
 <translation id="8260126382462817229">Try signing in again</translation>
 <translation id="8261506727792406068">Delete</translation>
 <translation id="8266862848225348053">Download location</translation>
@@ -978,6 +995,7 @@
 <translation id="9070377983101773829">Start voice search</translation>
 <translation id="9071742570345586758">To view virtual reality content, install Google VR Services</translation>
 <translation id="9074336505530349563">To get personalised content suggested by Google, sign in and turn on sync</translation>
+<translation id="9080642952018487277">Enter private mode</translation>
 <translation id="9086455579313502267">Unable to access the network</translation>
 <translation id="9099018167121903954"><ph name="KILOBYTES" /> KB downloaded</translation>
 <translation id="9100505651305367705">Offer to show articles in simplified view, when supported</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_es-419.xtb b/chrome/android/java/strings/translations/android_chrome_strings_es-419.xtb
index df96c38..2ab0f5f1 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_es-419.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_es-419.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Llamar</translation>
 <translation id="4874967477260347223">Licencias de medios</translation>
 <translation id="4875775213178255010">Sugerencias de contenido</translation>
-<translation id="4876188919622883022">Vista simplificada</translation>
 <translation id="4878404682131129617">Se produjo un error al establecer conexión a través del servidor proxy</translation>
 <translation id="4881695831933465202">Abrir</translation>
 <translation id="488187801263602086">Cambiar el nombre del archivo</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_es.xtb b/chrome/android/java/strings/translations/android_chrome_strings_es.xtb
index 659546db..bec4b625 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_es.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_es.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Llamar</translation>
 <translation id="4874967477260347223">Licencias de contenido multimedia</translation>
 <translation id="4875775213178255010">Sugerencias de contenido</translation>
-<translation id="4876188919622883022">Vista simplificada</translation>
 <translation id="4878404682131129617">No se ha podido establecer conexión a través del servidor proxy</translation>
 <translation id="4881695831933465202">Abrir</translation>
 <translation id="488187801263602086">Cambiar nombre de archivo</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_fa.xtb b/chrome/android/java/strings/translations/android_chrome_strings_fa.xtb
index b07df31..395c857 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_fa.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_fa.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">تماس</translation>
 <translation id="4874967477260347223">مجوزهای رسانه</translation>
 <translation id="4875775213178255010">محتواهای پیشنهادی</translation>
-<translation id="4876188919622883022">نمای ساده‌شده</translation>
 <translation id="4878404682131129617">برقراری تونل ازطریق سرور پروکسی ناموفق بود</translation>
 <translation id="4881695831933465202">باز کردن</translation>
 <translation id="488187801263602086">تغییر نام فایل</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_fi.xtb b/chrome/android/java/strings/translations/android_chrome_strings_fi.xtb
index 06a055e..91ae0728 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_fi.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_fi.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Soita</translation>
 <translation id="4874967477260347223">Medialisenssit</translation>
 <translation id="4875775213178255010">Sisältöehdotukset</translation>
-<translation id="4876188919622883022">Yksinkertaistettu näkymä</translation>
 <translation id="4878404682131129617">Tunnelin muodostaminen välityspalvelimen kautta epäonnistui</translation>
 <translation id="4881695831933465202">Avaa</translation>
 <translation id="488187801263602086">Nimeä tiedosto uudelleen</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_fil.xtb b/chrome/android/java/strings/translations/android_chrome_strings_fil.xtb
index 9a82213..551380c 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_fil.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_fil.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Tawagan</translation>
 <translation id="4874967477260347223">Mga Lisensya ng Media</translation>
 <translation id="4875775213178255010">Mga Iminumungkahing Content</translation>
-<translation id="4876188919622883022">Pinasimpleng view</translation>
 <translation id="4878404682131129617">Hindi nakagawa ng tunnel sa pamamagitan ng proxy server</translation>
 <translation id="4881695831933465202">Buksan</translation>
 <translation id="488187801263602086">Palitan ang pangalan ng file</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_fr.xtb b/chrome/android/java/strings/translations/android_chrome_strings_fr.xtb
index 8d945da90..a7f8578 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_fr.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_fr.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Appeler</translation>
 <translation id="4874967477260347223">Licences multimédias</translation>
 <translation id="4875775213178255010">Recommandations de contenus</translation>
-<translation id="4876188919622883022">Vue simplifiée</translation>
 <translation id="4878404682131129617">Échec de l'établissement d'un tunnel via un serveur proxy</translation>
 <translation id="4881695831933465202">Ouvrir</translation>
 <translation id="488187801263602086">Renommer le fichier</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_hi.xtb b/chrome/android/java/strings/translations/android_chrome_strings_hi.xtb
index 756fae5..6e3906e 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_hi.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_hi.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">कॉल करें</translation>
 <translation id="4874967477260347223">मीडिया लाइसेंस</translation>
 <translation id="4875775213178255010">सामग्री के सुझाव</translation>
-<translation id="4876188919622883022">सरल बनाया गया व्यू</translation>
 <translation id="4878404682131129617">प्रॉक्सी सर्वर के ज़रिए सुरंग बनाना विफल रहा</translation>
 <translation id="4881695831933465202">खोलें</translation>
 <translation id="488187801263602086">फ़ाइल का नाम बदलें</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_hr.xtb b/chrome/android/java/strings/translations/android_chrome_strings_hr.xtb
index e2227ff..cb54bb9 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_hr.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_hr.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Poziv</translation>
 <translation id="4874967477260347223">Licence medijskog sadržaja</translation>
 <translation id="4875775213178255010">Prijedlozi sadržaja</translation>
-<translation id="4876188919622883022">Pojednostavljeni prikaz</translation>
 <translation id="4878404682131129617">Uspostava tunela putem proxy poslužitelja nije uspjela</translation>
 <translation id="4881695831933465202">Otvori</translation>
 <translation id="488187801263602086">Promijenite naziv datoteke</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_hu.xtb b/chrome/android/java/strings/translations/android_chrome_strings_hu.xtb
index 755621c..d90a953 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_hu.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_hu.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Hívás</translation>
 <translation id="4874967477260347223">Médiaengedélyek</translation>
 <translation id="4875775213178255010">Javasolt tartalmak</translation>
-<translation id="4876188919622883022">Egyszerűsített nézet</translation>
 <translation id="4878404682131129617">Nem sikerült a proxyszerveren keresztüli alagút kialakítása</translation>
 <translation id="4881695831933465202">Megnyitás</translation>
 <translation id="488187801263602086">Fájl átnevezése</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_id.xtb b/chrome/android/java/strings/translations/android_chrome_strings_id.xtb
index 9671010..a79fff3 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_id.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_id.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Telepon</translation>
 <translation id="4874967477260347223">Lisensi Media</translation>
 <translation id="4875775213178255010">Saran Konten</translation>
-<translation id="4876188919622883022">Tampilan sederhana</translation>
 <translation id="4878404682131129617">Gagal membentuk saluran melalui server proxy</translation>
 <translation id="4881695831933465202">Buka</translation>
 <translation id="488187801263602086">Ganti nama file</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_it.xtb b/chrome/android/java/strings/translations/android_chrome_strings_it.xtb
index 8f025e2..04e41c9 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_it.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_it.xtb
@@ -229,7 +229,7 @@
 <translation id="2777555524387840389"><ph name="SECONDS" /> sec rimanenti</translation>
 <translation id="2781151931089541271">1 sec rimanente</translation>
 <translation id="2810645512293415242">Pagina semplificata per risparmiare dati e caricarla più velocemente.</translation>
-<translation id="281504910091592009">Visualizza e gestisci le password salvate nel tuo <ph name="BEGIN_LINK" />account Google<ph name="END_LINK" /></translation>
+<translation id="281504910091592009">Visualizza e gestisci le password salvate nel tuo <ph name="BEGIN_LINK" />Account Google<ph name="END_LINK" /></translation>
 <translation id="2818669890320396765">Accedi e attiva la sincronizzazione per trovare i tuoi preferiti su tutti i dispositivi</translation>
 <translation id="2836148919159985482">Tocca il pulsante Indietro per uscire dalla modalità a schermo intero.</translation>
 <translation id="2842985007712546952">Cartella principale</translation>
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Chiama</translation>
 <translation id="4874967477260347223">Licenze multimediali</translation>
 <translation id="4875775213178255010">Contenuti suggeriti</translation>
-<translation id="4876188919622883022">Visualizzazione semplificata</translation>
 <translation id="4878404682131129617">Creazione di un tunnel tramite server proxy non riuscita</translation>
 <translation id="4881695831933465202">Apri</translation>
 <translation id="488187801263602086">Rinomina file</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_iw.xtb b/chrome/android/java/strings/translations/android_chrome_strings_iw.xtb
index c20d942..1b454bb 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_iw.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_iw.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">התקשר</translation>
 <translation id="4874967477260347223">רישיונות מדיה</translation>
 <translation id="4875775213178255010">תוכן מוצע</translation>
-<translation id="4876188919622883022">תצוגה נקייה</translation>
 <translation id="4878404682131129617">‏יצירת מנהרה בעזרת שרת proxy נכשלה</translation>
 <translation id="4881695831933465202">פתח</translation>
 <translation id="488187801263602086">שינוי שם של קובץ</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ja.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ja.xtb
index 08a2b339..48b130e4 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ja.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ja.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">通話</translation>
 <translation id="4874967477260347223">メディア ライセンス</translation>
 <translation id="4875775213178255010">おすすめのコンテンツ</translation>
-<translation id="4876188919622883022">簡易表示</translation>
 <translation id="4878404682131129617">プロキシ サーバー経由のトンネルを確立できませんでした</translation>
 <translation id="4881695831933465202">開く</translation>
 <translation id="488187801263602086">ファイル名を変更してください</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ko.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ko.xtb
index df74e31..689a58b 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ko.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ko.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">전화걸기</translation>
 <translation id="4874967477260347223">미디어 라이선스</translation>
 <translation id="4875775213178255010">콘텐츠 추천</translation>
-<translation id="4876188919622883022">간단히 보기</translation>
 <translation id="4878404682131129617">프록시 서버를 통한 터널 설정에 실패했습니다</translation>
 <translation id="4881695831933465202">열기</translation>
 <translation id="488187801263602086">파일 이름 변경</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_lt.xtb b/chrome/android/java/strings/translations/android_chrome_strings_lt.xtb
index 0d8d39d6..81af827 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_lt.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_lt.xtb
@@ -5,6 +5,7 @@
 <translation id="1036727731225946849">Pridedamas APK „<ph name="WEBAPK_NAME" />“...</translation>
 <translation id="1041308826830691739">Iš svetainių</translation>
 <translation id="1049743911850919806">Inkognito</translation>
+<translation id="1054301162707478098">Įjungėte privatų režimą.</translation>
 <translation id="10614374240317010">Niekada neišsaugota</translation>
 <translation id="1068672505746868501">Niekada neversti puslapių, parašytų <ph name="SOURCE_LANGUAGE" /></translation>
 <translation id="10713315585330490"><ph name="FILE_SIZE" /> – <ph name="DESCRIPTION" /></translation>
@@ -35,6 +36,9 @@
 <translation id="124116460088058876">Daugiau kalbų</translation>
 <translation id="124678866338384709">Uždaryti dabartinį skirtuką</translation>
 <translation id="1258753120186372309">„Google“ papuoštas logotipas: <ph name="DOODLE_DESCRIPTION" /></translation>
+<translation id="1259100630977430756">Kai uždarysite visus privačius skirtukus, puslapiai, kuriuos žiūrite privačiu režimu, nebus saugomi naršyklės istorijoje, slapukų saugykloje ar paieškos istorijoje. Bus išsaugoti visi atsisiųsti failai ar sukurtos žymės.
+
+Tačiau nesate nematomi. Kai naudojate privatų režimą, jūsų darbdavys, interneto paslaugų teikėjas ar lankomos svetainės vis tiek gali pasiekti naršymo informaciją.</translation>
 <translation id="127138278192656016">Sinchronizavimo ir visų paslaugų naudojimas</translation>
 <translation id="1272079795634619415">Sustabdyti</translation>
 <translation id="1283039547216852943">Palieskite ir išskleiskite</translation>
@@ -137,6 +141,7 @@
 <translation id="2021896219286479412">Viso ekrano svetainės valdikliai</translation>
 <translation id="2038563949887743358">Įjungti stalinio kompiuterio svetainės užklausą</translation>
 <translation id="2045104531052923016"><ph name="GIGABYTES" /> GB užima kitos programos</translation>
+<translation id="2055670718290744343">Paieškos spartintuvas</translation>
 <translation id="2063713494490388661">Paliesti ir ieškoti</translation>
 <translation id="2079545284768500474">Anuliuoti</translation>
 <translation id="2082238445998314030">Rezultatų: <ph name="RESULT_NUMBER" /> iš <ph name="TOTAL_RESULTS" /></translation>
@@ -164,6 +169,7 @@
 <translation id="2234876718134438132">Sinchron. ir „Google“ paslaugos</translation>
 <translation id="2259659629660284697">Eksportuoti slaptažodžius…</translation>
 <translation id="2268044343513325586">Patikslinti</translation>
+<translation id="2280910239864711607">Atidaryti naują skirtuką privačiu režimu</translation>
 <translation id="2286841657746966508">Atsiskaitymo adresas</translation>
 <translation id="230115972905494466">Nerasta jokių suderinamų įrenginių</translation>
 <translation id="2315043854645842844">Kliento pasirinkto sertifikato nepalaiko operacinė sistema.</translation>
@@ -214,6 +220,7 @@
 <translation id="2621115761605608342">Leisti „JavaScript“ konkrečioje svetainėje.</translation>
 <translation id="2625189173221582860">Slaptažodis nukopijuotas</translation>
 <translation id="2631006050119455616">Išsaugota</translation>
+<translation id="2633278372998075009">Privatūs skirtukai</translation>
 <translation id="2647434099613338025">Pridėti kalbą</translation>
 <translation id="2650751991977523696">Atsisiųsti failą dar kartą?</translation>
 <translation id="2653659639078652383">Pateikti</translation>
@@ -273,6 +280,7 @@
 <translation id="321773570071367578">Jei pamiršote slaptafrazę arba norite pakeisti šį nustatymą, <ph name="BEGIN_LINK" />iš naujo nustatykite sinchronizavimą<ph name="END_LINK" /></translation>
 <translation id="3227137524299004712">Mikrofonas</translation>
 <translation id="3232754137068452469">Žiniatinklio programa</translation>
+<translation id="3234355010754616171">Naujas privatus skirtukas</translation>
 <translation id="3236059992281584593">Liko 1 min.</translation>
 <translation id="3244271242291266297">MM</translation>
 <translation id="3254409185687681395">Įtraukti šį puslapį į žymes</translation>
@@ -288,6 +296,7 @@
 <translation id="3350687908700087792">Uždaryti visus inkognito skirtukų lapus</translation>
 <translation id="3365671512111106261">Nepasiekiama, kai Duomenų taupymo priemonė įjungta</translation>
 <translation id="3367813778245106622">Prisijunkite dar kartą, kad pradėtumėte sinchronizavimą</translation>
+<translation id="3377025655491224618">Privatus skirtukas</translation>
 <translation id="3384347053049321195">Bendrinti vaizdą</translation>
 <translation id="3386292677130313581">Klausti prieš leidžiant svetainėms žinoti vietovę (rekomenduojama)</translation>
 <translation id="3387650086002190359">Nepavyko atsisiųsti „<ph name="FILE_NAME" />“ dėl failų sistemos klaidų.</translation>
@@ -385,6 +394,7 @@
 <translation id="4209895695669353772">Jei norite gauti „Google“ siūlomo suasmeninto turinio, įjunkite sinchronizavimą</translation>
 <translation id="4226663524361240545">Gavus pranešimą įrenginys gali vibruoti</translation>
 <translation id="4242533952199664413">Atidaryti nustatymus</translation>
+<translation id="4243710787042215766">Atidaryti privačiame skirtuke</translation>
 <translation id="424864128008805179">Atsijungti nuo „Chrome“?</translation>
 <translation id="4256782883801055595">Atvirojo šaltinio licencijos</translation>
 <translation id="4259722352634471385">Naršymas užblokuotas: <ph name="URL" /></translation>
@@ -462,10 +472,10 @@
 <translation id="4860895144060829044">Skambinti</translation>
 <translation id="4874967477260347223">Medijos licencijos</translation>
 <translation id="4875775213178255010">Turinio pasiūlymai</translation>
-<translation id="4876188919622883022">Supaprastintas rodinys</translation>
 <translation id="4878404682131129617">Nepavyko užmegzti tunelinio ryšio per tarpinį serverį</translation>
 <translation id="4881695831933465202">Atidaryti</translation>
 <translation id="488187801263602086">Failo pervardijimas</translation>
+<translation id="4883379392681899581">Išjungti privatų režimą</translation>
 <translation id="4885273946141277891">Nepalaikomas „Chrome“ atvejų skaičius.</translation>
 <translation id="4910889077668685004">Mokėjimo programos</translation>
 <translation id="4913161338056004800">Iš naujo nustatyti statistiką</translation>
@@ -507,6 +517,7 @@
 <translation id="5224771365102442243">Su vaizdo įrašu</translation>
 <translation id="5233638681132016545">Naujas skirtukas</translation>
 <translation id="5240817131241497236">Pasikeitė sinchronizavimo, suasmeninimo ir kitų „Google“ paslaugų, naudojamų naršyklėje „Chrome“, valdymo nustatymai. Tai gali turėti įtakos jūsų dabartiniams nustatymams.</translation>
+<translation id="5264003212305142034"><ph name="BEGIN_LINK1" />Nustatymus<ph name="END_LINK1" /> galima tinkinti bet kuriuo metu. „Google“ gali naudoti jūsų lankomų svetainių turinį, naršyklės sąveikas ir veiklą suasmenindama „Chrome“ ir kitas „Google“ paslaugas, pvz., Vertėją, Paiešką ir skelbimus.</translation>
 <translation id="5271967389191913893">Įrenginyje nepavyksta atidaryti norimo atsisiųsti turinio.</translation>
 <translation id="528192093759286357">Vilkite žymeklį nuo viršaus ir palieskite mygtuką „Atgal“, kad išeitumėte iš viso ekrano režimo.</translation>
 <translation id="5284584623296338184">Žymių, istorijos, slaptažodžių ir kitų nustatymų pakeitimai nebebus sinchronizuojami su „Google“ paskyra. Tačiau esami duomenys ir toliau bus saugomi „Google“ paskyroje.</translation>
@@ -592,6 +603,7 @@
 <translation id="583281660410589416">Nežinoma</translation>
 <translation id="5833397272224757657">Naudojamas svetainių, kuriose lankotės, turinys ir naršyklės veikla bei sąveikos suasmeninimo tikslais</translation>
 <translation id="5833984609253377421">Bendrinti nuorodą</translation>
+<translation id="584427517463557805">Pasirinktas privatus skirtukas</translation>
 <translation id="5854790677617711513">Senesni nei 30 dienų</translation>
 <translation id="5858741533101922242">„Chrome“ nepavyksta įjungti „Bluetooth“ adapterio</translation>
 <translation id="5860033963881614850">Išjungta</translation>
@@ -621,6 +633,7 @@
 <translation id="6075798973483050474">Redaguoti pagrindinį puslapį</translation>
 <translation id="60923314841986378">Liko <ph name="HOURS" /> val.</translation>
 <translation id="60924377787140961">Netrukus bus rodoma daugiau straipsnių. Geros popietės!</translation>
+<translation id="6099151465289169210">Perjungta į privačius skirtukus</translation>
 <translation id="6108923351542677676">Nustatoma…</translation>
 <translation id="6111020039983847643">išnaudoti duomenys</translation>
 <translation id="6112702117600201073">Puslapio atnaujinimas</translation>
@@ -650,6 +663,7 @@
 <translation id="6320088164292336938">Vibruoti</translation>
 <translation id="6324034347079777476">„Android“ sistemos sinchronizavimas išjungtas</translation>
 <translation id="6333140779060797560">Bendrinti per „<ph name="APPLICATION" />“</translation>
+<translation id="6336451774241870485">Naujas privatus skirtukas</translation>
 <translation id="6337234675334993532">Šifruotė</translation>
 <translation id="6341580099087024258">Klausti, kur saugoti failus</translation>
 <translation id="6343192674172527289">Nerasta jokių atsisiųstų elementų</translation>
@@ -687,6 +701,7 @@
 <translation id="6593061639179217415">Komp. skirta svetain. v.</translation>
 <translation id="6600954340915313787">Nukopijuota į „Chrome“</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
+<translation id="6610147964972079463">Uždaryti privačius skirtuk.</translation>
 <translation id="6612358246767739896">Apsaugotas turinys</translation>
 <translation id="6627583120233659107">Redaguoti aplanką</translation>
 <translation id="6643016212128521049">Išvalyti</translation>
@@ -790,6 +805,7 @@
 <translation id="7453467225369441013">Būsite atjungti nuo daugelio svetainių. Nebūsite atjungti nuo „Google“ paskyros.</translation>
 <translation id="7454641608352164238">Nepakanka vietos</translation>
 <translation id="7455923816558154057">Palieskite ir peržiūrėkite</translation>
+<translation id="7465104139234185284">Uždaryti visus privačius skirtukus</translation>
 <translation id="7473891865547856676">Ne, ačiū</translation>
 <translation id="7475192538862203634">Jei tai rodoma dažnai, peržiūrėkite šiuos <ph name="BEGIN_LINK" />pasiūlymus<ph name="END_LINK" />.</translation>
 <translation id="7475688122056506577">SD kortelė nerasta. Gali trūkti kai kurių failų.</translation>
@@ -885,6 +901,7 @@
 <translation id="8220488350232498290">Atsisiųsta <ph name="GIGABYTES" /> GB</translation>
 <translation id="8249310407154411074">Perkelti į viršų</translation>
 <translation id="8250920743982581267">Dokumentai</translation>
+<translation id="825412236959742607">Šis puslapis naudoja per daug atminties, todėl „Chrome“ pašalino šiek tiek turinio.</translation>
 <translation id="8260126382462817229">Bandykite prisijungti dar kartą</translation>
 <translation id="8261506727792406068">Ištrinti</translation>
 <translation id="8266862848225348053">Atsisiuntimo vieta</translation>
@@ -980,6 +997,7 @@
 <translation id="9070377983101773829">Pradėti paiešką balsu</translation>
 <translation id="9071742570345586758">Kad galėtumėte peržiūrėti virtualiosios realybės turinį, įdiekite „Google“ VR paslaugas</translation>
 <translation id="9074336505530349563">Jei norite gauti „Google“ siūlomo suasmeninto turinio, prisijunkite ir įjunkite sinchronizavimą</translation>
+<translation id="9080642952018487277">Įjungti privatų režimą</translation>
 <translation id="9086455579313502267">Nepavyko pasiekti tinklo</translation>
 <translation id="9099018167121903954">Atsisiųsta <ph name="KILOBYTES" /> KB</translation>
 <translation id="9100505651305367705">Siūlyti rodyti straipsnius supaprastintame rodinyje, kai palaikoma</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_lv.xtb b/chrome/android/java/strings/translations/android_chrome_strings_lv.xtb
index dc1297f0..8511c7b1 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_lv.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_lv.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Zvanīt</translation>
 <translation id="4874967477260347223">Multivides licences</translation>
 <translation id="4875775213178255010">Satura ieteikumi</translation>
-<translation id="4876188919622883022">Vienkāršotais skats</translation>
 <translation id="4878404682131129617">Neizdevās iestatīt porta pārsūtīšanu, izmantojot starpniekserveri.</translation>
 <translation id="4881695831933465202">Atvērt</translation>
 <translation id="488187801263602086">Faila pārdēvēšana</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_nl.xtb b/chrome/android/java/strings/translations/android_chrome_strings_nl.xtb
index e636e00..77e3e78 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_nl.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_nl.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Bellen</translation>
 <translation id="4874967477260347223">Medialicenties</translation>
 <translation id="4875775213178255010">Contentsuggesties</translation>
-<translation id="4876188919622883022">Vereenvoudigde weergave</translation>
 <translation id="4878404682131129617">Kan geen tunnel tot stand brengen via de proxyserver</translation>
 <translation id="4881695831933465202">Openen</translation>
 <translation id="488187801263602086">Naam van bestand wijzigen</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_no.xtb b/chrome/android/java/strings/translations/android_chrome_strings_no.xtb
index c343034c..883eb64 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_no.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_no.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Ring</translation>
 <translation id="4874967477260347223">Medielisenser</translation>
 <translation id="4875775213178255010">Innholdsforslag</translation>
-<translation id="4876188919622883022">Forenklet visning</translation>
 <translation id="4878404682131129617">Kunne ikke opprette tunnel via proxy-tjener.</translation>
 <translation id="4881695831933465202">Åpne</translation>
 <translation id="488187801263602086">Endre navnet på filen</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_pl.xtb b/chrome/android/java/strings/translations/android_chrome_strings_pl.xtb
index 9e51032..260e1285 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_pl.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_pl.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Zadzwoń</translation>
 <translation id="4874967477260347223">Licencje multimediów</translation>
 <translation id="4875775213178255010">Polecane treści</translation>
-<translation id="4876188919622883022">Widok uproszczony</translation>
 <translation id="4878404682131129617">Nie udało się utworzyć tunelu przez serwer proxy</translation>
 <translation id="4881695831933465202">Otwórz</translation>
 <translation id="488187801263602086">Zmień nazwę pliku</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_pt-BR.xtb b/chrome/android/java/strings/translations/android_chrome_strings_pt-BR.xtb
index 47a30f8..07af7580 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_pt-BR.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_pt-BR.xtb
@@ -463,7 +463,6 @@
 <translation id="4860895144060829044">Ligar</translation>
 <translation id="4874967477260347223">Licenças de mídia</translation>
 <translation id="4875775213178255010">Sugestões de conteúdo</translation>
-<translation id="4876188919622883022">Visualização simplificada</translation>
 <translation id="4878404682131129617">Falha ao estabelecer encapsulamento via servidor proxy</translation>
 <translation id="4881695831933465202">Abrir</translation>
 <translation id="488187801263602086">Renomear arquivo</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_pt-PT.xtb b/chrome/android/java/strings/translations/android_chrome_strings_pt-PT.xtb
index b665e2b7..a7d27cc2 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_pt-PT.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_pt-PT.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Telefonar</translation>
 <translation id="4874967477260347223">Licenças de multimédia</translation>
 <translation id="4875775213178255010">Sugestões de conteúdo</translation>
-<translation id="4876188919622883022">Vista simplificada</translation>
 <translation id="4878404682131129617">Falha ao estabelecer um túnel através do servidor proxy.</translation>
 <translation id="4881695831933465202">Abrir</translation>
 <translation id="488187801263602086">Mudar o nome do ficheiro</translation>
@@ -521,7 +520,7 @@
 <translation id="5335288049665977812">Permitir que os sites executem JavaScript (recomendado)</translation>
 <translation id="5345040418939504969"><ph name="BOOKMARK_TITLE" /> eliminado</translation>
 <translation id="5372829067651257087">URL copiado.</translation>
-<translation id="5391532827096253100">A sua ligação a este site não é segura. Informações do site.</translation>
+<translation id="5391532827096253100">A sua ligação a este site não é segura. Informações do site</translation>
 <translation id="5400569084694353794">Ao utilizar esta aplicação, concorda com os <ph name="BEGIN_LINK1" />Termos de Utilização<ph name="END_LINK1" /> e o <ph name="BEGIN_LINK2" />Aviso de Privacidade<ph name="END_LINK2" /> do Chrome.</translation>
 <translation id="5403644198645076998">Permitir apenas determinados sites</translation>
 <translation id="5414836363063783498">A validar…</translation>
@@ -636,7 +635,7 @@
 <translation id="618555311922999635">Painel de navegação aberto à altura total</translation>
 <translation id="6192333916571137726">Transferir ficheiro</translation>
 <translation id="6192792657125177640">Excepções</translation>
-<translation id="6206551242102657620">A ligação é segura. Informações do site.</translation>
+<translation id="6206551242102657620">A ligação é segura. Informações do site</translation>
 <translation id="6210748933810148297"><ph name="EMAIL" /> não é o seu email?</translation>
 <translation id="6216432067784365534">Opções de <ph name="NAME_OF_LIST_ITEM" /></translation>
 <translation id="6221633008163990886">Desbloqueie para exportar as palavras-passe.</translation>
@@ -728,7 +727,7 @@
 <translation id="6914783257214138813">As suas palavras-passe serão visíveis para todas as pessoas que consigam ver o ficheiro exportado.</translation>
 <translation id="6942665639005891494">Altere a localização de transferência predefinida a qualquer momento ao utilizar a opção do menu Definições.</translation>
 <translation id="6945221475159498467">Selecionar</translation>
-<translation id="6963642900430330478">Esta página é perigosa. Informações do site.</translation>
+<translation id="6963642900430330478">Esta página é perigosa. Informações do site</translation>
 <translation id="6963766334940102469">Eliminar marcadores</translation>
 <translation id="6965382102122355670">OK</translation>
 <translation id="6978479750597523876">Repor definições de tradução</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ro.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ro.xtb
index 22327934..3a479eb 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ro.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ro.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Apelează</translation>
 <translation id="4874967477260347223">Licențe media</translation>
 <translation id="4875775213178255010">Sugestii de conținut</translation>
-<translation id="4876188919622883022">Afișare simplificată</translation>
 <translation id="4878404682131129617">Nu s-a putut stabili un tunel prin serverul proxy</translation>
 <translation id="4881695831933465202">Deschide</translation>
 <translation id="488187801263602086">Redenumește fișierul</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_ru.xtb b/chrome/android/java/strings/translations/android_chrome_strings_ru.xtb
index 2b49d1c..ff45453 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_ru.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_ru.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Позвонить</translation>
 <translation id="4874967477260347223">Медиалицензии</translation>
 <translation id="4875775213178255010">Предлагаемый контент</translation>
-<translation id="4876188919622883022">Упрощенный просмотр</translation>
 <translation id="4878404682131129617">Не удалось создать туннель через прокси-сервер.</translation>
 <translation id="4881695831933465202">Открыть</translation>
 <translation id="488187801263602086">Необходимо переименовать файл</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_sk.xtb b/chrome/android/java/strings/translations/android_chrome_strings_sk.xtb
index 5a19165..caf1d74 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_sk.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_sk.xtb
@@ -5,6 +5,7 @@
 <translation id="1036727731225946849">Pridáva sa <ph name="WEBAPK_NAME" />...</translation>
 <translation id="1041308826830691739">Z webov</translation>
 <translation id="1049743911850919806">Inkognito</translation>
+<translation id="1054301162707478098">Ste v súkromnom režime.</translation>
 <translation id="10614374240317010">Neuložené</translation>
 <translation id="1068672505746868501">Nikdy neprekladať stránky v jazyku <ph name="SOURCE_LANGUAGE" /></translation>
 <translation id="10713315585330490"><ph name="FILE_SIZE" /> – <ph name="DESCRIPTION" /></translation>
@@ -35,6 +36,9 @@
 <translation id="124116460088058876">Ďalšie jazyky</translation>
 <translation id="124678866338384709">Zavretie aktuálnej karty</translation>
 <translation id="1258753120186372309">Sviatočné logo Google: <ph name="DOODLE_DESCRIPTION" /></translation>
+<translation id="1259100630977430756">Stránky, ktoré si zobrazíte na súkromných kartách, budú odstránené z histórie prehliadača, z histórie vyhľadávania aj zo súborov cookie okamžite po zatvorení všetkých súkromných kariet. Stiahnuté súbory a záložky, ktoré vytvoríte, ostanú uložené.
+
+Nie ste však neviditeľný/-á. Používanie súkromného režimu neukryje vaše prehliadanie pred zamestnávateľom, poskytovateľom internetových služieb či webmi, ktoré navštívite.</translation>
 <translation id="127138278192656016">Používať synchronizáciu a všetky služby</translation>
 <translation id="1272079795634619415">Zastaviť</translation>
 <translation id="1283039547216852943">Klepnutím rozbaliť</translation>
@@ -137,6 +141,7 @@
 <translation id="2021896219286479412">Ovládanie webu na celú obrazovku</translation>
 <translation id="2038563949887743358">Zapnutie žiadosti o verziu stránok pre počítače</translation>
 <translation id="2045104531052923016">Ďalšie aplikácie: <ph name="GIGABYTES" /> GB</translation>
+<translation id="2055670718290744343">Hľadať Zrýchlovač</translation>
 <translation id="2063713494490388661">Vyhľadávanie klepnutím</translation>
 <translation id="2079545284768500474">Späť</translation>
 <translation id="2082238445998314030">Výsledok <ph name="RESULT_NUMBER" /> z <ph name="TOTAL_RESULTS" /></translation>
@@ -164,6 +169,7 @@
 <translation id="2234876718134438132">Synchronizácia a služby Googlu</translation>
 <translation id="2259659629660284697">Exportovať heslá…</translation>
 <translation id="2268044343513325586">Upraviť</translation>
+<translation id="2280910239864711607">Otvorenie novej karty v súkromnom režime</translation>
 <translation id="2286841657746966508">Fakturačná adresa</translation>
 <translation id="230115972905494466">Nenašli sa žiadne kompatibilné zariadenia</translation>
 <translation id="2315043854645842844">Operačný systém nepodporuje výber certifikátu na strane klienta.</translation>
@@ -214,6 +220,7 @@
 <translation id="2621115761605608342">Povoliť JavaScript pre konkrétny web.</translation>
 <translation id="2625189173221582860">Heslo bolo skopírované.</translation>
 <translation id="2631006050119455616">Uložené</translation>
+<translation id="2633278372998075009">Súkromné karty</translation>
 <translation id="2647434099613338025">Pridať jazyk</translation>
 <translation id="2650751991977523696">Stiahnuť súbor znova?</translation>
 <translation id="2653659639078652383">Odoslať</translation>
@@ -273,6 +280,7 @@
 <translation id="321773570071367578">Ak ste zabudli prístupovú frázu alebo chcete toto nastavenie zmeniť, <ph name="BEGIN_LINK" />resetujte synchronizáciu<ph name="END_LINK" /></translation>
 <translation id="3227137524299004712">Mikrofón</translation>
 <translation id="3232754137068452469">Webová aplikácia</translation>
+<translation id="3234355010754616171">Nová súkromná karta</translation>
 <translation id="3236059992281584593">Zostáva: 1 min</translation>
 <translation id="3244271242291266297">MM</translation>
 <translation id="3254409185687681395">Vytvoriť záložku pre túto stránku</translation>
@@ -288,6 +296,7 @@
 <translation id="3350687908700087792">Zavrieť všetky karty inkognito</translation>
 <translation id="3365671512111106261">Keď je zapnutý Šetrič dát, nie je táto možnosť k dispozícii</translation>
 <translation id="3367813778245106622">Ak chcete spustiť synchronizáciu, znova sa prihláste</translation>
+<translation id="3377025655491224618">Súkromná karta</translation>
 <translation id="3384347053049321195">Zdieľať obrázok</translation>
 <translation id="3386292677130313581">Opýtať sa pred povolením webu zistiť vašu polohu (odporúčané)</translation>
 <translation id="3387650086002190359">Súbor <ph name="FILE_NAME" /> sa nepodarilo stiahnuť z dôvodu chýb systému súborov.</translation>
@@ -385,6 +394,7 @@
 <translation id="4209895695669353772">Ak chcete získavať prispôsobený obsah navrhnutý Googlom, zapnite synchronizáciu</translation>
 <translation id="4226663524361240545">Upozornenia môžu pri prijatí na zariadení spustiť vibrovanie</translation>
 <translation id="4242533952199664413">Otvoriť nastavenia</translation>
+<translation id="4243710787042215766">Otvoriť na súkromnej karte</translation>
 <translation id="424864128008805179">Odhlásiť sa z prehliadača Chrome?</translation>
 <translation id="4256782883801055595">Licencie open source</translation>
 <translation id="4259722352634471385">Navigácia je zablokovaná: <ph name="URL" /></translation>
@@ -462,10 +472,10 @@
 <translation id="4860895144060829044">Volajte</translation>
 <translation id="4874967477260347223">Licencie médií</translation>
 <translation id="4875775213178255010">Návrhy obsahu</translation>
-<translation id="4876188919622883022">Zjednodušené zobrazenie</translation>
 <translation id="4878404682131129617">Vytvorenie tunela prostredníctvom proxy servera zlyhalo</translation>
 <translation id="4881695831933465202">Otvoriť</translation>
 <translation id="488187801263602086">Premenovanie súboru</translation>
+<translation id="4883379392681899581">Ukončiť súkromný režim</translation>
 <translation id="4885273946141277891">Nepodporovaný počet inštancií prehliadača Chrome.</translation>
 <translation id="4910889077668685004">Platobné aplikácie</translation>
 <translation id="4913161338056004800">Obnoviť štatistiky</translation>
@@ -507,6 +517,7 @@
 <translation id="5224771365102442243">S videom</translation>
 <translation id="5233638681132016545">Nová karta</translation>
 <translation id="5240817131241497236">Nastavenia, ktoré riadia synchronizáciu, prispôsobenie a ďalšie služby v Chrome, boli zmenené. Môže to ovplyvniť vaše aktuálne nastavenia.</translation>
+<translation id="5264003212305142034"><ph name="BEGIN_LINK1" />Nastavenia<ph name="END_LINK1" /> si môžete kedykoľvek prispôsobiť. Google môže použiť obsah na weboch, ktoré navštívite, interakcie a aktivitu v prehliadači na prispôsobenie Chromu a služieb Googlu, ako sú Prekladač, Vyhľadávanie a reklamy.</translation>
 <translation id="5271967389191913893">Zariadenie nemôže otvoriť obsah na stiahnutie</translation>
 <translation id="528192093759286357">Režim celej obrazovky ukončíte potiahnutím z hornej časti a klepnutím na tlačidlo Späť.</translation>
 <translation id="5284584623296338184">Záložky, história, heslá a ďalšie nastavenia sa už nebudú ďalej synchronizovať do vášho účtu Google. Vaše súčasné údaje však zostanú v účte Google zachované.</translation>
@@ -592,6 +603,7 @@
 <translation id="583281660410589416">Neznáme</translation>
 <translation id="5833397272224757657">Používa obsah z navštívených webov, aktivitu prehliadača a interakcie s ním na prispôsobenie</translation>
 <translation id="5833984609253377421">Zdieľať odkaz</translation>
+<translation id="584427517463557805">Vybraná súkromná karta</translation>
 <translation id="5854790677617711513">Staršie ako 30 dní</translation>
 <translation id="5858741533101922242">Chrome nedokáže zapnúť adaptér Bluetooth</translation>
 <translation id="5860033963881614850">Vypnuté</translation>
@@ -621,6 +633,7 @@
 <translation id="6075798973483050474">Úprava domovskej stránky</translation>
 <translation id="60923314841986378">Zostáva: <ph name="HOURS" /> h</translation>
 <translation id="60924377787140961">Čoskoro sa zobrazia ďalšie články. Príjemné popoludnie!</translation>
+<translation id="6099151465289169210">Prepnuté na súkromné karty</translation>
 <translation id="6108923351542677676">Inštaluje sa...</translation>
 <translation id="6111020039983847643">využité dáta</translation>
 <translation id="6112702117600201073">Obnovenie stránky</translation>
@@ -650,6 +663,7 @@
 <translation id="6320088164292336938">Vibrovanie</translation>
 <translation id="6324034347079777476">Synchronizácia systému Android je zakázaná</translation>
 <translation id="6333140779060797560">Zdieľať prostredníctvom aplikácie <ph name="APPLICATION" /></translation>
+<translation id="6336451774241870485">Nová súkromná karta</translation>
 <translation id="6337234675334993532">Šifrovanie</translation>
 <translation id="6341580099087024258">Pýtať sa, kde uložiť súbory</translation>
 <translation id="6343192674172527289">Nenašli sa žiadne stiahnuté súbory</translation>
@@ -687,6 +701,7 @@
 <translation id="6593061639179217415">Web pre počítače</translation>
 <translation id="6600954340915313787">Skopírovaná do prehliadača Chrome</translation>
 <translation id="6608650720463149374"><ph name="GIGABYTES" /> GB</translation>
+<translation id="6610147964972079463">Zavrieť súkromné karty</translation>
 <translation id="6612358246767739896">Chránený obsah</translation>
 <translation id="6627583120233659107">Upraviť priečinok</translation>
 <translation id="6643016212128521049">Vymazať</translation>
@@ -790,6 +805,7 @@
 <translation id="7453467225369441013">Odhlási vás z väčšiny webov, ale nie z účtu Google.</translation>
 <translation id="7454641608352164238">Nedostatok miesta</translation>
 <translation id="7455923816558154057">Zobrazíte klepnutím</translation>
+<translation id="7465104139234185284">Zavrieť všetky súkromné karty</translation>
 <translation id="7473891865547856676">Nie, ďakujem</translation>
 <translation id="7475192538862203634">Ak sa vám táto stránka zobrazuje často, skúste použiť tieto <ph name="BEGIN_LINK" />návrhy<ph name="END_LINK" />.</translation>
 <translation id="7475688122056506577">SD karta sa nenašla. Môžu chýbať niektoré súbory.</translation>
@@ -885,6 +901,7 @@
 <translation id="8220488350232498290">Stiahnuté: <ph name="GIGABYTES" /> GB</translation>
 <translation id="8249310407154411074">Presunúť na začiatok</translation>
 <translation id="8250920743982581267">Dokumenty</translation>
+<translation id="825412236959742607">Táto stránka využíva príliš veľa pamäte, a preto Chrome odstránil niektorý obsah.</translation>
 <translation id="8260126382462817229">Skúste sa znova prihlásiť</translation>
 <translation id="8261506727792406068">Odstrániť</translation>
 <translation id="8266862848225348053">Umiestnenie sťahovaných súborov</translation>
@@ -978,6 +995,7 @@
 <translation id="9070377983101773829">Spustiť hlasové vyhľadávanie</translation>
 <translation id="9071742570345586758">Na zobrazenie obsahu virtuálnej reality je potrebné nainštalovať služby Google VR</translation>
 <translation id="9074336505530349563">Ak chcete získavať prispôsobený obsah navrhnutý Googlom, prihláste sa a zapnite synchronizáciu</translation>
+<translation id="9080642952018487277">Spustiť súkromný režim</translation>
 <translation id="9086455579313502267">Nepodarilo sa pristúpiť k sieti</translation>
 <translation id="9099018167121903954">Stiahnuté: <ph name="KILOBYTES" /> KB</translation>
 <translation id="9100505651305367705">Ponúkať zjednodušené zobrazenie článkov (ak je podporované)</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_sl.xtb b/chrome/android/java/strings/translations/android_chrome_strings_sl.xtb
index e042dc24..9e09a03 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_sl.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_sl.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Pokličite</translation>
 <translation id="4874967477260347223">Predstavnostne licence</translation>
 <translation id="4875775213178255010">Predlogi za vsebino</translation>
-<translation id="4876188919622883022">Poenostavljen pogled</translation>
 <translation id="4878404682131129617">Vzpostavljanje tunela prek strežnika proxy ni uspelo</translation>
 <translation id="4881695831933465202">Odpri</translation>
 <translation id="488187801263602086">Preimenovanje datoteke</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_sr.xtb b/chrome/android/java/strings/translations/android_chrome_strings_sr.xtb
index 2b6c972..0a75730c 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_sr.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_sr.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Позовите</translation>
 <translation id="4874967477260347223">Лиценце за медије</translation>
 <translation id="4875775213178255010">Предлози за садржај</translation>
-<translation id="4876188919622883022">Поједностављени приказ</translation>
 <translation id="4878404682131129617">Успостављање тунела преко прокси сервера није успело</translation>
 <translation id="4881695831933465202">Отвори</translation>
 <translation id="488187801263602086">Преименујте датотеку</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_sv.xtb b/chrome/android/java/strings/translations/android_chrome_strings_sv.xtb
index 42d2edc..c2c58d63 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_sv.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_sv.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Ring</translation>
 <translation id="4874967477260347223">Medielicenser</translation>
 <translation id="4875775213178255010">Förslag på innehåll</translation>
-<translation id="4876188919622883022">Förenklad visning</translation>
 <translation id="4878404682131129617">Det gick inte att upprätta en tunnel via proxyserver</translation>
 <translation id="4881695831933465202">Öppna</translation>
 <translation id="488187801263602086">Byt namn på fil</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_sw.xtb b/chrome/android/java/strings/translations/android_chrome_strings_sw.xtb
index b093ce4..590c3374 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_sw.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_sw.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Piga simu</translation>
 <translation id="4874967477260347223">Leseni za Maudhui</translation>
 <translation id="4875775213178255010">Mapendekezo ya Maudhui</translation>
-<translation id="4876188919622883022">Mwonekano rahisi</translation>
 <translation id="4878404682131129617">Imeshindwa kuanzisha mkondo kupitia seva mbadala</translation>
 <translation id="4881695831933465202">Fungua</translation>
 <translation id="488187801263602086">Badilisha jina la faili</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_th.xtb b/chrome/android/java/strings/translations/android_chrome_strings_th.xtb
index 890faa3a..3916fd22 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_th.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_th.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">โทร</translation>
 <translation id="4874967477260347223">ใบอนุญาตสื่อ</translation>
 <translation id="4875775213178255010">การแนะนำเนื้อหา</translation>
-<translation id="4876188919622883022">มุมมองอย่างง่าย</translation>
 <translation id="4878404682131129617">สร้างช่องทางผ่านพร็อกซีเซิร์ฟเวอร์ไม่ได้</translation>
 <translation id="4881695831933465202">เปิด</translation>
 <translation id="488187801263602086">เปลี่ยนชื่อไฟล์</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_tr.xtb b/chrome/android/java/strings/translations/android_chrome_strings_tr.xtb
index 5d0df54..6c7eef3 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_tr.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_tr.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Telefon et</translation>
 <translation id="4874967477260347223">Medya Lisansları</translation>
 <translation id="4875775213178255010">İçerik Önerileri</translation>
-<translation id="4876188919622883022">Basitleştirilmiş görünüm</translation>
 <translation id="4878404682131129617">Proxy sunucu üzerinden tünel oluşturulamadı</translation>
 <translation id="4881695831933465202">Aç</translation>
 <translation id="488187801263602086">Dosyayı yeniden adlandırın</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_uk.xtb b/chrome/android/java/strings/translations/android_chrome_strings_uk.xtb
index d86638e..53eef3f 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_uk.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_uk.xtb
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Зателефонувати</translation>
 <translation id="4874967477260347223">Медіа-ліцензії</translation>
 <translation id="4875775213178255010">Пропозиції вмісту</translation>
-<translation id="4876188919622883022">Спрощений перегляд</translation>
 <translation id="4878404682131129617">Не вдалося налагодити зв’язок через проксі-сервер</translation>
 <translation id="4881695831933465202">Відкрити</translation>
 <translation id="488187801263602086">Перейменувати файл</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_vi.xtb b/chrome/android/java/strings/translations/android_chrome_strings_vi.xtb
index 21657046a..a4ebb57 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_vi.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_vi.xtb
@@ -229,7 +229,7 @@
 <translation id="2777555524387840389">Còn <ph name="SECONDS" /> giây</translation>
 <translation id="2781151931089541271">Còn 1 giây</translation>
 <translation id="2810645512293415242">Trang đơn giản giúp lưu dữ liệu và tải nhanh hơn.</translation>
-<translation id="281504910091592009">Xem và quản lý mật khẩu đã lưu trong <ph name="BEGIN_LINK" />Tài khoản Google<ph name="END_LINK" /> của bạn</translation>
+<translation id="281504910091592009">Xem và quản lý các mật khẩu đã lưu trong <ph name="BEGIN_LINK" />Tài khoản Google<ph name="END_LINK" /> của bạn</translation>
 <translation id="2818669890320396765">Để sử dụng dấu trang trên tất cả các thiết bị, hãy đăng nhập và bật tính năng đồng bộ hóa</translation>
 <translation id="2836148919159985482">Chạm vào nút quay lại để thoát khỏi chế độ toàn màn hình.</translation>
 <translation id="2842985007712546952">Thư mục gốc</translation>
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">Gọi</translation>
 <translation id="4874967477260347223">Giấy phép phương tiện</translation>
 <translation id="4875775213178255010">Đề xuất nội dung</translation>
-<translation id="4876188919622883022">Chế độ xem đơn giản</translation>
 <translation id="4878404682131129617">Thiết lập đường hầm qua máy chủ proxy không thành công</translation>
 <translation id="4881695831933465202">Mở</translation>
 <translation id="488187801263602086">Đổi tên tệp</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_zh-CN.xtb b/chrome/android/java/strings/translations/android_chrome_strings_zh-CN.xtb
index 1552960a..8418d6fe 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_zh-CN.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_zh-CN.xtb
@@ -316,7 +316,7 @@
 <translation id="360207483134687714">帮助我们改进 Chrome 中的虚拟现实体验</translation>
 <translation id="3616113530831147358">音频</translation>
 <translation id="3620176948598597475">这项重置操作会清空流量节省程序的历史记录,包括曾访问过的网站的列表。</translation>
-<translation id="3630011985153972676">允许 Chrome 在连接到您在“设置”中指定的 Wi-Fi 网络时为您下载文章。</translation>
+<translation id="3630011985153972676">在“设置”中允许 Chrome 在连接到 Wi-Fi 网络时为您下载文章。</translation>
 <translation id="3632295766818638029">显示密码</translation>
 <translation id="363596933471559332">使用存储的凭据自动登录网站。当该功能处于停用状态时,系统会在您每次登录网站之前要求您进行验证。</translation>
 <translation id="3661699943263275414">第三方网站可以保存和读取 Cookie 数据</translation>
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">拨打</translation>
 <translation id="4874967477260347223">媒体许可</translation>
 <translation id="4875775213178255010">内容建议</translation>
-<translation id="4876188919622883022">简化版视图</translation>
 <translation id="4878404682131129617">未能成功通过代理服务器建立隧道</translation>
 <translation id="4881695831933465202">打开</translation>
 <translation id="488187801263602086">重命名文件</translation>
diff --git a/chrome/android/java/strings/translations/android_chrome_strings_zh-TW.xtb b/chrome/android/java/strings/translations/android_chrome_strings_zh-TW.xtb
index 1ed44c7f..1221d48 100644
--- a/chrome/android/java/strings/translations/android_chrome_strings_zh-TW.xtb
+++ b/chrome/android/java/strings/translations/android_chrome_strings_zh-TW.xtb
@@ -182,7 +182,7 @@
 <translation id="2387895666653383613">文字大小</translation>
 <translation id="2402980924095424747"><ph name="MEGABYTES" /> MB</translation>
 <translation id="2410754283952462441">選擇帳戶</translation>
-<translation id="2414672073755873541">這裡無內容</translation>
+<translation id="2414672073755873541">這裡沒有任何內容</translation>
 <translation id="2414886740292270097">深色</translation>
 <translation id="2416359993254398973">Chrome 需要相關權限,才能讓這個網站使用你的攝影機。</translation>
 <translation id="2426805022920575512">選擇其他帳戶</translation>
@@ -316,7 +316,7 @@
 <translation id="360207483134687714">協助我們改善 Chrome 的 VR 體驗</translation>
 <translation id="3616113530831147358">音訊</translation>
 <translation id="3620176948598597475">重設會清除 Data Saver 的記錄,包含造訪過的網站清單。</translation>
-<translation id="3630011985153972676">允許 Chrome 透過設定的 Wi-Fi 連線為你下載文章。</translation>
+<translation id="3630011985153972676">允許 Chrome 透過 Wi-Fi 連線為你下載文章 (可自行啟用這項設定)。</translation>
 <translation id="3632295766818638029">取消密碼遮罩</translation>
 <translation id="363596933471559332">自動使用已儲存的憑證登入網站。如果關閉這項功能,每次在您登入網站之前,網站一律會要求驗證。</translation>
 <translation id="3661699943263275414">第三方網站可以儲存及讀取 Cookie 資料</translation>
@@ -462,7 +462,6 @@
 <translation id="4860895144060829044">撥號</translation>
 <translation id="4874967477260347223">媒體授權</translation>
 <translation id="4875775213178255010">內容建議</translation>
-<translation id="4876188919622883022">簡易檢視</translation>
 <translation id="4878404682131129617">無法透過 Proxy 伺服器建立通道</translation>
 <translation id="4881695831933465202">開啟</translation>
 <translation id="488187801263602086">重新命名檔案</translation>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index 81cb23c..4adb6f2 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -108,6 +108,7 @@
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewBinder.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java",
+  "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryModel.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewBinder.java",
@@ -364,10 +365,12 @@
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationEventObserver.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabObserver.java",
+  "java/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicy.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java",
   "java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionService.java",
-  "java/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicy.java",
+  "java/src/org/chromium/chrome/browser/customtabs/FirstMeaningfulPaintObserver.java",
   "java/src/org/chromium/chrome/browser/customtabs/NavigationInfoCaptureTrigger.java",
+  "java/src/org/chromium/chrome/browser/customtabs/PageLoadMetricsObserver.java",
   "java/src/org/chromium/chrome/browser/customtabs/RequestThrottler.java",
   "java/src/org/chromium/chrome/browser/customtabs/SeparateTaskCustomTabActivity.java",
   "java/src/org/chromium/chrome/browser/customtabs/SeparateTaskCustomTabActivity0.java",
@@ -381,6 +384,7 @@
   "java/src/org/chromium/chrome/browser/customtabs/SeparateTaskCustomTabActivity8.java",
   "java/src/org/chromium/chrome/browser/customtabs/SeparateTaskCustomTabActivity9.java",
   "java/src/org/chromium/chrome/browser/customtabs/SeparateTaskManagedCustomTabActivity.java",
+  "java/src/org/chromium/chrome/browser/customtabs/TabObserverRegistrar.java",
   "java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityDelegate.java",
   "java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ActivityHostImpl.java",
   "java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/ModuleEntryPoint.java",
@@ -499,6 +503,7 @@
   "java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java",
   "java/src/org/chromium/chrome/browser/download/home/snackbars/DeleteUndoCoordinator.java",
   "java/src/org/chromium/chrome/browser/download/home/snackbars/UndoUiUtils.java",
+  "java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java",
   "java/src/org/chromium/chrome/browser/download/items/DownloadBlockedOfflineContentProvider.java",
   "java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
   "java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java",
@@ -804,6 +809,7 @@
   "java/src/org/chromium/chrome/browser/modelutil/ListObservable.java",
   "java/src/org/chromium/chrome/browser/modelutil/ListObservableImpl.java",
   "java/src/org/chromium/chrome/browser/modelutil/PropertyKey.java",
+  "java/src/org/chromium/chrome/browser/modelutil/PropertyListObservable.java",
   "java/src/org/chromium/chrome/browser/modelutil/PropertyModel.java",
   "java/src/org/chromium/chrome/browser/modelutil/PropertyModelChangeProcessor.java",
   "java/src/org/chromium/chrome/browser/modelutil/PropertyObservable.java",
@@ -971,7 +977,11 @@
   "java/src/org/chromium/chrome/browser/omnibox/SuggestionAnswer.java",
   "java/src/org/chromium/chrome/browser/omnibox/SuggestionView.java",
   "java/src/org/chromium/chrome/browser/omnibox/UrlBar.java",
+  "java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java",
   "java/src/org/chromium/chrome/browser/omnibox/UrlBarData.java",
+  "java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java",
+  "java/src/org/chromium/chrome/browser/omnibox/UrlBarProperties.java",
+  "java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinder.java",
   "java/src/org/chromium/chrome/browser/omnibox/UrlFocusChangeListener.java",
   "java/src/org/chromium/chrome/browser/omnibox/VoiceSuggestionProvider.java",
   "java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeader.java",
@@ -1622,7 +1632,6 @@
   "java/src/org/chromium/chrome/browser/vr/keyboard/GvrKeyboardLoaderClient.java",
   "java/src/org/chromium/chrome/browser/vr/keyboard/TextEditAction.java",
   "java/src/org/chromium/chrome/browser/vr/keyboard/VrInputMethodManagerWrapper.java",
-  "java/src/org/chromium/chrome/browser/vr/VrMainActivity.java",
   "java/src/org/chromium/chrome/browser/vr/EmptySniffingVrViewContainer.java",
   "java/src/org/chromium/chrome/browser/vr/NoopCanvas.java",
   "java/src/org/chromium/chrome/browser/vr/VrAlertDialog.java",
@@ -2234,6 +2243,7 @@
   "junit/src/org/chromium/chrome/browser/omnibox/AutocompleteStateUnitTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/KeyboardHideHelperUnitTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/SpannableAutocompleteEditTextModelUnitTest.java",
+  "junit/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTest.java",
   "junit/src/org/chromium/chrome/browser/omnibox/geo/VisibleNetworksTrackerTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java
index 6c996a5..5966863 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java
@@ -95,14 +95,21 @@
     @MediumTest
     public void testAddingTabToModelRendersTabsView() {
         final String kSampleAction = "Some Action";
-        mModel.getTabList().add(new Tab(null, null, R.layout.empty_accessory_sheet, view -> {
-            assertNotNull("The tab must have been created!", view);
-            assertTrue("Empty tab is a layout.", view instanceof LinearLayout);
-            LinearLayout baseLayout = (LinearLayout) view;
-            TextView sampleTextView = new TextView(mActivityTestRule.getActivity());
-            sampleTextView.setText(kSampleAction);
-            baseLayout.addView(sampleTextView);
-        }));
+        mModel.getTabList().add(new Tab(null, null, R.layout.empty_accessory_sheet,
+                AccessoryTabType.PASSWORDS, new Tab.Listener() {
+                    @Override
+                    public void onTabCreated(ViewGroup view) {
+                        assertNotNull("The tab must have been created!", view);
+                        assertTrue("Empty tab is a layout.", view instanceof LinearLayout);
+                        LinearLayout baseLayout = (LinearLayout) view;
+                        TextView sampleTextView = new TextView(mActivityTestRule.getActivity());
+                        sampleTextView.setText(kSampleAction);
+                        baseLayout.addView(sampleTextView);
+                    }
+
+                    @Override
+                    public void onTabShown() {}
+                }));
         mModel.setActiveTabIndex(0);
         // Shouldn't cause the view to be inflated.
         assertNull(mStubHolder.getView());
@@ -173,10 +180,17 @@
     }
 
     private Tab createTestTabWithTextView(String textViewCaption) {
-        return new Tab(null, null, R.layout.empty_accessory_sheet, view -> {
-            TextView sampleTextView = new TextView(mActivityTestRule.getActivity());
-            sampleTextView.setText(textViewCaption);
-            view.addView(sampleTextView);
-        });
+        return new Tab(null, null, R.layout.empty_accessory_sheet, AccessoryTabType.PASSWORDS,
+                new Tab.Listener() {
+                    @Override
+                    public void onTabCreated(ViewGroup view) {
+                        TextView sampleTextView = new TextView(mActivityTestRule.getActivity());
+                        sampleTextView.setText(textViewCaption);
+                        view.addView(sampleTextView);
+                    }
+
+                    @Override
+                    public void onTabShown() {}
+                });
     }
 }
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java
index 4e2d3d9..3f99c371 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java
@@ -20,6 +20,7 @@
 import static org.hamcrest.core.AllOf.allOf;
 import static org.junit.Assert.assertTrue;
 
+import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
 import static org.chromium.chrome.test.util.ViewUtils.VIEW_GONE;
 import static org.chromium.chrome.test.util.ViewUtils.VIEW_INVISIBLE;
 import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL;
@@ -68,6 +69,7 @@
                         android.R.drawable.ic_lock_lock), // Unused.
                 contentDescription,
                 R.layout.empty_accessory_sheet, // Unused.
+                AccessoryTabType.ALL,
                 null); // Unused.
     }
 
@@ -114,8 +116,8 @@
     @MediumTest
     public void testClickableActionAddedWhenChangingModel() {
         final AtomicReference<Boolean> buttonClicked = new AtomicReference<>();
-        final KeyboardAccessoryData.Action testAction =
-                new KeyboardAccessoryData.Action("Test Button", action -> buttonClicked.set(true));
+        final KeyboardAccessoryData.Action testAction = new KeyboardAccessoryData.Action(
+                "Test Button", GENERATE_PASSWORD_AUTOMATIC, action -> buttonClicked.set(true));
 
         ThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.setVisible(true);
@@ -134,8 +136,10 @@
         ThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.setVisible(true);
             mModel.getActionList().set(new KeyboardAccessoryData.Action[] {
-                    new KeyboardAccessoryData.Action("First", action -> {}),
-                    new KeyboardAccessoryData.Action("Second", action -> {})});
+                    new KeyboardAccessoryData.Action(
+                            "First", GENERATE_PASSWORD_AUTOMATIC, action -> {}),
+                    new KeyboardAccessoryData.Action(
+                            "Second", GENERATE_PASSWORD_AUTOMATIC, action -> {})});
         });
 
         onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("First")));
@@ -144,8 +148,8 @@
 
         ThreadUtils.runOnUiThreadBlocking(
                 ()
-                        -> mModel.getActionList().add(
-                                new KeyboardAccessoryData.Action("Third", action -> {})));
+                        -> mModel.getActionList().add(new KeyboardAccessoryData.Action(
+                                "Third", GENERATE_PASSWORD_AUTOMATIC, action -> {})));
 
         onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("Third")));
         onView(withText("First")).check(matches(isDisplayed()));
@@ -159,9 +163,12 @@
         ThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.setVisible(true);
             mModel.getActionList().set(new KeyboardAccessoryData.Action[] {
-                    new KeyboardAccessoryData.Action("First", action -> {}),
-                    new KeyboardAccessoryData.Action("Second", action -> {}),
-                    new KeyboardAccessoryData.Action("Third", action -> {})});
+                    new KeyboardAccessoryData.Action(
+                            "First", GENERATE_PASSWORD_AUTOMATIC, action -> {}),
+                    new KeyboardAccessoryData.Action(
+                            "Second", GENERATE_PASSWORD_AUTOMATIC, action -> {}),
+                    new KeyboardAccessoryData.Action(
+                            "Third", GENERATE_PASSWORD_AUTOMATIC, action -> {})});
         });
 
         onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("First")));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
index ff0967e..be53a78 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java
@@ -104,7 +104,8 @@
                 new KeyboardAccessoryData.Tab(
                         AppCompatResources.getDrawable(InstrumentationRegistry.getContext(),
                                 android.R.drawable.ic_lock_lock),
-                        "TestTabDescription", R.layout.empty_accessory_sheet, null));
+                        "TestTabDescription", R.layout.empty_accessory_sheet, AccessoryTabType.ALL,
+                        null));
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java
index 87b287a..3a8e49be 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java
@@ -13,6 +13,7 @@
 import android.support.v4.view.ViewPager;
 import android.support.v7.widget.RecyclerView;
 import android.text.method.PasswordTransformationMethod;
+import android.view.ViewGroup;
 import android.widget.TextView;
 
 import org.junit.After;
@@ -67,7 +68,8 @@
                     @Override
                     public void onPageScrollStateChanged(int i) {}
                 });
-        accessorySheet.addTab(new KeyboardAccessoryData.Tab(null, null, layout, listener));
+        accessorySheet.addTab(
+                new KeyboardAccessoryData.Tab(null, null, layout, AccessoryTabType.ALL, listener));
         ThreadUtils.runOnUiThreadBlocking(accessorySheet::show);
     }
 
@@ -75,12 +77,20 @@
     public void setUp() throws InterruptedException {
         mModel = new SimpleListObservable<>();
         mActivityTestRule.startMainActivityOnBlankPage();
-        openLayoutInAccessorySheet(R.layout.password_accessory_sheet, view -> {
-            mView.set((RecyclerView) view);
-            // Reuse coordinator code to create and wire the adapter. No mediator involved.
-            PasswordAccessorySheetViewBinder.initializeView(
-                    mView.get(), PasswordAccessorySheetCoordinator.createAdapter(mModel));
-        });
+        openLayoutInAccessorySheet(
+                R.layout.password_accessory_sheet, new KeyboardAccessoryData.Tab.Listener() {
+                    @Override
+                    public void onTabCreated(ViewGroup view) {
+                        mView.set((RecyclerView) view);
+                        // Reuse coordinator code to create and wire the adapter. No mediator
+                        // involved.
+                        PasswordAccessorySheetViewBinder.initializeView(mView.get(),
+                                PasswordAccessorySheetCoordinator.createAdapter(mModel));
+                    }
+
+                    @Override
+                    public void onTabShown() {}
+                });
         CriteriaHelper.pollUiThread(Criteria.equals(true, () -> mView.get() != null));
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedRefreshTaskTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedRefreshTaskTest.java
index f34f38ec..acd6d6a3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedRefreshTaskTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedRefreshTaskTest.java
@@ -15,7 +15,6 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -72,12 +71,19 @@
     public void setUp() throws InterruptedException {
         mTaskScheduler = new TestBackgroundTaskScheduler();
         BackgroundTaskSchedulerFactory.setSchedulerForTesting(mTaskScheduler);
+
+        // The FeedSchedulerHost might create a task during initialization. Clear out any tasks
+        // created before the test case starts.
         mActivityTestRule.startMainActivityOnBlankPage();
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            // Accessing the bridge will create if needed, and may run initialization logic.
+            FeedProcessScopeFactory.getFeedSchedulerBridge();
+            mTaskScheduler.getTaskInfoList().clear();
+        });
     }
 
     @Test
     @SmallTest
-    @DisabledTest(message = "crbug.com/868647")
     public void testSchedule() {
         ThreadUtils.runOnUiThreadBlocking(() -> {
             Assert.assertEquals(0, mTaskScheduler.getTaskInfoList().size());
@@ -111,12 +117,7 @@
     @SmallTest
     public void testReschedule() {
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            // The FeedSchedulerHost might create a task during its initialization, so clear
-            // out anything before reschedule() is called.
-            FeedProcessScopeFactory.getFeedSchedulerBridge();
-            mTaskScheduler.getTaskInfoList().clear();
             Assert.assertEquals(0, mTaskScheduler.getTaskInfoList().size());
-
             new FeedRefreshTask().reschedule(mActivityTestRule.getActivity());
             Assert.assertEquals(1, mTaskScheduler.getTaskInfoList().size());
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskTest.java
index b2b163c8..0e2702f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/prefetch/PrefetchBackgroundTaskTest.java
@@ -19,6 +19,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -283,6 +284,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest(message = "https://crbug.com/870295")
     public void testSuspend() throws Exception {
         PrefetchBackgroundTask.skipConditionCheckingForTesting();
         scheduleTask(0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
index f749e93..a2594eb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/OmniboxTest.java
@@ -181,7 +181,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable(){
             @Override
             public void run() {
-                urlBar.setUrl(UrlBarData.forUrl("http://www.example.com/"));
+                urlBar.setText("http://www.example.com/");
             }
         });
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
index b389ec9..3cc3d1e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
@@ -252,16 +252,10 @@
             }
         });
         Assert.assertFalse(state.hasAutocomplete);
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.SPANNABLE_INLINE_AUTOCOMPLETE)) {
-            // Note: new model clears autocomplete text when non-IME change has been made.
-            // The autocomplete gets removed.
-            Assert.assertEquals("tast", state.textWithoutAutocomplete);
-            Assert.assertEquals("tast", state.textWithAutocomplete);
-        } else {
-            // The autocomplete gets committed.
-            Assert.assertEquals("tasting is fun", state.textWithoutAutocomplete);
-            Assert.assertEquals("tasting is fun", state.textWithAutocomplete);
-        }
+        // Clears autocomplete text when non-IME change has been made.
+        // The autocomplete gets removed.
+        Assert.assertEquals("tast", state.textWithoutAutocomplete);
+        Assert.assertEquals("tast", state.textWithAutocomplete);
 
         // Replace part of the autocomplete text.
         setTextAndVerifyNoAutocomplete(urlBar, "test");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
index 9802431..7670344 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/searchwidget/SearchActivityTest.java
@@ -505,13 +505,18 @@
 
     @SuppressLint("SetTextI18n")
     private void setUrlBarText(final Activity activity, final String url) {
-        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+        CriteriaHelper.pollUiThread(new Criteria() {
             @Override
-            public void run() {
+            public boolean isSatisfied() {
                 UrlBar urlBar = (UrlBar) activity.findViewById(R.id.url_bar);
-                if (!urlBar.hasFocus()) urlBar.requestFocus();
-                urlBar.setText(url);
+                if (urlBar.isFocusable() && urlBar.hasFocus()) return true;
+                urlBar.requestFocus();
+                return false;
             }
         });
+        ThreadUtils.runOnUiThreadBlocking(() -> {
+            UrlBar urlBar = (UrlBar) activity.findViewById(R.id.url_bar);
+            urlBar.setText(url);
+        });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
index a3626cb3..ffa51144 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserControllerInputTest.java
@@ -11,6 +11,7 @@
 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
 import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
 
+import android.graphics.PointF;
 import android.support.test.filters.MediumTest;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
@@ -26,6 +27,7 @@
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.history.HistoryPage;
 import org.chromium.chrome.browser.vr.rules.ChromeTabbedActivityVrTestRule;
+import org.chromium.chrome.browser.vr.util.NativeUiUtils;
 import org.chromium.chrome.browser.vr.util.VrBrowserTransitionUtils;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeTabUtils;
@@ -274,4 +276,40 @@
                 POLL_CHECK_INTERVAL_LONG_MS);
         mVrBrowserTestFramework.assertNoJavaScriptErrors();
     }
+
+    /**
+     * Verifies that clicking and dragging down while at the top of the page triggers a page
+     * refresh. Automation of a manual test case from https://crbug.com/861949.
+     */
+    @Test
+    @MediumTest
+    public void testDragRefresh() throws InterruptedException, TimeoutException {
+        mVrTestRule.loadUrl(
+                VrBrowserTestFramework.getFileUrlForHtmlTestFile("test_controller_scrolling"),
+                PAGE_LOAD_TIMEOUT_S);
+        waitForPageToBeScrollable(RenderCoordinates.fromWebContents(mVrTestRule.getWebContents()));
+        // The navigationStart time should change anytime we refresh, so save the value and compare
+        // later.
+        // Use a double because apparently returning Unix timestamps as floating point is a logical
+        // thing for JavaScript to do and Long.valueOf is afraid of decimal points.
+        String navStart = "window.performance.timing.navigationStart";
+        final double navigationTimestamp =
+                Double.valueOf(mVrBrowserTestFramework.runJavaScriptOrFail(
+                                       navStart, POLL_TIMEOUT_SHORT_MS))
+                        .doubleValue();
+
+        // Click and drag from near the top center of the page to near the top bottom.
+        // Use the NativeUiUtils approach to controller input since we shouldn't be missing anything
+        // by bypassing VrCore for this test.
+        NativeUiUtils.clickAndDragElement(UserFriendlyElementName.CONTENT_QUAD, new PointF(0, 0.4f),
+                new PointF(0, -0.4f), 10 /* numInterpolatedSteps */);
+
+        // Wait for the navigationStart time to be newer than our saved time.
+        CriteriaHelper.pollInstrumentationThread(() -> {
+            return Double.valueOf(mVrBrowserTestFramework.runJavaScriptOrFail(
+                                          navStart, POLL_TIMEOUT_SHORT_MS))
+                           .doubleValue()
+                    > navigationTimestamp;
+        });
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserDialogTest.java
index 764aa0e..b9f7f5c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserDialogTest.java
@@ -138,11 +138,6 @@
         VrBrowserTransitionUtils.forceEnterVrBrowserOrFail(POLL_TIMEOUT_LONG_MS);
         Thread.sleep(VR_ENTRY_SLEEP_MS);
         NativeUiUtils.clickElementAndWaitForUiQuiescence(elementName, new PointF(0, 0));
-        // Technically not necessary, but clicking on native elements causes the laser to originate
-        // from the head, not the controller, which looks strange. Since the point of most of these
-        // tests is to verify that things look correct, better to have the laser in a normal
-        // position before taking screenshots.
-        NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
     }
 
     /**
@@ -259,7 +254,6 @@
         captureScreen("JavaScriptConfirm_Visible");
 
         NativeUiUtils.clickFallbackUiNegativeButton();
-        NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
         // Ensure the cancel button was clicked.
         Assert.assertTrue("JavaScript Confirm's cancel button was not clicked",
                 mVrBrowserTestFramework.runJavaScriptOrFail("c", POLL_TIMEOUT_SHORT_MS)
@@ -284,7 +278,6 @@
         // Capture image
         captureScreen("JavaScriptPrompt_Visible");
         NativeUiUtils.clickFallbackUiPositiveButton();
-        NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
         // This JavaScript will only run once the prompt has been dismissed, and the return value
         // will only be what we expect if the positive button was actually clicked (as opposed to
         // canceled).
@@ -323,4 +316,4 @@
         captureScreen("PageInfoAppearsOnSecurityTokenClick_Visible");
         mVrBrowserTestFramework.assertNoJavaScriptErrors();
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
index ecff9ddad..eae0bd7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/VrBrowserTransitionTest.java
@@ -151,8 +151,7 @@
         // Send a VR intent, which will open the link in a CTA.
         final String url =
                 VrBrowserTestFramework.getFileUrlForHtmlTestFile("test_navigation_2d_page");
-        VrBrowserTransitionUtils.sendVrLaunchIntent(
-                url, false /* autopresent */, true /* avoidRelaunch */);
+        VrBrowserTransitionUtils.sendVrLaunchIntent(url);
 
         // Wait until we enter VR and have the correct URL.
         VrBrowserTransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
index 74befc43..4d48a200 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
@@ -386,6 +386,10 @@
             throws InterruptedException {
         framework.loadUrlAndAwaitInitialization(url, PAGE_LOAD_TIMEOUT_S);
         framework.enterSessionWithUserGestureOrFail();
+        // TODO(https://crbug.com/870031): Remove this sleep if/when the controller disconnect/
+        // reconnect issue caused by DON flow skipping that flakily eats the app button press is
+        // resolved.
+        SystemClock.sleep(500);
         EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
         controller.pressReleaseAppButton();
         assertAppButtonEffect(true /* shouldHaveExited */, framework);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
index 1d3f32c..cd176faa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/NativeUiUtils.java
@@ -71,6 +71,38 @@
     }
 
     /**
+     * Clicks and drags within a single UI element.
+     *
+     * @param elementName The UserFriendlyElementName that will be clicked and dragged in.
+     * @param positionStart The PointF specifying where on the element to start the click/drag
+     *        relative to a unit square centered at (0, 0).
+     * @param positionEnd The PointF specifying where on the element to end the click/drag relative
+     *        to a unit square centered at (0, 0);
+     * @param numInterpolatedSteps How many steps to interpolate the drag between the provided
+     *        start and end positions.
+     */
+    public static void clickAndDragElement(
+            int elementName, PointF positionStart, PointF positionEnd, int numInterpolatedSteps) {
+        Assert.assertTrue(
+                "Given a negative number of steps to interpolate", numInterpolatedSteps >= 0);
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.CLICK_DOWN, positionStart);
+        PointF stepOffset =
+                new PointF((positionEnd.x - positionStart.x) / (numInterpolatedSteps + 1),
+                        (positionEnd.y - positionStart.y) / (numInterpolatedSteps + 1));
+        PointF currentPosition = positionStart;
+        for (int i = 0; i < numInterpolatedSteps; i++) {
+            currentPosition.offset(stepOffset.x, stepOffset.y);
+            TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                    elementName, VrControllerTestAction.MOVE, currentPosition);
+        }
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.MOVE, positionEnd);
+        TestVrShellDelegate.getInstance().performControllerActionForTesting(
+                elementName, VrControllerTestAction.CLICK_UP, positionEnd);
+    }
+
+    /**
      * Sets the native code to start using the real controller data again instead of fake testing
      * data.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrBrowserTransitionUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrBrowserTransitionUtils.java
index a7f020ce..f64bee1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrBrowserTransitionUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/util/VrBrowserTransitionUtils.java
@@ -14,9 +14,9 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
 import org.chromium.chrome.browser.vr.VrIntentUtils;
-import org.chromium.chrome.browser.vr.VrMainActivity;
 import org.chromium.chrome.browser.vr.VrShellDelegate;
 import org.chromium.chrome.browser.vr.VrShellImpl;
 import org.chromium.content.browser.test.util.CriteriaHelper;
@@ -97,34 +97,19 @@
     }
 
     /**
-     * Sends an intent to Chrome telling it to launch in VR mode. If the given autopresent param is
-     * true, this is expected to fail unless the trusted intent check is disabled in
-     * VrShellDelegate.
+     * Sends an intent to Chrome telling it to launch in VR mode.
      *
      * @param url String containing the URL to open.
-     * @param autopresent If this intent is expected to auto-present WebVR.
-     * @param avoidRelaunch Include an extra that prevents relaunching Chrome once the intent is
-     *        received.
      */
-    public static void sendVrLaunchIntent(String url, boolean autopresent, boolean avoidRelaunch) {
+    public static void sendVrLaunchIntent(String url) {
         // Create an intent that will launch Chrome at the specified URL.
         final Intent intent =
-                new Intent(ContextUtils.getApplicationContext(), VrMainActivity.class);
+                new Intent(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class);
+        intent.setAction(Intent.ACTION_VIEW);
         intent.setData(Uri.parse(url));
         intent.addCategory(VrIntentUtils.DAYDREAM_CATEGORY);
         VrIntentUtils.setupVrIntent(intent);
 
-        if (autopresent) {
-            // Daydream removes this category for deep-linked URLs for legacy reasons.
-            intent.removeCategory(VrIntentUtils.DAYDREAM_CATEGORY);
-            intent.putExtra(VrIntentUtils.AUTOPRESENT_WEVBVR_EXTRA, true);
-        }
-        if (avoidRelaunch) intent.putExtra(VrIntentUtils.AVOID_RELAUNCH_EXTRA, true);
-
-        // TODO(https://crbug.com/854327): Remove this workaround once the issue with launchInVr
-        // sometimes launching the given intent before entering VR is fixed.
-        intent.putExtra(VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, true);
-
         ThreadUtils.runOnUiThreadBlocking(
                 () -> { VrShellDelegate.getVrDaydreamApi().launchInVr(intent); });
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetControllerTest.java
index 7ae1ccb..3b8c004 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetControllerTest.java
@@ -22,7 +22,10 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.asynctask.CustomShadowAsyncTask;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
 import org.chromium.chrome.browser.modelutil.ListObservable;
@@ -32,7 +35,8 @@
  * Controller tests for the keyboard accessory bottom sheet component.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
+@Config(manifest = Config.NONE,
+        shadows = {CustomShadowAsyncTask.class, ShadowRecordHistogram.class})
 public class AccessorySheetControllerTest {
     @Mock
     private PropertyObservable.PropertyObserver<PropertyKey> mMockPropertyObserver;
@@ -44,8 +48,8 @@
     private ViewPager mMockView;
 
     private final Tab[] mTabs =
-            new Tab[] {new Tab(null, null, 0, null), new Tab(null, null, 0, null),
-                    new Tab(null, null, 0, null), new Tab(null, null, 0, null)};
+            new Tab[] {new Tab(null, null, 0, 0, null), new Tab(null, null, 0, 0, null),
+                    new Tab(null, null, 0, 0, null), new Tab(null, null, 0, 0, null)};
 
     private AccessorySheetCoordinator mCoordinator;
     private AccessorySheetMediator mMediator;
@@ -53,6 +57,7 @@
 
     @Before
     public void setUp() {
+        ShadowRecordHistogram.reset();
         MockitoAnnotations.initMocks(this);
         when(mMockViewStub.inflate()).thenReturn(mMockView);
         mCoordinator = new AccessorySheetCoordinator(mMockViewStub, /*unused*/ () -> null);
@@ -189,4 +194,27 @@
         assertThat(mModel.getTabList().size(), is(3));
         assertThat(mModel.getActiveTabIndex(), is(2));
     }
+
+    @Test
+    public void testRecordsSheetClosure() {
+        assertThat(RecordHistogram.getHistogramTotalCountForTesting(
+                           KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED),
+                is(0));
+
+        // Although sheets must be opened manually as of now, don't assume that every opened sheet
+        // in the future will be manually opened. Closing is the only thing to be tested here.
+        mCoordinator.show();
+        mCoordinator.hide();
+        assertThat(getTriggerMetricsCount(AccessorySheetTrigger.ANY_CLOSE), is(1));
+
+        // Log closing every time it happens.
+        mCoordinator.show();
+        mCoordinator.hide();
+        assertThat(getTriggerMetricsCount(AccessorySheetTrigger.ANY_CLOSE), is(2));
+    }
+
+    private int getTriggerMetricsCount(@AccessorySheetTrigger int bucket) {
+        return RecordHistogram.getHistogramValueCountForTesting(
+                KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_TRIGGERED, bucket);
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryControllerTest.java
index 119b455..50c8a80 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryControllerTest.java
@@ -24,7 +24,10 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.asynctask.CustomShadowAsyncTask;
 import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.PropertyProvider;
@@ -36,7 +39,8 @@
  * Controller tests for the keyboard accessory component.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
+@Config(manifest = Config.NONE,
+        shadows = {CustomShadowAsyncTask.class, ShadowRecordHistogram.class})
 public class KeyboardAccessoryControllerTest {
     @Mock
     private PropertyObserver<KeyboardAccessoryModel.PropertyKey> mMockPropertyObserver;
@@ -54,7 +58,7 @@
     private KeyboardAccessoryView mMockView;
 
     private final KeyboardAccessoryData.Tab mTestTab =
-            new KeyboardAccessoryData.Tab(null, null, 0, null);
+            new KeyboardAccessoryData.Tab(null, null, 0, 0, null);
 
     private KeyboardAccessoryCoordinator mCoordinator;
     private KeyboardAccessoryModel mModel;
@@ -62,6 +66,7 @@
 
     @Before
     public void setUp() {
+        ShadowRecordHistogram.reset();
         MockitoAnnotations.initMocks(this);
         when(mMockViewStub.inflate()).thenReturn(mMockView);
         mCoordinator = new KeyboardAccessoryCoordinator(
@@ -114,7 +119,7 @@
     @Test
     public void testModelNotifiesAboutActionsChangedByProvider() {
         final PropertyProvider<Action> testProvider = new PropertyProvider<>();
-        final Action testAction = new Action(null, null);
+        final Action testAction = new Action(null, 0, null);
 
         mModel.addActionListObserver(mMockActionListObserver);
         mCoordinator.registerActionListProvider(testProvider);
@@ -199,7 +204,7 @@
         assertThat(mModel.isVisible(), is(false));
 
         // Adding actions while the keyboard is visible triggers the accessory.
-        mModel.getActionList().add(new Action(null, null));
+        mModel.getActionList().add(new Action(null, 0, null));
         assertThat(mModel.isVisible(), is(true));
     }
 
@@ -207,7 +212,7 @@
     public void testActionsRemovedWhenNotVisible() {
         // Make the accessory visible and add an action to it.
         mMediator.keyboardVisibilityChanged(true);
-        mModel.getActionList().add(new Action(null, null));
+        mModel.getActionList().add(new Action(null, 0, null));
 
         // Hiding the accessory should also remove actions.
         mMediator.keyboardVisibilityChanged(false);
@@ -248,4 +253,76 @@
         mCoordinator.closeActiveTab();
         verifyNoMoreInteractions(mMockPropertyObserver, mMockVisibilityDelegate);
     }
+
+    @Test
+    public void testRecordsOneImpressionForEveryInitialContentOnVisibilityChange() {
+        assertThat(RecordHistogram.getHistogramTotalCountForTesting(
+                           KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
+                is(0));
+
+        // Adding a tab contributes to the tabs and the total bucket.
+        mCoordinator.addTab(mTestTab);
+        mMediator.keyboardVisibilityChanged(true);
+
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
+
+        // Adding an action contributes to the actions bucket. Tabs and total are logged again.
+        mMediator.keyboardVisibilityChanged(false); // Hide, so it's brought up again.
+        mModel.getActionList().add(new Action(null, 0, null));
+        mMediator.keyboardVisibilityChanged(true);
+
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(2));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(2));
+
+        // Adding suggestions adds to the suggestions bucket - and again to tabs and total.
+        mMediator.keyboardVisibilityChanged(false); // Hide, so it's brought up again.
+        mMediator.setSuggestions(mock(AutofillKeyboardSuggestions.class));
+        mMediator.keyboardVisibilityChanged(true);
+
+        // Hiding the keyboard clears actions, so don't log more actions from here on out.
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(3));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(3));
+
+        // Removing suggestions adds to everything but the suggestions bucket. The value remains.
+        mMediator.keyboardVisibilityChanged(false); // Hide, so it's brought up again.
+        mMediator.setSuggestions(null);
+        mMediator.keyboardVisibilityChanged(true);
+
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(4));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(4));
+    }
+
+    @Test
+    public void testRecordsContentImpressionsOnlyOnInitialShowing() {
+        assertThat(RecordHistogram.getHistogramTotalCountForTesting(
+                           KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
+                is(0));
+
+        // First showing contains actions only.
+        mModel.getActionList().add(new Action(null, 0, null));
+        mMediator.keyboardVisibilityChanged(true);
+
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
+
+        // Adding a tabs or suggestions now doesn't change the impression count
+        mCoordinator.addTab(mTestTab);
+        mMediator.setSuggestions(mock(AutofillKeyboardSuggestions.class));
+        mMediator.keyboardVisibilityChanged(true);
+
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(0));
+        assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
+        assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
+    }
+
+    private int getShownMetricsCount(@AccessoryBarContents int bucket) {
+        return RecordHistogram.getHistogramValueCountForTesting(
+                KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN, bucket);
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
index 208cad79..37dfeee 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java
@@ -13,6 +13,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
 import static org.chromium.chrome.browser.tab.Tab.INVALID_TAB_ID;
 import static org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType.FROM_BROWSER_ACTIONS;
 import static org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType.FROM_CLOSE;
@@ -112,7 +113,7 @@
 
         assertThat(keyboardAccessoryModel.getTabList().size(), is(0));
         mController.getMediatorForTesting().addTab(
-                new KeyboardAccessoryData.Tab(null, null, 0, null));
+                new KeyboardAccessoryData.Tab(null, null, 0, 0, null));
 
         verify(mMockTabListObserver).onItemRangeInserted(keyboardAccessoryModel.getTabList(), 0, 1);
         verify(mMockTabListObserver).onItemRangeInserted(accessorySheetModel.getTabList(), 0, 1);
@@ -186,7 +187,8 @@
         // Simulate opening a new tab which automatically triggers the registration:
         Tab firstTab = addTab(mediator, 1111, null);
         mController.registerActionProvider(firstTabProvider);
-        firstTabProvider.notifyObservers(new Action[] {new Action("Generate Password", p -> {})});
+        firstTabProvider.notifyObservers(new Action[] {
+                new Action("Generate Password", GENERATE_PASSWORD_AUTOMATIC, p -> {})});
         mMockItemListObserver.onItemRangeInserted(keyboardActions, 0, 1);
         assertThat(keyboardActions.get(0).getCaption(), is("Generate Password"));
 
@@ -274,7 +276,8 @@
         Tab secondTab = addTab(mediator, 1111, tab);
         PropertyProvider<Action> provider = new PropertyProvider<>();
         mController.registerActionProvider(provider);
-        provider.notifyObservers(new Action[] {new Action("Test Action", (action) -> {})});
+        provider.notifyObservers(new Action[] {
+                new Action("Test Action", GENERATE_PASSWORD_AUTOMATIC, (action) -> {})});
         assertThat(keyboardAccessoryModel.getActionList().size(), is(1));
 
         switchTab(mediator, secondTab, tab);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java
index 64a2c10..eab2172 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java
@@ -22,7 +22,11 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.asynctask.CustomShadowAsyncTask;
+import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Item;
 import org.chromium.chrome.browser.modelutil.ListObservable;
 import org.chromium.chrome.browser.modelutil.SimpleListObservable;
 
@@ -30,7 +34,8 @@
  * Controller tests for the password accessory sheet.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
+@Config(manifest = Config.NONE,
+        shadows = {CustomShadowAsyncTask.class, ShadowRecordHistogram.class})
 public class PasswordAccessorySheetControllerTest {
     @Mock
     private RecyclerView mMockView;
@@ -38,10 +43,11 @@
     private ListObservable.ListObserver<Void> mMockItemListObserver;
 
     private PasswordAccessorySheetCoordinator mCoordinator;
-    private SimpleListObservable<KeyboardAccessoryData.Item> mModel;
+    private SimpleListObservable<Item> mModel;
 
     @Before
     public void setUp() {
+        ShadowRecordHistogram.reset();
         MockitoAnnotations.initMocks(this);
         mCoordinator = new PasswordAccessorySheetCoordinator(RuntimeEnvironment.application);
         assertNotNull(mCoordinator);
@@ -67,33 +73,89 @@
 
     @Test
     public void testModelNotifiesAboutActionsChangedByProvider() {
-        final KeyboardAccessoryData.PropertyProvider<KeyboardAccessoryData.Item> testProvider =
+        final KeyboardAccessoryData.PropertyProvider<Item> testProvider =
                 new KeyboardAccessoryData.PropertyProvider<>();
-        final KeyboardAccessoryData.Item testItem =
-                KeyboardAccessoryData.Item.createLabel("Test Item", null);
+        final Item testItem = Item.createLabel("Test Item", null);
 
         mModel.addObserver(mMockItemListObserver);
         mCoordinator.registerItemProvider(testProvider);
 
         // If the coordinator receives an initial items, the model should report an insertion.
-        testProvider.notifyObservers(new KeyboardAccessoryData.Item[] {testItem});
+        testProvider.notifyObservers(new Item[] {testItem});
         verify(mMockItemListObserver).onItemRangeInserted(mModel, 0, 1);
         assertThat(mModel.size(), is(1));
         assertThat(mModel.get(0), is(equalTo(testItem)));
 
         // If the coordinator receives a new set of items, the model should report a change.
-        testProvider.notifyObservers(new KeyboardAccessoryData.Item[] {testItem});
+        testProvider.notifyObservers(new Item[] {testItem});
         verify(mMockItemListObserver).onItemRangeChanged(mModel, 0, 1, null);
         assertThat(mModel.size(), is(1));
         assertThat(mModel.get(0), is(equalTo(testItem)));
 
         // If the coordinator receives an empty set of items, the model should report a deletion.
-        testProvider.notifyObservers(new KeyboardAccessoryData.Item[] {});
+        testProvider.notifyObservers(new Item[] {});
         verify(mMockItemListObserver).onItemRangeRemoved(mModel, 0, 1);
         assertThat(mModel.size(), is(0));
 
         // There should be no notification if no item are reported repeatedly.
-        testProvider.notifyObservers(new KeyboardAccessoryData.Item[] {});
+        testProvider.notifyObservers(new Item[] {});
         verifyNoMoreInteractions(mMockItemListObserver);
     }
+
+    @Test
+    public void testRecordsActionImpressionsWhenShown() {
+        assertThat(getActionImpressions(AccessoryAction.MANAGE_PASSWORDS), is(0));
+
+        // Assuming that "Manage Passwords" remains a default option, showing means an impression.
+        mCoordinator.onTabShown();
+
+        assertThat(getActionImpressions(AccessoryAction.MANAGE_PASSWORDS), is(1));
+    }
+
+    @Test
+    public void testRecordsSuggestionsImpressionsWhenShown() {
+        assertThat(
+                RecordHistogram.getHistogramTotalCountForTesting(
+                        KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTIONS),
+                is(0));
+        assertThat(getSuggestionsImpressions(AccessoryTabType.PASSWORDS, 0), is(0));
+        assertThat(getSuggestionsImpressions(AccessoryTabType.ALL, 0), is(0));
+
+        // If the tab is shown without interactive item, log "0" samples.
+        mModel.set(new Item[] {Item.createLabel("No passwords!", ""), Item.createDivider(),
+                Item.createOption("Manage all passwords", "", null),
+                Item.createOption("Generate password", "", null)});
+        mCoordinator.onTabShown();
+
+        assertThat(getSuggestionsImpressions(AccessoryTabType.PASSWORDS, 0), is(1));
+        assertThat(getSuggestionsImpressions(AccessoryTabType.ALL, 0), is(1));
+
+        // If the tab is shown with X interactive item, record "X" samples.
+        mModel.set(new Item[] {Item.createLabel("Your passwords", ""),
+                Item.createSuggestion("Interactive 1", "", false, (v) -> {}, null),
+                Item.createSuggestion("Non-interactive 1", "", true, null, null),
+                Item.createSuggestion("Interactive 2", "", false, (v) -> {}, null),
+                Item.createSuggestion("Non-interactive 2", "", true, null, null),
+                Item.createSuggestion("Interactive 3", "", false, (v) -> {}, null),
+                Item.createSuggestion("Non-interactive 3", "", true, null, null),
+                Item.createDivider(), Item.createOption("Manage all passwords", "", null),
+                Item.createOption("Generate password", "", null)});
+        mCoordinator.onTabShown();
+
+        assertThat(getSuggestionsImpressions(AccessoryTabType.PASSWORDS, 3), is(1));
+        assertThat(getSuggestionsImpressions(AccessoryTabType.ALL, 3), is(1));
+    }
+
+    private int getActionImpressions(@AccessoryAction int bucket) {
+        return RecordHistogram.getHistogramValueCountForTesting(
+                KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_ACTION_IMPRESSION, bucket);
+    }
+
+    private int getSuggestionsImpressions(@AccessoryTabType int type, int sample) {
+        return RecordHistogram.getHistogramValueCountForTesting(
+                KeyboardAccessoryMetricsRecorder.getHistogramForType(
+                        KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_SHEET_SUGGESTIONS,
+                        type),
+                sample);
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/modelutil/PropertyModelTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/modelutil/PropertyModelTest.java
index 93da4ca..d5ded5b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/modelutil/PropertyModelTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/modelutil/PropertyModelTest.java
@@ -75,6 +75,7 @@
         @SuppressWarnings("unchecked")
         PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
         model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
 
         model.setValue(key, value);
         verify(observer).onPropertyChanged(model, key);
@@ -103,6 +104,7 @@
         @SuppressWarnings("unchecked")
         PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
         model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
 
         model.setValue(key, value);
         verify(observer).onPropertyChanged(model, key);
@@ -127,6 +129,7 @@
         @SuppressWarnings("unchecked")
         PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
         model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
 
         model.setValue(key, value);
         verify(observer).onPropertyChanged(model, key);
@@ -162,6 +165,7 @@
         @SuppressWarnings("unchecked")
         PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
         model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
 
         model.setValue(key, value);
         verify(observer).onPropertyChanged(model, key);
@@ -184,6 +188,7 @@
         @SuppressWarnings("unchecked")
         PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
         model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
 
         model.setValue(BOOLEAN_PROPERTY_A, true);
         model.setValue(FLOAT_PROPERTY_A, 1f);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
index 25c3f54..964763a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ntp/cards/SuggestionsSectionTest.java
@@ -16,6 +16,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doNothing;
@@ -576,6 +577,7 @@
             @SuppressWarnings("unchecked")
             Callback<String> callback = mock(Callback.class);
             section.dismissItem(1, callback);
+            verify(callback).onResult(anyString());
         }
         assertEquals(0, section.getSuggestionsCount());
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java
new file mode 100644
index 0000000..80e8d51f
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/omnibox/UrlBarMediatorUnitTest.java
@@ -0,0 +1,216 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.omnibox;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.modelutil.PropertyKey;
+import org.chromium.chrome.browser.modelutil.PropertyModel;
+import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver;
+
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class UrlBarMediatorUnitTest {
+    @Test
+    public void setUrlData_SendsUpdates() {
+        PropertyModel model = new PropertyModel(UrlBarProperties.ALL_KEYS);
+        UrlBarMediator mediator = new UrlBarMediator(model);
+
+        UrlBarData baseData = UrlBarData.create(
+                "http://www.example.com", spannable("www.example.com"), 0, 14, "Blah");
+        UrlBarData dataWithDifferentDisplay = UrlBarData.create(
+                "http://www.example.com", spannable("www.foo.com"), 0, 11, "Blah");
+        UrlBarData dataWithDifferentEditing = UrlBarData.create(
+                "http://www.example.com", spannable("www.example.com"), 0, 14, "Bar");
+
+        Assert.assertTrue(mediator.setUrlBarData(baseData, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+
+        @SuppressWarnings("unchecked")
+        PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
+        model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
+
+        Assert.assertTrue(mediator.setUrlBarData(
+                dataWithDifferentDisplay, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+        Assert.assertTrue(mediator.setUrlBarData(
+                dataWithDifferentEditing, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+        Assert.assertTrue(mediator.setUrlBarData(
+                dataWithDifferentEditing, UrlBar.ScrollType.SCROLL_TO_BEGINNING, 4));
+
+        Mockito.verify(observer, Mockito.times(3))
+                .onPropertyChanged(model, UrlBarProperties.TEXT_STATE);
+    }
+
+    @Test
+    public void setUrlData_PreventsDuplicateUpdates() {
+        PropertyModel model = new PropertyModel(UrlBarProperties.ALL_KEYS);
+        UrlBarMediator mediator = new UrlBarMediator(model);
+
+        UrlBarData data1 = UrlBarData.create(
+                "http://www.example.com", spannable("www.example.com"), 0, 0, "Blah");
+        UrlBarData data2 = UrlBarData.create(
+                "http://www.example.com", spannable("www.example.com"), 0, 0, "Blah");
+
+        Assert.assertTrue(mediator.setUrlBarData(data1, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+
+        @SuppressWarnings("unchecked")
+        PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
+        model.addObserver(observer);
+        Mockito.<PropertyObserver>reset(observer);
+
+        Assert.assertFalse(mediator.setUrlBarData(data1, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+        Assert.assertFalse(mediator.setUrlBarData(data2, UrlBar.ScrollType.SCROLL_TO_TLD, 4));
+
+        Mockito.verifyZeroInteractions(observer);
+    }
+
+    @Test
+    public void urlDataComparison_equals() {
+        Assert.assertTrue(UrlBarMediator.isNewTextEquivalentToExistingText(null, null));
+
+        // No editing text, equal display text
+        Assert.assertTrue(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, spannable("Test"), 0, 0, null),
+                UrlBarData.create(null, spannable("Test"), 0, 0, null)));
+
+        // Equal display and editing text
+        Assert.assertTrue(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, spannable("Test"), 0, 0, "Blah"),
+                UrlBarData.create(null, spannable("Test"), 0, 0, "Blah")));
+
+        // Equal complex display text and editing text
+        SpannableStringBuilder text1 = spannable("Test");
+        text1.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(3), 0, 3, 0);
+        text1.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(4), 1, 3, 0);
+        text1.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisSecurityErrorSpan(), 0, 1, 0);
+
+        SpannableStringBuilder text2 = spannable("Test");
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(3), 0, 3, 0);
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(4), 1, 3, 0);
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisSecurityErrorSpan(), 0, 1, 0);
+
+        Assert.assertTrue(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, text1, 0, 0, "Blah"),
+                UrlBarData.create(null, text2, 0, 0, "Blah")));
+
+        // Ensure adding non-emphasis spans does not mess up equality.
+        text1.setSpan(new Object(), 0, 3, 0);
+        Selection.setSelection(text2, 0, 1);
+        Assert.assertTrue(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, text1, 0, 0, "Blah"),
+                UrlBarData.create(null, text2, 0, 0, "Blah")));
+    }
+
+    @Test
+    public void urlDataComparison_notEquals() {
+        Assert.assertFalse(
+                UrlBarMediator.isNewTextEquivalentToExistingText(null, UrlBarData.EMPTY));
+        Assert.assertFalse(
+                UrlBarMediator.isNewTextEquivalentToExistingText(UrlBarData.EMPTY, null));
+
+        // Different display texts
+        Assert.assertFalse(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, spannable("Test"), 0, 0, null),
+                UrlBarData.create(null, spannable("Test2"), 0, 0, null)));
+
+        // Mismatched spannable state of display text
+        Assert.assertFalse(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, spannable("Test"), 0, 0, null),
+                UrlBarData.create(null, "Test2", 0, 0, null)));
+
+        // Equal display text, different editing text
+        Assert.assertFalse(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, spannable("Test"), 0, 0, "Blah"),
+                UrlBarData.create(null, spannable("Test"), 0, 0, "Blah2")));
+
+        // Equal display text content, but different emphasis spans
+        SpannableStringBuilder text1 = spannable("Test");
+        SpannableStringBuilder text2 = spannable("Test");
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(3), 0, 3, 0);
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(4), 1, 3, 0);
+        text2.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisSecurityErrorSpan(), 0, 1, 0);
+
+        Assert.assertFalse(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, text1, 0, 0, "Blah"),
+                UrlBarData.create(null, text2, 0, 0, "Blah")));
+
+        // Add a subset of emphasis spans, but not all.
+        text1.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(3), 0, 3, 0);
+        text1.setSpan(new OmniboxUrlEmphasizer.UrlEmphasisColorSpan(4), 1, 3, 0);
+        Assert.assertFalse(UrlBarMediator.isNewTextEquivalentToExistingText(
+                UrlBarData.create(null, text1, 0, 0, "Blah"),
+                UrlBarData.create(null, text2, 0, 0, "Blah")));
+    }
+
+    @Test
+    public void pasteTextValidation() {
+        PropertyModel model = new PropertyModel(UrlBarProperties.ALL_KEYS);
+        UrlBarMediator mediator = new UrlBarMediator(model) {
+            @Override
+            protected String sanitizeTextForPaste(String text) {
+                return text.trim();
+            }
+        };
+
+        ClipboardManager clipboard =
+                (ClipboardManager) RuntimeEnvironment.application.getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+        clipboard.setPrimaryClip(null);
+        Assert.assertNull(mediator.getTextToPaste());
+
+        clipboard.setPrimaryClip(ClipData.newPlainText("", ""));
+        Assert.assertEquals("", mediator.getTextToPaste());
+
+        clipboard.setPrimaryClip(ClipData.newPlainText("", "test"));
+        Assert.assertEquals("test", mediator.getTextToPaste());
+
+        clipboard.setPrimaryClip(ClipData.newPlainText("", "    test     "));
+        Assert.assertEquals("test", mediator.getTextToPaste());
+    }
+
+    @Test
+    public void cutCopyReplacementTextValidation() {
+        PropertyModel model = new PropertyModel(UrlBarProperties.ALL_KEYS);
+        UrlBarMediator mediator = new UrlBarMediator(model);
+        String url = "https://www.test.com/blah";
+        String displayText = "test.com/blah";
+        String editingText = "www.test.com/blah";
+        mediator.setUrlBarData(UrlBarData.create(url, displayText, 0, 12, editingText),
+                UrlBar.ScrollType.NO_SCROLL, UrlBarCoordinator.SelectionState.SELECT_ALL);
+
+        // Replacement is only valid if selecting the full text.
+        Assert.assertNull(mediator.getReplacementCutCopyText(editingText, 1, 2));
+
+        // Editing text will be replaced with the full URL if selecting all of the text.
+        Assert.assertEquals(
+                url, mediator.getReplacementCutCopyText(editingText, 0, editingText.length()));
+
+        // If selecting just the URL portion of the editing text, it should be replaced with the
+        // unformatted URL.
+        Assert.assertEquals(
+                "https://www.test.com", mediator.getReplacementCutCopyText(editingText, 0, 12));
+
+        // If the path changed in the editing text changed but the domain is untouched, it should
+        // be replaced with the full domain from the unformatted URL.
+        Assert.assertEquals("https://www.test.com/foo",
+                mediator.getReplacementCutCopyText("www.test.com/foo", 0, 16));
+    }
+
+    private static SpannableStringBuilder spannable(String text) {
+        return new SpannableStringBuilder(text);
+    }
+}
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 070b0e54..20a9057b 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-70.0.3508.3_rc-r1.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-70.0.3510.3_rc-r1.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index f2dc573..3a525470 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5015,14 +5015,6 @@
         Open a new tab to browse two sites at once
       </message>
 
-      <!-- NUX Experiment strings -->
-      <if expr="is_win">
-        <!-- Note: Each experiment should add their own message instead of using this default -->
-        <message name="IDS_BOOKMARK_BAR_PROMO_DEFAULT" desc="Default text shown when promoting a bookmark in the bookmark bar.">
-          A bookmark has been added for you
-        </message>
-      </if>
-
       <!-- Browser Hung Plugin Detector -->
       <if expr="is_win">
         <message name="IDS_BROWSER_HANGMONITOR" desc="A plugin on a page has hung">
diff --git a/chrome/app/resources/chromium_strings_ca.xtb b/chrome/app/resources/chromium_strings_ca.xtb
index 693d1c8..0e02891 100644
--- a/chrome/app/resources/chromium_strings_ca.xtb
+++ b/chrome/app/resources/chromium_strings_ca.xtb
@@ -124,13 +124,13 @@
 <translation id="5479196819031988440">Chromium OS no pot obrir aquesta pàgina.</translation>
 <translation id="5480860683791598150">Chromium necessita accedir a la teva ubicació per compartir-la amb aquest lloc web</translation>
 <translation id="549669000822060376">Espereu mentre Chromium instal·la les darreres actualitzacions del sistema.</translation>
-<translation id="5514308096618405748">Chrome OS és possible gràcies a <ph name="BEGIN_LINK_CROS_OSS" />programari lliure addicional<ph name="END_LINK_CROS_OSS" />, així com Linux (versió beta).</translation>
+<translation id="5514308096618405748">Chrome OS és possible gràcies a <ph name="BEGIN_LINK_CROS_OSS" />programari lliure<ph name="END_LINK_CROS_OSS" /> addicional, així com Linux (versió beta).</translation>
 <translation id="5529843986978123325">{0,plural, =1{Chromium OS es reiniciarà d'aquí a 1 minut}other{Chromium OS es reiniciarà d'aquí a # minuts}}</translation>
 <translation id="5631814766731275228">Nom i foto de Chromium</translation>
 <translation id="5634636535844844681">Chromium requereix Windows 7 o una versió posterior.</translation>
 <translation id="5680901439334282664">Inicia la sessió a Chromium</translation>
 <translation id="5698481217667032250">Mostra Chromium en aquest idioma</translation>
-<translation id="5712253116097046984">L'administrador recomana que reiniciïs Chromium OS per aplicar aquesta actualització</translation>
+<translation id="5712253116097046984">L'administrador demana que reiniciïs Chromium OS per aplicar aquesta actualització</translation>
 <translation id="5726838626470692954">El gestor t'ha de suprimir i tornar a afegir a Chromium.</translation>
 <translation id="5768914737813585044">Mostra Chromium OS en aquest idioma</translation>
 <translation id="5796460469508169315">Chromium ja està quasi llest.</translation>
diff --git a/chrome/app/resources/chromium_strings_cs.xtb b/chrome/app/resources/chromium_strings_cs.xtb
index 6cc1eb4..5be7884b 100644
--- a/chrome/app/resources/chromium_strings_cs.xtb
+++ b/chrome/app/resources/chromium_strings_cs.xtb
@@ -234,6 +234,7 @@
 <translation id="8157153840442649507">Chromium se zobrazuje v tomto jazyce</translation>
 <translation id="81770708095080097">Tento soubor je nebezpečný, proto jej prohlížeč Chromium zablokoval.</translation>
 <translation id="8222496066431494154">Nainstalujte si Chromium do telefonu. Zašleme vám SMS na toto telefonní číslo pro obnovení účtu: <ph name="PHONE_NUMBER" />.</translation>
+<translation id="825412236959742607">Tato stránka využívá příliš mnoho paměti, Chrome proto odstranil část obsahu.</translation>
 <translation id="8269379391216269538">Pomozte projekt Chromium vylepšit</translation>
 <translation id="8290862415967981663">Tento soubor může být nebezpečný, proto jej prohlížeč Chromium zablokoval.</translation>
 <translation id="8330519371938183845">Po přihlášení si budete moci Chromium synchronizovat a upravit na různých zařízeních</translation>
diff --git a/chrome/app/resources/chromium_strings_en-GB.xtb b/chrome/app/resources/chromium_strings_en-GB.xtb
index 042ab22..eb90adf 100644
--- a/chrome/app/resources/chromium_strings_en-GB.xtb
+++ b/chrome/app/resources/chromium_strings_en-GB.xtb
@@ -234,6 +234,7 @@
 <translation id="8157153840442649507">Chromium is displayed in this language</translation>
 <translation id="81770708095080097">This file is dangerous, so Chromium has blocked it.</translation>
 <translation id="8222496066431494154">Install Chromium on your phone. We’ll send an SMS to your account recovery phone number: <ph name="PHONE_NUMBER" /></translation>
+<translation id="825412236959742607">This page uses too much memory, so Chrome removed some content.</translation>
 <translation id="8269379391216269538">Help make Chromium better</translation>
 <translation id="8290862415967981663">This file may be dangerous, so Chromium has blocked it.</translation>
 <translation id="8330519371938183845">Sign in to sync and personalise Chromium across your devices</translation>
diff --git a/chrome/app/resources/chromium_strings_it.xtb b/chrome/app/resources/chromium_strings_it.xtb
index 12a9697..3fd5053 100644
--- a/chrome/app/resources/chromium_strings_it.xtb
+++ b/chrome/app/resources/chromium_strings_it.xtb
@@ -8,7 +8,7 @@
 <translation id="1170115874949214249">Installa Chromium sul telefono. Invieremo un SMS al tuo numero di telefono per il recupero dell'account.</translation>
 <translation id="1174473354587728743">Condividi un computer con altre persone? Ora puoi configurare Chromium come preferisci.</translation>
 <translation id="1185134272377778587">Informazioni su Chromium</translation>
-<translation id="1209657686917656928">{0,plural, =0{Chromium verrà riavviato ora}=1{Chromium verrà riavviato tra 1 secondo}other{Chromium verrà riavviato tra # secondi}}</translation>
+<translation id="1209657686917656928">{0,plural, =0{Chromium verrà riavviato ora}=1{Chromium verrà riavviato tra un secondo}other{Chromium verrà riavviato tra # secondi}}</translation>
 <translation id="1267419686153937460">{0,plural, =1{Riavvia Chromium entro un giorno}other{Riavvia Chromium entro # giorni}}</translation>
 <translation id="1298199220304005244">Ricevi assistenza per l'utilizzo di Chromium OS</translation>
 <translation id="1396446129537741364">Chromium sta cercando di visualizzare le password.</translation>
@@ -125,7 +125,7 @@
 <translation id="5480860683791598150">Chromium deve poter accedere alla tua posizione per condividerla con questo sito</translation>
 <translation id="549669000822060376">Attendi mentre Chromium installa gli aggiornamenti di sistema più recenti.</translation>
 <translation id="5514308096618405748">La realizzazione di Chrome OS è stata possibile grazie a <ph name="BEGIN_LINK_CROS_OSS" />software open source<ph name="END_LINK_CROS_OSS" /> come Linux (beta).</translation>
-<translation id="5529843986978123325">{0,plural, =1{Chromium OS verrà riavviato tra 1 minuto}other{Chromium OS verrà riavviato tra # minuti}}</translation>
+<translation id="5529843986978123325">{0,plural, =1{Chromium OS verrà riavviato tra un minuto}other{Chromium OS verrà riavviato tra # minuti}}</translation>
 <translation id="5631814766731275228">Nome e immagine di Chromium</translation>
 <translation id="5634636535844844681">Chromium richiede Windows 7 o versioni successive.</translation>
 <translation id="5680901439334282664">accedi a Chromium</translation>
@@ -147,7 +147,7 @@
 <translation id="6072279588547424923">L'estensione <ph name="EXTENSION_NAME" /> è stata aggiunta a Chromium</translation>
 <translation id="608189560609172163">Impossibile sincronizzare i dati in Chromium a causa di un errore durante l'accesso.</translation>
 <translation id="6096348254544841612">È disponibile un nuovo aggiornamento.</translation>
-<translation id="6120345080069858279">Chromium salverà la password nel tuo account Google affinché tu non debba memorizzarla.</translation>
+<translation id="6120345080069858279">Chromium salverà la password nel tuo Account Google affinché tu non debba memorizzarla.</translation>
 <translation id="6129621093834146363"><ph name="FILE_NAME" /> è pericoloso, pertanto è stato bloccato da Chromium.</translation>
 <translation id="620022061217911843">L'amministratore richiede di riavviare Chromium OS per installare un aggiornamento</translation>
 <translation id="6212496753309875659">Questo computer ha già una versione più recente di Chromium. Se il software non funziona, disinstalla Chromium e riprova.</translation>
@@ -242,7 +242,7 @@
 <translation id="8493179195440786826">Chromium non è aggiornato</translation>
 <translation id="85843667276690461">Ricevi assistenza per l'utilizzo di Chromium</translation>
 <translation id="8586442755830160949">Copyright <ph name="YEAR" /> The Chromium Authors. Tutti i diritti riservati.</translation>
-<translation id="8599548569518771270">{0,plural, =0{Chromium OS verrà riavviato ora}=1{Chromium verrà riavviato tra 1 secondo}other{Chromium OS verrà riavviato tra # secondi}}</translation>
+<translation id="8599548569518771270">{0,plural, =0{Chromium OS verrà riavviato ora}=1{Chromium verrà riavviato tra un secondo}other{Chromium OS verrà riavviato tra # secondi}}</translation>
 <translation id="8619360774459241877">Avvio di Chromium...</translation>
 <translation id="8621669128220841554">Installazione non riuscita a causa di un errore imprecisato. Scarica di nuovo Chromium.</translation>
 <translation id="8628626585870903697">Chromium non include il visualizzatore PDF necessario per la funzione Anteprima di stampa.</translation>
diff --git a/chrome/app/resources/chromium_strings_lt.xtb b/chrome/app/resources/chromium_strings_lt.xtb
index 9a23f67..0dfbc72 100644
--- a/chrome/app/resources/chromium_strings_lt.xtb
+++ b/chrome/app/resources/chromium_strings_lt.xtb
@@ -234,6 +234,7 @@
 <translation id="8157153840442649507">„Chromium“ pateikiama šia kalba</translation>
 <translation id="81770708095080097">Šis failas pavojingas, todėl „Chromium“ jį užblokavo.</translation>
 <translation id="8222496066431494154">Įdiekite „Chromium“ telefone. Išsiųsime SMS pranešimą paskyros atkūrimo telefono numeriu: <ph name="PHONE_NUMBER" /></translation>
+<translation id="825412236959742607">Šis puslapis naudoja per daug atminties, todėl „Chrome“ pašalino šiek tiek turinio.</translation>
 <translation id="8269379391216269538">Padėkite tobulinti „Chromium“</translation>
 <translation id="8290862415967981663">Šis failas gali būti pavojingas, todėl „Chromium“ jį užblokavo.</translation>
 <translation id="8330519371938183845">Prisijunkite, kad galėtumėte sinchronizuoti ir suasmeninti „Chromium“ skirtinguose įrenginiuose</translation>
diff --git a/chrome/app/resources/chromium_strings_ml.xtb b/chrome/app/resources/chromium_strings_ml.xtb
index e39910102..20edabb1 100644
--- a/chrome/app/resources/chromium_strings_ml.xtb
+++ b/chrome/app/resources/chromium_strings_ml.xtb
@@ -151,7 +151,7 @@
 <translation id="6096348254544841612">Chromium ഇഷ്ടാനുസൃതമാക്കി നിയന്ത്രിക്കുക. അപ്‌ഡേറ്റ് ലഭ്യമാണ്.</translation>
 <translation id="6120345080069858279">നിങ്ങളുടെ Google അക്കൗണ്ടിൽ Chromium ഈ പാസ്‌വേഡ് സംരക്ഷിക്കും. നിങ്ങൾ അത് ഓർത്ത് വയ്ക്കേണ്ടതില്ല.</translation>
 <translation id="6129621093834146363"><ph name="FILE_NAME" /> അപകടകരമായതിനാൽ, Chromium ഇതിനെ ബ്ലോക്കുചെയ്‌തു.</translation>
-<translation id="620022061217911843">ഈ അപ്‌ഡേറ്റ് ബാധകമാക്കുന്നതിന് നിങ്ങൾ Chromium OS പുനഃരാരംഭിക്കുന്നത് അഡ്‌മിന് ആവശ്യമാണ്.</translation>
+<translation id="620022061217911843">ഒരു അപ്‌ഡേറ്റ് ബാധകമാകാൻ നിങ്ങൾ Chromium OS പുനഃരാരംഭിക്കണമെന്നത് അഡ്‌മിന്റെ ആവശ്യമാണ്.</translation>
 <translation id="6212496753309875659">Chromium-ത്തിന്റെ ഏറ്റവും സമീപകാലത്തുള്ള പതിപ്പ് ഈ കമ്പ്യൂട്ടറില്‍ ഇതിനകം തന്നെയുണ്ട്. സോഫ്റ്റ്‌വെയര്‍ പ്രവര്‍ത്തിക്കുന്നില്ലെങ്കില്‍, Chromium അൺഇൻസ്റ്റാളുചെയ്‌ത് വീണ്ടും ശ്രമിക്കുക.</translation>
 <translation id="6248213926982192922">Chromium-ത്തെ സ്ഥിരസ്ഥിതി ബ്രൗസറാക്കി മാറ്റുക</translation>
 <translation id="6268381023930128611">Chromium-ത്തിൽ നിന്ന് സൈൻ ഔട്ട് ചെയ്യണോ?</translation>
diff --git a/chrome/app/resources/chromium_strings_mr.xtb b/chrome/app/resources/chromium_strings_mr.xtb
index 3e3ba18d..8969949a 100644
--- a/chrome/app/resources/chromium_strings_mr.xtb
+++ b/chrome/app/resources/chromium_strings_mr.xtb
@@ -232,6 +232,7 @@
 <translation id="8157153840442649507">Chromium या भाषेत प्रदर्शित केले आहे</translation>
 <translation id="81770708095080097">ही फाईल धोकादायक आहे, त्यामुळे Chromium ने अवरोधित केली आहे.</translation>
 <translation id="8222496066431494154">आपल्या फोनवर Chromium इंस्टॉल करा. आम्ही आपल्या या खाते पुनर्प्राप्ती फोन नंबरवर एक SMS पाठवू: <ph name="PHONE_NUMBER" /></translation>
+<translation id="825412236959742607">हे पेज खूप जास्त मेमरी वापरत असल्यामुळे, Chrome ने काही आशय काढून टाकला आहे.</translation>
 <translation id="8269379391216269538">Chromium उत्कृष्ट करण्यात मदत करा</translation>
 <translation id="8290862415967981663">ही फाईल कदाचित धोकादायक असू शकते, त्यामुळे Chromium ने ती अवरोधित केली आहे.</translation>
 <translation id="8330519371938183845">तुमच्या डिव्हाइसवर Chromium सिंक आणि पर्सनलाइझ करण्यासाठी साइन इन करा</translation>
diff --git a/chrome/app/resources/chromium_strings_sk.xtb b/chrome/app/resources/chromium_strings_sk.xtb
index 83f6de75..962e69af 100644
--- a/chrome/app/resources/chromium_strings_sk.xtb
+++ b/chrome/app/resources/chromium_strings_sk.xtb
@@ -234,6 +234,7 @@
 <translation id="8157153840442649507">Chromium sa zobrazuje v tomto jazyku</translation>
 <translation id="81770708095080097">Tento súbor je nebezpečný, a preto ho prehliadač Chromium zablokoval.</translation>
 <translation id="8222496066431494154">Nainštalujte si Chromium do telefónu. Pošleme vám správu SMS na telefónne číslo na obnovenie účtu: <ph name="PHONE_NUMBER" /></translation>
+<translation id="825412236959742607">Táto stránka využíva príliš veľa pamäte, a preto Chrome odstránil niektorý obsah.</translation>
 <translation id="8269379391216269538">Pomôže zlepšiť Chromium</translation>
 <translation id="8290862415967981663">Tento súbor môže byť nebezpečný, a preto ho prehliadač Chromium zablokoval.</translation>
 <translation id="8330519371938183845">Po prihlásení budete môcť Chromium synchronizovať a prispôsobiť v rôznych zariadeniach.</translation>
diff --git a/chrome/app/resources/chromium_strings_sw.xtb b/chrome/app/resources/chromium_strings_sw.xtb
index af38ea5..dba1fff 100644
--- a/chrome/app/resources/chromium_strings_sw.xtb
+++ b/chrome/app/resources/chromium_strings_sw.xtb
@@ -10,7 +10,7 @@
 <translation id="1170115874949214249">Sakinisha Chromium kwenye simu yako. Tutatuma SMS kwenye nambari yako ya simu ya mbinu za kurejesha uwezo wa kufikia akaunti.</translation>
 <translation id="1174473354587728743">Ungependa kushiriki kompyuta? Sasa unaweza kusanidi Chromium kama tu unavyoipenda.</translation>
 <translation id="1185134272377778587">Kuhusu Chromium</translation>
-<translation id="1209657686917656928">{0,plural, =0{Chromium itawaka upya hivi sasa}=1{Chromium itawaka upya baada ya sekunde 1}other{Chromium itawaka upya baada ya sekunde #}}</translation>
+<translation id="1209657686917656928">{0,plural, =0{Chromium itafunguka upya hivi sasa}=1{Chromium itafunguka upya baada ya sekunde 1}other{Chromium itafunguka upya baada ya sekunde #}}</translation>
 <translation id="1267419686153937460">{0,plural, =1{Anzisha Chromium upya baada ya siku moja}other{Anzisha Chromium upya baada ya siku #}}</translation>
 <translation id="1298199220304005244">Pata msaada unapotumia Chromium OS</translation>
 <translation id="1396446129537741364">Chromium inajaribu kuonyesha manenosiri.</translation>
@@ -57,7 +57,7 @@
 <translation id="2977470724722393594">Chromium imesasishwa</translation>
 <translation id="3032787606318309379">Inaongeza kwenye Chromium...</translation>
 <translation id="3046695367536568084">Unapaswa kuwa umeingia katika akaunti ya Chromium ili utumie programu hizi. Hii inaruhusu Chromium kusawazisha programu, alamisho, historia, manenosiri yako, na mipangilio mingine katika vifaa vyote.</translation>
-<translation id="3052899382720782935">{0,plural, =1{Chromium itawaka upya baada ya dakika 1}other{Chromium itawaka upya baada ya dakika #}}</translation>
+<translation id="3052899382720782935">{0,plural, =1{Chromium itafunguka upya baada ya dakika 1}other{Chromium itafunguka upya baada ya dakika #}}</translation>
 <translation id="3068515742935458733">Saidia kuboresha Chromium kwa kutuma ripoti za kuacha kufanya kazi na <ph name="UMA_LINK" /> kwenda Google</translation>
 <translation id="3103660991484857065">Kisakinishi kilishindwa kufinyuza kumbukumbu. Tafadhali pakua Chromium tena.</translation>
 <translation id="3130323860337406239">Chromium inatumia maikrofoni yako.</translation>
@@ -132,7 +132,7 @@
 <translation id="5634636535844844681">Chromium inahitaji Windows 7 au toleo jipya zaidi.</translation>
 <translation id="5680901439334282664">ingia katika Chromium</translation>
 <translation id="5698481217667032250">Onyesha Chromium katika lugha hii</translation>
-<translation id="5712253116097046984">Msimamizi wako anakuomba uzime kisha uwashe mfumo wa uendeshaji wa Chromium ili utumie sasisho hili</translation>
+<translation id="5712253116097046984">Msimamizi wako anakuomba uzime kisha uwashe mfumo wa uendeshaji wa Chromium ili uweke sasisho hili</translation>
 <translation id="5726838626470692954">Lazima msimamizi wako akuondoe na kukuongeza tena kwenye Chromium.</translation>
 <translation id="5768914737813585044">Onyesha Mfumo wa Uendeshaji wa Chromium katika lugha hii</translation>
 <translation id="5796460469508169315">Chromium inakaribia kuwa tayari.</translation>
@@ -151,7 +151,7 @@
 <translation id="6096348254544841612">Weka mapendeleo na udhibiti Chromium. Sasisho linapatikana.</translation>
 <translation id="6120345080069858279">Chromium itahifadhi nenosiri hili kwenye Akaunti yako ya Google. Hutahitaji kulikumbuka.</translation>
 <translation id="6129621093834146363"><ph name="FILE_NAME" /> ni hatari, kwa hivyo Chromium imeizuia.</translation>
-<translation id="620022061217911843">Msimamizi wako anakushauri uwashe kisha uzime mfumo wa uendeshaji wa Chromium ili utumie sasisho</translation>
+<translation id="620022061217911843">Msimamizi wako anakushauri uwashe kisha uzime mfumo wa uendeshaji wa Chromium ili uweke sasisho</translation>
 <translation id="6212496753309875659">Kompyuta hii tayari ina toleo la hivi punde la Chromium. Ikiwa programu haifanyikazi, tafadhali sanidua Chromium na ujaribu tena.</translation>
 <translation id="6248213926982192922">Fanya Chromium kuwa kivinjari chaguomsingi</translation>
 <translation id="6268381023930128611">Ungependa kuondoka kwenye Chromium?</translation>
@@ -238,7 +238,7 @@
 <translation id="8330519371938183845">Ingia katika akaunti ili usawazishe na uweke mapendeleo kwenye Chromium katika vifaa vyako vyote</translation>
 <translation id="8340674089072921962"><ph name="USER_EMAIL_ADDRESS" /> ilikuwa ikitumia Chromium awali</translation>
 <translation id="8375950122744241554">Wasifu wa watumiaji wanaosimamiwa hautapatikana tena katika toleo la kuanzia Chromium 70.</translation>
-<translation id="8379713241968949941">{0,plural, =1{Chromium itaanza upya baada ya saa moja}other{Chromium itaanza upya baada ya saa #}}</translation>
+<translation id="8379713241968949941">{0,plural, =1{Chromium itafunguka upya baada ya saa moja}other{Chromium itafunguka upya baada ya saa #}}</translation>
 <translation id="8453117565092476964">Kumbukumbu ya kisakinishi imeharibika au ni batili. Tafadhali pakua Chromium tena.</translation>
 <translation id="8493179195440786826">Chromium Imepitwa na Wakati</translation>
 <translation id="85843667276690461">Pata msaada kwa kutumia Chromium</translation>
diff --git a/chrome/app/resources/chromium_strings_ta.xtb b/chrome/app/resources/chromium_strings_ta.xtb
index 40bd8a1b..4cc5b37 100644
--- a/chrome/app/resources/chromium_strings_ta.xtb
+++ b/chrome/app/resources/chromium_strings_ta.xtb
@@ -25,7 +25,7 @@
 <translation id="1808667845054772817">Chromium ஐ மீண்டும்நிறுவு</translation>
 <translation id="1869480248812203386">பாதுகாப்பிற்கு இடையூறு விளைவிக்கும் சாத்தியமுள்ள செயல்பாடு குறித்த விவரங்களைத் தானாகவே Google க்கு அனுப்புவதன் மூலம், Chromium ஐ மேலும் பாதுகாப்பானதாகவும், பயன்படுத்துவதற்கு எளிதானதாகவும் மாற்ற உதவவும்.</translation>
 <translation id="1881322772814446296">நீங்கள் நிர்வகிக்கப்படும் கணக்கு மூலம் உள்நுழைகிறீர்கள், மேலும் அதன் நிர்வாகிக்கு உங்கள் Chromium சுயவிவரத்தின் கட்டுப்பாட்டை வழங்குகிறீர்கள். உங்கள் பயன்பாடுகள், புக்மார்க்குகள், வரலாறு, கடவுச்சொற்கள் போன்ற உங்கள் Chromium தரவு மற்றும் பிற அமைப்புகள் நிரந்தரமாக <ph name="USER_NAME" /> உடன் இணைக்கப்படும். இந்தத் தரவை Google கணக்குகளின் டாஷ்போர்டு வழியாக நீக்க முடியும், ஆனால் இந்தத் தரவை வேறொரு கணக்குடன் தொடர்புபடுத்த முடியாது. விரும்பினால், உங்களுடைய நடப்பு Chromium தரவைத் தனிப்பட்ட முறையில் வைத்திருக்க நீங்கள் புதிய சுயவிவரத்தை உருவாக்கலாம். <ph name="LEARN_MORE" /></translation>
-<translation id="1895626441344023878">{0,plural, =0{Chromium புதுப்பிப்பு உள்ளது}=1{Chromium புதுப்பிப்பு உள்ளது}other{Chromium புதுப்பிப்பு, # நாட்களுக்குக் கிடைக்கிறது}}</translation>
+<translation id="1895626441344023878">{0,plural, =0{Chromium புதுப்பிப்பு உள்ளது}=1{Chromium புதுப்பிப்பு உள்ளது}other{Chromium புதுப்பிப்பு, # நாட்களாக இருக்கிறது}}</translation>
 <translation id="1929939181775079593">Chromium பதிலளிக்கவில்லை. இப்போது மீண்டும் தொடங்கவா?</translation>
 <translation id="1966382378801805537">இயல்புநிலை உலாவியைக் கண்டறியவோ அமைக்கவோ Chromium ஆல் முடியவில்லை</translation>
 <translation id="2008474315282236005">வெளியேறினால், இந்தச் சாதனத்திலிருந்து ஓர் உருப்படி நீக்கப்படும். பின்னர் தரவை மீட்டமைக்க, Chromium இல் <ph name="USER_EMAIL" /> எனும் முகவரியின் மூலம் உள்நுழையவும்.</translation>
@@ -111,7 +111,7 @@
 <translation id="4943838377383847465">Chromium பின்புல பயன்முறையில் இயங்குகிறது.</translation>
 <translation id="4987820182225656817">எதையும் விட்டுசெல்லாமல் விருந்தினர்கள் Chromium ஐப் பயன்படுத்தலாம்.</translation>
 <translation id="4994636714258228724">உங்களை Chromium இல் சேர்க்கவும்</translation>
-<translation id="5021854341188256296">{0,plural, =0{Chromium OS புதுப்பிப்பு உள்ளது}=1{Chromium OS புதுப்பிப்பு உள்ளது}other{Chromium OS புதுப்பிப்பு, # நாட்களுக்குக் கிடைக்கிறது}}</translation>
+<translation id="5021854341188256296">{0,plural, =0{Chromium OS புதுப்பிப்பு உள்ளது}=1{Chromium OS புதுப்பிப்பு உள்ளது}other{Chromium OS புதுப்பிப்பு, # நாட்களாக இருக்கிறது}}</translation>
 <translation id="5032989939245619637">விவரங்களை Chromium இல் சேமி</translation>
 <translation id="5045248521775609809">Chromiumஐ எல்லா இடத்திற்கும் எடுத்துச் செல்லலாம்</translation>
 <translation id="5116586539350239523">Chromium உங்கள் தனிப்பட்ட விவரங்களைப் பாதுகாப்பாகச் சேமிக்கும் என்பதால் அவற்றை மீண்டும் உள்ளிட வேண்டியதில்லை.</translation>
@@ -124,7 +124,7 @@
 <translation id="5479196819031988440">Chromium OS ஆல் இந்தப் பக்கத்தைத் திறக்க முடியாது.</translation>
 <translation id="5480860683791598150">இந்தத் தளத்துடன் இருப்பிடத்தைப் பகிர, Chromiumக்கு உங்கள் இருப்பிடத்திற்கான அணுகல் தேவை</translation>
 <translation id="549669000822060376">சமீபத்திய முறைமை புதுப்பிப்புகளை Chromium நிறுவும் வரை காத்திருக்கவும்.</translation>
-<translation id="5514308096618405748">Chrome OS, Linuxஸை (பீட்டா) போலவே கூடுதல் <ph name="BEGIN_LINK_CROS_OSS" />ஓப்பன் சோர்ஸ் மென்பொருளால்<ph name="END_LINK_CROS_OSS" /> உருவாக்கப்பட்டுள்ளது.</translation>
+<translation id="5514308096618405748">Chrome OS, Linuxஸைப் (பீட்டா) போலவே கூடுதல் <ph name="BEGIN_LINK_CROS_OSS" />ஓப்பன் சோர்ஸ் மென்பொருளால்<ph name="END_LINK_CROS_OSS" /> உருவாக்கப்பட்டுள்ளது.</translation>
 <translation id="5529843986978123325">{0,plural, =1{1 நிமிடத்தில் Chromium OS மீண்டும் தொடங்கும்}other{# நிமிடங்களில் Chromium OS மீண்டும் தொடங்கும்}}</translation>
 <translation id="5631814766731275228">Chromium பெயர் மற்றும் படம்</translation>
 <translation id="5634636535844844681">Windows 7 அல்லது அதற்குப் பிந்தைய பதிப்புகளில் மட்டுமே Chromium இயங்கும்.</translation>
@@ -236,7 +236,7 @@
 <translation id="8290862415967981663">இந்தக் கோப்பு ஆபத்தானதாக இருக்கக்கூடும் என்பதால், அதை Chromium தடுத்துள்ளது.</translation>
 <translation id="8330519371938183845">உங்கள் எல்லாச் சாதனங்களிலும் Chromiumஐ ஒத்திசைக்க மற்றும் தனிப்பயனாக்க, உள்நுழையவும்</translation>
 <translation id="8340674089072921962"><ph name="USER_EMAIL_ADDRESS" /> ஏற்கனவே Chromiumஐப் பயன்படுத்திக் கொண்டிருந்தது</translation>
-<translation id="8375950122744241554">Chromium 70 பதிப்பு முதல், மேற்பார்வையிடப்படும் பயனர் சுயவிவரங்கள் கிடைக்காது.</translation>
+<translation id="8375950122744241554">Chromium 70 பதிப்பு முதல், மேற்பார்வையிடப்படும் பயனர் கணக்குகள் கிடைக்காது.</translation>
 <translation id="8379713241968949941">{0,plural, =1{ஒரு மணிநேரத்திற்குள் Chromium மீண்டும் தொடங்கும்}other{ # மணிநேரத்திற்குள் Chromium மீண்டும் தொடங்கும்}}</translation>
 <translation id="8453117565092476964">நிறுவி காப்பகம் சிதைந்துள்ளது அல்லது தவறானது. Chromium ஐ மீண்டும் பதிவிறக்கவும்.</translation>
 <translation id="8493179195440786826">Chromium காலாவதியானது</translation>
diff --git a/chrome/app/resources/chromium_strings_te.xtb b/chrome/app/resources/chromium_strings_te.xtb
index 37d8c50a..de6ed96 100644
--- a/chrome/app/resources/chromium_strings_te.xtb
+++ b/chrome/app/resources/chromium_strings_te.xtb
@@ -147,10 +147,12 @@
 <translation id="6394232988457703198">మీరు ఇప్పుడే Chromiumని పునఃప్రారంభించాలి.</translation>
 <translation id="6400072781405947421">Chromiumకి ఇప్పుడు Mac OS X 10.9లో మద్దతు లేనందున ఇది సరిగ్గా పని చేయకపోవచ్చు.</translation>
 <translation id="6403826409255603130">Chromium అనేది మెరుపు వేగంతో వెబ్‌పేజీలను మరియు అనువర్తనాలను అమలు చేసే వెబ్ బ్రౌజర్. ఇది వేగవంతమైనది, స్థిరమైనది మరియు ఉపయోగించడానికి సులభమైనది. Chromiumలో రూపొందించిన మాల్వేర్ మరియు ఫిషింగ్ రక్షణతో మరింత సురక్షితంగా వెబ్‌లో బ్రౌజ్ చేయండి.</translation>
+<translation id="641451971369018375">బ్రౌజింగ్ మరియు Chromiumను మెరుగుపరచడానికి Googleను సంప్రదిస్తుంది</translation>
 <translation id="6457450909262716557">{SECONDS,plural, =1{Chromium 1 సెకనులో పునఃప్రారంభమవుతుంది}other{Chromium # సెకన్లలో పునఃప్రారంభమవుతుంది}}</translation>
 <translation id="6475912303565314141">ఇది మీరు Chromiumని ప్రారంభించేటప్పుడు చూపబడే పేజీని కూడా నియంత్రిస్తుంది.</translation>
 <translation id="6485906693002546646">మీరు మీ Chromium అంశాలను సమకాలీకరించడానికి <ph name="PROFILE_EMAIL" />ని ఉపయోగిస్తున్నారు. మీ సమకాలీకరణ ప్రాధాన్యతను నవీకరించడానికి లేదా Google ఖాతా లేకుండా Chromiumని ఉపయోగించడానికి, <ph name="SETTINGS_LINK" />ను సందర్శించండి.</translation>
 <translation id="6510925080656968729">Chromiumని అన్ఇన్‌స్టాల్ చేయి</translation>
+<translation id="6570579332384693436">అక్షరక్రమ లోపాలను పరిష్కరించడానికి, Chrome మీరు వచన ఫీల్డ్‌లలో టైప్ చేసే వచనాన్ని Googleకి పంపుతుంది</translation>
 <translation id="6598877126913850652">Chromium నోటిఫికేషన్ సెట్టింగ్‌లకు వెళ్లు</translation>
 <translation id="6676384891291319759">ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి</translation>
 <translation id="6717134281241384636">మీ ప్రొఫైల్ క్రొత్త Chromium సంస్కరణ అయినందున ఇది ఉపయోగించబడదు.
diff --git a/chrome/app/resources/chromium_strings_th.xtb b/chrome/app/resources/chromium_strings_th.xtb
index 7c12c77aa..1dd5d20 100644
--- a/chrome/app/resources/chromium_strings_th.xtb
+++ b/chrome/app/resources/chromium_strings_th.xtb
@@ -8,7 +8,7 @@
 <translation id="1170115874949214249">ติดตั้ง Chromium ในโทรศัพท์ เราจะส่ง SMS ไปยังหมายเลขโทรศัพท์สำหรับการกู้คืนบัญชีของคุณ</translation>
 <translation id="1174473354587728743">หากต้องการแชร์คอมพิวเตอร์ ตอนนี้คุณสามารถตั้งค่า Chromium ในแบบที่คุณชอบได้แล้ว</translation>
 <translation id="1185134272377778587">เกี่ยวกับ Chromium</translation>
-<translation id="1209657686917656928">{0,plural, =0{Chromium กำลังจะเปิดขึ้นมาใหม่}=1{Chromium จะเปิดขึ้นมาใหม่ใน 1 วินาที}other{Chromium จะเปิดขึ้นมาใหม่ใน # วินาที}}</translation>
+<translation id="1209657686917656928">{0,plural, =0{Chromium จะเปิดขึ้นมาใหม่ตอนนี้}=1{Chromium จะเปิดขึ้นมาใหม่ใน 1 วินาที}other{Chromium จะเปิดขึ้นมาใหม่ใน # วินาที}}</translation>
 <translation id="1267419686153937460">{0,plural, =1{เปิด Chromium ขึ้นมาใหม่ภายใน 1 วัน}other{เปิด Chromium ขึ้นมาใหม่ภายใน # วัน}}</translation>
 <translation id="1298199220304005244">รับความช่วยเหลือเกี่ยวกับการใช้ Chromium OS</translation>
 <translation id="1396446129537741364">Chromium กำลังพยายามแสดงรหัสผ่าน</translation>
@@ -124,7 +124,7 @@
 <translation id="5479196819031988440">Chromium OS ไม่สามารถเปิดหน้านี้</translation>
 <translation id="5480860683791598150">Chromium ต้องการสิทธิ์เข้าถึงตำแหน่งของคุณเพื่อแชร์ตำแหน่งกับเว็บไซต์นี้</translation>
 <translation id="549669000822060376">โปรดรอขณะที่ Chromium ติดตั้งการอัปเดตระบบล่าสุด</translation>
-<translation id="5514308096618405748">Chrome OS เกิดขึ้นได้ด้วยการสนับสนุนจาก<ph name="BEGIN_LINK_CROS_OSS" />ซอฟต์แวร์โอเพนซอร์<ph name="END_LINK_CROS_OSS" />เพิ่มเติม ซึ่งก็คือ Linux (เบต้า)</translation>
+<translation id="5514308096618405748">Chrome OS เกิดขึ้นได้ด้วยการสนับสนุนจาก<ph name="BEGIN_LINK_CROS_OSS" />ซอฟต์แวร์โอเพนซอร์ส<ph name="END_LINK_CROS_OSS" />เพิ่มเติม เช่นเดียวกับ Linux (เบต้า)</translation>
 <translation id="5529843986978123325">{0,plural, =1{Chromium OS จะรีสตาร์ทใน 1 นาที}other{Chromium OS จะรีสตาร์ทใน # นาที}}</translation>
 <translation id="5631814766731275228">ชื่อและภาพโปรไฟล์ Chromium</translation>
 <translation id="5634636535844844681">Chromium ต้องใช้ Windows 7 ขึ้นไป</translation>
@@ -242,7 +242,7 @@
 <translation id="8493179195440786826">Chromium ล้าสมัย</translation>
 <translation id="85843667276690461">รับความช่วยเหลือเกี่ยวกับการใช้ Chromium</translation>
 <translation id="8586442755830160949">ลิขสิทธิ์ <ph name="YEAR" /> The Chromium Authors สงวนลิขสิทธิ์</translation>
-<translation id="8599548569518771270">{0,plural, =0{Chromium OS กำลังจะรีสตาร์ท}=1{Chromium OS จะรีสตาร์ทใน 1 วินาที}other{Chromium OS จะรีสตาร์ทใน # วินาที}}</translation>
+<translation id="8599548569518771270">{0,plural, =0{Chromium OS จะรีสตาร์ทตอนนี้}=1{Chromium OS จะรีสตาร์ทใน 1 วินาที}other{Chromium OS จะรีสตาร์ทใน # วินาที}}</translation>
 <translation id="8619360774459241877">กำลังเปิด Chromium...</translation>
 <translation id="8621669128220841554">การติดตั้งล้มเหลวเนื่องจากเกิดข้อผิดพลาดที่ไม่ระบุ โปรดดาวน์โหลด Chromium อีกครั้ง</translation>
 <translation id="8628626585870903697">Chromium ไม่มีโปรแกรมดู PDF ที่ต้องใช้ในการทำงานของหน้าตัวอย่างก่อนพิมพ์</translation>
diff --git a/chrome/app/resources/chromium_strings_tr.xtb b/chrome/app/resources/chromium_strings_tr.xtb
index dea1d22..3dd403d 100644
--- a/chrome/app/resources/chromium_strings_tr.xtb
+++ b/chrome/app/resources/chromium_strings_tr.xtb
@@ -128,7 +128,7 @@
 <translation id="5634636535844844681">Chromium, Windows 7 veya daha sonraki bir sürümü gerektirir.</translation>
 <translation id="5680901439334282664">Chromium'da oturum açın</translation>
 <translation id="5698481217667032250">Chromium'u bu dilde görüntüle</translation>
-<translation id="5712253116097046984">Yöneticiniz bu güncellemeyi uygulamak için Chromium OS'u yeniden başlatmanızı istiyor</translation>
+<translation id="5712253116097046984">Yöneticiniz bu güncellemeyi uygulamak için Chromium OS'i yeniden başlatmanızı istiyor</translation>
 <translation id="5726838626470692954">Yöneticinizin sizi Chromium'dan kaldırması ve daha sonra tekrar eklemesi gerekir.</translation>
 <translation id="5768914737813585044">Chromium OS'yi bu dilde görüntüle</translation>
 <translation id="5796460469508169315">Chromium neredeyse hazır.</translation>
@@ -147,7 +147,7 @@
 <translation id="6096348254544841612">Chromium'u özelleştirin ve kontrol edin. Güncelleme mevcut.</translation>
 <translation id="6120345080069858279">Chromium, şifrenizi Google Hesabınızda kaydedecek. Bu şekilde şifreyi hatırlamanız gerekmez.</translation>
 <translation id="6129621093834146363"><ph name="FILE_NAME" /> tehlikeli olduğu için Chromium tarafından engellendi.</translation>
-<translation id="620022061217911843">Yöneticiniz bu güncellemeyi uygulamak için Chromium OS'u yeniden başlatmanızı gerektiriyor</translation>
+<translation id="620022061217911843">Yöneticiniz bu güncellemeyi uygulamak için Chromium OS'i yeniden başlatmanızı gerektiriyor</translation>
 <translation id="6212496753309875659">Bu bilgisayarda Chromium'un daha yeni bir sürümü de var. Yazılım çalışmıyorsa, Chromium'u kaldırın ve tekrar deneyin.</translation>
 <translation id="6248213926982192922">Chromium varsayılan tarayıcı olsun</translation>
 <translation id="6268381023930128611">Chromium oturumunu kapatıyor musunuz?</translation>
@@ -201,10 +201,10 @@
 <ph name="BEGIN_LINK_2" />Daha fazla bilgi edinin<ph name="END_LINK_2" />
 
 Daha fazla talimat için lütfen e-postanızı (<ph name="ACCOUNT_EMAIL" />) kontrol edin.</translation>
-<translation id="7448255348454382571">Chromium OS'u yeniden başlatın</translation>
+<translation id="7448255348454382571">Chromium OS'i yeniden başlatın</translation>
 <translation id="7449453770951226939"><ph name="PAGE_TITLE" /> - Chromium Dev</translation>
 <translation id="7451052299415159299">Chromium'un bu sitede kameranıza erişmesi için izin gerekiyor</translation>
-<translation id="7471302858145901434">{0,plural, =1{Chromium OS'u bir gün içinde yeniden başlatın}other{Chromium OS'u # gün içinde yeniden başlatın}}</translation>
+<translation id="7471302858145901434">{0,plural, =1{Chromium OS'i bir gün içinde yeniden başlatın}other{Chromium OS'i # gün içinde yeniden başlatın}}</translation>
 <translation id="7483335560992089831">Şu anda çalışan aynı Chromium sürümünü yükleyemezsiniz. Lütfen Chromium'u kapatın ve yeniden deneyin.</translation>
 <translation id="7549178288319965365">Chromium OS hakkında</translation>
 <translation id="7577193603922410712">Chromium için özel bir güvenlik güncellemesi uygulandı. Güncellemenin geçerli olması için cihazı şimdi yeniden başlatmalısınız (sekmeleriniz geri yüklenecektir).</translation>
diff --git a/chrome/app/resources/chromium_strings_zh-TW.xtb b/chrome/app/resources/chromium_strings_zh-TW.xtb
index 80cc071..9aec59d 100644
--- a/chrome/app/resources/chromium_strings_zh-TW.xtb
+++ b/chrome/app/resources/chromium_strings_zh-TW.xtb
@@ -130,7 +130,7 @@
 <translation id="5634636535844844681">Chromium 僅支援 Windows 7 以上版本的作業系統。</translation>
 <translation id="5680901439334282664">登入 Chromium</translation>
 <translation id="5698481217667032250">將 Chromium 的介面文字設為這種語言</translation>
-<translation id="5712253116097046984">管理員請你重新啟動 Chromium 作業系統,以套用這項更新</translation>
+<translation id="5712253116097046984">你的管理員請你重新啟動 Chromium 作業系統,以套用這項更新</translation>
 <translation id="5726838626470692954">管理員必須先將你移除,再重新加入 Chromium 中。</translation>
 <translation id="5768914737813585044">將 Chromium 作業系統的介面文字設為這種語言</translation>
 <translation id="5796460469508169315">Chromium 即將準備就緒。</translation>
@@ -149,7 +149,7 @@
 <translation id="6096348254544841612">有可用的更新,歡迎自訂及控制 Chromium。</translation>
 <translation id="6120345080069858279">Chromium 會將這組密碼儲存至你的 Google 帳戶,你不須記住密碼。</translation>
 <translation id="6129621093834146363"><ph name="FILE_NAME" /> 並不安全,因此遭到 Chromium 封鎖。</translation>
-<translation id="620022061217911843">管理員要求你重新啟動 Chromium 作業系統,以套用更新</translation>
+<translation id="620022061217911843">你的管理員要求你重新啟動 Chromium 作業系統,以套用更新</translation>
 <translation id="6212496753309875659">這台電腦已安裝較新的 Chromium 版本。如果軟體無法順利運作,請解除安裝 Chromium,然後再試一次。</translation>
 <translation id="6248213926982192922">將 Chromium 設為預設瀏覽器</translation>
 <translation id="6268381023930128611">要登出 Chromium 嗎?</translation>
diff --git a/chrome/app/resources/generated_resources_am.xtb b/chrome/app/resources/generated_resources_am.xtb
index d053911c..1987744 100644
--- a/chrome/app/resources/generated_resources_am.xtb
+++ b/chrome/app/resources/generated_resources_am.xtb
@@ -4166,7 +4166,6 @@
 <translation id="7387829944233909572">«የአሰሳ ውሂብ አጽዳ» መገናኛ</translation>
 <translation id="7388044238629873883">በቃ ሊጨርሱ ነው!</translation>
 <translation id="7388222713940428051">የእንግዳ መስኮትን ክፈት</translation>
-<translation id="7391648274628927397">አንድ ዕልባት ለእርስዎ ታክሏል</translation>
 <translation id="7392118418926456391">የቫይረስ ቅኝት አልተሳካም</translation>
 <translation id="7392915005464253525">የተ&amp;ዘጋውን መስኮት ዳግም ክፈት</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> ስርዓት በሚነሳበት ጊዜ ይጀመርና ሁሉንም የሌሎች <ph name="PRODUCT_NAME" /> መስኮቶችን ከዘጉ በኋላም እንኳ በጀርባ ውስጥ መሄዱን ይቀጥላል።</translation>
diff --git a/chrome/app/resources/generated_resources_ar.xtb b/chrome/app/resources/generated_resources_ar.xtb
index ddc6f49d..f9b7baf 100644
--- a/chrome/app/resources/generated_resources_ar.xtb
+++ b/chrome/app/resources/generated_resources_ar.xtb
@@ -4164,7 +4164,6 @@
 <translation id="7387829944233909572">مربع حوار "محو بيانات التصفّح"</translation>
 <translation id="7388044238629873883">لقد أوشكت على الانتهاء.</translation>
 <translation id="7388222713940428051">فتح نافذة "ضيف"</translation>
-<translation id="7391648274628927397">تمت إضافة إشارة مرجعية</translation>
 <translation id="7392118418926456391">تعذّر فحص الفيروسات</translation>
 <translation id="7392915005464253525">إ&amp;عادة فتح النافذة المغلقة</translation>
 <translation id="7396845648024431313">سيتم تشغيل <ph name="APP_NAME" /> عند بدء تشغيل النظام وسيستمر تشغيله في الخلفية حتى بعد إغلاق كل نوافذ <ph name="PRODUCT_NAME" /> الأخرى.</translation>
diff --git a/chrome/app/resources/generated_resources_bg.xtb b/chrome/app/resources/generated_resources_bg.xtb
index 64c5699..dc96085 100644
--- a/chrome/app/resources/generated_resources_bg.xtb
+++ b/chrome/app/resources/generated_resources_bg.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Диалогов прозорец „Изчистване на данните за сърфирането“</translation>
 <translation id="7388044238629873883">Почти приключихте!</translation>
 <translation id="7388222713940428051">Отваряне на прозорец в режим на гост</translation>
-<translation id="7391648274628927397">Добавена бе отметка за вас</translation>
 <translation id="7392118418926456391">Сканирането за вируси не бе успешно</translation>
 <translation id="7392915005464253525">По&amp;вторно отваряне на затворен прозорец</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> ще се изпълнява при стартиране на системата и ще продължи да работи на заден план дори след като затворите всички други прозорци на <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_bn.xtb b/chrome/app/resources/generated_resources_bn.xtb
index 72551da6..e7042ffd6 100644
--- a/chrome/app/resources/generated_resources_bn.xtb
+++ b/chrome/app/resources/generated_resources_bn.xtb
@@ -4164,7 +4164,6 @@
 <translation id="7387829944233909572">"ব্রাউজিং ডেটা সাফ করুন" কথোপকথন</translation>
 <translation id="7388044238629873883">আপনি প্রায় সম্পন্ন করে ফেলেছেন!</translation>
 <translation id="7388222713940428051">গেস্ট উইন্ডো খুলুন</translation>
-<translation id="7391648274628927397">আপনার জন্য একটি বুকমার্ক যোগ করা হয়েছে</translation>
 <translation id="7392118418926456391">ভাইরাস স্ক্যান ব্যর্থ হয়েছে</translation>
 <translation id="7392915005464253525">বন্ধ হওয়া উইন্ডো পু&amp;নরায় খোলা</translation>
 <translation id="7396845648024431313">সিস্টেমের প্রারম্ভে <ph name="APP_NAME" /> শুরু হবে এবং এমনকি আপনি অন্য সব <ph name="PRODUCT_NAME" /> উইনডোকে বন্ধ করার পরেও পৃষ্টভূমিতে চালনার জন্য অবিরত রাখা হবে৷</translation>
diff --git a/chrome/app/resources/generated_resources_ca.xtb b/chrome/app/resources/generated_resources_ca.xtb
index a75accd0..7c086a3b 100644
--- a/chrome/app/resources/generated_resources_ca.xtb
+++ b/chrome/app/resources/generated_resources_ca.xtb
@@ -222,7 +222,7 @@
 <translation id="1316136264406804862">S'està cercant...</translation>
 <translation id="1316495628809031177">La sincronització està en pausa</translation>
 <translation id="1319979322914001937">Una aplicació que mostra un llista filtrada de les extensions de Chrome Web Store. Les extensions de la llista es poden instal·lar directament des de l'aplicació.</translation>
-<translation id="1322046419516468189">Consulta i gestiona les contrasenyes desades a <ph name="SAVED_PASSWORDS_STORE" /></translation>
+<translation id="1322046419516468189">Consulta i gestiona les contrasenyes desades al <ph name="SAVED_PASSWORDS_STORE" /></translation>
 <translation id="1326317727527857210">Inicieu la sessió a Chrome per accedir a les vostres pestanyes des dels altres dispositius que tingueu.</translation>
 <translation id="1327074568633507428">Impressora a Google Cloud Print</translation>
 <translation id="1327977588028644528">Passarel·la</translation>
@@ -466,7 +466,7 @@
 <translation id="1673137583248014546"><ph name="URL" /> vol veure la marca i el model de la teva clau de seguretat</translation>
 <translation id="167832068858235403">menys vol.</translation>
 <translation id="1679068421605151609">Eines per a desenvolupadors</translation>
-<translation id="1680849702532889074">S'ha produït un error en instal·lar l'aplicació Linux.</translation>
+<translation id="1680849702532889074">S'ha produït un error en instal·lar l'aplicació per a Linux.</translation>
 <translation id="16815041330799488">No permetis que els llocs web vegin el text i les imatges copiats al porta-retalls</translation>
 <translation id="1682548588986054654">Finestra d'incògnit nova</translation>
 <translation id="168715261339224929">Per accedir a les adreces d'interès des de tots els dispositius, activa la sincronització.</translation>
@@ -1194,7 +1194,7 @@
 <translation id="2775104091073479743">Edita les empremtes digitals</translation>
 <translation id="2776441542064982094">Sembla que no hi ha cap dispositiu disponible per registrar-lo a la xarxa. Si el vostre dispositiu està encès i connectat a Internet, proveu de registrar-lo seguint les instruccions del manual.</translation>
 <translation id="2781692009645368755">Google Pay</translation>
-<translation id="2782104745158847185">S'ha produït un error en instal·lar l'aplicació Linux</translation>
+<translation id="2782104745158847185">S'ha produït un error en instal·lar l'aplicació per a Linux</translation>
 <translation id="2783298271312924866">Baixat</translation>
 <translation id="2783321960289401138">Crea una drecera...</translation>
 <translation id="2783829359200813069">Selecciona els tipus d'encriptació</translation>
@@ -2106,7 +2106,7 @@
 <translation id="4192273449750167573">Revisa la configuració a la pantalla següent</translation>
 <translation id="4193154014135846272">Document de Google</translation>
 <translation id="4194570336751258953">Activa la funció de tocar per fer clic</translation>
-<translation id="4195249722193633765">Instal·la l'aplicació Linux (versió beta)</translation>
+<translation id="4195249722193633765">Instal·la l'aplicació per a Linux (versió beta)</translation>
 <translation id="4195643157523330669">Obre en una pestanya nova</translation>
 <translation id="4195814663415092787">Continua des d'on ho vaig deixar</translation>
 <translation id="4197674956721858839">Selecció de ZIP</translation>
@@ -2324,7 +2324,7 @@
 <translation id="4576541033847873020">Vincula un dispositiu Bluetooth</translation>
 <translation id="4579581181964204535">No es pot emetre <ph name="HOST_NAME" />.</translation>
 <translation id="4580526846085481512">Confirmes que vols suprimir  $1 elements?</translation>
-<translation id="4582497162516204941">Instal·la Linux (versió beta)</translation>
+<translation id="4582497162516204941">Instal·la amb Linux (versió beta)</translation>
 <translation id="4582563038311694664">Restableix tota la configuració</translation>
 <translation id="4585793705637313973">Edita la pàgina</translation>
 <translation id="4589268276914962177">Terminal nou</translation>
@@ -2796,7 +2796,7 @@
 <translation id="529175790091471945">Formata aquest dispositiu</translation>
 <translation id="5292195676005197571">Per fer servir la majoria de les claus, només cal prémer el botó</translation>
 <translation id="5293170712604732402">Restableix la configuració als valors predeterminats originals</translation>
-<translation id="5297082477358294722">S'ha desat la contrasenya. Consulta i gestiona les contrasenyes desades a <ph name="SAVED_PASSWORDS_STORE" />.</translation>
+<translation id="5297082477358294722">S'ha desat la contrasenya. Consulta i gestiona les contrasenyes desades al <ph name="SAVED_PASSWORDS_STORE" />.</translation>
 <translation id="5298219193514155779">Tema creat per</translation>
 <translation id="5299109548848736476">Opció de no seguiment</translation>
 <translation id="5299682071747318445">Totes les dades s'encripten amb la vostra frase de contrasenya de sincronització</translation>
@@ -3066,7 +3066,7 @@
 <translation id="5677503058916217575">Idioma de la pàgina:</translation>
 <translation id="5677928146339483299">Bloquejades</translation>
 <translation id="5678550637669481956">S'ha concedit accés de lectura i d'escriptura a <ph name="VOLUME_NAME" />.</translation>
-<translation id="5678784840044122290">L'aplicació Linux estarà disponible al terminal i és possible que també inclogui una icona al menú d'aplicacions.</translation>
+<translation id="5678784840044122290">L'aplicació per a Linux estarà disponible al terminal i és possible que també inclogui una icona al menú d'aplicacions.</translation>
 <translation id="5678955352098267522">Llegir les dades del lloc <ph name="WEBSITE_1" /></translation>
 <translation id="5684661240348539843">Identificador d'elements</translation>
 <translation id="5686799162999241776"><ph name="BEGIN_BOLD" />No es pot desconnectar d'un arxiu o d'un disc virtual<ph name="END_BOLD" />
@@ -3162,7 +3162,7 @@
 <translation id="5832805196449965646">Afegeix una persona</translation>
 <translation id="583281660410589416">Desconegut</translation>
 <translation id="5832976493438355584">Bloquejat</translation>
-<translation id="5833397272224757657">Per a la personalització, utilitza el contingut dels llocs web que visites, així com l'activitat i les interaccions del navegador</translation>
+<translation id="5833397272224757657">Utilitza el contingut dels llocs web que visites, així com l'activitat i les interaccions del navegador per oferir una experiència personalitzada.</translation>
 <translation id="5833610766403489739">Aquest fitxer s'ha extraviat en algun lloc. Comproveu el paràmetre d'ubicació de les baixades i torneu-ho a provar.</translation>
 <translation id="5833726373896279253">Només el propietari d'aquesta configuració la pot modificar:</translation>
 <translation id="5834581999798853053">Queda cosa de <ph name="TIME" /> minuts</translation>
@@ -3216,7 +3216,7 @@
 <translation id="5908769186679515905">Impedeix que els llocs executin Flash</translation>
 <translation id="5910363049092958439">De&amp;sa la imatge com a...</translation>
 <translation id="5911737117543891828">Els fitxers temporals sense connexió de Google Drive se suprimiran. Els fitxers que hagis definit com a disponibles sense connexió no se suprimiran del dispositiu.</translation>
-<translation id="5911887972742538906">S'ha produït un error en instal·lar l'aplicació Linux.</translation>
+<translation id="5911887972742538906">S'ha produït un error en instal·lar l'aplicació per a Linux.</translation>
 <translation id="5912378097832178659">&amp;Edita els motors de cerca...</translation>
 <translation id="5914724413750400082">Mòdul (<ph name="MODULUS_NUM_BITS" /> bits):
   <ph name="MODULUS_HEX_DUMP" />
@@ -4004,7 +4004,7 @@
 <translation id="7127980134843952133">Historial de baixades</translation>
 <translation id="7131040479572660648">Llegir les dades dels llocs <ph name="WEBSITE_1" />, <ph name="WEBSITE_2" /> i <ph name="WEBSITE_3" /></translation>
 <translation id="713122686776214250">Afegeix la pà&amp;gina...</translation>
-<translation id="7133578150266914903">L'administrador vol revertir el dispositiu a una versió anterior (<ph name="PROGRESS_PERCENT" />)</translation>
+<translation id="7133578150266914903">L'administrador està revertint el dispositiu a una versió anterior (<ph name="PROGRESS_PERCENT" />)</translation>
 <translation id="7134098520442464001">Fa el text més petit</translation>
 <translation id="7136694880210472378">Estableix com a predeterminat</translation>
 <translation id="7136984461011502314">Benvingut a <ph name="PRODUCT_NAME" /></translation>
@@ -4168,7 +4168,6 @@
 <translation id="7387829944233909572">Diàleg "Esborra les dades de navegació"</translation>
 <translation id="7388044238629873883">Gairebé heu acabat</translation>
 <translation id="7388222713940428051">Obre la finestra de convidat</translation>
-<translation id="7391648274628927397">S'ha afegit una adreça d'interès</translation>
 <translation id="7392118418926456391">S'ha produït un error en l'anàlisi antivirus</translation>
 <translation id="7392915005464253525">T&amp;orna a obrir la finestra tancada</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> s'iniciarà quan s'iniciï el sistema i continuarà executant-se en segon pla, fins i tot quan hàgiu tancat la resta de finestres de <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_cs.xtb b/chrome/app/resources/generated_resources_cs.xtb
index 12606cf6..7d44f9f 100644
--- a/chrome/app/resources/generated_resources_cs.xtb
+++ b/chrome/app/resources/generated_resources_cs.xtb
@@ -401,6 +401,7 @@
 <translation id="1589055389569595240">Zobrazit pravopis a gramatiku</translation>
 <translation id="1593594475886691512">Formátování...</translation>
 <translation id="159359590073980872">Mezipaměť obrázků</translation>
+<translation id="1593926297800505364">Uložit platební metodu</translation>
 <translation id="1598233202702788831">Administrátor aktualizace zakázal.</translation>
 <translation id="1600857548979126453">Získat přístup k ladicímu programu stránky na serveru</translation>
 <translation id="1601560923496285236">Použít</translation>
@@ -511,6 +512,7 @@
 <translation id="1744060673522309905">Zařízení do domény nelze přidat. Zkontrolujte, zda jste nepřekročili maximální počet zařízení, která můžete přidat.</translation>
 <translation id="1744108098763830590">stránka na pozadí</translation>
 <translation id="1745520510852184940">Vždy provádět tuto akci</translation>
+<translation id="1746417874336251387">Nabízet nové funkce, které využívají připojení telefonu k Chromebooku</translation>
 <translation id="174937106936716857">Celkový počet souborů</translation>
 <translation id="175196451752279553">Znovu ot&amp;evřít zavřenou kartu</translation>
 <translation id="1753905327828125965">Nejnavštěvovanější</translation>
@@ -641,8 +643,10 @@
 <translation id="1932098463447129402">Nikoli před</translation>
 <translation id="1933809209549026293">Připojte prosím myš nebo klávesnici. Pokud používáte zařízení Bluetooth, zkontrolujte, zda je připraveno ke spárování.</translation>
 <translation id="1936157145127842922">Zobrazit ve složce</translation>
+<translation id="1938351510777341717">Externí příkaz</translation>
 <translation id="1940546824932169984">Připojená zařízení</translation>
 <translation id="1942765061641586207">Rozlišení obrázku</translation>
+<translation id="1943097386230153518">Nainstalovat novou službu</translation>
 <translation id="1944921356641260203">Byla nalezena aktualizace</translation>
 <translation id="1951615167417147110">Přejít o 1 stránku nahoru</translation>
 <translation id="1954813140452229842">Při připojování sdílené složky došlo k chybě. Zkontrolujte identifikační údaje a zkuste to znovu.</translation>
@@ -1108,6 +1112,7 @@
 <translation id="2653659639078652383">Odeslat</translation>
 <translation id="265390580714150011">Hodnota pole</translation>
 <translation id="2654166010170466751">Povolit webům instalovat obslužné nástroje pro platby</translation>
+<translation id="2659381484350128933"><ph name="FOOTNOTE_POINTER" />Funkce se v různých zařízeních liší</translation>
 <translation id="2660779039299703961">Událost</translation>
 <translation id="266079277508604648">K tiskárně se nelze připojit. Zkontrolujte, zda je tiskárna zapnutá a připojená k Chromebooku přes Wi-Fi nebo USB.</translation>
 <translation id="2661146741306740526">16 : 9</translation>
@@ -1214,6 +1219,7 @@
 <translation id="2803375539583399270">Zadejte kód PIN</translation>
 <translation id="2805646850212350655">Systém souborů Microsoft EFS</translation>
 <translation id="2805756323405976993">Aplikace</translation>
+<translation id="2806891468525657116">Zkratka už existuje</translation>
 <translation id="2807517655263062534">Zde se zobrazují stažené soubory</translation>
 <translation id="2809586584051668049">a další (<ph name="NUMBER_ADDITIONAL_DISABLED" />)</translation>
 <translation id="281133045296806353">Ve stávající relaci prohlížeče bylo vytvořeno nové okno.</translation>
@@ -1598,6 +1604,7 @@
 <translation id="3428419049384081277">Jste přihlášeni!</translation>
 <translation id="3429275422858276529">Přidejte si tuto stránku do záložek, abyste ji později snáze našli</translation>
 <translation id="3429599832623003132">Počet položek: $1</translation>
+<translation id="3430342160185525240">Umožňuje Asistentovi zobrazovat vám oznámení.</translation>
 <translation id="3432227430032737297">Odstranit všechny zobrazené</translation>
 <translation id="3432757130254800023">Odesílat zvuk a video na displeje v místní síti</translation>
 <translation id="3432762828853624962">Shared Workers</translation>
@@ -1961,6 +1968,7 @@
 <translation id="3941565636838060942">Chcete-li skrýt přístup k tomuto programu, je třeba jej odinstalovat pomocí apletu <ph name="CONTROL_PANEL_APPLET_NAME" /> v ovládacích panelech.
 
  Chcete spustit aplet <ph name="CONTROL_PANEL_APPLET_NAME" />?</translation>
+<translation id="394183848452296464">Zkratku nelze vytvořit</translation>
 <translation id="3943582379552582368">&amp;Zpět</translation>
 <translation id="3943857333388298514">Vložit</translation>
 <translation id="3948116654032448504">&amp;Vyhledat obrázek pomocí vyhledávače <ph name="SEARCH_ENGINE" /></translation>
@@ -2268,6 +2276,7 @@
 <translation id="4481530544597605423">Nespárovaná zařízení</translation>
 <translation id="4482194545587547824">Google vaši historii procházení může používat k přizpůsobení Vyhledávání a dalších služeb Google.</translation>
 <translation id="4495419450179050807">Nezobrazovat na této stránce</translation>
+<translation id="4499718683476608392">Zapněte automatické vyplňování platebních karet a vyplňujte webové formuláře jedním kliknutím</translation>
 <translation id="4500114933761911433">Plugin <ph name="PLUGIN_NAME" /> selhal</translation>
 <translation id="450099669180426158">Ikona vykřičníku</translation>
 <translation id="4501530680793980440">Potvrdit odstranění</translation>
@@ -3111,6 +3120,7 @@
 <translation id="5752453871435543420">Zálohování Chrome OS do cloudu</translation>
 <translation id="5756163054456765343">C&amp;entrum nápovědy</translation>
 <translation id="5759728514498647443">Dokumenty odeslané k tisku prostřednictvím aplikace <ph name="APP_NAME" /> lze číst v aplikaci <ph name="APP_NAME" />.</translation>
+<translation id="5762172915276660232">Nastavení platebních karet</translation>
 <translation id="5763751966069581670">Nebyla nalezena žádná zařízení USB</translation>
 <translation id="5764483294734785780">Uložit &amp;zvuk jako...</translation>
 <translation id="57646104491463491">Datum úpravy</translation>
@@ -3182,6 +3192,7 @@
 <translation id="5855773610748894548">Jejda, došlo k chybě modulu zabezpečení</translation>
 <translation id="5856721540245522153">Aktivovat funkce ladění</translation>
 <translation id="5857090052475505287">Nová složka</translation>
+<translation id="585979798156957858">Externí klávesa Meta</translation>
 <translation id="5860033963881614850">Vypnuto</translation>
 <translation id="5860209693144823476">Karta 3</translation>
 <translation id="5860491529813859533">Zapnout</translation>
@@ -3742,6 +3753,7 @@
 <translation id="6710213216561001401">Předchozí</translation>
 <translation id="6718273304615422081">Komprimování...</translation>
 <translation id="671928215901716392">Zamknout obrazovku</translation>
+<translation id="6720847671508630642">Automaticky s Chromebookem sdílejte to nejlepší z Androidu. Připojte telefon, abyste z počítače mohli odesílat textové zprávy, sdílet připojení telefonu k internetu a odemykat obrazovku Chromebooku.<ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />Další informace<ph name="LINK_END" /></translation>
 <translation id="6721678857435001674">Zobrazení modelu a značky vašeho bezpečnostního klíče</translation>
 <translation id="6721972322305477112">&amp;Soubor</translation>
 <translation id="672213144943476270">Před zahájením procházení v režimu hosta prosím odemkněte svůj profil.</translation>
@@ -3888,6 +3900,7 @@
 <translation id="6970856801391541997">Tisknout konkrétní stránky</translation>
 <translation id="6972180789171089114">Zvuk/video</translation>
 <translation id="6973630695168034713">Složky</translation>
+<translation id="6974609594866392343">Ukázkový režim offline</translation>
 <translation id="6976108581241006975">Konzole JavaScriptu</translation>
 <translation id="6977381486153291903">Revize firmwaru</translation>
 <translation id="6978121630131642226">Vyhledávače</translation>
@@ -4030,6 +4043,7 @@
 <translation id="7175353351958621980">Načteno z:</translation>
 <translation id="7180611975245234373">Obnovit</translation>
 <translation id="7180865173735832675">Personalizovat</translation>
+<translation id="7182359331070524176">Vyberte album ve Fotkách Google</translation>
 <translation id="7186088072322679094">Ponechat na liště</translation>
 <translation id="7187428571767585875">Záznamy v registru, které budou odstraněny nebo změněny:</translation>
 <translation id="7189234443051076392">V zařízení musí být dost místa</translation>
@@ -4164,7 +4178,6 @@
 <translation id="7387829944233909572">Dialogové okno Vymazat údaje o prohlížení</translation>
 <translation id="7388044238629873883">Už jste skoro hotovi!</translation>
 <translation id="7388222713940428051">Otevřít okno hosta</translation>
-<translation id="7391648274628927397">Byla vám přidána záložka</translation>
 <translation id="7392118418926456391">Při vyhledávání virů došlo k chybě</translation>
 <translation id="7392915005464253525">Znovu ot&amp;evřít zavřené okno</translation>
 <translation id="7396845648024431313">Aplikace <ph name="APP_NAME" /> se spustí při zapnutí systému a poběží na pozadí i v případě, že zavřete všechna okna prohlížeče <ph name="PRODUCT_NAME" />.</translation>
@@ -4480,6 +4493,7 @@
 <translation id="7857949311770343000">Je toto stránka nové karty, kterou jste očekávali?</translation>
 <translation id="786073089922909430">Služba: <ph name="ARC_PROCESS_NAME" /></translation>
 <translation id="7861215335140947162">&amp;Stažené soubory</translation>
+<translation id="7864662577698025113">Přidat novou službu</translation>
 <translation id="7868378670806575181">{NUM_COOKIES,plural, =1{1 soubor cookie}few{# soubory cookie}many{# souboru cookie}other{# souborů cookie}}</translation>
 <translation id="786957569166715433"><ph name="DEVICE_NAME" /> – spárováno</translation>
 <translation id="7870730066603611552">Po nastavení zkontrolovat možnosti synchronizace</translation>
@@ -4994,6 +5008,7 @@
 <translation id="8666584013686199826">Zeptat se, když chce web získat přístup k zařízením USB</translation>
 <translation id="8667328578593601900">Stránka <ph name="FULLSCREEN_ORIGIN" /> je teď na celé obrazovce a deaktivovala ukazatel myši.</translation>
 <translation id="8669284339312441707">Teplejší</translation>
+<translation id="8669919703154928649">Umožnit Asistentovi zobrazovat vám oznámení</translation>
 <translation id="8669949407341943408">Přesouvání...</translation>
 <translation id="8671210955687109937">Může přidávat komentáře</translation>
 <translation id="8673026256276578048">Vyhledávání na webu...</translation>
@@ -5381,6 +5396,7 @@
 <translation id="964286338916298286">Váš správce IT pro toto zařízení využití promoakcí Chrome Goodies zakázal.</translation>
 <translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{Aplikace}few{Aplikace}many{Aplikace}other{Aplikace}}</translation>
 <translation id="967007123645306417">Tímto se odhlásíte ze svých účtů Google. Změny záložek, historie, hesel a dalších nastavení se již nebudou synchronizovat do účtu Google. Vaše existující data v účtu Google zůstanou uložena a lze je spravovat na <ph name="BEGIN_LINK" />Hlavním panelu Google<ph name="END_LINK" />.</translation>
+<translation id="967624055006145463">Uložená data</translation>
 <translation id="968000525894980488">Zapněte Služby Google Play.</translation>
 <translation id="968174221497644223">Mezipaměť aplikace</translation>
 <translation id="969096075394517431">Změnit jazyky</translation>
diff --git a/chrome/app/resources/generated_resources_da.xtb b/chrome/app/resources/generated_resources_da.xtb
index f43938bf..b0d92e6 100644
--- a/chrome/app/resources/generated_resources_da.xtb
+++ b/chrome/app/resources/generated_resources_da.xtb
@@ -4167,7 +4167,6 @@
 <translation id="7387829944233909572">Dialogboksen "Ryd browserdata"</translation>
 <translation id="7388044238629873883">Du er næsten færdig.</translation>
 <translation id="7388222713940428051">Åbn gæstevindue</translation>
-<translation id="7391648274628927397">Der blev tilføjet et bogmærke til dig</translation>
 <translation id="7392118418926456391">Virusscanningen mislykkedes</translation>
 <translation id="7392915005464253525">G&amp;enåbn lukkede faner</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> vil starte ved opstart af systemet og fortsætte med at køre i baggrunden, selv når du har lukket alle andre vinduer i <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_de.xtb b/chrome/app/resources/generated_resources_de.xtb
index e53e217..e2a2e093 100644
--- a/chrome/app/resources/generated_resources_de.xtb
+++ b/chrome/app/resources/generated_resources_de.xtb
@@ -4164,7 +4164,6 @@
 <translation id="7387829944233909572">Dialog "Browserdaten löschen"</translation>
 <translation id="7388044238629873883">Sie haben es fast geschafft.</translation>
 <translation id="7388222713940428051">Gastfenster öffnen</translation>
-<translation id="7391648274628927397">Ein Lesezeichen wurde für Sie hinzugefügt</translation>
 <translation id="7392118418926456391">Fehler beim Virenscan</translation>
 <translation id="7392915005464253525">Geschlossenes Fenster wieder öffn&amp;en</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> wird beim Systemstart gestartet und auch dann weiter im Hintergrund ausgeführt, wenn Sie alle anderen <ph name="PRODUCT_NAME" />-Fenster geschlossen haben.</translation>
diff --git a/chrome/app/resources/generated_resources_el.xtb b/chrome/app/resources/generated_resources_el.xtb
index b70bb91..5745243 100644
--- a/chrome/app/resources/generated_resources_el.xtb
+++ b/chrome/app/resources/generated_resources_el.xtb
@@ -4035,6 +4035,7 @@
 <translation id="7175353351958621980">Έγινε φόρτωση από:</translation>
 <translation id="7180611975245234373">Ανανέωση</translation>
 <translation id="7180865173735832675">Προσαρμογή</translation>
+<translation id="7182359331070524176">Επιλέξτε ένα λεύκωμα Φωτογραφιών Google</translation>
 <translation id="7186088072322679094">Διατήρηση στη γραμμή εργαλείων</translation>
 <translation id="7187428571767585875">Καταχωρίσεις μητρώου προς κατάργηση ή αλλαγή:</translation>
 <translation id="7189234443051076392">Βεβαιωθείτε ότι υπάρχει αρκετός αποθηκευτικός χώρος στη συσκευή σας</translation>
@@ -4169,7 +4170,6 @@
 <translation id="7387829944233909572">Παράθυρο διαλόγου "Διαγραφή δεδομένων περιήγησης"</translation>
 <translation id="7388044238629873883">Σχεδόν τελειώσατε!</translation>
 <translation id="7388222713940428051">Άνοιγμα παραθύρου περιήγησης επισκέπτη</translation>
-<translation id="7391648274628927397">Προστέθηκε ένας σελιδοδείκτης για εσάς</translation>
 <translation id="7392118418926456391">Απέτυχε η σάρωση για ιούς</translation>
 <translation id="7392915005464253525">Ε&amp;παναφορά παραθύρου που έχει κλείσει</translation>
 <translation id="7396845648024431313">Η εφαρμογή <ph name="APP_NAME" /> θα εκκινείται κατά την εκκίνηση του συστήματος και θα συνεχίζει να εκτελείται στο παρασκήνιο αφού πρώτα κλείσετε όλα τα υπόλοιπα παράθυρα του <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_en-GB.xtb b/chrome/app/resources/generated_resources_en-GB.xtb
index 8768f6f9..4aeea52 100644
--- a/chrome/app/resources/generated_resources_en-GB.xtb
+++ b/chrome/app/resources/generated_resources_en-GB.xtb
@@ -401,6 +401,7 @@
 <translation id="1589055389569595240">Show Spelling and Grammar</translation>
 <translation id="1593594475886691512">Formatting...</translation>
 <translation id="159359590073980872">Image Cache</translation>
+<translation id="1593926297800505364">Save payment method</translation>
 <translation id="1598233202702788831">Updates are disabled by your administrator.</translation>
 <translation id="1600857548979126453">Access the page debugger backend</translation>
 <translation id="1601560923496285236">Apply</translation>
@@ -511,6 +512,7 @@
 <translation id="1744060673522309905">Can't join the device to the domain. Make sure that you haven’t exceeded the number of devices that you can add.</translation>
 <translation id="1744108098763830590">background page</translation>
 <translation id="1745520510852184940">Always Do This</translation>
+<translation id="1746417874336251387">Offer new features that use your phone's connection to your Chromebook</translation>
 <translation id="174937106936716857">Total file count</translation>
 <translation id="175196451752279553">R&amp;eopen closed tab</translation>
 <translation id="1753905327828125965">Most Visited</translation>
@@ -641,8 +643,10 @@
 <translation id="1932098463447129402">Not Before</translation>
 <translation id="1933809209549026293">Please connect a mouse or a keyboard. If you are using a Bluetooth device, make sure that it is ready to pair.</translation>
 <translation id="1936157145127842922">Show in Folder</translation>
+<translation id="1938351510777341717">External Command</translation>
 <translation id="1940546824932169984">Connected devices</translation>
 <translation id="1942765061641586207">Image resolution</translation>
+<translation id="1943097386230153518">Install new service</translation>
 <translation id="1944921356641260203">Update found</translation>
 <translation id="1951615167417147110">Scroll up one page</translation>
 <translation id="1954813140452229842">Error mounting share. Please check your credentials and try again.</translation>
@@ -1107,6 +1111,7 @@
 <translation id="2653659639078652383">Submit</translation>
 <translation id="265390580714150011">Field Value</translation>
 <translation id="2654166010170466751">Allow sites to install payment handlers</translation>
+<translation id="2659381484350128933"><ph name="FOOTNOTE_POINTER" />Features vary by device</translation>
 <translation id="2660779039299703961">Event</translation>
 <translation id="266079277508604648">Can’t connect to printer. Check that the printer is turned on and is connected to your Chromebook by Wi-Fi or USB.</translation>
 <translation id="2661146741306740526">16 x 9</translation>
@@ -1213,6 +1218,7 @@
 <translation id="2803375539583399270">Enter PIN</translation>
 <translation id="2805646850212350655">Microsoft Encrypting File System</translation>
 <translation id="2805756323405976993">Apps</translation>
+<translation id="2806891468525657116">Shortcut already exists</translation>
 <translation id="2807517655263062534">Files you download appear here</translation>
 <translation id="2809586584051668049">and <ph name="NUMBER_ADDITIONAL_DISABLED" /> more</translation>
 <translation id="281133045296806353">Created new window in existing browser session.</translation>
@@ -1599,6 +1605,7 @@
 <translation id="3428419049384081277">You're signed in!</translation>
 <translation id="3429275422858276529">Bookmark this page to easily find it later</translation>
 <translation id="3429599832623003132">$1 items</translation>
+<translation id="3430342160185525240">Enables the Assistant to show you notifications.</translation>
 <translation id="3432227430032737297">Remove all shown</translation>
 <translation id="3432757130254800023">Send audio and video to displays on the local network</translation>
 <translation id="3432762828853624962">Shared Workers</translation>
@@ -1963,6 +1970,7 @@
   <ph name="CONTROL_PANEL_APPLET_NAME" /> in Control Panel.
 
   Would you like to start <ph name="CONTROL_PANEL_APPLET_NAME" />?</translation>
+<translation id="394183848452296464">Can’t create shortcut</translation>
 <translation id="3943582379552582368">&amp;Back</translation>
 <translation id="3943857333388298514">Paste</translation>
 <translation id="3948116654032448504">&amp;Search <ph name="SEARCH_ENGINE" /> for Image</translation>
@@ -2270,6 +2278,7 @@
 <translation id="4481530544597605423">Unpaired devices</translation>
 <translation id="4482194545587547824">Google may use your browsing history to personalise Search and other Google services</translation>
 <translation id="4495419450179050807">Don't show on this page</translation>
+<translation id="4499718683476608392">Enable credit card Auto-fill to fill in forms in a single click</translation>
 <translation id="4500114933761911433"><ph name="PLUGIN_NAME" /> has crashed</translation>
 <translation id="450099669180426158">Exclamation mark icon</translation>
 <translation id="4501530680793980440">Confirm Removal</translation>
@@ -3114,6 +3123,7 @@
 <translation id="5752453871435543420">Chrome OS Cloud backup</translation>
 <translation id="5756163054456765343">Help centre</translation>
 <translation id="5759728514498647443">Documents that you send to print via <ph name="APP_NAME" /> can be read by <ph name="APP_NAME" />.</translation>
+<translation id="5762172915276660232">Credit card settings</translation>
 <translation id="5763751966069581670">No USB devices found</translation>
 <translation id="5764483294734785780">Sa&amp;ve audio as...</translation>
 <translation id="57646104491463491">Date Modified</translation>
@@ -3185,6 +3195,7 @@
 <translation id="5855773610748894548">Oops, secure module error.</translation>
 <translation id="5856721540245522153">Enable debugging features</translation>
 <translation id="5857090052475505287">New Folder</translation>
+<translation id="585979798156957858">External Meta</translation>
 <translation id="5860033963881614850">Off</translation>
 <translation id="5860209693144823476">Tab 3</translation>
 <translation id="5860491529813859533">Turn on</translation>
@@ -3745,6 +3756,7 @@
 <translation id="6710213216561001401">Previous</translation>
 <translation id="6718273304615422081">Zipping...</translation>
 <translation id="671928215901716392">Lock screen</translation>
+<translation id="6720847671508630642">Share the best of Android with your Chromebook automatically. Connect your phone so that you can text from your computer, share your phone's Internet connection and unlock your Chromebook screen.<ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />Find out more<ph name="LINK_END" /></translation>
 <translation id="6721678857435001674">See the make and model of your Security Key</translation>
 <translation id="6721972322305477112">&amp;File</translation>
 <translation id="672213144943476270">Please unlock your profile before browsing as a guest.</translation>
@@ -3891,6 +3903,7 @@
 <translation id="6970856801391541997">Print Specific Pages</translation>
 <translation id="6972180789171089114">Audio/Video</translation>
 <translation id="6973630695168034713">Folders</translation>
+<translation id="6974609594866392343">Offline demo mode</translation>
 <translation id="6976108581241006975">JavaScript console</translation>
 <translation id="6977381486153291903">Firmware revision</translation>
 <translation id="6978121630131642226">Search Engines</translation>
@@ -4168,7 +4181,6 @@
 <translation id="7387829944233909572">"Clear browsing data" dialogue</translation>
 <translation id="7388044238629873883">You've almost finished!</translation>
 <translation id="7388222713940428051">Open Guest window</translation>
-<translation id="7391648274628927397">A bookmark has been added for you</translation>
 <translation id="7392118418926456391">Virus scan failed</translation>
 <translation id="7392915005464253525">R&amp;eopen closed window</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> will launch at system startup and continue to run in the background, even once you've closed all other <ph name="PRODUCT_NAME" /> windows.</translation>
@@ -4486,6 +4498,7 @@
 <translation id="7857949311770343000">Is this the new tab page that you were expecting?</translation>
 <translation id="786073089922909430">Service: <ph name="ARC_PROCESS_NAME" /></translation>
 <translation id="7861215335140947162">&amp;Downloads</translation>
+<translation id="7864662577698025113">Add new service</translation>
 <translation id="7868378670806575181">{NUM_COOKIES,plural, =1{1 cookie}other{# cookies}}</translation>
 <translation id="786957569166715433"><ph name="DEVICE_NAME" /> – Paired</translation>
 <translation id="7870730066603611552">Review sync options following setup</translation>
@@ -5000,6 +5013,7 @@
 <translation id="8666584013686199826">Ask when a site wants to access USB devices</translation>
 <translation id="8667328578593601900"><ph name="FULLSCREEN_ORIGIN" /> is now full screen and has disabled your mouse cursor.</translation>
 <translation id="8669284339312441707">Warmer</translation>
+<translation id="8669919703154928649">Let Assistant show you notifications</translation>
 <translation id="8669949407341943408">Moving...</translation>
 <translation id="8671210955687109937">Can comment</translation>
 <translation id="8673026256276578048">Search the web...</translation>
@@ -5387,6 +5401,7 @@
 <translation id="964286338916298286">Your IT administrator has disabled Chrome Goodies for your device.</translation>
 <translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{Application}other{Applications}}</translation>
 <translation id="967007123645306417">This will sign you out of your Google accounts. Changes to your bookmarks, history, passwords and other settings will no longer be synced to your Google account. However, your existing data will remain stored in your Google account and can be managed on <ph name="BEGIN_LINK" />Google Dashboard<ph name="END_LINK" />.</translation>
+<translation id="967624055006145463">Data stored</translation>
 <translation id="968000525894980488">Turn on Google Play services.</translation>
 <translation id="968174221497644223">Application cache</translation>
 <translation id="969096075394517431">Change languages</translation>
diff --git a/chrome/app/resources/generated_resources_es-419.xtb b/chrome/app/resources/generated_resources_es-419.xtb
index ad6ced3..6b0339cd 100644
--- a/chrome/app/resources/generated_resources_es-419.xtb
+++ b/chrome/app/resources/generated_resources_es-419.xtb
@@ -788,7 +788,7 @@
 <translation id="2178098616815594724">El complemento <ph name="PEPPER_PLUGIN_NAME" /> del dominio <ph name="PEPPER_PLUGIN_DOMAIN" /> desea acceder a tu computadora</translation>
 <translation id="2178614541317717477">Compromiso de entidad de certificación</translation>
 <translation id="218070003709087997">Usa un número para indicar la cantidad de copias que deseas imprimir (1 a 999).</translation>
-<translation id="2183558561014688873">Accede a tu Asistente cada vez que digas "OK Google" cuando tu dispositivo está activo y desbloqueado.</translation>
+<translation id="2183558561014688873">Accede a tu Asistente cada vez que digas "OK Google" cuando tu dispositivo esté activo y desbloqueado.</translation>
 <translation id="2187895286714876935">Error en el servidor de importación de certificado</translation>
 <translation id="2187906491731510095">Se actualizaron las extensiones</translation>
 <translation id="2188881192257509750">Abrir <ph name="APPLICATION" /></translation>
@@ -1914,7 +1914,7 @@
 <translation id="3871092408932389764">Muy bajo</translation>
 <translation id="3872220884670338524">Más acciones (cuenta guardada para <ph name="USERNAME" /> en <ph name="DOMAIN" />)</translation>
 <translation id="3872991219937722530">Libera espacio en el disco o el dispositivo dejará de funcionar.</translation>
-<translation id="3873315167136380065">Para activar esta opción, <ph name="BEGIN_LINK" />restablece la sincronización<ph name="END_LINK" /> para quitar la frase de contraseña de sincronización</translation>
+<translation id="3873315167136380065">A fin de activar esta opción, <ph name="BEGIN_LINK" />restablece la sincronización<ph name="END_LINK" /> para quitar la frase de contraseña de sincronización</translation>
 <translation id="3878840326289104869">Creando usuario supervisado</translation>
 <translation id="3879748587602334249">Administrador de descargas</translation>
 <translation id="3880709822663530586">La llave de seguridad solo funciona cuando está activada la conexión Bluetooth de tu dispositivo</translation>
@@ -4165,7 +4165,6 @@
 <translation id="7387829944233909572">Diálogo "Eliminar datos de navegación"</translation>
 <translation id="7388044238629873883">Falta muy poco</translation>
 <translation id="7388222713940428051">Abrir una ventana como invitado</translation>
-<translation id="7391648274628927397">Se agregó un favorito para ti</translation>
 <translation id="7392118418926456391">Error en el análisis antivirus</translation>
 <translation id="7392915005464253525">Volver a abrir una ventana cerrada</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> empezará a ejecutarse cuando se inicie el sistema y seguirá ejecutándose en segundo plano aunque cierres todas las demás ventanas de <ph name="PRODUCT_NAME" />.</translation>
@@ -5019,7 +5018,7 @@
 <translation id="8698464937041809063">Dibujo de Google</translation>
 <translation id="869884720829132584">Menú Aplicaciones</translation>
 <translation id="869891660844655955">Fecha de vencimiento</translation>
-<translation id="8699566574894671540">Para activar esta opción, primero selecciona Comprobar ortografía mientras se escribe en el menú Editar</translation>
+<translation id="8699566574894671540">Para activar esta opción, primero selecciona Comprobar ortografía mientras se escribe, en el menú Editar</translation>
 <translation id="870073306461175568">Network File Shares</translation>
 <translation id="8701677791353449257">El nombre del dispositivo debe coincidir con la expresión regular <ph name="REGEX" />.</translation>
 <translation id="8704521619148782536">Esta acción está tardando más de lo normal. Puedes seguir esperando o cancelar la acción y volver a intentarlo más tarde.</translation>
diff --git a/chrome/app/resources/generated_resources_es.xtb b/chrome/app/resources/generated_resources_es.xtb
index 6bb90213..f263ce3 100644
--- a/chrome/app/resources/generated_resources_es.xtb
+++ b/chrome/app/resources/generated_resources_es.xtb
@@ -4000,7 +4000,7 @@
 <translation id="7127980134843952133">Historial de descargas</translation>
 <translation id="7131040479572660648">Leer tus datos en <ph name="WEBSITE_1" />, <ph name="WEBSITE_2" /> y <ph name="WEBSITE_3" /></translation>
 <translation id="713122686776214250">Añadir pá&amp;gina...</translation>
-<translation id="7133578150266914903">El administrador va a instalar una versión anterior en este dispositivo (<ph name="PROGRESS_PERCENT" />)</translation>
+<translation id="7133578150266914903">Tu administrador va a instalar una versión anterior en este dispositivo (<ph name="PROGRESS_PERCENT" />)</translation>
 <translation id="7134098520442464001">Reducir el tamaño del texto</translation>
 <translation id="7136694880210472378">Establecer como predeterminado</translation>
 <translation id="7136984461011502314">Te damos la bienvenida a <ph name="PRODUCT_NAME" /></translation>
@@ -4164,7 +4164,6 @@
 <translation id="7387829944233909572">Diálogo "Borrar datos de navegación"</translation>
 <translation id="7388044238629873883">Casi has terminado</translation>
 <translation id="7388222713940428051">Abrir ventana de invitado</translation>
-<translation id="7391648274628927397">Se ha añadido un marcador</translation>
 <translation id="7392118418926456391">Error en el análisis antivirus</translation>
 <translation id="7392915005464253525">Volv&amp;er a abrir ventana cerrada</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> empezará a ejecutarse cuando se inicie el sistema y seguirá ejecutándose en segundo plano aunque cierres todas las demás ventanas de <ph name="PRODUCT_NAME" />.</translation>
@@ -5213,7 +5212,7 @@
 <translation id="9009369504041480176">Subiendo (<ph name="PROGRESS_PERCENT" />%)...</translation>
 <translation id="9011163749350026987">Mostrar siempre el icono</translation>
 <translation id="9011178328451474963">Última pestaña</translation>
-<translation id="9013707997379828817">El administrador ha instalado una versión anterior en este dispositivo. Guarda los archivos importantes y, a continuación, reinícialo. Se eliminarán todos los datos del dispositivo.</translation>
+<translation id="9013707997379828817">Tu administrador ha instalado una versión anterior en este dispositivo. Guarda los archivos importantes y, a continuación, reinícialo. Se eliminarán todos los datos del dispositivo.</translation>
 <translation id="9014987600015527693">Mostrar otro teléfono</translation>
 <translation id="9018218886431812662">Instalación completada</translation>
 <translation id="901834265349196618">correo electrónico</translation>
diff --git a/chrome/app/resources/generated_resources_et.xtb b/chrome/app/resources/generated_resources_et.xtb
index ad00022..1fb9b70 100644
--- a/chrome/app/resources/generated_resources_et.xtb
+++ b/chrome/app/resources/generated_resources_et.xtb
@@ -4168,7 +4168,6 @@
 <translation id="7387829944233909572">Dialoog „Tühjenda sirvimisandmed”</translation>
 <translation id="7388044238629873883">Olete peaaegu valmis.</translation>
 <translation id="7388222713940428051">Ava külalise aken</translation>
-<translation id="7391648274628927397">Järjehoidja on lisatud</translation>
 <translation id="7392118418926456391">Viiruste skannimine ebaõnnestus</translation>
 <translation id="7392915005464253525">A&amp;va suletud aken uuesti</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> käivitub süsteemi käivitamisel ja jätkab tööd taustal ka siis, kui sulgete kõik muud rakenduse <ph name="PRODUCT_NAME" /> aknad.</translation>
diff --git a/chrome/app/resources/generated_resources_fa.xtb b/chrome/app/resources/generated_resources_fa.xtb
index 2726eab..cc32251 100644
--- a/chrome/app/resources/generated_resources_fa.xtb
+++ b/chrome/app/resources/generated_resources_fa.xtb
@@ -4030,6 +4030,7 @@
 <translation id="7175353351958621980">بارگیری شده از:</translation>
 <translation id="7180611975245234373">بازخوانی</translation>
 <translation id="7180865173735832675">سفارشی کردن</translation>
+<translation id="7182359331070524176">‏انتخاب آلبوم Google Photos</translation>
 <translation id="7186088072322679094">نگه داشتن در نوار ابزار</translation>
 <translation id="7187428571767585875">ورودی‌های رجیستری برای برداشتن یا تغییر دادن:</translation>
 <translation id="7189234443051076392">مطمئن شوید که در دستگاهتان فضای کافی وجود داشته باشد</translation>
@@ -4164,7 +4165,6 @@
 <translation id="7387829944233909572">کادر گفتگوی "پاک کردن داده مرور"</translation>
 <translation id="7388044238629873883">شما تقریباً آماده‌اید!</translation>
 <translation id="7388222713940428051">باز کردن پنجره مهمان</translation>
-<translation id="7391648274628927397">نشانک برای شما اضافه شد</translation>
 <translation id="7392118418926456391">اسکن ویروس انجام نشد</translation>
 <translation id="7392915005464253525">با&amp;ز کردن مجدد پنجره بسته</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> در هنگام راه‌اندازی سیستم شروع به کار می‌کند و در پس‌زمینه همچنان فعال خواهد بود، حتی زمانی که تمام پنجره‌های دیگر <ph name="PRODUCT_NAME" /> را ببندید.</translation>
diff --git a/chrome/app/resources/generated_resources_fi.xtb b/chrome/app/resources/generated_resources_fi.xtb
index e06a7a5..7fef625 100644
--- a/chrome/app/resources/generated_resources_fi.xtb
+++ b/chrome/app/resources/generated_resources_fi.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Tyhjennä selaustiedot -ikkuna</translation>
 <translation id="7388044238629873883">Melkein valmista.</translation>
 <translation id="7388222713940428051">Avaa vierasikkuna</translation>
-<translation id="7391648274628927397">Kirjanmerkki on lisätty</translation>
 <translation id="7392118418926456391">Virustarkistus epäonnistui</translation>
 <translation id="7392915005464253525">A&amp;vaa suljettu ikkuna uudelleen</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> käynnistyy tietokoneen käynnistyksen yhteydessä ja on käynnissä taustalla vielä kaikkien <ph name="PRODUCT_NAME" /> -ikkunoiden sulkemisen jälkeen.</translation>
diff --git a/chrome/app/resources/generated_resources_fil.xtb b/chrome/app/resources/generated_resources_fil.xtb
index 0127367..27fac8b6 100644
--- a/chrome/app/resources/generated_resources_fil.xtb
+++ b/chrome/app/resources/generated_resources_fil.xtb
@@ -4034,6 +4034,7 @@
 <translation id="7175353351958621980">Nag-load mula sa:</translation>
 <translation id="7180611975245234373">I-refresh</translation>
 <translation id="7180865173735832675">I-customize</translation>
+<translation id="7182359331070524176">Pumili ng album sa Google Photos</translation>
 <translation id="7186088072322679094">Panatilihin sa Toolbar</translation>
 <translation id="7187428571767585875">Mga aalisin o papalitang entry sa registry:</translation>
 <translation id="7189234443051076392">Tiyaking may sapat na espasyo sa iyong device</translation>
@@ -4168,7 +4169,6 @@
 <translation id="7387829944233909572">Dialog na "Linisin ang data mula sa  pagba-browse"</translation>
 <translation id="7388044238629873883">Halos tapos ka na!</translation>
 <translation id="7388222713940428051">Buksan ang window ng Bisita</translation>
-<translation id="7391648274628927397">May naidagdag na bookmark para sa iyo</translation>
 <translation id="7392118418926456391">Nabigo ang pag-scan ng virus</translation>
 <translation id="7392915005464253525">M&amp;uling buksan ang nakasarang window</translation>
 <translation id="7396845648024431313">Ilulunsad ang <ph name="APP_NAME" /> sa system startup at patuloy na paganahin sa background kahit na sa sandaling maisara mo ang lahat ng ibang mga window ng <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_fr.xtb b/chrome/app/resources/generated_resources_fr.xtb
index 42e2cea..08efa3b 100644
--- a/chrome/app/resources/generated_resources_fr.xtb
+++ b/chrome/app/resources/generated_resources_fr.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Boîte de dialogue "Effacer les données de navigation"</translation>
 <translation id="7388044238629873883">Vous avez presque terminé !</translation>
 <translation id="7388222713940428051">Ouvrir une fenêtre de navigation en tant qu'invité</translation>
-<translation id="7391648274628927397">Un favori a été ajouté pour vous</translation>
 <translation id="7392118418926456391">Échec de l'analyse antivirus.</translation>
 <translation id="7392915005464253525">&amp;Rouvrir la fenêtre fermée</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> sera lancé au démarrage du système et continuera de s'exécuter en arrière-plan, même si toutes les fenêtres de <ph name="PRODUCT_NAME" /> sont fermées.</translation>
diff --git a/chrome/app/resources/generated_resources_gu.xtb b/chrome/app/resources/generated_resources_gu.xtb
index 16b7f38e..a70f841 100644
--- a/chrome/app/resources/generated_resources_gu.xtb
+++ b/chrome/app/resources/generated_resources_gu.xtb
@@ -4166,7 +4166,6 @@
 <translation id="7387829944233909572">"બ્રાઉઝિંગ ડેટા સાફ કરો" સંવાદ </translation>
 <translation id="7388044238629873883">તમે લગભગ પૂર્ણ કરી લીધું છે!</translation>
 <translation id="7388222713940428051">અતિથિ વિંડો ખોલો</translation>
-<translation id="7391648274628927397">તમારા માટે એક બુકમાર્ક ઉમેરવામાં આવ્યું છે</translation>
 <translation id="7392118418926456391">વાયરસ સ્કેન નિષ્ફળ થયું</translation>
 <translation id="7392915005464253525">બંધ કરેલી વિંડો ફ&amp;રીથી ખોલો</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> સિસ્ટમ શરૂ થવા પર લોંચ થશે અને તમે બાકી તમામ <ph name="PRODUCT_NAME" /> વિંડોઝ બંધ કરી દેશો તે પછી પણ તે પૃષ્ઠભૂમિમાં ચાલુ રહે છે.</translation>
diff --git a/chrome/app/resources/generated_resources_hi.xtb b/chrome/app/resources/generated_resources_hi.xtb
index e3013085..04f05c14 100644
--- a/chrome/app/resources/generated_resources_hi.xtb
+++ b/chrome/app/resources/generated_resources_hi.xtb
@@ -4167,7 +4167,6 @@
 <translation id="7387829944233909572">"ब्राउजिंग डेटा साफ़ करें" डॉयलॉग</translation>
 <translation id="7388044238629873883">आप लगभग पूर्ण कर चुके हैं!</translation>
 <translation id="7388222713940428051">मेहमान विंडो खोलें</translation>
-<translation id="7391648274628927397">आपके लिए एक बुकमार्क जोड़ा गया है</translation>
 <translation id="7392118418926456391">वायरस स्कैन विफल रहा</translation>
 <translation id="7392915005464253525">बंद की गई विंडो पु&amp;न: खोलें</translation>
 <translation id="7396845648024431313">सिस्‍टम प्रारंभ होने पर <ph name="APP_NAME" /> लॉन्‍च होगा और आपके द्वारा सभी अन्‍य <ph name="PRODUCT_NAME" /> विंडो बंद कर देने के बाद भी पृष्‍ठभूमि में चलता रहेगा.</translation>
diff --git a/chrome/app/resources/generated_resources_hr.xtb b/chrome/app/resources/generated_resources_hr.xtb
index 153221c..0ba926b 100644
--- a/chrome/app/resources/generated_resources_hr.xtb
+++ b/chrome/app/resources/generated_resources_hr.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Dijalog "Brisanje podataka preglednika"</translation>
 <translation id="7388044238629873883">Uskoro ste gotovi!</translation>
 <translation id="7388222713940428051">Otvori prozor u načinu rada za goste</translation>
-<translation id="7391648274628927397">Dodali smo oznaku za vas</translation>
 <translation id="7392118418926456391">Pretraživanje virusa nije uspjelo</translation>
 <translation id="7392915005464253525">P&amp;onovo otvorite zatvoreni prozor</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> pokrenut će se pri pokretanju sustava i nastavi raditi u pozadini čak i nakon što zatvorite sve ostale prozore proizvoda <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_hu.xtb b/chrome/app/resources/generated_resources_hu.xtb
index 70b2947..297252f 100644
--- a/chrome/app/resources/generated_resources_hu.xtb
+++ b/chrome/app/resources/generated_resources_hu.xtb
@@ -4170,7 +4170,6 @@
 <translation id="7387829944233909572">"Böngészési adatok törlése" párbeszédablak</translation>
 <translation id="7388044238629873883">Már majdnem elkészült.</translation>
 <translation id="7388222713940428051">Vendég módú ablak megnyitása</translation>
-<translation id="7391648274628927397">Új könyvjelző lett hozzáadva</translation>
 <translation id="7392118418926456391">A víruskeresés nem sikerült</translation>
 <translation id="7392915005464253525">Bezárt ablak újram&amp;egnyitása</translation>
 <translation id="7396845648024431313">A(z) <ph name="APP_NAME" />  el fog indulni a rendszer indításakor, és akkor is tovább fut a háttérben, ha már bezárta az összes többi <ph name="PRODUCT_NAME" />-ablakot.</translation>
diff --git a/chrome/app/resources/generated_resources_id.xtb b/chrome/app/resources/generated_resources_id.xtb
index 129617a..1bbb8b6 100644
--- a/chrome/app/resources/generated_resources_id.xtb
+++ b/chrome/app/resources/generated_resources_id.xtb
@@ -791,7 +791,7 @@
 <translation id="2178098616815594724">Plugin <ph name="PEPPER_PLUGIN_NAME" /> di <ph name="PEPPER_PLUGIN_DOMAIN" /> ingin mengakses komputer Anda</translation>
 <translation id="2178614541317717477">CA Mencurigakan</translation>
 <translation id="218070003709087997">Gunakan angka untuk mengindikasikan jumlah salinan yang akan dicetak (1 sampai 999).</translation>
-<translation id="2183558561014688873">Mengakses Asisten setiap kali Anda mengucapkan "OK Google" saat perangkat aktif dan tidak terkunci</translation>
+<translation id="2183558561014688873">Mengakses Asisten setiap kali Anda mengucapkan "Ok Google" saat perangkat aktif dan tidak terkunci</translation>
 <translation id="2187895286714876935">Kesalahan Impor Sertifikat Server</translation>
 <translation id="2187906491731510095">Ekstensi diupdate</translation>
 <translation id="2188881192257509750">Buka <ph name="APPLICATION" /></translation>
@@ -2041,7 +2041,7 @@
 <translation id="4068776064906523561">Sidik jari yang disimpan</translation>
 <translation id="407173827865827707">Saat diklik</translation>
 <translation id="4071770069230198275"><ph name="PROFILE_NAME" />: kesalahan saat masuk</translation>
-<translation id="4071828814509176232">“OK, Google”</translation>
+<translation id="4071828814509176232">“Ok Google”</translation>
 <translation id="4074900173531346617">Sertifikat Penandatangan Email</translation>
 <translation id="407520071244661467">Skala</translation>
 <translation id="4075639477629295004">Tidak dapat mentransmisikan <ph name="FILE_NAME" />.</translation>
@@ -2170,7 +2170,7 @@
 <translation id="428608937826130504">Item rak 8</translation>
 <translation id="4287502004382794929">Anda tidak memiliki cukup lisensi software untuk mendaftarkan perangkat ini. Hubungi pihak penjualan untuk membeli yang lainnya. Jika Anda yakin Anda tidak seharusnya mendapatkan pesan ini, hubungi dukungan.</translation>
 <translation id="4289540628985791613">Ikhtisar</translation>
-<translation id="4295072614469448764">Aplikasi tersedia di terminal Anda. Mungkin juga ada ikon di Peluncur.</translation>
+<translation id="4295072614469448764">Aplikasi tersedia di terminal Anda. Ikonnya juga mungkin tersedia di Peluncur.</translation>
 <translation id="4296575653627536209">Tambahkan Pengguna yang Dilindungi</translation>
 <translation id="4297219207642690536">Mulai ulang dan setel ulang</translation>
 <translation id="4297322094678649474">Ubah Bahasa</translation>
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Dialog "Hapus data penjelajahan"</translation>
 <translation id="7388044238629873883">Anda hampir selesai!</translation>
 <translation id="7388222713940428051">Buka jendela Tamu</translation>
-<translation id="7391648274628927397">Bookmark telah ditambahkan untuk Anda</translation>
 <translation id="7392118418926456391">Pemindaian virus gagal</translation>
 <translation id="7392915005464253525">Buka kembali jendela yang tertutup</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> akan diluncurkan pada permulaan sistem dan tetap berjalan di latar meskipun Anda telah menutup semua jendela <ph name="PRODUCT_NAME" />.</translation>
@@ -4207,7 +4206,7 @@
 <translation id="7463006580194749499">Tambahkan pengguna</translation>
 <translation id="7464490149090366184">Gagal membuat file zip, item sudah ada: "$1"</translation>
 <translation id="7465778193084373987">Netscape Certificate Revocation URL</translation>
-<translation id="7469406957790636836">Untuk mengaktifkannya, terlebih dahulu aktifkan periksa ejaan di <ph name="BEGIN_LINK" />Bahasa dan masukan<ph name="END_LINK" /></translation>
+<translation id="7469406957790636836">Untuk mengaktifkannya, terlebih dahulu aktifkan fitur periksa ejaan di <ph name="BEGIN_LINK" />Bahasa dan masukan<ph name="END_LINK" /></translation>
 <translation id="7469894403370665791">Sambungkan ke jaringan ini secara otomatis</translation>
 <translation id="747114903913869239">Kesalahan: Tidak dapat mendekodekan ekstensi</translation>
 <translation id="7473753388963818366">Mari menyiapkan <ph name="DEVICE_TYPE" /> Anda</translation>
diff --git a/chrome/app/resources/generated_resources_it.xtb b/chrome/app/resources/generated_resources_it.xtb
index f0d0b4c..ad57d98 100644
--- a/chrome/app/resources/generated_resources_it.xtb
+++ b/chrome/app/resources/generated_resources_it.xtb
@@ -1219,7 +1219,7 @@
 <translation id="2812944337881233323">Prova a uscire e ad accedere di nuovo</translation>
 <translation id="2812989263793994277">Non mostrare le immagini</translation>
 <translation id="2814489978934728345">Interrompi il caricamento della pagina</translation>
-<translation id="281504910091592009">Visualizza e gestisci le password salvate nel tuo <ph name="BEGIN_LINK" />account Google<ph name="END_LINK" /></translation>
+<translation id="281504910091592009">Visualizza e gestisci le password salvate nel tuo <ph name="BEGIN_LINK" />Account Google<ph name="END_LINK" /></translation>
 <translation id="2815500128677761940">Barra dei Preferiti</translation>
 <translation id="2815693974042551705">Aggiungi la cartella ai segnalibri</translation>
 <translation id="2818476747334107629">Dettagli stampante</translation>
@@ -2568,7 +2568,7 @@
 <translation id="4953808748584563296">Avatar arancione predefinito</translation>
 <translation id="4955814292505481804">Annuale</translation>
 <translation id="4957949153200969297">Attiva solo le funzionalità relative alla sincronizzazione di <ph name="IDS_SHORT_PRODUCT_NAME" /></translation>
-<translation id="4959262764292427323">Le password vengono salvate nel tuo account Google affinché tu possa usarle su qualsiasi dispositivo</translation>
+<translation id="4959262764292427323">Le password vengono salvate nel tuo Account Google affinché tu possa usarle su qualsiasi dispositivo</translation>
 <translation id="4960294539892203357"><ph name="WINDOW_TITLE" /> - <ph name="PROFILE_NAME" /></translation>
 <translation id="496226124210045887">La cartella che hai selezionato contiene file delicati. Vuoi concedere a "$1" accesso di lettura permanente a questa cartella?</translation>
 <translation id="4964455510556214366">Disposizione</translation>
@@ -4164,7 +4164,6 @@
 <translation id="7387829944233909572">Finestra di dialogo "Cancella dati di navigazione"</translation>
 <translation id="7388044238629873883">La procedura è quasi terminata.</translation>
 <translation id="7388222713940428051">Apri finestra Ospite</translation>
-<translation id="7391648274628927397">È stato aggiunto un preferito</translation>
 <translation id="7392118418926456391">Scansione virus non riuscita</translation>
 <translation id="7392915005464253525">R&amp;iapri finestra chiusa</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> si attiverà all'avvio del sistema e continuerà a funzionare in background anche dopo la chiusura di tutte le altre finestre di <ph name="PRODUCT_NAME" />.</translation>
@@ -4556,7 +4555,7 @@
 <translation id="7974936243149753750">Overscan</translation>
 <translation id="7977551819349545646">Aggiornamento del Chromebox...</translation>
 <translation id="7978412674231730200">Chiave privata</translation>
-<translation id="7978450511781612192">I tuoi account Google verranno scollegati e verrà interrotta la sincronizzazione di preferiti, cronologia, password e altro.</translation>
+<translation id="7978450511781612192">I tuoi Account Google verranno scollegati e verrà interrotta la sincronizzazione di preferiti, cronologia, password e altro.</translation>
 <translation id="7979036127916589816">Errore di sincronizzazione</translation>
 <translation id="7980084013673500153">ID asset: <ph name="ASSET_ID" /></translation>
 <translation id="7981313251711023384">Le pagine vengono precaricate per velocizzare la navigazione e la ricerca</translation>
diff --git a/chrome/app/resources/generated_resources_iw.xtb b/chrome/app/resources/generated_resources_iw.xtb
index 5bdf7ce1..2d858e8 100644
--- a/chrome/app/resources/generated_resources_iw.xtb
+++ b/chrome/app/resources/generated_resources_iw.xtb
@@ -4166,7 +4166,6 @@
 <translation id="7387829944233909572">תיבת הדו-שיח 'ניקוי נתוני גלישה'</translation>
 <translation id="7388044238629873883">כמעט סיימת!</translation>
 <translation id="7388222713940428051">פתיחת חלון במצב אורח</translation>
-<translation id="7391648274628927397">המערכת הוסיפה עבורך סימניה</translation>
 <translation id="7392118418926456391">סריקת וירוסים נכשלה</translation>
 <translation id="7392915005464253525">פ&amp;תח מחדש חלון סגור</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> יופעל עם אתחול המערכת וימשיך לפעול ברקע גם כאשר תסגור את כל החלונות האחרים של <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_ja.xtb b/chrome/app/resources/generated_resources_ja.xtb
index 386b15b..ec12997 100644
--- a/chrome/app/resources/generated_resources_ja.xtb
+++ b/chrome/app/resources/generated_resources_ja.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">[閲覧履歴の消去] ダイアログ</translation>
 <translation id="7388044238629873883">もうすぐ終わりです。</translation>
 <translation id="7388222713940428051">ゲスト ウィンドウを開く</translation>
-<translation id="7391648274628927397">おすすめのブックマークが追加されました</translation>
 <translation id="7392118418926456391">ウイルス スキャンに失敗しました</translation>
 <translation id="7392915005464253525">閉じたウィンドウを開く(&amp;E)</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> はシステム起動時に開始され、他の <ph name="PRODUCT_NAME" /> ウィンドウをすべて閉じてもバックグラウンドで動作し続けます。</translation>
diff --git a/chrome/app/resources/generated_resources_kn.xtb b/chrome/app/resources/generated_resources_kn.xtb
index 3429fe4..1964b77 100644
--- a/chrome/app/resources/generated_resources_kn.xtb
+++ b/chrome/app/resources/generated_resources_kn.xtb
@@ -4166,7 +4166,6 @@
 <translation id="7387829944233909572">"ಬ್ರೌಸಿಂಗ್ ಡೇಟಾ ತೆರವುಗೊಳಿಸು" ಸಂವಾದ</translation>
 <translation id="7388044238629873883">ನೀವು ಬಹುತೇಕ ಪೂರೈಸಿರುವಿರಿ!</translation>
 <translation id="7388222713940428051">ಅತಿಥಿ ವಿಂಡೋವನ್ನು ತೆರೆಯಿರಿ</translation>
-<translation id="7391648274628927397">ನಿಮಗಾಗಿ ಬುಕ್‌ಮಾರ್ಕ್ ಅನ್ನು ಸೇರಿಸಲಾಗಿದೆ</translation>
 <translation id="7392118418926456391">ವೈರಸ್ ಸ್ಕ್ಯಾನ್ ವಿಫಲವಾಗಿದೆ</translation>
 <translation id="7392915005464253525">ಮು&amp;ಚ್ಚಿದ ವಿಂಡೋವನ್ನು ಮತ್ತೆ ತೆರೆ</translation>
 <translation id="7396845648024431313">ಸಿಸ್ಟಂ ಪ್ರಾರಂಭಗೊಳ್ಳುವಾಗ <ph name="APP_NAME" /> ಪ್ರಾರಂಭಗೊಳ್ಳುತ್ತದೆ ಮತ್ತು ಒಮ್ಮೆ ನೀವು ಇತರೆ ಎಲ್ಲ <ph name="PRODUCT_NAME" /> ವಿಂಡೊಗಳನ್ನು ಮುಚ್ಚಿದರೂ ಸಹ ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಚಾಲನೆಗೊಳ್ಳಲು ಮುಂದುವರಿಯುತ್ತದೆ.</translation>
diff --git a/chrome/app/resources/generated_resources_ko.xtb b/chrome/app/resources/generated_resources_ko.xtb
index a03116a..63eb4645 100644
--- a/chrome/app/resources/generated_resources_ko.xtb
+++ b/chrome/app/resources/generated_resources_ko.xtb
@@ -4167,7 +4167,6 @@
 <translation id="7387829944233909572">'인터넷 사용 기록 삭제' 대화상자</translation>
 <translation id="7388044238629873883">거의 완료되었습니다.</translation>
 <translation id="7388222713940428051">게스트 창 열기</translation>
-<translation id="7391648274628927397">북마크가 추가되었습니다.</translation>
 <translation id="7392118418926456391">바이러스 검사 실패</translation>
 <translation id="7392915005464253525">닫은 탭 다시 열기(&amp;E)</translation>
 <translation id="7396845648024431313">시스템이 시작할 때 <ph name="APP_NAME" />이(가) 실행되며 다른 모든 <ph name="PRODUCT_NAME" /> 창을 종료한 후에도 백그라운드에서 계속 실행됩니다.</translation>
diff --git a/chrome/app/resources/generated_resources_lt.xtb b/chrome/app/resources/generated_resources_lt.xtb
index 889bc11..3bd45ca 100644
--- a/chrome/app/resources/generated_resources_lt.xtb
+++ b/chrome/app/resources/generated_resources_lt.xtb
@@ -401,6 +401,7 @@
 <translation id="1589055389569595240">Rodyti rašybos ir gramatikos tikrinimą</translation>
 <translation id="1593594475886691512">Formatuojama...</translation>
 <translation id="159359590073980872">Vaizdų talpykla</translation>
+<translation id="1593926297800505364">Išsaugoti mokėjimo metodą</translation>
 <translation id="1598233202702788831">Administratorius išjungė naujinius</translation>
 <translation id="1600857548979126453">Pasiekti puslapio derintuvės vidinę pusę</translation>
 <translation id="1601560923496285236">Taikyti</translation>
@@ -511,6 +512,7 @@
 <translation id="1744060673522309905">Nepavyko prijungti įrenginio prie domeno. Įsitikinkite, kad neviršijote įrenginių, kuriuos galite pridėti, skaičiaus.</translation>
 <translation id="1744108098763830590">foninis puslapis</translation>
 <translation id="1745520510852184940">Visada tai daryti</translation>
+<translation id="1746417874336251387">Pridėkite naujų funkcijų, naudojančių telefono ryšį su „Chromebook“</translation>
 <translation id="174937106936716857">Bendras failų skaičius</translation>
 <translation id="175196451752279553">I&amp;š naujo atidaryti uždarytą skirtuką</translation>
 <translation id="1753905327828125965">Dažniausiai lankomi</translation>
@@ -641,8 +643,10 @@
 <translation id="1932098463447129402">Nuo</translation>
 <translation id="1933809209549026293">Prijunkite pelę ar klaviatūrą. Jei naudojate „Bluetooth“ įrenginį, įsitikinkite, kad jis paruoštas susieti.</translation>
 <translation id="1936157145127842922">Rodyti aplanke</translation>
+<translation id="1938351510777341717">Išorinė komanda</translation>
 <translation id="1940546824932169984">Prijungti įrenginiai</translation>
 <translation id="1942765061641586207">Vaizdo skyra</translation>
+<translation id="1943097386230153518">Naujos paslaugos diegimas</translation>
 <translation id="1944921356641260203">Naujinys rastas</translation>
 <translation id="1951615167417147110">Slinkti aukštyn vienu puslapiu</translation>
 <translation id="1954813140452229842">Klaida įdedant bendrinamą objektą. Patikrinkite prisijungimo duomenis ir bandykite dar kartą.</translation>
@@ -1108,6 +1112,7 @@
 <translation id="2653659639078652383">Pateikti</translation>
 <translation id="265390580714150011">Lauko vertė</translation>
 <translation id="2654166010170466751">Leisti svetainėms diegti mokėjimų dorokles</translation>
+<translation id="2659381484350128933"><ph name="FOOTNOTE_POINTER" />Funkcijos skiriasi pagal įrenginį</translation>
 <translation id="2660779039299703961">Įvykis</translation>
 <translation id="266079277508604648">Nepavyko prijungti spausdintuvo. Patikrinkite, ar spausdintuvas yra įjungtas ir prijungtas prie „Chromebook“ naudojant „Wi-Fi“ arba USB.</translation>
 <translation id="2661146741306740526">16 x 9</translation>
@@ -1214,6 +1219,7 @@
 <translation id="2803375539583399270">Įveskite PIN kodą</translation>
 <translation id="2805646850212350655">„Microsoft“ koduojamųjų failų sistema</translation>
 <translation id="2805756323405976993">Taikomosios programos</translation>
+<translation id="2806891468525657116">Spartusis klavišas jau yra</translation>
 <translation id="2807517655263062534">Atsisiųsti failai rodomi čia</translation>
 <translation id="2809586584051668049">ir dar <ph name="NUMBER_ADDITIONAL_DISABLED" /></translation>
 <translation id="281133045296806353">Esamos naršyklės sesijos metu sukurtas naujas langas.</translation>
@@ -1600,6 +1606,7 @@
 <translation id="3428419049384081277">Esate prisijungę!</translation>
 <translation id="3429275422858276529">Pažymėkite šį puslapį, kad galėtumėte lengvai jį rasti vėliau</translation>
 <translation id="3429599832623003132">Elementų: $1</translation>
+<translation id="3430342160185525240">Padėjėjui leidžiama rodyti pranešimus.</translation>
 <translation id="3432227430032737297">Pašalinti visus rodomus</translation>
 <translation id="3432757130254800023">Siųsti garso ir vaizdo įrašus vietiniame tinkle</translation>
 <translation id="3432762828853624962">Shared Workers</translation>
@@ -1964,6 +1971,7 @@
 „<ph name="CONTROL_PANEL_APPLET_NAME" />“ valdymo skydelyje.
 
 Ar norite paleisti „<ph name="CONTROL_PANEL_APPLET_NAME" />“?</translation>
+<translation id="394183848452296464">Nepavyksta sukurti sparčiojo klavišo</translation>
 <translation id="3943582379552582368">&amp;Grįžti</translation>
 <translation id="3943857333388298514">Įklijuoti</translation>
 <translation id="3948116654032448504">&amp;Ieškoti vaizdo sistemoje „<ph name="SEARCH_ENGINE" />“</translation>
@@ -2271,6 +2279,7 @@
 <translation id="4481530544597605423">Atsieti įrenginiai</translation>
 <translation id="4482194545587547824">„Google“ gali naudoti jūsų naršymo istoriją, kad suasmenintų Paiešką ir kitas „Google“ paslaugas</translation>
 <translation id="4495419450179050807">Nerodyti šiame puslapyje</translation>
+<translation id="4499718683476608392">Įgalinamas automatinis kredito kortelės informacijos pildymas, kad būtų galima užpildyti formas vienu paspaudimu</translation>
 <translation id="4500114933761911433">Papildinys „<ph name="PLUGIN_NAME" />“ užstrigo</translation>
 <translation id="450099669180426158">Šauktuko piktograma</translation>
 <translation id="4501530680793980440">Patvirtinti pašalinimą</translation>
@@ -3116,6 +3125,7 @@
 <translation id="5752453871435543420">„Chrome OS“ atsarginis kopijavimas debesyje</translation>
 <translation id="5756163054456765343">P&amp;agalbos centras</translation>
 <translation id="5759728514498647443">Dokumentus, kuriuos siunčiate spausdinti naudodami „<ph name="APP_NAME" />“, gali skaityti „<ph name="APP_NAME" />“.</translation>
+<translation id="5762172915276660232">Kredito kortelių nustatymai</translation>
 <translation id="5763751966069581670">Nerasta jokių USB įrenginių</translation>
 <translation id="5764483294734785780">Iš&amp;saugoti garso įrašą kaip...</translation>
 <translation id="57646104491463491">Koregavimo data</translation>
@@ -3187,6 +3197,7 @@
 <translation id="5855773610748894548">Oi, saugaus modulio klaida.</translation>
 <translation id="5856721540245522153">Įgalinti derinimo funkcijas</translation>
 <translation id="5857090052475505287">Naujas aplankas</translation>
+<translation id="585979798156957858">Išorinis metaduomenų klavišas</translation>
 <translation id="5860033963881614850">Išjungta</translation>
 <translation id="5860209693144823476">3 skirtukas</translation>
 <translation id="5860491529813859533">Įjungti</translation>
@@ -3747,6 +3758,7 @@
 <translation id="6710213216561001401">Ankstesnis</translation>
 <translation id="6718273304615422081">Glaudinama...</translation>
 <translation id="671928215901716392">Užrakinti ekraną</translation>
+<translation id="6720847671508630642">Automatiškai bendrinkite geriausias „Android“ funkcijas su „Chromebook“. Susiekite telefoną, kad galėtumėte siųsti teksto pranešimus iš kompiuterio, bendrinti telefono interneto ryšį ir atrakinti „Chromebook“ ekraną.<ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />Sužinokite daugiau<ph name="LINK_END" /></translation>
 <translation id="6721678857435001674">Peržiūrėti jūsų saugos rakto tipą ir modelį</translation>
 <translation id="6721972322305477112">&amp;Failas</translation>
 <translation id="672213144943476270">Prieš naršydami kaip svečias atrakinkite savo profilį.</translation>
@@ -3893,6 +3905,7 @@
 <translation id="6970856801391541997">Spausdinti konkrečius puslapius</translation>
 <translation id="6972180789171089114">Garso / vaizdo įrašai</translation>
 <translation id="6973630695168034713">Aplankai</translation>
+<translation id="6974609594866392343">Demonstracinis režimas neprisijungus</translation>
 <translation id="6976108581241006975">„JavaScript“ pultas</translation>
 <translation id="6977381486153291903">Programinės aparatinės įrangos taisymas</translation>
 <translation id="6978121630131642226">Paieškos sistemos</translation>
@@ -4169,7 +4182,6 @@
 <translation id="7387829944233909572">Dialogo langas „Išvalyti naršymo duomenis“</translation>
 <translation id="7388044238629873883">Jau beveik baigėte!</translation>
 <translation id="7388222713940428051">Atidaryti svečio langą</translation>
-<translation id="7391648274628927397">Žymė pridėta</translation>
 <translation id="7392118418926456391">Nepavyko žvalgyti, ar yra virusų</translation>
 <translation id="7392915005464253525">I&amp;š naujo atidaryti uždarytą langą</translation>
 <translation id="7396845648024431313">„<ph name="APP_NAME" />“ bus paleista kartu su sistema ir fone veiks toliau, net ir išjungus visus kitus „<ph name="PRODUCT_NAME" />“ langus.</translation>
@@ -4487,6 +4499,7 @@
 <translation id="7857949311770343000">Ar tai naujo skirtuko puslapis, kurį tikėjotės išvysti?</translation>
 <translation id="786073089922909430">Paslauga: <ph name="ARC_PROCESS_NAME" /></translation>
 <translation id="7861215335140947162">&amp;Atsisiuntimai</translation>
+<translation id="7864662577698025113">Pridėti naują paslaugą</translation>
 <translation id="7868378670806575181">{NUM_COOKIES,plural, =1{1 slapukas}one{# slapukas}few{# slapukai}many{# slapuko}other{# slapukų}}</translation>
 <translation id="786957569166715433">„<ph name="DEVICE_NAME" />“ – susietas</translation>
 <translation id="7870730066603611552">Nustačius peržiūrėti sinchronizavimo parinktis</translation>
@@ -5001,6 +5014,7 @@
 <translation id="8666584013686199826">Paklausti, kai svetainė norite pasiekti USB įrenginius</translation>
 <translation id="8667328578593601900"><ph name="FULLSCREEN_ORIGIN" /> dabar veikia viso ekrano režimu ir neleidžiamas pelės žymeklis.</translation>
 <translation id="8669284339312441707">Šiltesnio atspalvio</translation>
+<translation id="8669919703154928649">Leidimas Padėjėjui rodyti pranešimus</translation>
 <translation id="8669949407341943408">Perkeliama...</translation>
 <translation id="8671210955687109937">Galima komentuoti</translation>
 <translation id="8673026256276578048">Ieškoti žiniatinklyje...</translation>
@@ -5388,6 +5402,7 @@
 <translation id="964286338916298286">IT administratorius jūsų įrenginyje išjungė naudingas „Chrome“ funkcijas.</translation>
 <translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{Programa}one{Programos}few{Programos}many{Programos}other{Programos}}</translation>
 <translation id="967007123645306417">Būsite atjungti nuo „Google“ paskyrų. Žymių, istorijos, slaptažodžių ir kitų nustatymų pakeitimai nebebus sinchronizuojami su „Google“ paskyra. Tačiau esami duomenys toliau bus saugomi „Google“ paskyroje ir juos bus galima tvarkyti <ph name="BEGIN_LINK" />„Google“ informacijos suvestinėje<ph name="END_LINK" />.</translation>
+<translation id="967624055006145463">Saugomi duomenys</translation>
 <translation id="968000525894980488">Įjunkite „Google Play“ paslaugas.</translation>
 <translation id="968174221497644223">Programos talpykla</translation>
 <translation id="969096075394517431">Pakeisti kalbas</translation>
diff --git a/chrome/app/resources/generated_resources_lv.xtb b/chrome/app/resources/generated_resources_lv.xtb
index ffdd1585..0c4cb1e 100644
--- a/chrome/app/resources/generated_resources_lv.xtb
+++ b/chrome/app/resources/generated_resources_lv.xtb
@@ -4168,7 +4168,6 @@
 <translation id="7387829944233909572">Dialoglodziņš "Notīrīt pārlūkošanas datus"</translation>
 <translation id="7388044238629873883">Jūs esat gandrīz pabeidzis!</translation>
 <translation id="7388222713940428051">Atvērt viesa režīma logu</translation>
-<translation id="7391648274628927397">Tika pievienota grāmatzīme</translation>
 <translation id="7392118418926456391">Vīrusu skenēšana neizdevās.</translation>
 <translation id="7392915005464253525">A&amp;tkārtoti atvērt aizvērto logu</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> tiks startēta pie sistēmas startēšanas un turpinās darboties fonā arī tad, ja aizvērsiet visus citus <ph name="PRODUCT_NAME" /> logus.</translation>
diff --git a/chrome/app/resources/generated_resources_ml.xtb b/chrome/app/resources/generated_resources_ml.xtb
index c2d9fa5..a304d52 100644
--- a/chrome/app/resources/generated_resources_ml.xtb
+++ b/chrome/app/resources/generated_resources_ml.xtb
@@ -499,7 +499,7 @@
 <translation id="1732215134274276513">ടാബുകള്‍ അണ്‍പിന്‍ചെയ്യുക </translation>
 <translation id="1734824808160898225"><ph name="PRODUCT_NAME" />-ന്, സ്വയം അപ്ഡേറ്റ് ചെയ്യാന്‍ സാധിക്കുന്നില്ലായിരിക്കാം</translation>
 <translation id="1736419249208073774">അടുത്തറിയുക</translation>
-<translation id="1736827427463982819">Linux ഷട്ട് ഡൗൺ ചെയ്യുക (ബീറ്റ)</translation>
+<translation id="1736827427463982819">Linux (ബീറ്റ) ഷട്ട് ഡൗൺ ചെയ്യുക</translation>
 <translation id="1737968601308870607">ഫയല്‍ പിശക്</translation>
 <translation id="1741314857973421784">തുടരുക</translation>
 <translation id="174173592514158117">എല്ലാ Play ഫോൾഡറുകളും കാണിക്കുക</translation>
@@ -672,7 +672,7 @@
 <translation id="1997484222658892567">നിങ്ങളുടെ ലോക്കൽ കമ്പ്യൂട്ടറിൽ ശാശ്വതമായി വലിയ ഡാറ്റ സംഭരിക്കാൻ <ph name="URL" />ആഗ്രഹിക്കുന്നു</translation>
 <translation id="1997616988432401742">നിങ്ങളുടെ സർട്ടിഫിക്കറ്റുകൾ</translation>
 <translation id="1999115740519098545">തുടക്കത്തില്‍</translation>
-<translation id="2000419248597011803">നിങ്ങളുടെ ഡിഫോൾട്ട് തിരയൽ എഞ്ചിനിലേക്ക് വിലാസ ബാറിൽ നിന്നും തിരയൽ ബോക്‌സിൽ നിന്നുള്ള തിരയലുകളും കുറച്ച് കുക്കികളും അയയ്ക്കുന്നു</translation>
+<translation id="2000419248597011803">നിങ്ങളുടെ ഡിഫോൾട്ട് തിരയൽ എഞ്ചിനിലേക്ക് വിലാസ ബാറിൽ നിന്നും തിരയൽ ബോക്‌സിൽ നിന്നുമുള്ള തിരയലുകളും കുറച്ച് കുക്കികളും അയയ്ക്കുന്നു</translation>
 <translation id="2001796770603320721">ഡ്രൈവിൽ മാനേജ് ചെയ്യുക</translation>
 <translation id="2004663115385769400">$1 ഉപയോഗിച്ച് തുറക്കാനായില്ല</translation>
 <translation id="200544492091181894">നിങ്ങൾക്കിത് എപ്പോൾ വേണമെങ്കിലും ക്രമീകരണത്തിൽ മാറ്റാം</translation>
@@ -4167,7 +4167,6 @@
 <translation id="7387829944233909572">“ബ്രൌസിംഗ് ഡാറ്റ മായ്ക്കുക” ഡയലോഗ്</translation>
 <translation id="7388044238629873883">നിങ്ങൾ മിക്കവാറും പൂർത്തിയാക്കി!</translation>
 <translation id="7388222713940428051">അതിഥി വിൻഡോ തുറക്കുക</translation>
-<translation id="7391648274628927397">നിങ്ങൾക്കായി ഒരു ബുക്ക്‌മാർക്ക് ചേർത്തു</translation>
 <translation id="7392118418926456391">വൈറസ് സ്‌കാൻ പരാജയപ്പെട്ടു</translation>
 <translation id="7392915005464253525">അ&amp;ടച്ച വിന്‍ഡോ വീണ്ടും തുറക്കുക</translation>
 <translation id="7396845648024431313">ഒരിക്കല്‍ നിങ്ങള്‍ മറ്റെല്ലാ <ph name="PRODUCT_NAME" /> വിന്‍ഡോകളും അടച്ചാല്‍ പോലും <ph name="APP_NAME" /> സിസ്റ്റം സ്റ്റാര്‍ട്ടപ്പ് സമാരംഭിക്കുകയും പശ്ചാത്തലത്തില്‍ പ്രവര്‍ത്തിക്കുന്നന്നത് തുടരുകയും ചെയ്യും.</translation>
@@ -5402,7 +5401,7 @@
 <translation id="988978206646512040">പാസ്‌ഫ്രെയ്‌സ് പൂരിപ്പിക്കാതിരിക്കാൻ കഴിയില്ല</translation>
 <translation id="992032470292211616">വിപുലീകരണങ്ങൾ, അപ്ലിക്കേഷനുകൾ, തീമുകൾ എന്നിവ നിങ്ങളുടെ  ഉപകരണത്തിന് ദോഷകരമാകാം. തുടരാൻ നിങ്ങൾക്ക് താൽപ്പര്യമുണ്ടോ?</translation>
 <translation id="992592832486024913">ChromeVox (സ്‌പോക്കൺ ഫീഡ്‌ബാക്ക്) പ്രവർത്തനരഹിതമാക്കുക</translation>
-<translation id="993540765962421562">ഇൻസ്‌റ്റലേഷൻ പുരോഗതിയിലാണ്</translation>
+<translation id="993540765962421562">ഇൻസ്‌റ്റലേഷൻ പുരോഗമിക്കുകയാണ്</translation>
 <translation id="994289308992179865">&amp;ലൂപ്പുചെയ്യുക</translation>
 <translation id="996250603853062861">സുരക്ഷിത കണക്ഷന്‍‌ സ്ഥാപിക്കുന്നു...</translation>
 <translation id="998747458861718449">പരിശോധിക്കുക</translation>
diff --git a/chrome/app/resources/generated_resources_mr.xtb b/chrome/app/resources/generated_resources_mr.xtb
index d9fd0290..e5201a7 100644
--- a/chrome/app/resources/generated_resources_mr.xtb
+++ b/chrome/app/resources/generated_resources_mr.xtb
@@ -400,6 +400,7 @@
 <translation id="1589055389569595240">शुद्धलेखन आणि व्याकरण दर्शवा</translation>
 <translation id="1593594475886691512">स्वरूपन करत आहे...</translation>
 <translation id="159359590073980872">इमेज कॅश  </translation>
+<translation id="1593926297800505364">पेमेंट पद्धत सेव्ह करा</translation>
 <translation id="1598233202702788831">आपल्‍या प्रशासकाने अपडेट अक्षम केली आहेत.</translation>
 <translation id="1600857548979126453">पृष्‍ठ ‍डीबगर बॅकएंडवर प्रवेश करा</translation>
 <translation id="1601560923496285236">लागू करा</translation>
@@ -510,6 +511,7 @@
 <translation id="1744060673522309905">डिव्हाइस डोमेनशी जोडू शकत नाही. तुम्हाला जोडता येऊ शकणाऱ्या डिव्हाइसची संख्या तुम्ही ओलांडलेली नाही याची खात्री करा.</translation>
 <translation id="1744108098763830590">पार्श्वभूमी पृष्ठ</translation>
 <translation id="1745520510852184940">हे नेहमी करा</translation>
+<translation id="1746417874336251387">तुमच्या Chromebook वर तुमच्या फोनचे कनेक्शन वापरणारी नवीन वैशिष्ट्ये ऑफर करते</translation>
 <translation id="174937106936716857">एकूण फाईल संख्या</translation>
 <translation id="175196451752279553">पु&amp;न्हा बंद टॅब उघडा</translation>
 <translation id="1753905327828125965">सर्वाधिक भेट दिलेले</translation>
@@ -640,8 +642,10 @@
 <translation id="1932098463447129402">पूर्वी नाही</translation>
 <translation id="1933809209549026293">कृपया एक माउस किंवा एक कीबोर्ड कनेक्ट करा. आपण Bluetooth डिव्हाइस वापरत असल्यास, जोडण्यासाठी ते तयार असल्याचे सुनिश्चित करा.</translation>
 <translation id="1936157145127842922">फोल्डरमध्ये दर्शवा</translation>
+<translation id="1938351510777341717">बाह्य कमांड</translation>
 <translation id="1940546824932169984">कनेक्‍ट केलेले डिव्हाइस</translation>
 <translation id="1942765061641586207">इमेज रिझोल्यूशन</translation>
+<translation id="1943097386230153518">नवीन सेवा इंस्टॉल करा</translation>
 <translation id="1944921356641260203">अपडेट आढळले</translation>
 <translation id="1951615167417147110">एक पृष्ठ वर स्क्रोल करा</translation>
 <translation id="1954813140452229842">शेअर माउंट करताना एरर आली. कृपया तुमची क्रेडेंशियल तपासा आणि पुन्हा प्रयत्न करा.</translation>
@@ -1107,6 +1111,7 @@
 <translation id="2653659639078652383">सबमिट करा</translation>
 <translation id="265390580714150011">फील्ड मूल्य</translation>
 <translation id="2654166010170466751">पेमेंट हँडलर इंस्टॉल करण्यासाठी साइटना अनुमती द्या</translation>
+<translation id="2659381484350128933"><ph name="FOOTNOTE_POINTER" />वैशिष्ट्ये डिव्हाइसनुसार बदलतात</translation>
 <translation id="2660779039299703961">इव्‍हेंट</translation>
 <translation id="266079277508604648">प्रिंटरशी कनेक्ट करू शकत नाही. प्रिंटर चालू आहे का आणि तो वाय-फाय किंवा USB ने तुमच्या Chromebook शी कनेक्ट केला आहे का ते तपासा.</translation>
 <translation id="2661146741306740526">16x9</translation>
@@ -1213,6 +1218,7 @@
 <translation id="2803375539583399270">पिन एंटर करा</translation>
 <translation id="2805646850212350655">Microsoft Encrypting File System</translation>
 <translation id="2805756323405976993">अॅप्लिकेशन</translation>
+<translation id="2806891468525657116">शॉर्टकट आधीच अस्तित्वात आहे</translation>
 <translation id="2807517655263062534">तुम्ही डाउनलोड केलेल्या फायली येथे दिसतात</translation>
 <translation id="2809586584051668049">आणि <ph name="NUMBER_ADDITIONAL_DISABLED" /> अधिक</translation>
 <translation id="281133045296806353">विद्यमान ब्राउझर सत्रात नवीन विंडो तयार केली.</translation>
@@ -1600,6 +1606,7 @@
 <translation id="3428419049384081277">तुम्ही साइन इन केलेले आहे!</translation>
 <translation id="3429275422858276529">हे पेज नंतर सहजपणे शोधण्यासाठी ते बुकमार्क करा</translation>
 <translation id="3429599832623003132">$1 आयटम</translation>
+<translation id="3430342160185525240">तुम्हाला सूचना दाखवण्यासाठी असिस्टंटला सुरू करते.</translation>
 <translation id="3432227430032737297">सर्व दर्शविलेले काढा</translation>
 <translation id="3432757130254800023">स्थानिक नेटवर्कच्या प्रदर्शनांवर ऑडिओ आणि व्हिडिओ पाठवा</translation>
 <translation id="3432762828853624962">Shared Workers</translation>
@@ -1964,6 +1971,7 @@
   <ph name="CONTROL_PANEL_APPLET_NAME" /> वापरून तो अनइंस्टॉल करण्याची आवश्यकता आहे.
 
   आपण <ph name="CONTROL_PANEL_APPLET_NAME" /> प्रारंभ करू इच्छिता?</translation>
+<translation id="394183848452296464">शॉर्टकट तयार करू शकत नाही</translation>
 <translation id="3943582379552582368">&amp;मागील</translation>
 <translation id="3943857333388298514">पेस्ट करा</translation>
 <translation id="3948116654032448504">इमेज <ph name="SEARCH_ENGINE" /> वर शोधा</translation>
@@ -2270,6 +2278,7 @@
 <translation id="4481530544597605423">जोडणी वेगळी केलेली डिव्हाइस</translation>
 <translation id="4482194545587547824">शोध आणि इतर Google सेवा वैयक्तीकृत करण्यासाठी Google आपल्या ब्राउझिंग इतिहासाचा वापर करू शकते</translation>
 <translation id="4495419450179050807">या पृष्ठावर दर्शवू नका</translation>
+<translation id="4499718683476608392">एका क्लिकमध्ये फॉर्म भरण्यासाठी क्रेडिट कार्ड ऑटोफिल सुरू करा</translation>
 <translation id="4500114933761911433"><ph name="PLUGIN_NAME" /> क्रॅश झाले आहे</translation>
 <translation id="450099669180426158">उद्गार चिन्हाचे चिन्ह</translation>
 <translation id="4501530680793980440">काढून टाकण्याची पुष्टी करा</translation>
@@ -3115,6 +3124,7 @@
 <translation id="5752453871435543420">Chrome OS क्लाउड बॅकअप</translation>
 <translation id="5756163054456765343">म&amp;दत केंद्र</translation>
 <translation id="5759728514498647443">आपण <ph name="APP_NAME" /> द्वारे प्रिंट करण्यासाठी पाठविलेले दस्तऐवज <ph name="APP_NAME" /> द्वारे वाचले जाऊ शकतात.</translation>
+<translation id="5762172915276660232">क्रेडिट कार्ड सेटिंग्ज</translation>
 <translation id="5763751966069581670">कोणतेही USB डिव्हाइस सापडले नाहीत</translation>
 <translation id="5764483294734785780">म्हणून ऑडिओ ज&amp;तन करा...</translation>
 <translation id="57646104491463491">सुधारणा तारीख</translation>
@@ -3186,6 +3196,7 @@
 <translation id="5855773610748894548">अरेरे, सुरक्षित मॉड्यूल एरर</translation>
 <translation id="5856721540245522153">डीबगिंग वैशिष्ट्ये सक्षम करा</translation>
 <translation id="5857090052475505287">नवीन फोल्डर</translation>
+<translation id="585979798156957858">बाह्य मेटा</translation>
 <translation id="5860033963881614850">बंद</translation>
 <translation id="5860209693144823476">टॅब 3</translation>
 <translation id="5860491529813859533">चालू करा</translation>
@@ -3748,6 +3759,7 @@
 <translation id="6710213216561001401">मागील</translation>
 <translation id="6718273304615422081">झिप करत आहे...</translation>
 <translation id="671928215901716392">लॉक स्क्रीन</translation>
+<translation id="6720847671508630642">Android ची उत्कृष्टता आपोआप तुमच्या Chromebook सोबत शेअर करा. तुमचा फोन कनेक्ट करून तुम्ही कॉंप्युटरवरून एसएमएस पाठवू शकता, तुमच्या फोनचे इंटरनेट कनेक्शन शेअर करू शकता आणि तुमच्या Chromebook ची स्क्रीन अनलॉक करू शकता. <ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />अधिक जाणून घ्या<ph name="LINK_END" /></translation>
 <translation id="6721678857435001674">तुमच्या सिक्युरिटी कीची निर्मिती आणि मॉडेल पहा</translation>
 <translation id="6721972322305477112">&amp;फाइल</translation>
 <translation id="672213144943476270">कृपया अतिथी म्हणून ब्राउझ करण्यापूर्वी आपले प्रोफाईल अनलॉक करा.</translation>
@@ -3894,6 +3906,7 @@
 <translation id="6970856801391541997">विशिष्ट पेज प्रिंट करा</translation>
 <translation id="6972180789171089114">ऑडिओ/व्हिडिओ</translation>
 <translation id="6973630695168034713">फोल्डर</translation>
+<translation id="6974609594866392343">ऑफलाइन डेमो मोड</translation>
 <translation id="6976108581241006975">JavaScript कन्सोल</translation>
 <translation id="6977381486153291903">फर्मवेयर पुनरावृत्ती</translation>
 <translation id="6978121630131642226">शोध इंजिने</translation>
@@ -4171,7 +4184,6 @@
 <translation id="7387829944233909572">"ब्राउझ केलेला डेटा क्लिअर करा" संवाद</translation>
 <translation id="7388044238629873883">आपण जवळपास पूर्ण केले!</translation>
 <translation id="7388222713940428051">अतिथी विंडो उघडा</translation>
-<translation id="7391648274628927397">तुमच्यासाठी बुकमार्क जोडला गेला आहे</translation>
 <translation id="7392118418926456391">व्हायरस स्कॅन अयशस्वी</translation>
 <translation id="7392915005464253525">बंद केलेली विंडो पु&amp;न्हा उघडा</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> सिस्टम सुरू करताना लाँच केले जाईल आणि एकदा आपण सर्व अन्य <ph name="PRODUCT_NAME" /> विंडो बंद केल्यावर देखील पार्श्वभूमीमध्ये चालू राहील.</translation>
@@ -4487,6 +4499,7 @@
 <translation id="7857949311770343000">आपण अपेक्षा करत होता ते हे नवीन पृष्ठ आहे?</translation>
 <translation id="786073089922909430">सेवा: <ph name="ARC_PROCESS_NAME" /></translation>
 <translation id="7861215335140947162">&amp;डाउनलोड</translation>
+<translation id="7864662577698025113">नवीन सेवा जोडा</translation>
 <translation id="7868378670806575181">{NUM_COOKIES,plural, =1{एक कुकी}one{# कुकी}other{# कुकी}}</translation>
 <translation id="786957569166715433"><ph name="DEVICE_NAME" /> - जोडणी केली</translation>
 <translation id="7870730066603611552">सेटअपनंतर सिंक पर्यायांचे परीक्षण करा</translation>
@@ -5001,6 +5014,7 @@
 <translation id="8666584013686199826">साइटला केव्हा USB डिव्हाइस अॅक्सेस करायचे आहेत हे विचारा</translation>
 <translation id="8667328578593601900"><ph name="FULLSCREEN_ORIGIN" /> आता फुलस्क्रीन असून आपला माऊस कर्सर अक्षम केला आहे.</translation>
 <translation id="8669284339312441707">गरम</translation>
+<translation id="8669919703154928649">असिस्टंटला तुम्हाला सूचना दाखवू द्या</translation>
 <translation id="8669949407341943408">हलवत आहे...</translation>
 <translation id="8671210955687109937">टिप्पणी देऊ शकत नाही</translation>
 <translation id="8673026256276578048">वेबवर शोधा...</translation>
@@ -5389,6 +5403,7 @@
 <translation id="964286338916298286">तुमच्या IT अॅडमिनिस्ट्रेटरने तुमच्या डिव्हाइससाठी Chrome Goodies बंद केले आहे.</translation>
 <translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{अॅप्लिकेशन}one{अॅप्लिकेशन}other{अॅप्लिकेशन}}</translation>
 <translation id="967007123645306417">हे तुम्हाला तुमच्या Google खात्यांवरून साइन आउट करेल. तुमचे बुकमार्क, इतिहास, पासवर्ड आणि इतर सेटिंग्जमध्ये केलेले बदल तुमच्या Google खात्याशी सिंक केले जाणार नाहीत. मात्र तुमचा सद्य डेटा तुमच्या Google खात्यामध्ये स्टोअर केलेला राहील आणि तो <ph name="BEGIN_LINK" />Google डॅशबोर्ड<ph name="END_LINK" /> वर व्यवस्थापित करता येईल.</translation>
+<translation id="967624055006145463">डेटा स्टोअर केला</translation>
 <translation id="968000525894980488">Google Play सेवा चालू करा.</translation>
 <translation id="968174221497644223">अॅप्लिकेशन कॅश  </translation>
 <translation id="969096075394517431">भाषा बदला</translation>
diff --git a/chrome/app/resources/generated_resources_ms.xtb b/chrome/app/resources/generated_resources_ms.xtb
index dd0c962..cf8ae9e 100644
--- a/chrome/app/resources/generated_resources_ms.xtb
+++ b/chrome/app/resources/generated_resources_ms.xtb
@@ -4170,7 +4170,6 @@
 <translation id="7387829944233909572">Dialog "Kosongkan data semakan imbas"</translation>
 <translation id="7388044238629873883">Anda hampir selesai!</translation>
 <translation id="7388222713940428051">Buka tetingkap Tetamu</translation>
-<translation id="7391648274628927397">Penanda halaman telah ditambahkan untuk anda</translation>
 <translation id="7392118418926456391">Imbasan virus gagal</translation>
 <translation id="7392915005464253525">B&amp;uka semula tetingkap ditutup</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> akan melancarkan permulaan sistem dan terus dijalankan di latar belakang walaupun sebaik sahaja anda menutup semua tetingkap <ph name="PRODUCT_NAME" /> lain.</translation>
diff --git a/chrome/app/resources/generated_resources_nl.xtb b/chrome/app/resources/generated_resources_nl.xtb
index f5fae77c..389fdd43 100644
--- a/chrome/app/resources/generated_resources_nl.xtb
+++ b/chrome/app/resources/generated_resources_nl.xtb
@@ -2172,7 +2172,7 @@
 <translation id="4289540628985791613">Overzicht</translation>
 <translation id="4295072614469448764">De app is beschikbaar in je terminal. Mogelijk staat er ook een pictogram van de app in de Launcher.</translation>
 <translation id="4296575653627536209">Gebruiker met beperkte rechten toevoegen</translation>
-<translation id="4297219207642690536">Opnieuw starten en resetten</translation>
+<translation id="4297219207642690536">Opnieuw opstarten en resetten</translation>
 <translation id="4297322094678649474">Talen wijzigen</translation>
 <translation id="4300305918532693141">Als je deze instelling wilt wijzigen, moet je de <ph name="BEGIN_LINK" />synchronisatie resetten<ph name="END_LINK" />.</translation>
 <translation id="4305227814872083840">lang (2 sec.)</translation>
@@ -4170,7 +4170,6 @@
 <translation id="7387829944233909572">Dialoogvenster 'Browsegegevens wissen'</translation>
 <translation id="7388044238629873883">Je bent bijna klaar.</translation>
 <translation id="7388222713940428051">Gastvenster openen</translation>
-<translation id="7391648274628927397">Er is een bladwijzer voor je toegevoegd</translation>
 <translation id="7392118418926456391">Virusscan is mislukt</translation>
 <translation id="7392915005464253525">G&amp;esloten venster opnieuw openen</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> wordt gestart zodra je het systeem opstart en blijft zelfs op de achtergrond actief als je alle andere vensters van <ph name="PRODUCT_NAME" /> sluit.</translation>
diff --git a/chrome/app/resources/generated_resources_no.xtb b/chrome/app/resources/generated_resources_no.xtb
index 7fc3f1b1..d83a9c2 100644
--- a/chrome/app/resources/generated_resources_no.xtb
+++ b/chrome/app/resources/generated_resources_no.xtb
@@ -4025,6 +4025,7 @@
 <translation id="7175353351958621980">Lastet fra:</translation>
 <translation id="7180611975245234373">Last inn på nytt</translation>
 <translation id="7180865173735832675">Tilpass</translation>
+<translation id="7182359331070524176">Velg et Google Foto-album</translation>
 <translation id="7186088072322679094">Behold i verktøyraden</translation>
 <translation id="7187428571767585875">Registeroppføringer som skal fjernes eller endres:</translation>
 <translation id="7189234443051076392">Sørg for at det er nok plass på enheten</translation>
@@ -4159,7 +4160,6 @@
 <translation id="7387829944233909572">Dialogboksen Fjern Internett-data</translation>
 <translation id="7388044238629873883">Du er nesten ferdig!</translation>
 <translation id="7388222713940428051">Åpne et gjestevindu</translation>
-<translation id="7391648274628927397">Et bokmerke er lagt til for deg</translation>
 <translation id="7392118418926456391">Virusskanningen mislyktes</translation>
 <translation id="7392915005464253525">Gj&amp;enåpne det lukkede vinduet</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> starter ved systemoppstart, og fortsetter å kjøre i bakgrunnen selv når du har lukket alle andre vinduer for <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_pl.xtb b/chrome/app/resources/generated_resources_pl.xtb
index bf2dcf3..1551595 100644
--- a/chrome/app/resources/generated_resources_pl.xtb
+++ b/chrome/app/resources/generated_resources_pl.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Okno dialogowe „Wyczyść dane przeglądania”</translation>
 <translation id="7388044238629873883">Wszystko jest już prawie gotowe.</translation>
 <translation id="7388222713940428051">Otwórz okno trybu gościa</translation>
-<translation id="7391648274628927397">Masz nową zakładkę</translation>
 <translation id="7392118418926456391">Skanowanie antywirusowe nie powiodło się</translation>
 <translation id="7392915005464253525">Otwórz ponowni&amp;e zamknięte okno</translation>
 <translation id="7396845648024431313">Aplikacja <ph name="APP_NAME" /> zostanie włączona podczas uruchamiania systemu i będzie działać w tle nawet po zamknięciu wszystkich pozostałych okien przeglądarki <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_pt-BR.xtb b/chrome/app/resources/generated_resources_pt-BR.xtb
index 206e2dd9..eef7fec 100644
--- a/chrome/app/resources/generated_resources_pt-BR.xtb
+++ b/chrome/app/resources/generated_resources_pt-BR.xtb
@@ -4171,7 +4171,6 @@
 <translation id="7387829944233909572">Caixa de diálogo "Limpar dados de navegação"</translation>
 <translation id="7388044238629873883">Está quase terminando!</translation>
 <translation id="7388222713940428051">Abrir janela de visitante</translation>
-<translation id="7391648274628927397">Um favorito foi adicionado para você</translation>
 <translation id="7392118418926456391">Falha na verificação de vírus</translation>
 <translation id="7392915005464253525">R&amp;eabrir janela fechada</translation>
 <translation id="7396845648024431313">O <ph name="APP_NAME" /> será iniciado na inicialização do sistema e continuará sendo executado em segundo plano, mesmo quando você fechar todas as outras janelas de <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_pt-PT.xtb b/chrome/app/resources/generated_resources_pt-PT.xtb
index 9de4ceab..0675274 100644
--- a/chrome/app/resources/generated_resources_pt-PT.xtb
+++ b/chrome/app/resources/generated_resources_pt-PT.xtb
@@ -4171,7 +4171,6 @@
 <translation id="7387829944233909572">Menu de diálogo "Limpar dados de navegação"</translation>
 <translation id="7388044238629873883">Está quase!</translation>
 <translation id="7388222713940428051">Abrir janela de convidado</translation>
-<translation id="7391648274628927397">Foi adicionado um marcador.</translation>
 <translation id="7392118418926456391">Falha ao analisar vírus</translation>
 <translation id="7392915005464253525">Voltar a abrir a jan&amp;ela fechada</translation>
 <translation id="7396845648024431313">O <ph name="APP_NAME" /> é iniciado no arranque do sistema e continua a ser executado em segundo plano, mesmo depois de fechar todas as restantes janelas do <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_ro.xtb b/chrome/app/resources/generated_resources_ro.xtb
index 885f897c..5942da0 100644
--- a/chrome/app/resources/generated_resources_ro.xtb
+++ b/chrome/app/resources/generated_resources_ro.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Caseta de dialog „Șterge datele de navigare”</translation>
 <translation id="7388044238629873883">Aproape ați terminat!</translation>
 <translation id="7388222713940428051">Deschide o fereastră pentru invitați</translation>
-<translation id="7391648274628927397">A fost adăugat un marcaj</translation>
 <translation id="7392118418926456391">Scanarea antivirus nu a reușit</translation>
 <translation id="7392915005464253525">R&amp;edeschide fereastra închisă</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> se va lansa la pornirea sistemului și va continua să ruleze în fundal, chiar după ce ai închis toate celelalte ferestre ale <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_ru.xtb b/chrome/app/resources/generated_resources_ru.xtb
index 4337a63f..ff6f03b 100644
--- a/chrome/app/resources/generated_resources_ru.xtb
+++ b/chrome/app/resources/generated_resources_ru.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Диалоговое окно "Очистка истории"</translation>
 <translation id="7388044238629873883">Почти готово!</translation>
 <translation id="7388222713940428051">Открыть окно в гостевом режиме</translation>
-<translation id="7391648274628927397">Закладка добавлена.</translation>
 <translation id="7392118418926456391">Не удалось выполнить проверку на вирусы</translation>
 <translation id="7392915005464253525">О&amp;ткрыть закрытое окно</translation>
 <translation id="7396845648024431313">Приложение <ph name="APP_NAME" /> будет запущено при старте системы и продолжит работу в фоновом режиме даже после закрытия всех окон <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_sk.xtb b/chrome/app/resources/generated_resources_sk.xtb
index 62a6371..7bea0a3 100644
--- a/chrome/app/resources/generated_resources_sk.xtb
+++ b/chrome/app/resources/generated_resources_sk.xtb
@@ -401,6 +401,7 @@
 <translation id="1589055389569595240">Zobraziť pravopis a gramatiku</translation>
 <translation id="1593594475886691512">Prebieha formátovanie...</translation>
 <translation id="159359590073980872">Vyrovnávacia pamäť obrázkov</translation>
+<translation id="1593926297800505364">Uložiť spôsob platby</translation>
 <translation id="1598233202702788831">Aktualizácie zakázal váš správca.</translation>
 <translation id="1600857548979126453">Pristupovať k ladiacemu nástroju stránok na strane servera</translation>
 <translation id="1601560923496285236">Použiť</translation>
@@ -511,6 +512,7 @@
 <translation id="1744060673522309905">Zariadenie sa nepodarilo pripojiť k doméne. Skontrolujte, či ste nepresiahli počet zariadení, ktoré môžete pridať.</translation>
 <translation id="1744108098763830590">stránka na pozadí</translation>
 <translation id="1745520510852184940">Vždy prekladať</translation>
+<translation id="1746417874336251387">Poskytujte nové funkcie, ktoré využívajú pripojenie telefónu k Chromebooku</translation>
 <translation id="174937106936716857">Celkový počet súborov</translation>
 <translation id="175196451752279553">Znovu o&amp;tvoriť zatvorenú kartu</translation>
 <translation id="1753905327828125965">Najviac navšt.</translation>
@@ -641,8 +643,10 @@
 <translation id="1932098463447129402">Nie pred</translation>
 <translation id="1933809209549026293">Pripojte myš alebo klávesnicu. Ak používate zariadenie Bluetooth, uistite sa, že je pripravené na párovanie.</translation>
 <translation id="1936157145127842922">Zobraziť v priečinku</translation>
+<translation id="1938351510777341717">Externý príkaz</translation>
 <translation id="1940546824932169984">Pripojené zariadenia</translation>
 <translation id="1942765061641586207">Rozlíšenie obrázka</translation>
+<translation id="1943097386230153518">Inštalovať novú službu</translation>
 <translation id="1944921356641260203">Je k dispozícii aktualizácia</translation>
 <translation id="1951615167417147110">Posunúť hore o jednu stránku</translation>
 <translation id="1954813140452229842">Pri pripájaní zdieľaného úložiska sa vyskytla chyba. Skontrolujte poverenia a skúste to znova.</translation>
@@ -1108,6 +1112,7 @@
 <translation id="2653659639078652383">Odoslať</translation>
 <translation id="265390580714150011">Hodnota poľa</translation>
 <translation id="2654166010170466751">Povoliť webom inštalovať obslužné nástroje platieb</translation>
+<translation id="2659381484350128933"><ph name="FOOTNOTE_POINTER" />Funkcie sa môžu v rôznych zariadeniach líšiť</translation>
 <translation id="2660779039299703961">Udalosť</translation>
 <translation id="266079277508604648">K tlačiarni sa nepodarilo pripojiť. Skontrolujte, či je zapnutá a pripojená k Chromebooku prostredníctvom Wi-Fi alebo USB.</translation>
 <translation id="2661146741306740526">16x9</translation>
@@ -1214,6 +1219,7 @@
 <translation id="2803375539583399270">Zadajte kód PIN</translation>
 <translation id="2805646850212350655">Systém šifrovania súborov spoločnosti Microsoft</translation>
 <translation id="2805756323405976993">Aplikácie</translation>
+<translation id="2806891468525657116">Skratka už existuje</translation>
 <translation id="2807517655263062534">Tu sa zobrazia súbory, ktoré stiahnete</translation>
 <translation id="2809586584051668049">a ďalšie (počet: <ph name="NUMBER_ADDITIONAL_DISABLED" />)</translation>
 <translation id="281133045296806353">Vytvorené nové okno v existujúcej relácii prehliadača.</translation>
@@ -1600,6 +1606,7 @@
 <translation id="3428419049384081277">Ste prihlásený/-á!</translation>
 <translation id="3429275422858276529">Uložte si túto stránku ako záložku, aby ste ju neskôr ľahko našli</translation>
 <translation id="3429599832623003132">počet položiek: $1</translation>
+<translation id="3430342160185525240">Umožňuje Asistentovi zobraziť vám upozornenia.</translation>
 <translation id="3432227430032737297">Odstrániť všetky zobrazené</translation>
 <translation id="3432757130254800023">Poslať zvuk a video na obrazovky v miestnej sieti</translation>
 <translation id="3432762828853624962">Zdieľaní pracovníci</translation>
@@ -1964,6 +1971,7 @@
   <ph name="CONTROL_PANEL_APPLET_NAME" /> na ovládacom paneli.
 
   Chcete spustiť aplet <ph name="CONTROL_PANEL_APPLET_NAME" />?</translation>
+<translation id="394183848452296464">Skratka sa nedá vytvoriť</translation>
 <translation id="3943582379552582368">&amp;Naspäť</translation>
 <translation id="3943857333388298514">Prilepiť</translation>
 <translation id="3948116654032448504">&amp;Hľadať obrázok v službe <ph name="SEARCH_ENGINE" /></translation>
@@ -2271,6 +2279,7 @@
 <translation id="4481530544597605423">Nespárované zariadenia</translation>
 <translation id="4482194545587547824">Google môže používať vašu históriu prehliadania na prispôsobenie Vyhľadávania a ďalších služieb Google</translation>
 <translation id="4495419450179050807">Nezobrazovať na tejto stránke</translation>
+<translation id="4499718683476608392">Povoľte automatické dopĺňanie kreditných kariet na vypĺňanie formulárov jediným kliknutím</translation>
 <translation id="4500114933761911433">Doplnok <ph name="PLUGIN_NAME" /> zlyhal</translation>
 <translation id="450099669180426158">Ikona Výkričník</translation>
 <translation id="4501530680793980440">Potvrdiť odstránenie</translation>
@@ -3115,6 +3124,7 @@
 <translation id="5752453871435543420">Chrome OS – záloha v cloude</translation>
 <translation id="5756163054456765343">Centrum pomoci</translation>
 <translation id="5759728514498647443">Dokumenty odoslané na tlač prostredníctvom aplikácie <ph name="APP_NAME" /> môžete čítať pomocou aplikácie <ph name="APP_NAME" />.</translation>
+<translation id="5762172915276660232">Nastavenia kreditnej karty</translation>
 <translation id="5763751966069581670">Nenašli sa žiadne zariadenia USB</translation>
 <translation id="5764483294734785780">Uložiť &amp;audio ako...</translation>
 <translation id="57646104491463491">Dátum úpravy</translation>
@@ -3186,6 +3196,7 @@
 <translation id="5855773610748894548">Ojoj, chyba bezpečného modulu.</translation>
 <translation id="5856721540245522153">Aktivácia funkcií ladenia</translation>
 <translation id="5857090052475505287">Nový priečinok</translation>
+<translation id="585979798156957858">Externý metakláves</translation>
 <translation id="5860033963881614850">Vypnuté</translation>
 <translation id="5860209693144823476">Karta 3</translation>
 <translation id="5860491529813859533">Zapnúť</translation>
@@ -3746,6 +3757,7 @@
 <translation id="6710213216561001401">Dozadu</translation>
 <translation id="6718273304615422081">Prebieha komprimovanie...</translation>
 <translation id="671928215901716392">Uzamknúť obrazovku</translation>
+<translation id="6720847671508630642">Automaticky zdieľajte to najlepšie z Androidu so svojím Chromebookom. Pripojte svoj telefón, aby ste mohli odosielať textové správy z počítača, zdieľať internetové pripojenie telefónu a odomykať obrazovku Chromebooku.<ph name="FOOTNOTE_POINTER" /> <ph name="LINK_BEGIN" />Ďalšie informácie<ph name="LINK_END" /></translation>
 <translation id="6721678857435001674">Zobrazenie modelu a značky vášho bezpečnostného kľúča</translation>
 <translation id="6721972322305477112">&amp;Súbor</translation>
 <translation id="672213144943476270">Skôr než začnete prehliadať ako hosť, odomknite svoj profil.</translation>
@@ -3892,6 +3904,7 @@
 <translation id="6970856801391541997">Vytlačiť konkrétne stránky</translation>
 <translation id="6972180789171089114">Zvuk / video</translation>
 <translation id="6973630695168034713">Priečinky</translation>
+<translation id="6974609594866392343">Offline režim ukážky</translation>
 <translation id="6976108581241006975">Konzola jazyka JavaScript</translation>
 <translation id="6977381486153291903">Revízia firmvéru</translation>
 <translation id="6978121630131642226">Vyhľadávače</translation>
@@ -4169,7 +4182,6 @@
 <translation id="7387829944233909572">Dialógové okno „Vymazať údaje prehliadania“</translation>
 <translation id="7388044238629873883">Už ste skoro na konci!</translation>
 <translation id="7388222713940428051">Otvoriť okno hosťa</translation>
-<translation id="7391648274628927397">Pridali sme vám záložku</translation>
 <translation id="7392118418926456391">Antivírusová kontrola zlyhala</translation>
 <translation id="7392915005464253525">Znovu o&amp;tvoriť zatvorené okno</translation>
 <translation id="7396845648024431313">Aplikácia <ph name="APP_NAME" /> sa spustí pri spustení systému a bude fungovať na pozadí aj po zatvorení všetkých okien programu <ph name="PRODUCT_NAME" />.</translation>
@@ -4487,6 +4499,7 @@
 <translation id="7857949311770343000">Je toto stránka na novej karte, ktorú ste čakali?</translation>
 <translation id="786073089922909430">Služba: <ph name="ARC_PROCESS_NAME" /></translation>
 <translation id="7861215335140947162">&amp;Stiahnuté</translation>
+<translation id="7864662577698025113">Pridať novú službu</translation>
 <translation id="7868378670806575181">{NUM_COOKIES,plural, =1{1 súbor cookie}few{# súbory cookie}many{# cookies}other{# súborov cookie}}</translation>
 <translation id="786957569166715433"><ph name="DEVICE_NAME" /> – spárované</translation>
 <translation id="7870730066603611552">Po nastavení prezrieť možnosti synchronizácie</translation>
@@ -5001,6 +5014,7 @@
 <translation id="8666584013686199826">Opýtať sa, keď chce web získať prístup k zariadeniam USB</translation>
 <translation id="8667328578593601900">Stránka <ph name="FULLSCREEN_ORIGIN" /> je teraz zobrazená na celú obrazovku a zakázala kurzor myši.</translation>
 <translation id="8669284339312441707">Teplejšie</translation>
+<translation id="8669919703154928649">Povoliť Asistentovi zobrazovať upozornenia</translation>
 <translation id="8669949407341943408">Presúva sa...</translation>
 <translation id="8671210955687109937">Môže pridávať komentáre</translation>
 <translation id="8673026256276578048">Vyhľadávanie na webe...</translation>
@@ -5388,6 +5402,7 @@
 <translation id="964286338916298286">Správca IT zakázal v zariadení doplnky Chrome Goodies.</translation>
 <translation id="964439421054175458">{NUM_APLLICATIONS,plural, =1{Aplikácia}few{Aplikácie}many{Aplikácie}other{Aplikácie}}</translation>
 <translation id="967007123645306417">Týmto sa odhlásite z účtov Google. Zmeny záložiek, histórie hesiel a ďalších nastavení sa už nebudú synchronizovať do vášho účtu Google. Vaše existujúce dáta však zostanú uložené v účte Google a môžete ich spravovať pomocou služby <ph name="BEGIN_LINK" />Google Dashboard<ph name="END_LINK" />.</translation>
+<translation id="967624055006145463">Uložené dáta</translation>
 <translation id="968000525894980488">Zapnite Služby Google Play</translation>
 <translation id="968174221497644223">Vyrovnávacia pamäť aplikácie</translation>
 <translation id="969096075394517431">Zmeniť jazyky</translation>
diff --git a/chrome/app/resources/generated_resources_sl.xtb b/chrome/app/resources/generated_resources_sl.xtb
index d4e14eb..b842f938 100644
--- a/chrome/app/resources/generated_resources_sl.xtb
+++ b/chrome/app/resources/generated_resources_sl.xtb
@@ -4168,7 +4168,6 @@
 <translation id="7387829944233909572">Pogovorno okno »Izbriši podatke brskanja«</translation>
 <translation id="7388044238629873883">Skoraj ste že končali.</translation>
 <translation id="7388222713940428051">Odpri okno načina za goste</translation>
-<translation id="7391648274628927397">Dodan je bil zaznamek</translation>
 <translation id="7392118418926456391">Protivirusni pregled ni uspel</translation>
 <translation id="7392915005464253525">&amp;Znova odpri zaprto okno</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> se bo zagnal ob zagonu sistema in se izvajal v ozadju, tudi ko zaprete vsa druga okna programa <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_sr.xtb b/chrome/app/resources/generated_resources_sr.xtb
index a3f0212..4b60f8d 100644
--- a/chrome/app/resources/generated_resources_sr.xtb
+++ b/chrome/app/resources/generated_resources_sr.xtb
@@ -4165,7 +4165,6 @@
 <translation id="7387829944233909572">Дијалог „Обриши податке прегледања" </translation>
 <translation id="7388044238629873883">Још мало па готово!</translation>
 <translation id="7388222713940428051">Отвори прозор госта</translation>
-<translation id="7391648274628927397">Обележивач је додат за вас</translation>
 <translation id="7392118418926456391">Скенирање вируса није успело</translation>
 <translation id="7392915005464253525">П&amp;оново отвори затворен прозор</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> ће се покренути приликом покретања система и наставити да ради у позадини чак и када затворите све остале <ph name="PRODUCT_NAME" /> прозоре.</translation>
diff --git a/chrome/app/resources/generated_resources_sv.xtb b/chrome/app/resources/generated_resources_sv.xtb
index d562a00..9fb23a6 100644
--- a/chrome/app/resources/generated_resources_sv.xtb
+++ b/chrome/app/resources/generated_resources_sv.xtb
@@ -4167,7 +4167,6 @@
 <translation id="7387829944233909572">Dialogrutan Rensa sökdata</translation>
 <translation id="7388044238629873883">Du är nästan klar.</translation>
 <translation id="7388222713940428051">Öppna ett gästfönster</translation>
-<translation id="7391648274628927397">Vi har lagt till ett bokmärke åt dig</translation>
 <translation id="7392118418926456391">Virussökningen misslyckades</translation>
 <translation id="7392915005464253525">Ö&amp;ppna ett stängt fönster igen</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> startas när systemet startas och fortsätter att köras i bakgrunden även när du har stängt alla andra fönster i <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_sw.xtb b/chrome/app/resources/generated_resources_sw.xtb
index 279dc308..e726e0f 100644
--- a/chrome/app/resources/generated_resources_sw.xtb
+++ b/chrome/app/resources/generated_resources_sw.xtb
@@ -1876,7 +1876,7 @@
 <translation id="3827774300009121996">&amp;Skrini Kamili</translation>
 <translation id="3828029223314399057">Tafuta katika alamisho</translation>
 <translation id="3830674330436234648">Kipengele cha kucheza hakipatikani.</translation>
-<translation id="3831436149286513437">Mapendekeo ya utafutaji ya Hifadhi ya Google</translation>
+<translation id="3831436149286513437">Mapendekezo ya utafutaji ya Hifadhi ya Google</translation>
 <translation id="3831486154586836914">Hali ya muhtasari wa dirisha imeingizwa</translation>
 <translation id="383161972796689579">Mmiliki wa kifaa hiki amelemaza kuongezwa kwa watumiaji wapya</translation>
 <translation id="3834775135533257713">Hukuweza kuongeza programu ya "<ph name="TO_INSTALL_APP_NAME" />" kwa sababu inakinzana na "<ph name="INSTALLED_APP_NAME" />".</translation>
@@ -4159,7 +4159,6 @@
 <translation id="7387829944233909572">"Futa data ya kuvinjari" kidadisi</translation>
 <translation id="7388044238629873883">Unakaribia kumaliza!</translation>
 <translation id="7388222713940428051">Fungua Dirisha la Mgeni</translation>
-<translation id="7391648274628927397">Tumekuongezea alamisho</translation>
 <translation id="7392118418926456391">Utambazaji wa Virusi Umeshindwa</translation>
 <translation id="7392915005464253525">&amp;Fungua tena ukurasa uliofungwa</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> itazinduliwa katika kuanzishwa kwa mfumo na iendelee kuendeshwa katika mandharinyuma hata pindi tu umefunga madirisha mengine <ph name="PRODUCT_NAME" /> yote.</translation>
@@ -4554,7 +4553,7 @@
 <translation id="7974936243149753750">Angalia kwa ujumla</translation>
 <translation id="7977551819349545646">Inasasisha Chromebox...</translation>
 <translation id="7978412674231730200">Ufunguo binafsi</translation>
-<translation id="7978450511781612192">Hii itakuondoa kwenye Akaunti za Google. Alamisho, historia, manenosiri yako na mengineyo hayatasawazishwa tena.</translation>
+<translation id="7978450511781612192">Hatua hii itakuondoa kwenye Akaunti za Google. Alamisho, historia, manenosiri yako na mengineyo hayatasawazishwa tena.</translation>
 <translation id="7979036127916589816">Hitilafu ya Usawazishaji</translation>
 <translation id="7980084013673500153">Kitambulisho cha Kipengee: <ph name="ASSET_ID" /></translation>
 <translation id="7981313251711023384">Pakia mapema kurasa ili upate huduma ya haraka ya kuvinjari na kutafuta</translation>
diff --git a/chrome/app/resources/generated_resources_ta.xtb b/chrome/app/resources/generated_resources_ta.xtb
index 788d950..5c6d3c59 100644
--- a/chrome/app/resources/generated_resources_ta.xtb
+++ b/chrome/app/resources/generated_resources_ta.xtb
@@ -425,7 +425,7 @@
 <translation id="1627408615528139100">ஏற்கனவே பதிவிறக்கப்பட்டது</translation>
 <translation id="1632803087685957583">ஒரு விசை மீண்டும் மீண்டும் உள்ளிடப்படும் வேகம், சொல்லைக் கணித்தல் போன்ற விசைப்பலகை அமைப்புகள் பலவற்றை மாற்ற அனுமதிக்கும்</translation>
 <translation id="1635033183663317347">பாதுகாப்பாளர் நிறுவியது.</translation>
-<translation id="1635885551358739414">Chrome மற்றும் மொழியாக்கம், தேடல், விளம்பரங்கள் போன்ற Google சேவைகளைத் தனிப்பயனாக்க, நீங்கள் பார்வையிடும் தளங்களில் உள்ள உள்ளடக்கம், நீங்கள் மேற்கொள்ளும் உலாவல் செயல்பாடு, ஊடாடல்கள் போன்றவற்றை Google பயன்படுத்தக்கூடும். இதை எப்போது வேண்டுமானாலும் அமைப்புகளில் மாற்றியமைக்கலாம்.</translation>
+<translation id="1635885551358739414">Chrome மற்றும் 'மொழியாக்கம்', 'தேடல்', விளம்பரங்கள் போன்ற Google சேவைகளைத் தனிப்பயனாக்க, நீங்கள் பார்வையிடும் தளங்களில் உள்ள உள்ளடக்கம், நீங்கள் மேற்கொள்ளும் உலாவல் செயல்பாடு, ஊடாடல்கள் போன்றவற்றை Google பயன்படுத்தக்கூடும். இதை எப்போது வேண்டுமானாலும் அமைப்புகளில் மாற்றியமைக்கலாம்.</translation>
 <translation id="1637224376458524414">இந்தப் புத்தகக்குறியை உங்கள் iPhone இல் பெறுங்கள்</translation>
 <translation id="1637765355341780467">சுயவிவரத்தைத் திறக்கும் போது, ஏதோ தவறாகிவிட்டது. சில அம்சங்கள் கிடைக்காமல் போகக்கூடும்.</translation>
 <translation id="1639239467298939599">ஏற்றுகிறது</translation>
@@ -456,7 +456,7 @@
 <translation id="166179487779922818">கடவுச்சொல் மிகச் சிறியதாக உள்ளது.</translation>
 <translation id="1661867754829461514">PIN இல்லை</translation>
 <translation id="16620462294541761">மன்னிக்கவும், உங்கள் கடவுச்சொல்லைச் சரிபார்க்க முடியவில்லை. தயவுசெய்து மீண்டும் முயற்சி செய்க.</translation>
-<translation id="1662550410081243962">சேமி &amp; கட்டண முறைகளைத் தானாக நிரப்பு</translation>
+<translation id="1662550410081243962">கட்டண முறைகளைச் சேமித்துத் தானாக நிரப்பு</translation>
 <translation id="166278006618318542">பொருள் பொது விசை அல்காரிதம்</translation>
 <translation id="166439687370499867">பகிர்ந்த நெட்வொர்க் உள்ளமைவுகளை மாற்றுவதற்கு அனுமதியில்லை</translation>
 <translation id="1665611772925418501">கோப்பை மாற்ற முடியவில்லை.</translation>
@@ -675,7 +675,7 @@
 <translation id="1997484222658892567">உங்கள் அகக் கணினியில், அதிக அளவு தரவை நிரந்தரமாகச் சேமிக்க <ph name="URL" /> விரும்புகிறது</translation>
 <translation id="1997616988432401742">உங்கள் சான்றிதழ்கள்</translation>
 <translation id="1999115740519098545">தொடக்கத்தில்</translation>
-<translation id="2000419248597011803">முகவரிப் பட்டி மற்றும் தேடல் பெட்டியிலிருந்து சில குக்கீகளையும் தேடல்களையும் உங்கள் இயல்புத் தேடல் இன்ஜினுக்கு அனுப்பும்</translation>
+<translation id="2000419248597011803">முகவரிப் பட்டியிலிருந்தும், தேடல் பெட்டியிலிருந்தும் சில குக்கீகளையும் தேடல்களையும் உங்கள் இயல்புத் தேடல் இன்ஜினுக்கு அனுப்பும்</translation>
 <translation id="2001796770603320721">இயக்ககத்தில் நிர்வகி</translation>
 <translation id="2004663115385769400">$1 இல் திறக்க இயலவில்லை</translation>
 <translation id="200544492091181894">இதை எப்போது வேண்டுமானாலும் பின்னர் அமைப்புகளில் மாற்றிக்கொள்ளலாம்</translation>
@@ -791,7 +791,7 @@
 <translation id="2178098616815594724"><ph name="PEPPER_PLUGIN_DOMAIN" /> இல் உள்ள <ph name="PEPPER_PLUGIN_NAME" /> உங்கள் கணினியை அணுக விரும்புகிறது</translation>
 <translation id="2178614541317717477">CA இணக்கம்</translation>
 <translation id="218070003709087997">எத்தனை நகல்கள் அச்சிடப்பட வேண்டும் என்பதைக் குறிக்க, எண்ணைப் (1 - 999) பயன்படுத்தவும்.</translation>
-<translation id="2183558561014688873">சாதனம் இயக்கத்திலுள்ளபோது மற்றும் பூட்டப்படாதபோது, "OK Google" என்று சொல்லி எந்த நேரத்திலும் அசிஸ்டண்ட்டை அணுகலாம்.</translation>
+<translation id="2183558561014688873">சாதனம் பூட்டப்படாமல் இயங்கிக் கொண்டிருக்கும்போது, "OK Google" என்று சொல்லி எந்த நேரத்திலும் அசிஸ்டண்ட்டை அணுகலாம்.</translation>
 <translation id="2187895286714876935">சேவையக சான்றிதழ் இறக்குமதி பிழை</translation>
 <translation id="2187906491731510095">நீட்டிப்புகள் புதுப்பிக்கப்பட்டன</translation>
 <translation id="2188881192257509750"><ph name="APPLICATION" />ஐத் திற</translation>
@@ -863,7 +863,7 @@
 <translation id="2282146716419988068">GPU செயல்முறை</translation>
 <translation id="2282155092769082568">தானியங்கு உள்ளமைவு URL:</translation>
 <translation id="2283117145434822734">F6</translation>
-<translation id="2283340219607151381">சேமி &amp; முகவரிகளைத் தானாக நிரப்பு</translation>
+<translation id="2283340219607151381">முகவரிகளைச் சேமித்துத் தானாக நிரப்பு</translation>
 <translation id="2286841657746966508">பில்லிங் முகவரி</translation>
 <translation id="2288181517385084064">வீடியோ ரெக்கார்டருக்கு மாறு</translation>
 <translation id="2288735659267887385">அணுகல்தன்மை அமைப்புகள்</translation>
@@ -1196,7 +1196,7 @@
 <translation id="2775104091073479743">கைரேகைகளை மாற்று</translation>
 <translation id="2776441542064982094">பிணையத்தில் பதிவுசெய்வதற்கான சாதனங்கள் எதுவும் இல்லாததுபோல் தெரிகிறது. உங்கள் சாதனம் இயக்கத்தில் இருந்து இணையத்துடன் இணைக்கப்பட்டிருந்தால், அதன் வழிகாட்டி கையேட்டில் உள்ள வழிமுறைகளைப் பயன்படுத்தி பதிவுசெய்ய முயற்சிக்கவும்.</translation>
 <translation id="2781692009645368755">Google Pay</translation>
-<translation id="2782104745158847185">Linux ஆப்ஸை நிறுவும்போது பிழை நேர்ந்தது</translation>
+<translation id="2782104745158847185">ஒரு Linux ஆப்ஸை நிறுவும்போது பிழை நேர்ந்தது</translation>
 <translation id="2783298271312924866">பதிவிறக்கப்பட்டது</translation>
 <translation id="2783321960289401138">குறுக்கு வழியை உருவாக்கு...</translation>
 <translation id="2783829359200813069">என்க்ரிப்ஷன் வகைகளைத் தேர்வுசெய்யவும்</translation>
@@ -1221,7 +1221,7 @@
 <translation id="2812944337881233323">வெளியேறி, மீண்டும் உள்நுழையவும்</translation>
 <translation id="2812989263793994277">எந்தப் படங்களையும் காண்பிக்க வேண்டாம்</translation>
 <translation id="2814489978934728345">இந்த பக்கத்தை நினைவேற்றுவதை நிறுத்து</translation>
-<translation id="281504910091592009">உங்கள் <ph name="BEGIN_LINK" />Google கணக்கில்<ph name="END_LINK" /> சேமிக்கப்பட்ட கடவுச்சொற்களைப் பார்க்கவும் &amp; நிர்வகிக்கவும்</translation>
+<translation id="281504910091592009">உங்கள் <ph name="BEGIN_LINK" />Google கணக்கில்<ph name="END_LINK" /> சேமிக்கப்பட்ட கடவுச்சொற்களைப் பார்த்து, நிர்வகிக்கவும்</translation>
 <translation id="2815500128677761940">புக்மார்க் பட்டி</translation>
 <translation id="2815693974042551705">புத்தகக்குறி கோப்புறை</translation>
 <translation id="2818476747334107629">பிரிண்டர் விவரங்கள்</translation>
@@ -1917,7 +1917,7 @@
 <translation id="3871092408932389764">மிகக்குறைவானது</translation>
 <translation id="3872220884670338524">கூடுதல் செயல்கள், <ph name="DOMAIN" /> இல் <ph name="USERNAME" />க்கான சேமித்த கணக்கு</translation>
 <translation id="3872991219937722530">சாதனத்தில் காலி இடத்தை உருவாக்கவும் அல்லது உங்கள் சாதனம் இயங்காது.</translation>
-<translation id="3873315167136380065">இதை இயக்குவதற்கு, உங்கள் ஒத்திசைவுக் கடவுச்சொற்றொடரை அகற்ற, <ph name="BEGIN_LINK" />ஒத்திசைவை மீட்டமைக்கவும்<ph name="END_LINK" /></translation>
+<translation id="3873315167136380065">இதை இயக்குவதற்கு, <ph name="BEGIN_LINK" />ஒத்திசைவை மீட்டமைத்து<ph name="END_LINK" /> உங்கள் ஒத்திசைவுக் கடவுச்சொற்றொடரை அகற்றவும்.</translation>
 <translation id="3878840326289104869">கண்காணிக்கப்படும் பயனரை உருவாக்குகிறது</translation>
 <translation id="3879748587602334249">பதிவிறக்க நிர்வாகி</translation>
 <translation id="3880709822663530586">சாதனத்தின் புளூடூத் இயக்கப்பட்டிருக்கும்போது மட்டுமே பாதுகாப்பு விசை வேலை செய்யும்</translation>
@@ -2751,7 +2751,7 @@
 <translation id="5240817131241497236">Chromeமில் ஒத்திசைவு, தனிப்பயனாக்கம் மற்றும் பிற Google சேவைகளைக் கட்டுப்படுத்தும் அமைப்புகள் மாற்றப்பட்டுள்ளன. இது உங்கள் தற்போதைய அமைப்புகளைப் பாதிக்கலாம்.</translation>
 <translation id="5241128660650683457">நீங்கள் செல்லும் இணையதளங்களில் உள்ள உங்கள் தரவு அனைத்தையும் படிக்கவும்</translation>
 <translation id="5242724311594467048">"<ph name="EXTENSION_NAME" />" ஐ இயக்கவா?</translation>
-<translation id="5243522832766285132">ஒரு சில நிமிடங்களில் மீண்டும் முயலவும்</translation>
+<translation id="5243522832766285132">ஒரு சில வினாடிகளில் மீண்டும் முயலவும்</translation>
 <translation id="5244474230056479698"><ph name="EMAIL" />க்கு ஒத்திசைக்கிறது</translation>
 <translation id="5246282308050205996"><ph name="APP_NAME" /> செயலிழந்தது. பயன்பாட்டை மறுதொடக்கம் செய்ய இந்த பலூனைக் கிளிக் செய்க.</translation>
 <translation id="5247051749037287028">காட்சிப் பெயர் (விரும்பினால்)</translation>
@@ -3067,7 +3067,7 @@
 <translation id="5677503058916217575">பக்கத்தின் மொழி:</translation>
 <translation id="5677928146339483299">தடுக்கப்பட்டது</translation>
 <translation id="5678550637669481956"><ph name="VOLUME_NAME" /> இல் எழுதுவதற்கான, படிப்பதற்கான அணுகல் வழங்கப்பட்டது.</translation>
-<translation id="5678784840044122290">Linux ஆப்ஸ் உங்கள் முனையத்திற்குள் இருக்கும், அத்துடன் உங்கள் தொடக்கியிலும் ஒரு ஐகான் காண்பிக்கப்படலாம்.</translation>
+<translation id="5678784840044122290">இந்த Linux ஆப்ஸ் உங்கள் முனையத்திற்குள் இருக்கும், அத்துடன் உங்கள் தொடக்கியிலும் ஒரு ஐகான் காண்பிக்கப்படலாம்.</translation>
 <translation id="5678955352098267522">உங்கள் தரவை <ph name="WEBSITE_1" /> இல் படிக்கவும்</translation>
 <translation id="5684661240348539843">பண்பு அடையாளங்காட்டி</translation>
 <translation id="5686799162999241776"><ph name="BEGIN_BOLD" />காப்பகம் அல்லது விர்ச்சுவல் வட்டிலிருந்து இணைப்பை நீக்க முடியவில்லை<ph name="END_BOLD" />
@@ -3217,7 +3217,7 @@
 <translation id="5908769186679515905">Flashஐ இயக்குவதிலிருந்து தளங்களைத் தடு</translation>
 <translation id="5910363049092958439">படத்தை இவ்வாறு சே&amp;மி...</translation>
 <translation id="5911737117543891828">தற்காலிக Google இயக்கக ஆஃப்லைன் கோப்புகள் நீக்கப்படும். ஆஃப்லைனில் கிடைக்கும்படி நீங்கள் அமைத்த கோப்புகள், இந்தச் சாதனத்திலிருந்து நீக்கப்படாது.</translation>
-<translation id="5911887972742538906">Linux ஆப்ஸை நிறுவும்போது பிழை நேர்ந்தது.</translation>
+<translation id="5911887972742538906">உங்கள் Linux ஆப்ஸை நிறுவும்போது பிழை நேர்ந்தது.</translation>
 <translation id="5912378097832178659">&amp;தேடுபொறிகளைத் திருத்து...</translation>
 <translation id="5914724413750400082">தனிமதிப்பு (<ph name="MODULUS_NUM_BITS" /> பிட்கள்):
   <ph name="MODULUS_HEX_DUMP" />
@@ -4035,6 +4035,7 @@
 <translation id="7175353351958621980">இதிலிருந்து ஏற்றப்பட்டது:</translation>
 <translation id="7180611975245234373">புதுப்பி</translation>
 <translation id="7180865173735832675">தனிப்பயனாக்கு</translation>
+<translation id="7182359331070524176">Google புகைப்பட ஆல்பத்தைத் தேர்ந்தெடுக்கவும்</translation>
 <translation id="7186088072322679094">கருவிப்பட்டியில் வை</translation>
 <translation id="7187428571767585875">அகற்ற வேண்டிய அல்லது மாற்ற வேண்டிய பதிவக உள்ளீடுகள்:</translation>
 <translation id="7189234443051076392">சாதனத்தில் போதுமான சேமிப்பிடம் இருப்பதை உறுதிப்படுத்திக்கொள்ளவும்</translation>
@@ -4135,7 +4136,7 @@
 <translation id="7334190995941642545">தற்போது Smart Lock கிடைக்கவில்லை. பின்னர் முயற்சிக்கவும்.</translation>
 <translation id="7334274148831027933">டாக் செய்யப்பட்ட பெரிதாக்கியை இயக்கு</translation>
 <translation id="7339763383339757376">PKCS #7, ஒற்றைச் சான்றிதழ்</translation>
-<translation id="7339785458027436441">தட்டச்சு செய்யும்போதே எழுத்துப்பிழை சரிபார்</translation>
+<translation id="7339785458027436441">Check Spelling While Typing</translation>
 <translation id="7339898014177206373">புதிய சாளரம்</translation>
 <translation id="7340431621085453413"><ph name="FULLSCREEN_ORIGIN" />, இப்போது முழுத் திரையில் உள்ளது.</translation>
 <translation id="7340650977506865820">தளமானது உங்கள் திரையைப் பகிர்கிறது</translation>
@@ -4169,7 +4170,6 @@
 <translation id="7387829944233909572">"உலாவல் தரவை அழி" உரையாடல்</translation>
 <translation id="7388044238629873883">கிட்டத்தட்ட முடித்துவிட்டீர்கள்!</translation>
 <translation id="7388222713940428051">விருந்தினர் சாளரத்தைத் திற</translation>
-<translation id="7391648274628927397">உங்களுக்காக ஒரு புத்தகக்குறி சேர்க்கப்பட்டுள்ளது</translation>
 <translation id="7392118418926456391">வைரஸ் ஸ்கேன் தோல்வி</translation>
 <translation id="7392915005464253525">மூடப்பட்ட சாளரத்தை மீ&amp;ண்டும் திற</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> ஆனது முறைமை தொடக்கத்தின்போதே தொடங்கப்படும், மேலும் நீங்கள் பிற <ph name="PRODUCT_NAME" /> சாளரங்களை மூடி விட்டாலும் கூட பின்னணியில் தொடர்ந்து இயங்கும்.</translation>
@@ -4561,7 +4561,7 @@
 <translation id="7974936243149753750">ஓவர்ஸ்கேன்</translation>
 <translation id="7977551819349545646">Chromeboxஐப் புதுப்பிக்கிறது...</translation>
 <translation id="7978412674231730200">தனிப்பட்ட விசை</translation>
-<translation id="7978450511781612192">உங்கள் Google கணக்குகளிலிருந்து வெளியேற்றப்படுவீர்கள். இனி உங்கள் புத்தகக்குறிகள், வரலாறு, கடவுச்சொற்கள் மற்றும் பிற அமைப்புகள் ஒத்திசைக்கப்படாது.</translation>
+<translation id="7978450511781612192">உங்கள் Google கணக்குகளிலிருந்து வெளியேற்றப்படுவீர்கள். இனி உங்கள் புக்மார்க்குகள், வரலாறு, கடவுச்சொற்கள் மற்றும் பிற அமைப்புகள் ஒத்திசைக்கப்படாது.</translation>
 <translation id="7979036127916589816">ஒத்திசைவு பிழை</translation>
 <translation id="7980084013673500153">பண்புக்கூறு ஐடி: <ph name="ASSET_ID" /></translation>
 <translation id="7981313251711023384">மிக விரைவான உலாவலுக்காகவும் தேடலிற்காகவும் பக்கங்களை முன்னதாக ஏற்றும்</translation>
@@ -4891,7 +4891,7 @@
 <translation id="8497219075884839166">Windows கருவிகள்</translation>
 <translation id="8498214519255567734">மங்கலான ஒளியில் திரையைப் பார்ப்பதை அல்லது படிப்பதை எளிதாக்கும்</translation>
 <translation id="8498395510292172881">Chromeமில் தொடர்ந்து படிக்கவும்</translation>
-<translation id="8502536196501630039">Google Playயிலிருந்து ஆப்ஸைப் பயன்படுத்த, முதலில் உங்கள் ஆப்ஸை மீட்டெடுக்க வேண்டும். இதனால் தரவு இழப்பு ஏற்படலாம்.</translation>
+<translation id="8502536196501630039">Google Playயிலிருந்து ஆப்ஸைப் பயன்படுத்த, முதலில் உங்கள் ஆப்ஸை மீட்டெடுக்க வேண்டும். ஏதேனும் தரவு இழப்பு ஏற்பட்டிருக்கலாம்.</translation>
 <translation id="8503813439785031346">பயனர்பெயர்</translation>
 <translation id="850875081535031620">தீங்கிழைக்கும் மென்பொருள் இல்லை</translation>
 <translation id="8509646642152301857">எழுத்துப் பிழை சரிபார்ப்பு அகராதியைப் பதிவிறக்குவதில் தோல்வி.</translation>
@@ -5020,7 +5020,7 @@
 <translation id="8698464937041809063">Google வரைதல்</translation>
 <translation id="869884720829132584">பயன்பாடுகள் மெனு</translation>
 <translation id="869891660844655955">காலாவதியாகும் தேதி</translation>
-<translation id="8699566574894671540">இதை இயக்க, முதலில் “திருத்தம்” மெனுவில் “தட்டச்சுச் செய்யும்போதே எழுத்துப்பிழை சரிபார்” என்பதைத் தேர்ந்தெடுக்கவும்</translation>
+<translation id="8699566574894671540">இதை இயக்க, முதலில் Edit மெனுவில் Check Spelling While Typing என்பதைத் தேர்ந்தெடுக்கவும்</translation>
 <translation id="870073306461175568">நெட்வொர்க் கோப்புப் பகிர்வுகள்</translation>
 <translation id="8701677791353449257">சாதனத்தின் பெயர், <ph name="REGEX" /> என்ற ரெகுலர் எக்ஸ்ப்ரெஷனுடன் பொருந்த வேண்டும்.</translation>
 <translation id="8704521619148782536">இது வழக்கமான நேரத்தைவிட அதிகமாக எடுக்கிறது. நீங்கள் தொடர்ந்து காத்திருக்கலாம் அல்லது ரத்துசெய்து, பின்னர் முயற்சிக்கலாம்.</translation>
diff --git a/chrome/app/resources/generated_resources_te.xtb b/chrome/app/resources/generated_resources_te.xtb
index 07fe5b1..9c7fb67 100644
--- a/chrome/app/resources/generated_resources_te.xtb
+++ b/chrome/app/resources/generated_resources_te.xtb
@@ -126,6 +126,7 @@
 <translation id="1178581264944972037">పాజ్ చేయి</translation>
 <translation id="1181037720776840403">తీసివేయి</translation>
 <translation id="1183237619868651138">స్థానిక కాష్‌లో <ph name="EXTERNAL_CRX_FILE" />ని ఇన్‌స్టాల్ చేయడం సాధ్యపడదు.</translation>
+<translation id="1183378459020939734">మీ భద్రతా కీని జత చేయడానికి సిద్ధంగా ఉన్నారా?</translation>
 <translation id="1185924365081634987">మీరు ఈ నెట్‌వర్క్ లోపాన్ని పరిష్కరించడానికి <ph name="GUEST_SIGNIN_LINK_START" />అతిథిగా బ్రౌజ్ చేయడాన్ని<ph name="GUEST_SIGNIN_LINK_END" /> కూడా ప్రయత్నించవచ్చు.</translation>
 <translation id="1186771945450942097">హానికరమైన సాఫ్ట్‌వేర్‌ని తీసివేయండి</translation>
 <translation id="1187722533808055681">నిష్క్రియ మేల్కొలుపులు</translation>
@@ -533,6 +534,7 @@
 <translation id="1782530111891678861">టచ్ HUD మోడ్‌ను మార్చు</translation>
 <translation id="1784849162047402014">పరికరంలో ఖాళీ డిస్క్ స్థలం తక్కువగా ఉంది</translation>
 <translation id="1786636458339910689">బృంద డిస్క్‌లు</translation>
+<translation id="1792161662640298233">మీ భద్రతా కీని ధృవీకరిస్తోంది</translation>
 <translation id="1792619191750875668">విస్తారిత డిస్‌ప్లే</translation>
 <translation id="1794791083288629568">ఈ సమస్యను పరిష్కరించడంలో మాకు సహాయం చేయడానికి అభిప్రాయాన్ని పంపుతుంది.</translation>
 <translation id="1795214765651529549">క్లాసిక్‌ను ఉపయోగించు</translation>
@@ -592,6 +594,7 @@
 <translation id="186612162884103683">తనిఖీ చేయబడిన స్థానాల్లో "<ph name="EXTENSION" />" చిత్రాలను, వీడియోను మరియు సౌండ్ ఫైల్‌లను చదవగలదు మరియు వ్రాయగలదు.</translation>
 <translation id="1867780286110144690">మీ వ్యవస్థాపనను పూర్తి చెయ్యడానికి <ph name="PRODUCT_NAME" /> సిద్ధంగా ఉంది</translation>
 <translation id="1871615898038944731">మీ <ph name="DEVICE_TYPE" /> తాజాగా ఉంది</translation>
+<translation id="1875312262568496299">ప్రారంభించు</translation>
 <translation id="1875387611427697908">దీనిని <ph name="CHROME_WEB_STORE" /> నుండి మాత్రమే జోడించవచ్చు</translation>
 <translation id="1877520246462554164">ప్రమాణీకరణ టోకెన్‌ని పొందడం విఫలమైంది. దయచేసి సైన్ అవుట్ చేసి, సైన్ ఇన్ చేసిన తర్వాత మళ్లీ ప్రయత్నించండి.</translation>
 <translation id="1878302395768190018">మీరు ఏ సమయంలో అయినా దీనిని Chrome సెట్టింగ్‌లలో అనుకూలీకరించవచ్చు</translation>
@@ -736,6 +739,7 @@
 <translation id="2114896190328250491">ఫోటో తీసినది <ph name="NAME" /></translation>
 <translation id="2115103655317273167">ఫోన్‌కి పంపు</translation>
 <translation id="2119349053129246860"><ph name="APP" />లో తెరువు</translation>
+<translation id="2120297377148151361">కార్యకలాపం మరియు పరస్పర చర్యలు</translation>
 <translation id="2121825465123208577">పరిమాణం మార్చు</translation>
 <translation id="2124930039827422115">{1,plural, =1{ఒక వినియోగదారుచే <ph name="AVERAGE_RATING" /> రేట్ చేయబడింది.}other{# వినియోగదారులచే <ph name="AVERAGE_RATING" /> రేట్ చేయబడింది.}}</translation>
 <translation id="2126167708562367080">సమకాలీకరణను మీ నిర్వాహకులు నిలిపివేసారు.</translation>
@@ -870,6 +874,7 @@
 <translation id="2318143611928805047">కాగితపు పరిమాణం</translation>
 <translation id="2318817390901984578">Android ఆప్‌లను ఉపయోగించాలంటే, మీ <ph name="DEVICE_TYPE" />ని ఛార్జ్ చేసి, అప్‌డేట్ చేయండి.</translation>
 <translation id="2318923050469484167">ప్రస్తుత అజ్ఞాత సెషన్ (<ph name="EMBEDDING" />)</translation>
+<translation id="2321482478556590128"><ph name="APP_NAME" />తో మీ భద్రతా కీని ఉపయోగించండి</translation>
 <translation id="2322193970951063277">హెడర్‌లు మరియు ఫుటర్‌లు</translation>
 <translation id="2325650632570794183">ఈ ఫైల్ రకానికి మద్దతు లేదు. దయచేసి ఈ రకమైన ఫైల్‌ను తెరవగల అనువర్తనాన్ని కనుగొనడానికి Chrome వెబ్ స్టోర్‌ని సందర్శించండి.</translation>
 <translation id="2326931316514688470">అనువర్తనాన్ని &amp;మళ్లీ లోడ్ చేయి</translation>
@@ -903,6 +908,7 @@
 <translation id="2367199180085172140">ఫైల్ షేర్‌ని జోడించండి</translation>
 <translation id="2367972762794486313">అనువర్తనాలను చూపు</translation>
 <translation id="2369536625682139252">కుక్కీలు మినహా <ph name="SITE" /> నిల్వ చేసిన మొత్తం డేటా తొలగించబడుతుంది.</translation>
+<translation id="237058345584060620">మీ కీని ఈ పరికరానికి జత చేస్తే, తద్వారా మీరు మీ ఖాతాకు సైన్ ఇన్ చేయడానికి దాన్ని ఉపయోగించవచ్చు</translation>
 <translation id="2371076942591664043">&amp;పూర్తవగానే తెరువు</translation>
 <translation id="2376559921867170420">మీ Chromebook సెటప్ చేసిన తర్వాత, ఏ సమయంలోనైనా మీ అసిస్టెంట్‌ నుండి సహాయాన్ని పొందడానికి Assistant బటన్‌ను నొక్కండి లేదా "Ok Google" అని చెప్పండి.</translation>
 <translation id="2377319039870049694">జాబితా వీక్షణకు మార్చు</translation>
@@ -969,6 +975,7 @@
 <translation id="248861575772995840">మీ ఫోన్‌ను కనుగొనలేము. మీ <ph name="DEVICE_TYPE" /> యొక్క బ్లూటూత్ ఆన్ చేసి ఉన్నట్లు నిర్ధారించుకోండి. &lt;a&gt;మరింత తెలుసుకోండి&lt;/a&gt;</translation>
 <translation id="2489918096470125693">&amp;ఫోల్డర్‌ను జోడించు...</translation>
 <translation id="249113932447298600">క్షమించండి, ఈ సమయంలో <ph name="DEVICE_LABEL" /> పరికరానికి మద్దతు లేదు.</translation>
+<translation id="2492040222276243256">మీ భద్రతా కీపై ఉన్న బటన్‌ను కనీసం 5 సెకన్ల పాటు నొక్కి ఉంచండి</translation>
 <translation id="2493021387995458222">"ఒకసారికి ఒక పదం"ని ఎంచుకోండి</translation>
 <translation id="249303669840926644">నమోదు పూర్తి చేయడం కుదరలేదు</translation>
 <translation id="2495777824269688114">మరిన్ని ఫీచర్‌లను కనుగొనండి లేదా సమాధానాలను పొందండి. సహాయం కోసం "?" ఎంచుకోండి.</translation>
@@ -990,12 +997,14 @@
 <translation id="2509495747794740764">స్కేల్ ప్రమాణం తప్పనిసరిగా 10 మరియు 200 మధ్య ఉండే సంఖ్య అయ్యి ఉండాలి.</translation>
 <translation id="2509566264613697683">8x</translation>
 <translation id="2512222046227390255">ఫారమ్‌లను స్వయంచాలకంగా నింపండి</translation>
+<translation id="2513403576141822879">గోప్యత, భద్రత మరియు డేటా సేకరణకు సంబంధించిన మరిన్ని సెట్టింగ్‌ల కోసం, <ph name="BEGIN_LINK" />సమకాలీకరణ మరియు Google సేవలను<ph name="END_LINK" /> చూడండి</translation>
 <translation id="2515586267016047495">Alt</translation>
 <translation id="2517472476991765520">స్కాన్ చేయి</translation>
 <translation id="2518024842978892609">మీ క్లయింట్ ప్రమాణపత్రాలను ఉపయోగించడానికి అనుమతి</translation>
 <translation id="2520644704042891903">అందుబాటులో ఉన్న సాకెట్ కోసం వేచి ఉంది...</translation>
 <translation id="252219247728877310">అంశం నవీకరించబడలేదు</translation>
 <translation id="2522791476825452208">చాలా దగ్గరగా ఉండాలి</translation>
+<translation id="2523184218357549926">మీరు సందర్శించే పేజీల URLను Googleకి పంపుతుంది</translation>
 <translation id="2525250408503682495">క్రిప్టోనైట్! కియోస్క్ అనువర్తనం కోసం క్రిప్టోహోమ్ మౌంట్ చేయబడలేదు.</translation>
 <translation id="2526277209479171883">ఇన్‌స్టాల్ చేసి, కొనసాగించండి</translation>
 <translation id="2526590354069164005">డెస్క్‌టాప్</translation>
@@ -1022,6 +1031,7 @@
 <translation id="2562743677925229011"><ph name="SHORT_PRODUCT_NAME" />కు సైన్ ఇన్ చేయలేదు</translation>
 <translation id="2563856802393254086">అభినందనలు! మీ '<ph name="NAME" />' డేటా సేవ సక్రియం చేయబడింది మరియు పని చేయడానికి సిద్ధంగా ఉంది.</translation>
 <translation id="2564520396658920462">AppleScript ద్వారా JavaScriptని అమలు చేయడం ఆఫ్ చేయబడింది. దీనిని ఆన్ చేయడం కోసం, మెనూ బార్ నుండి, వీక్షించండి &gt; డెవలపర్ &gt; Apple ఈవెంట్‌ల నుండి JavaScriptని అనుమతించండి ఎంపికను ఎంచుకోండి. మరింత సమాచారం కోసం ఇక్కడ చూడండి: https://support.google.com/chrome/?p=applescript</translation>
+<translation id="2564653188463346023">మెరుగుపరిచిన స్పెల్ చెక్</translation>
 <translation id="2566124945717127842">మీ <ph name="IDS_SHORT_PRODUCT_NAME" /> పరికరాన్ని కొత్త దాని వలె రీసెట్ చేయడానికి పవర్‌వాష్ చేయండి.</translation>
 <translation id="2568774940984945469">సమాచారబార్ కంటైనర్</translation>
 <translation id="2570454805927264159">మీ అసిస్టెంట్ నుండి అత్యంత ఎక్కువ ప్రయోజనం పొందండి</translation>
@@ -1070,6 +1080,7 @@
 <translation id="2633199387167390344"><ph name="NAME" /> డిస్క్ ఖాళీలో <ph name="USAGE" /> MBని ఉపయోగిస్తున్నారు.</translation>
 <translation id="2633212996805280240">"<ph name="EXTENSION_NAME" />"ను తీసివేయాలా?</translation>
 <translation id="263325223718984101"><ph name="PRODUCT_NAME" /> వ్యవస్థాపనను పూర్తి చేయలేక పోయింది, కానీ దీని డిస్క్ చిత్రం నుండి అమలు చేయడానికి కొనసాగుతుంది.</translation>
+<translation id="2633326789677284179">మీ కీ వెనుక భాగాన ముద్రించిన పేరుని చూడండి</translation>
 <translation id="2635276683026132559">సంతకం చేస్తోంది</translation>
 <translation id="2636625531157955190">చిత్రాన్ని Chrome ప్రాప్యత చేయడం సాధ్యపడదు.</translation>
 <translation id="2638087589890736295">సమకాలీకరణను ప్రారంభించడానికి రహస్య పదబంధం అవసరం</translation>
@@ -1381,6 +1392,7 @@
 <translation id="3090819949319990166">బాహ్య crx ఫైల్‌ను <ph name="TEMP_CRX_FILE" />కి కాపీ చేయడం సాధ్యపడదు.</translation>
 <translation id="3090871774332213558">"<ph name="DEVICE_NAME" />" జత చేయబడింది</translation>
 <translation id="3101709781009526431">తేదీ మరియు సమయం</translation>
+<translation id="3104900172193317662">మీ భద్రతా కీ యొక్క వినియోగాన్ని అనుమతించండి</translation>
 <translation id="310671807099593501">సైట్ బ్లూటూత్‌ని ఉపయోగిస్తోంది</translation>
 <translation id="3115128645424181617">మీ ఫోన్‌ను కనుగొనలేము. అది చేతికి అందేంత దగ్గర్లో ఉందని మరియు బ్లూటూత్ ఆన్ చేసి ఉన్నట్లు నిర్ధారించుకోండి.</translation>
 <translation id="3115147772012638511">కాష్ కోసం వేచి ఉంది...</translation>
@@ -1867,6 +1879,7 @@
 <translation id="3839516600093027468">క్లిప్‌బోర్డ్‌ను చూడనీయకుండా ఎల్లప్పుడూ <ph name="HOST" />ని బ్లాక్ చేయి</translation>
 <translation id="3840053866656739575">మీ Chromeboxకి కనెక్షన్ పోయింది. దయచేసి దగ్గరికి తరలించండి లేదా మేము మళ్లీ కనెక్ట్ చేయడానికి ప్రయత్నించేటప్పుడు మీ పరికరాన్ని తనిఖీ చేయండి.</translation>
 <translation id="3842552989725514455">Serif ఫాంట్</translation>
+<translation id="3846116211488856547">వెబ్‌సైట్‌లు, Android యాప్‌లు మరియు మరిన్నింటిని అభివృద్ధి చేయడానికి సాధనాలను పొందండి. Linuxను ఇన్‌స్టాల్ చేయడం ద్వారా <ph name="DOWNLOAD_SIZE" /> డేటా డౌన్‌లోడ్ అవుతుంది.</translation>
 <translation id="385051799172605136">వెనుకకు</translation>
 <translation id="3851428669031642514">అసురక్షిత స్క్రిప్ట్‌లను లోడ్ చేయి</translation>
 <translation id="3854599674806204102">ఒక ఎంపికను ఎంచుకోండి</translation>
@@ -1890,6 +1903,7 @@
 <translation id="3872991219937722530">డిస్క్ స్థలాన్ని ఖాళీ చేయండి, లేదంటే పరికరం ప్రతిస్పందనరహితం అవుతుంది.</translation>
 <translation id="3878840326289104869">పర్యవేక్షించబడే వినియోగదారుని సృష్టిస్తోంది</translation>
 <translation id="3879748587602334249">డౌన్‌లోడ్ నిర్వాహికి</translation>
+<translation id="3880709822663530586">మీ పరికరం బ్లూటూత్ ఆన్‌లో ఉన్నప్పుడు మాత్రమే మీ భద్రతా కీ పని చేస్తుంది</translation>
 <translation id="3888550877729210209"><ph name="LOCK_SCREEN_APP_NAME" />తో గమనికలు రూపొందిస్తున్నారు</translation>
 <translation id="3892414795099177503">OpenVPN / L2TPని జోడించు...</translation>
 <translation id="3893536212201235195">మీ ప్రాప్యత సౌలభ్య సెట్టింగ్‌లను చదవడానికి మరియు మార్చడానికి అనుమతి</translation>
@@ -2352,6 +2366,7 @@
 <translation id="4681930562518940301">అసలు &amp;చిత్రాన్ని కొత్త ట్యాబ్‌లో తెరువు</translation>
 <translation id="4682551433947286597">వాల్‌పేపర్‌లు సైన్ ఇన్ స్క్రీన్‌లో కనిపిస్తాయి.</translation>
 <translation id="4684427112815847243">ప్రతి ఒక్కటి సమకాలీకరించండి</translation>
+<translation id="4689235506267737042">మీ డెమో ప్రాధాన్యతలను ఎంచుకోండి</translation>
 <translation id="4689421377817139245">ఈ బుక్‌మార్క్‌ని మీ iPhoneకు సమకాలీకరించండి</translation>
 <translation id="4690091457710545971">&lt;Intel Wi-Fi ఫర్మ్‌వేర్ రూపొందించిన నాలుగు ఫైల్‌లు: csr.lst, fh_regs.lst, radio_reg.lst, monitor.lst.sysmon.  మొదటి మూడు బైనరీ ఫైల్‌లు, వీటిలో రిజిస్టర్ డంప్‌లు ఉంటాయి మరియు వ్యక్తిగత లేదా పరికర సమాచారం కలిగి ఉండకుండా వీటిని Intel అందిస్తోంది.  చివరిది Intel ఫర్మ్‌వేర్ అందించే, అమలు స్థితిగతిని కనుగొనే ఫైల్; ఇందులో వ్యక్తిగత లేదా పరికర గుర్తింపు సమాచారం తీసివేయబడింది, కానీ ఇది చాలా పెద్దదిగా ఉన్న కారణంగా ఇక్కడ ప్రదర్శించడం సాధ్యం కాదు.  మీ పరికరంలో ఉన్న ఇటీవల Wi-Fi సమస్యలకు ప్రతిస్పందనగా ఈ ఫైల్‌లు రూపొందించబడ్డాయి మరియు ఈ సమస్యలను పరిష్కరించడంలో సహాయపడటం కోసం ఇవి Intelతో షేర్ చేయబడతాయి.&gt;</translation>
 <translation id="4692302215262324251">మీ <ph name="DEVICE_TYPE" /> ఎంటర్‌ప్రైజ్ నిర్వహణ కోసం <ph name="BEGIN_BOLD" /><ph name="DOMAIN" /><ph name="END_BOLD" /> ద్వారా విజయవంతంగా నమోదు చేయబడింది.
@@ -2478,6 +2493,7 @@
 <translation id="4880827082731008257">శోధన చరిత్ర</translation>
 <translation id="4881695831933465202">తెరువు</translation>
 <translation id="4882473678324857464">బుక్‌మార్క్‌లను ఫోకస్ చేయి</translation>
+<translation id="4882831918239250449">శోధన, ప్రకటనలు మరియు మరిన్నింటిని వ్యక్తిగతీకరించడానికి మీ బ్రౌజింగ్ చరిత్ర ఎలా ఉపయోగించబడుతుందో నియంత్రించండి</translation>
 <translation id="4883178195103750615">బుక్‌మార్క్‌లను HTML ఫైల్‌కి ఎగుమతి చేయండి...</translation>
 <translation id="4883436287898674711">అన్ని <ph name="WEBSITE_1" /> వెబ్‌సైట్‌లు</translation>
 <translation id="48838266408104654">విధి సంచాలకులు</translation>
@@ -2753,6 +2769,7 @@
 <translation id="5288678174502918605">మూసిన టాబ్‌ను మళ్ళీ &amp;తెరువు</translation>
 <translation id="52912272896845572">వ్యక్తిగతమైన కీ ఫైల్ చెల్లదు.</translation>
 <translation id="529175790091471945">ఈ పరికరాన్ని ఫార్మాట్ చేయి</translation>
+<translation id="5292195676005197571">ఎక్కువ కీలను ఉపయోగించడానికి, బటన్‌ని నొక్కండి</translation>
 <translation id="5293170712604732402">సెట్టింగ్‌లను వాటి అసలు డిఫాల్ట్ విలువలకు పునరుద్ధరించండి</translation>
 <translation id="5298219193514155779">థీమ్ వీరిచే సృష్టించబడింది</translation>
 <translation id="5299109548848736476">ట్రాక్ చేయవద్దు</translation>
@@ -3012,6 +3029,7 @@
 <translation id="5658415415603568799">అదనపు భద్రత కోసం, 20 గంటల తర్వాత మీ పాస్‌వర్డ్‌‌ను నమోదు చేయమని మీ Smart Lock అడుగుతుంది.</translation>
 <translation id="5659593005791499971">ఇమెయిల్</translation>
 <translation id="5659833766619490117">ఈ పేజీని అనువదించడం సాధ్యపడలేదు</translation>
+<translation id="5660204307954428567"><ph name="DEVICE_NAME" />తో జత చేయండి</translation>
 <translation id="5662477687021125631">శాశ్వతం</translation>
 <translation id="5667546120811588575">Google Play సెటప్ చేస్తోంది...</translation>
 <translation id="5669267381087807207">సక్రియం చేస్తోంది</translation>
@@ -3181,6 +3199,9 @@
 <translation id="5920835625712313205">Chrome OS సిస్టమ్ ఇమేజ్ రైటర్</translation>
 <translation id="5921745308587794300">విండోని తిప్పు</translation>
 <translation id="5924047253200400718">సహాయం పొందండి<ph name="SCANNING_STATUS" /></translation>
+<translation id="5924527146239595929">కొత్త ఫోటోను తీసుకోండి లేదా ఇప్పటికే ఉన్న ఫోటో లేదా చిహ్నాన్ని ఎంచుకోండి.
+    <ph name="LINE_BREAK" />
+    ఈ చిత్రం Chromebook సైన్ ఇన్ స్క్రీన్ మరియు లాక్ స్క్రీన్‌లలో చూపబడుతుంది.</translation>
 <translation id="5925147183566400388">సర్టిఫికేషన్ ప్రాక్టీస్ ప్రకటన పాయింటర్</translation>
 <translation id="592880897588170157">PDF ఫైల్‌లను స్వయంచాలకంగా Chromeలో తెరవడానికి బదులుగా వాటిని డౌన్‌లోడ్ చేసుకోండి</translation>
 <translation id="5931146425219109062">మీరు సందర్శించే వెబ్‌సైట్‌ల్లో మీ మొత్తం డేటాను చదవడం మరియు మార్చడం</translation>
@@ -3276,6 +3297,7 @@
 <translation id="6078752646384677957">దయచేసి మీ మైక్రోఫోన్ మరియు ఆడియో స్థాయిలను తనిఖీ చేయండి.</translation>
 <translation id="6080515710685820702">షేర్డ్ కంప్యూటర్‌ని ఉపయోగిస్తున్నారా? అజ్ఞాత విండోలో తెరవండి.</translation>
 <translation id="6080689532560039067">మీ సిస్టమ్ సమయాన్ని తనిఖీ చేయండి</translation>
+<translation id="6082111513049964958">అన్‌లాక్ చేయడానికి పాస్‌వర్డ్</translation>
 <translation id="6082651258230788217">సాధనపట్టీలో చూపండి</translation>
 <translation id="6086846494333236931">మీ నిర్వాహకుడు ఇన్‌స్టాల్ చేసారు</translation>
 <translation id="6087960857463881712">అద్భుతమైన ముఖం</translation>
@@ -3500,6 +3522,7 @@
 <translation id="642282551015776456">ఈ పేరును ఫైల్ యొక్క ఫోల్డర్ పేరుగా ఉపయోగించలేము.</translation>
 <translation id="642469772702851743">ఈ (SN: <ph name="SERIAL_NUMBER" />) పరికరం యజమానిచే లాక్ చేయబడింది.</translation>
 <translation id="6426200009596957090">ChromeVox సెట్టింగ్‌లను తెరువు</translation>
+<translation id="6427415464407526111">మీ భద్రతా కీని ఎంచుకోండి</translation>
 <translation id="6429384232893414837">అప్‌డేట్‌లో ఎర్రర్</translation>
 <translation id="6430814529589430811">Base64-ఎన్‌కోడ్ చేసిన ASCII, ఒక్క సర్టిఫికెట్</translation>
 <translation id="6431217872648827691"><ph name="TIME" />న మీ Google పాస్‌వర్డ్‌తో మొత్తం డేటా
@@ -3927,6 +3950,7 @@
 <translation id="7099337801055912064">పెద్ద PPDని లోడ్ చేయడం సాధ్యం కాదు. గరిష్ట పరిమాణం 250 kB.</translation>
 <translation id="7100897339030255923"><ph name="COUNT" /> అంశాలు ఎంచుకోబడ్డాయి</translation>
 <translation id="7102687220333134671">స్వయంచాలక నవీకరణలు ఆన్ చేయబడ్డాయి</translation>
+<translation id="7102832101143475489">అభ్యర్థన సమయం ముగిసింది</translation>
 <translation id="7106346894903675391">మరింత నిల్వని కొనుగోలు చేయండి...</translation>
 <translation id="7108338896283013870">దాచిపెట్టు</translation>
 <translation id="7108634116785509031"><ph name="HOST" /> మీ కెమెరాను ఉపయోగించాలనుకుంటోంది</translation>
@@ -4110,7 +4134,6 @@
 <translation id="7387829944233909572">"బ్రౌజింగ్ డేటాను క్లియర్ చెయ్యి" డైలాగ్</translation>
 <translation id="7388044238629873883">మీరు దాదాపు పూర్తి చేసారు!</translation>
 <translation id="7388222713940428051">అతిథి విండోని తెరువు</translation>
-<translation id="7391648274628927397">మీ కోసం ఒక బుక్‌మార్క్ జోడించబడింది</translation>
 <translation id="7392118418926456391">వైరస్‌ను స్కాన్ చేయడంలో విఫలమైంది</translation>
 <translation id="7392915005464253525">మూ&amp;సిన విండోని మళ్ళీ తెరువు</translation>
 <translation id="7396845648024431313">సిస్టమ్ ఆరంభంలో <ph name="APP_NAME" /> ఆరంభించబడుతుంది మరియు మీరు అన్ని <ph name="PRODUCT_NAME" /> విండోలని మూసినపుడు నేపథ్యంలో రన్ చేయడం కొనసాగుతుంది.</translation>
@@ -4291,6 +4314,7 @@
 <translation id="7701869757853594372">వినియోగదారు నిర్వహించేవి</translation>
 <translation id="7702907602086592255">డొమైన్</translation>
 <translation id="7704305437604973648">విధి</translation>
+<translation id="7704317875155739195">స్వయంపూర్తి శోధనలు మరియు URLలు</translation>
 <translation id="7704521324619958564">Play స్టోర్‌ను తెరువు</translation>
 <translation id="7705276765467986571">బుక్‌మార్క్ నమూనాని లోడ్ చెయ్యడం సాధ్యం కాలేదు.</translation>
 <translation id="7705524343798198388">VPN</translation>
@@ -4543,6 +4567,7 @@
 <translation id="8030656706657716245">ప్రింటర్‌ను జోడించండి</translation>
 <translation id="8032244173881942855">ట్యాబ్‌ను ప్రసారం చేయడం సాధ్యపడలేదు.</translation>
 <translation id="8033827949643255796">ఎంచుకోబడ్డాయి</translation>
+<translation id="8033958968890501070">సమయం ముగిసింది</translation>
 <translation id="803435727213847625">{COUNT,plural, =0{అన్నింటినీ &amp;అజ్ఞాత విండోలో తెరవండి}=1{&amp;అజ్ఞాత విండోలో తెరవండి}other{అన్నింటినీ (#) &amp;అజ్ఞాత విండోలో తెరవండి}}</translation>
 <translation id="8037117027592400564">సంశ్లేషణ ప్రసంగాన్ని ఉపయోగించి మాట్లాడిన మొత్తం వచనాన్ని చదవడం</translation>
 <translation id="8037357227543935929">అడగాలి (డిఫాల్ట్)</translation>
@@ -4631,6 +4656,8 @@
 <translation id="8180239481735238521"> పేజీ</translation>
 <translation id="8180786512391440389">"<ph name="EXTENSION" />" ఎంచుకున్న స్థానాల్లోని చిత్రాలను, వీడియోను మరియు సౌండ్ ఫైల్‌లను చదవగలదు మరియు తొలగించగలదు.</translation>
 <translation id="8181215761849004992">డొమైన్‌కు చేర్చడం సాధ్యపడలేదు. పరికరాలను జోడించడానికి తగిన అధికారాలు మీకు ఉన్నాయో లేదో మీ ఖాతాలో తనిఖీ చేయండి.</translation>
+<translation id="8182664696082410784"><ph name="REASON" />
+    ఈ సైట్‌ను బ్లాక్ చేసి ఉండకూడదు!</translation>
 <translation id="8184288427634747179"><ph name="AVATAR_NAME" />కి మార్చు</translation>
 <translation id="8184318863960255706">మరింత సమాచారం</translation>
 <translation id="8185331656081929126">నెట్‌వర్క్‌లో కొత్త ప్రింటర్‌లు గుర్తించబడినప్పుడు నోటిఫికేషన్‌లను చూపు</translation>
@@ -4775,6 +4802,7 @@
 <translation id="8435395510592618362"><ph name="APP_NAME" /> ద్వారా మీ గుర్తింపుని ధృవీకరించండి</translation>
 <translation id="843760761634048214">క్రెడిట్ కార్డ్‌ను సేవ్ చేయి</translation>
 <translation id="8438328416656800239">స్మార్ట్ బ్రౌజర్‌కు మారండి</translation>
+<translation id="8438566539970814960">శోధనలు మరియు బ్రౌజింగ్‌ను మెరుగుపరచండి</translation>
 <translation id="8439506636278576865">ఈ భాషలో పేజీలకు అనువాదం అందించు</translation>
 <translation id="8446884382197647889">మరింత తెలుసుకోండి</translation>
 <translation id="8447409163267621480">Ctrl లేదా Altను చేర్చండి</translation>
@@ -5126,6 +5154,7 @@
 <translation id="8980951173413349704"><ph name="WINDOW_TITLE" /> - క్రాష్ అయ్యింది</translation>
 <translation id="8983677657449185470">సురక్షిత బ్రౌజింగ్‌ను మెరుగుపరచడంలో సహాయపడండి</translation>
 <translation id="8984654317541110628">ఫైల్ షేర్ URL</translation>
+<translation id="8984872292925913496">మీ కీ వెనుకవైపు 6-అంకెల PINని చూడండి</translation>
 <translation id="8986362086234534611">మరిచిపోయారా</translation>
 <translation id="8986494364107987395">Googleకు స్వయంచాలకంగా ఉపయోగ గణాంకాలను మరియు క్రాష్ నివేదికలను పంపు</translation>
 <translation id="8987927404178983737">నెల</translation>
diff --git a/chrome/app/resources/generated_resources_th.xtb b/chrome/app/resources/generated_resources_th.xtb
index 3f4c517f..b05d863 100644
--- a/chrome/app/resources/generated_resources_th.xtb
+++ b/chrome/app/resources/generated_resources_th.xtb
@@ -2170,7 +2170,7 @@
 <translation id="428608937826130504">รายการชั้นวาง 8</translation>
 <translation id="4287502004382794929">คุณมีใบอนุญาตซอฟต์แวร์ไม่เพียงพอสำหรับการลงทะเบียนอุปกรณ์นี้ โปรดติดต่อฝ่ายขายเพื่อสั่งซื้อเพิ่มเติม หากคุณเชื่อว่าคุณเห็นข้อความนี้เนื่องจากข้อผิดพลาด โปรดติดต่อฝ่ายสนับสนุน</translation>
 <translation id="4289540628985791613">ภาพรวม</translation>
-<translation id="4295072614469448764">แอปพร้อมให้ใช้งานในเทอร์มินัลของคุณ และอาจจะมีไอคอนใน Launcher ของคุณด้วย</translation>
+<translation id="4295072614469448764">แอปพร้อมให้ใช้งานในเทอร์มินัลของคุณ และอาจจะมีไอคอนใน Launcher ด้วย</translation>
 <translation id="4296575653627536209">เพิ่มผู้ใช้ภายใต้การดูแล</translation>
 <translation id="4297219207642690536">รีสตาร์ทและรีเซ็ต</translation>
 <translation id="4297322094678649474">เปลี่ยนภาษา</translation>
@@ -4168,7 +4168,6 @@
 <translation id="7387829944233909572">ช่องโต้ตอบ "ล้างข้อมูลการท่องเว็บ"</translation>
 <translation id="7388044238629873883">ใกล้จะเสร็จเรียบร้อยแล้ว!</translation>
 <translation id="7388222713940428051">เปิดหน้าต่างผู้มาเยือน</translation>
-<translation id="7391648274628927397">เราเพิ่มบุ๊กมาร์กให้คุณแล้ว</translation>
 <translation id="7392118418926456391">สแกนไวรัสล้มเหลว</translation>
 <translation id="7392915005464253525">เ&amp;ปิดหน้าต่างที่ถูกปิดขึ้นใหม่</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> จะเปิดใช้งานเมื่อเริ่มต้นระบบและทำงานต่อไปในแบบเบื้องหลัง แม้คุณจะปิดหน้าต่าง <ph name="PRODUCT_NAME" /> อื่นๆ จนหมดแล้ว</translation>
@@ -5021,7 +5020,7 @@
 <translation id="8698464937041809063">ภาพวาดของ Google</translation>
 <translation id="869884720829132584">เมนูของแอปพลิเคชัน</translation>
 <translation id="869891660844655955">วันหมดอายุ</translation>
-<translation id="8699566574894671540">หากต้องการเปิดการตั้งค่านี้ ก่อนอื่นให้เลือกการตรวจตัวสะกดระหว่างที่พิมพ์ในเมนู "แก้ไข"</translation>
+<translation id="8699566574894671540">หากต้องการเปิดการตั้งค่านี้ ก่อนอื่นให้เลือก "ตรวจสอบตัวสะกดขณะพิมพ์" ในเมนู "แก้ไข"</translation>
 <translation id="870073306461175568">การแชร์ไฟล์เครือข่าย</translation>
 <translation id="8701677791353449257">ชื่ออุปกรณ์ต้องตรงกับนิพจน์ทั่วไป <ph name="REGEX" /></translation>
 <translation id="8704521619148782536">การดำเนินการนี้ใช้เวลานานกว่าปกติ คุณสามารถรอต่อไป หรือยกเลิกและลองอีกครั้งในภายหลัง</translation>
diff --git a/chrome/app/resources/generated_resources_tr.xtb b/chrome/app/resources/generated_resources_tr.xtb
index ca135e9..21dca4d5 100644
--- a/chrome/app/resources/generated_resources_tr.xtb
+++ b/chrome/app/resources/generated_resources_tr.xtb
@@ -2571,7 +2571,7 @@
 <translation id="4953808748584563296">Varsayılan turuncu avatar</translation>
 <translation id="4955814292505481804">Yıllık</translation>
 <translation id="4957949153200969297">Yalnızca <ph name="IDS_SHORT_PRODUCT_NAME" /> Senkronizasyonu ile ilişkili özellikleri etkinleştirin</translation>
-<translation id="4959262764292427323">Şifreler Google Hesabınızda kaydedildiğinden bunları herhangi bir cihazda kullanabilirsiniz</translation>
+<translation id="4959262764292427323">Şifreler Google Hesabınıza kaydedildiğinden bunları herhangi bir cihazda kullanabilirsiniz</translation>
 <translation id="4960294539892203357"><ph name="WINDOW_TITLE" /> - <ph name="PROFILE_NAME" /></translation>
 <translation id="496226124210045887">Seçtiğiniz klasör hassas dosyalar içeriyor. "$1" için bu klasöre kalıcı okuma erişimi vermek istediğinizden emin misiniz?</translation>
 <translation id="4964455510556214366">Düzenleme</translation>
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">"Gözatma verilerini temizle" iletişim kutusu</translation>
 <translation id="7388044238629873883">Neredeyse bitti!</translation>
 <translation id="7388222713940428051">Davetli penceresini aç</translation>
-<translation id="7391648274628927397">Sizin için bir yer işareti eklendi</translation>
 <translation id="7392118418926456391">Virüs taraması başarısız oldu</translation>
 <translation id="7392915005464253525">Kapatılan p&amp;encereyi yeniden aç</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" />, sistem açıldığında başlayacak ve diğer tüm <ph name="PRODUCT_NAME" /> pencerelerini kapatsanız da arka planda çalışmaya devam edecektir.</translation>
diff --git a/chrome/app/resources/generated_resources_uk.xtb b/chrome/app/resources/generated_resources_uk.xtb
index 4d0c34f..9579ec1 100644
--- a/chrome/app/resources/generated_resources_uk.xtb
+++ b/chrome/app/resources/generated_resources_uk.xtb
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Діалогове вікно "Очистити дані веб-перегляду"</translation>
 <translation id="7388044238629873883">Ви майже закінчили.</translation>
 <translation id="7388222713940428051">Відкрити вікно в режимі гостя</translation>
-<translation id="7391648274628927397">Додано закладку</translation>
 <translation id="7392118418926456391">Помилка перевірки на віруси</translation>
 <translation id="7392915005464253525">В&amp;ідкрити закрите вікно знову</translation>
 <translation id="7396845648024431313">Програма <ph name="APP_NAME" /> запускатиметься під час запуску системи та продовжуватиме працювати у фоновому режимі навіть після закриття всіх інших вікон <ph name="PRODUCT_NAME" />.</translation>
diff --git a/chrome/app/resources/generated_resources_vi.xtb b/chrome/app/resources/generated_resources_vi.xtb
index 2683345..c523be2 100644
--- a/chrome/app/resources/generated_resources_vi.xtb
+++ b/chrome/app/resources/generated_resources_vi.xtb
@@ -791,7 +791,7 @@
 <translation id="2178098616815594724"><ph name="PEPPER_PLUGIN_NAME" /> trên <ph name="PEPPER_PLUGIN_DOMAIN" /> muốn truy cập vào máy tính của bạn</translation>
 <translation id="2178614541317717477">Lộ CA</translation>
 <translation id="218070003709087997">Sử dụng một số để cho biết số bản cần in (1 đến 999).</translation>
-<translation id="2183558561014688873">Truy cập vào Trợ lý bất cứ lúc nào bạn nói "OK Google" khi thiết bị đang ở chế độ bật và mở khóa.</translation>
+<translation id="2183558561014688873">Khi thiết bị đang ở chế độ bật và mở khóa, nói "Ok Google" bất cứ lúc nào để sử dụng Trợ lý.</translation>
 <translation id="2187895286714876935">Lỗi nhập chứng chỉ máy chủ</translation>
 <translation id="2187906491731510095">Đã cập nhật tiện ích</translation>
 <translation id="2188881192257509750">Mở <ph name="APPLICATION" /></translation>
@@ -1220,7 +1220,7 @@
 <translation id="2812944337881233323">Thử đăng xuất và đăng nhập lại</translation>
 <translation id="2812989263793994277">Không hiển thị bất kỳ hình ảnh nào</translation>
 <translation id="2814489978934728345">Dừng tải trang này</translation>
-<translation id="281504910091592009">Xem và quản lý mật khẩu đã lưu trong <ph name="BEGIN_LINK" />Tài khoản Google<ph name="END_LINK" /> của bạn</translation>
+<translation id="281504910091592009">Xem và quản lý các mật khẩu đã lưu trong <ph name="BEGIN_LINK" />Tài khoản Google<ph name="END_LINK" /> của bạn</translation>
 <translation id="2815500128677761940">Thanh dấu trang</translation>
 <translation id="2815693974042551705">Thư mục dấu trang</translation>
 <translation id="2818476747334107629">Chi tiết về máy in</translation>
@@ -2170,7 +2170,7 @@
 <translation id="428608937826130504">Mục giá 8</translation>
 <translation id="4287502004382794929">Bạn không có đủ giấy phép phần mềm để đăng ký thiết bị này. Vui lòng liên hệ với bộ phận bán hàng để mua thêm. Nếu bạn nhận được thư này do nhầm lẫn, vui lòng liên hệ với bộ phận hỗ trợ.</translation>
 <translation id="4289540628985791613">Tổng quan</translation>
-<translation id="4295072614469448764">Ứng dụng này hiện đã có trong trạm thanh toán của bạn. Ngoài ra, có thể có một biểu tượng trong Trình chạy.</translation>
+<translation id="4295072614469448764">Ứng dụng này hiện đã có trong thiết bị của bạn. Ngoài ra, có thể có một biểu tượng trong Trình chạy.</translation>
 <translation id="4296575653627536209">Thêm người dùng được giám sát</translation>
 <translation id="4297219207642690536">Khởi động lại và đặt lại</translation>
 <translation id="4297322094678649474">Thay đổi ngôn ngữ</translation>
@@ -2571,7 +2571,7 @@
 <translation id="4953808748584563296">Hình đại diện màu cam mặc định</translation>
 <translation id="4955814292505481804">Hàng năm</translation>
 <translation id="4957949153200969297">Chỉ bật các tính năng liên quan đến <ph name="IDS_SHORT_PRODUCT_NAME" /> Sync</translation>
-<translation id="4959262764292427323">Các mật khẩu được lưu trong Tài khoản Google của bạn, vì vậy bạn có thể sử dụng các mật khẩu đó trên bất cứ thiết bị nào</translation>
+<translation id="4959262764292427323">Các mật khẩu được lưu trong Tài khoản Google của bạn, vì vậy, bạn có thể sử dụng các mật khẩu đó trên bất cứ thiết bị nào</translation>
 <translation id="4960294539892203357"><ph name="WINDOW_TITLE" /> - <ph name="PROFILE_NAME" /></translation>
 <translation id="496226124210045887">Thư mục bạn đã chọn chứa các tệp nhạy cảm. Bạn có chắc chắn muốn cấp quyền truy cập đọc vĩnh viễn "$1" cho thư mục này không?</translation>
 <translation id="4964455510556214366">Sắp xếp</translation>
@@ -3066,7 +3066,7 @@
 <translation id="5677503058916217575">Ngôn ngữ trang:</translation>
 <translation id="5677928146339483299">Bị chặn</translation>
 <translation id="5678550637669481956">Quyền truy cập đọc và ghi vào <ph name="VOLUME_NAME" /> đã được cấp.</translation>
-<translation id="5678784840044122290">Ứng dụng Linux sẽ có trong Trạm thanh toán của bạn và cũng có thể hiển thị một biểu tượng trong Trình chạy.</translation>
+<translation id="5678784840044122290">Ứng dụng Linux sẽ có trong Thiết bị của bạn và cũng có thể hiển thị một biểu tượng trong Trình chạy.</translation>
 <translation id="5678955352098267522">Đọc dữ liệu của bạn trên <ph name="WEBSITE_1" /></translation>
 <translation id="5684661240348539843">Số nhận dạng phần tử</translation>
 <translation id="5686799162999241776"><ph name="BEGIN_BOLD" />Không thể ngắt kết nối khỏi vùng lưu trữ hoặc ổ đĩa ảo<ph name="END_BOLD" />
@@ -4005,7 +4005,7 @@
 <translation id="7127980134843952133">Lịch sử tải xuống</translation>
 <translation id="7131040479572660648">Đọc dữ liệu của bạn trên <ph name="WEBSITE_1" />, <ph name="WEBSITE_2" /> và <ph name="WEBSITE_3" /></translation>
 <translation id="713122686776214250">Thêm tran&amp;g...</translation>
-<translation id="7133578150266914903">Quản trị viên đang khôi phục thiết bị này (<ph name="PROGRESS_PERCENT" />)</translation>
+<translation id="7133578150266914903">Quản trị viên đang hạ cấp hệ điều hành của thiết bị này (<ph name="PROGRESS_PERCENT" />)</translation>
 <translation id="7134098520442464001">Thu nhỏ Văn bản</translation>
 <translation id="7136694880210472378">Đặt làm mặc định</translation>
 <translation id="7136984461011502314">Chào mừng bạn đến với <ph name="PRODUCT_NAME" /></translation>
@@ -4169,7 +4169,6 @@
 <translation id="7387829944233909572">Hộp thoại "Xóa dữ liệu duyệt web"</translation>
 <translation id="7388044238629873883">Bạn sắp hoàn tất!</translation>
 <translation id="7388222713940428051">Mở Cửa sổ khách</translation>
-<translation id="7391648274628927397">Chúng tôi đã thêm một dấu trang cho bạn</translation>
 <translation id="7392118418926456391">Quét vi-rút không thành công</translation>
 <translation id="7392915005464253525">&amp;Mở lại cửa sổ đã đóng</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> sẽ chạy khi khởi động hệ thống và tiếp tục chạy ở chế độ nền ngay cả khi bạn đã đóng tất cả các cửa sổ khác của <ph name="PRODUCT_NAME" />.</translation>
@@ -4207,7 +4206,7 @@
 <translation id="7463006580194749499">Thêm người</translation>
 <translation id="7464490149090366184">Nén không thành công, mục tồn tại: "$1"</translation>
 <translation id="7465778193084373987">ULR Thu hồi của Tổ chức Cấp Chứng chỉ Netscape</translation>
-<translation id="7469406957790636836">Để bật tính năng này, trước tiên hãy bật tính năng kiểm tra chính tả trong <ph name="BEGIN_LINK" />Ngôn ngữ và phương thức nhập<ph name="END_LINK" /></translation>
+<translation id="7469406957790636836">Để bật tính năng này, trước tiên hãy bật tính năng kiểm tra chính tả trong phần <ph name="BEGIN_LINK" />Ngôn ngữ và phương thức nhập<ph name="END_LINK" /></translation>
 <translation id="7469894403370665791">Tự động kết nối vào mạng này</translation>
 <translation id="747114903913869239">Lỗi: Không thể giải mã tiện ích</translation>
 <translation id="7473753388963818366">Hãy bắt đầu thiết lập <ph name="DEVICE_TYPE" /> của bạn</translation>
@@ -4563,7 +4562,7 @@
 <translation id="7974936243149753750">Quét thừa</translation>
 <translation id="7977551819349545646">Đang cập nhật Chromebox...</translation>
 <translation id="7978412674231730200">Khoá cá nhân</translation>
-<translation id="7978450511781612192">Tùy chọn này sẽ đăng xuất bạn khỏi Tài khoản Google. Dấu trang, lịch sử, mật khẩu và các dữ liệu khác của bạn sẽ không còn đồng bộ hóa nữa.</translation>
+<translation id="7978450511781612192">Việc này này sẽ đăng xuất bạn khỏi các Tài khoản Google. Dấu trang, lịch sử, mật khẩu và các dữ liệu khác của bạn sẽ không còn đồng bộ hóa nữa.</translation>
 <translation id="7979036127916589816">Lỗi Đồng bộ hóa</translation>
 <translation id="7980084013673500153">ID phần tử: <ph name="ASSET_ID" /></translation>
 <translation id="7981313251711023384">Tải trước các trang để tìm kiếm và duyệt web nhanh hơn</translation>
@@ -5022,7 +5021,7 @@
 <translation id="8698464937041809063">Bản vẽ Google</translation>
 <translation id="869884720829132584">Menu ứng dụng</translation>
 <translation id="869891660844655955">Ngày hết hạn</translation>
-<translation id="8699566574894671540">Để bật tính năng này, trước tiên hãy chọn mục cài đặt Kiểm tra chính tả trong khi nhập trong menu Chỉnh sửa</translation>
+<translation id="8699566574894671540">Để bật tính năng này, trước tiên hãy chọn mục Kiểm tra chính tả trong khi nhập trong menu Chỉnh sửa</translation>
 <translation id="870073306461175568">Chia sẻ tệp trong mạng</translation>
 <translation id="8701677791353449257">Tên thiết bị phải khớp với biểu thức chính quy <ph name="REGEX" />.</translation>
 <translation id="8704521619148782536">Thao tác này mất nhiều thời gian hơn nhiều so với bình thường. Bạn có thể tiếp tục chờ hoặc hủy và thử lại sau.</translation>
@@ -5218,7 +5217,7 @@
 <translation id="9009369504041480176">Đang tải lên (<ph name="PROGRESS_PERCENT" />%)...</translation>
 <translation id="9011163749350026987">Luôn hiển thị biểu tượng</translation>
 <translation id="9011178328451474963">Tab sau cùng</translation>
-<translation id="9013707997379828817">Quản trị viên đã khôi phục thiết bị này. Vui lòng lưu các tệp quan trọng rồi khởi động lại. Tất cả dữ liệu trên thiết bị sẽ bị xóa.</translation>
+<translation id="9013707997379828817">Quản trị viên đã hạ cấp hệ điều hành của thiết bị này. Vui lòng lưu các tệp quan trọng rồi khởi động lại. Tất cả dữ liệu trên thiết bị sẽ bị xóa.</translation>
 <translation id="9014987600015527693">Hiển thị điện thoại khác</translation>
 <translation id="9018218886431812662">Đã cài đặt xong</translation>
 <translation id="901834265349196618">email</translation>
diff --git a/chrome/app/resources/generated_resources_zh-CN.xtb b/chrome/app/resources/generated_resources_zh-CN.xtb
index bb15f9e..f51bedc 100644
--- a/chrome/app/resources/generated_resources_zh-CN.xtb
+++ b/chrome/app/resources/generated_resources_zh-CN.xtb
@@ -2163,7 +2163,7 @@
 <translation id="428608937826130504">栏中第 8 项</translation>
 <translation id="4287502004382794929">您没有足够的软件许可来注册此设备。请与销售代表联系,以购买更多软件许可。如果您认为自己不应看到此讯息,请与支持人员联系。</translation>
 <translation id="4289540628985791613">概述</translation>
-<translation id="4295072614469448764">应用已出现在您的终端上,且可能在启动器中显示图标。</translation>
+<translation id="4295072614469448764">应用已可在您的终端上使用,且可能在启动器中显示图标。</translation>
 <translation id="4296575653627536209">添加受监管用户</translation>
 <translation id="4297219207642690536">重启并重置</translation>
 <translation id="4297322094678649474">更改语言</translation>
@@ -3053,7 +3053,7 @@
 <translation id="5677503058916217575">网页语言:</translation>
 <translation id="5677928146339483299">已屏蔽</translation>
 <translation id="5678550637669481956">已授予对 <ph name="VOLUME_NAME" />的读写权限。</translation>
-<translation id="5678784840044122290">此 Linux 应用将出现在您的终端上,且可能在启动器中显示图标。</translation>
+<translation id="5678784840044122290">此 Linux 应用将可以在您的终端上使用,且可能在启动器中显示图标。</translation>
 <translation id="5678955352098267522">读取您在 <ph name="WEBSITE_1" /> 上的数据</translation>
 <translation id="5684661240348539843">资产标识符</translation>
 <translation id="5686799162999241776"><ph name="BEGIN_BOLD" />无法断开与档案或虚拟磁盘的连接<ph name="END_BOLD" />
@@ -4154,7 +4154,6 @@
 <translation id="7387829944233909572">“清除浏览数据”对话框</translation>
 <translation id="7388044238629873883">即将终结!</translation>
 <translation id="7388222713940428051">以访客身份打开一个窗口</translation>
-<translation id="7391648274628927397">已为您添加书签</translation>
 <translation id="7392118418926456391">病毒扫描失败</translation>
 <translation id="7392915005464253525">重新打开关闭的窗口(&amp;E)</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> 会在系统启动时启动,即使您将其他所有 <ph name="PRODUCT_NAME" />窗口关闭,它也会继续在后台运行。</translation>
diff --git a/chrome/app/resources/generated_resources_zh-TW.xtb b/chrome/app/resources/generated_resources_zh-TW.xtb
index 563da8a..4caca609 100644
--- a/chrome/app/resources/generated_resources_zh-TW.xtb
+++ b/chrome/app/resources/generated_resources_zh-TW.xtb
@@ -1301,7 +1301,7 @@
 <translation id="2939938020978911855">顯示可用的藍牙裝置</translation>
 <translation id="2941112035454246133">低</translation>
 <translation id="2942560570858569904">等待中…</translation>
-<translation id="2942581856830209953">自訂此頁</translation>
+<translation id="2942581856830209953">自訂這個頁面</translation>
 <translation id="2943400156390503548">投影片</translation>
 <translation id="2943503720238418293">請縮短名稱</translation>
 <translation id="2946119680249604491">新增連線</translation>
@@ -4002,7 +4002,7 @@
 <translation id="7127980134843952133">下載記錄</translation>
 <translation id="7131040479572660648">讀取你在 <ph name="WEBSITE_1" />、<ph name="WEBSITE_2" /> 和 <ph name="WEBSITE_3" /> 上產生的資料</translation>
 <translation id="713122686776214250">新增網頁(&amp;G)...</translation>
-<translation id="7133578150266914903">管理員正在復原這個裝置 (進度:<ph name="PROGRESS_PERCENT" />)</translation>
+<translation id="7133578150266914903">你的管理員正在復原這個裝置 (進度:<ph name="PROGRESS_PERCENT" />)</translation>
 <translation id="7134098520442464001">縮小文字</translation>
 <translation id="7136694880210472378">設為預設</translation>
 <translation id="7136984461011502314">歡迎使用 <ph name="PRODUCT_NAME" /></translation>
@@ -4165,7 +4165,6 @@
 <translation id="7387829944233909572">[清除瀏覽資料] 對話方塊</translation>
 <translation id="7388044238629873883">就快完成了!</translation>
 <translation id="7388222713940428051">開啟訪客視窗</translation>
-<translation id="7391648274628927397">已為你新增書籤</translation>
 <translation id="7392118418926456391">病毒掃描失敗</translation>
 <translation id="7392915005464253525">重新開啟已關閉視窗(&amp;E)</translation>
 <translation id="7396845648024431313"><ph name="APP_NAME" /> 會隨著系統一併啟動,即使在所有 <ph name="PRODUCT_NAME" /> 視窗都關閉後仍會在背景繼續執行。</translation>
@@ -5213,7 +5212,7 @@
 <translation id="9009369504041480176">上傳中 (<ph name="PROGRESS_PERCENT" />%)...</translation>
 <translation id="9011163749350026987">一律顯示圖示</translation>
 <translation id="9011178328451474963">最後一個分頁</translation>
-<translation id="9013707997379828817">管理員已復原這個裝置。請儲存重要檔案後再重新啟動。重新啟動後,裝置上的資料將全數刪除。</translation>
+<translation id="9013707997379828817">你的管理員已復原這個裝置。請儲存重要檔案後再重新啟動。重新啟動後,裝置上的資料將全數刪除。</translation>
 <translation id="9014987600015527693">顯示其他手機</translation>
 <translation id="9018218886431812662">安裝完成</translation>
 <translation id="901834265349196618">電子郵件</translation>
diff --git a/chrome/app/resources/google_chrome_strings_ca.xtb b/chrome/app/resources/google_chrome_strings_ca.xtb
index 7a2040c..5a4178a 100644
--- a/chrome/app/resources/google_chrome_strings_ca.xtb
+++ b/chrome/app/resources/google_chrome_strings_ca.xtb
@@ -165,7 +165,7 @@
 <translation id="5386244825306882791">També controla la pàgina que es mostra quan inicieu Chrome o quan feu una cerca des de l'omnibox.</translation>
 <translation id="5430073640787465221">El fitxer de preferències està malmès o no és vàlid. Google Chrome no pot recuperar la configuració.</translation>
 <translation id="5483595757826856374">{0,plural, =0{Chrome es reiniciarà ara}=1{Chrome es reiniciarà d'aquí a 1 segon}other{Chrome es reiniciarà d'aquí a # segons}}</translation>
-<translation id="5514308096618405748">Chrome OS és possible gràcies a <ph name="BEGIN_LINK_CROS_OSS" />programari lliure addicional<ph name="END_LINK_CROS_OSS" />, així com Linux (versió beta).</translation>
+<translation id="5514308096618405748">Chrome OS és possible gràcies a <ph name="BEGIN_LINK_CROS_OSS" />programari lliure<ph name="END_LINK_CROS_OSS" /> addicional, així com Linux (versió beta).</translation>
 <translation id="556024056938947818">Google Chrome està provant de mostrar les contrasenyes.</translation>
 <translation id="5566025111015594046">Google Chrome (mDNS-In)</translation>
 <translation id="565744775970812598">Chrome ha bloquejat <ph name="FILE_NAME" /> perquè pot ser perillós.</translation>
@@ -246,7 +246,7 @@
 <translation id="8008534537613507642">Torna a instal·lar Chrome</translation>
 <translation id="8030318113982266900">S'està actualitzant el vostre dispositiu al canal <ph name="CHANNEL_NAME" />...</translation>
 <translation id="8032142183999901390">Després de suprimir el vostre compte de Chrome, pot ser que hàgiu de tornar a carregar les pestanyes obertes.</translation>
-<translation id="8077352834923175128">L'administrador recomana que reiniciïs Chrome OS per aplicar aquesta actualització</translation>
+<translation id="8077352834923175128">L'administrador demana que reiniciïs Chrome OS per aplicar aquesta actualització</translation>
 <translation id="8129812357326543296">Quant a &amp;Google Chrome</translation>
 <translation id="8179874765710681175">Instal·la Chrome al telèfon. T'enviarem un SMS al telèfon.</translation>
 <translation id="8183957050892517584">Chrome emmagatzemarà de manera segura les vostres dades personals perquè no les hàgiu de tornar a escriure</translation>
diff --git a/chrome/app/resources/google_chrome_strings_cs.xtb b/chrome/app/resources/google_chrome_strings_cs.xtb
index 21e8812..df8931e 100644
--- a/chrome/app/resources/google_chrome_strings_cs.xtb
+++ b/chrome/app/resources/google_chrome_strings_cs.xtb
@@ -259,6 +259,7 @@
 <translation id="8179874765710681175">Nainstalujte si Chrome do telefonu. Zašleme vám na telefon SMS.</translation>
 <translation id="8183957050892517584">Chrome vaše osobní údaje bezpečně uloží, abyste je nemuseli zadávat znovu.</translation>
 <translation id="8226081633851087288">{0,plural, =0{Systém Chrome OS se teď restartuje}=1{Systém Chrome OS se za 1 sekundu restartuje}few{Systém Chrome OS se za # sekundy restartuje}many{Systém Chrome OS se za # sekundy restartuje}other{Systém Chrome OS se za # sekund restartuje}}</translation>
+<translation id="825412236959742607">Tato stránka využívá příliš mnoho paměti, Chrome proto odstranil část obsahu.</translation>
 <translation id="8255190535488645436">Google Chrome používá vaši kameru a mikrofon.</translation>
 <translation id="8286862437124483331">Aplikace Google Chrome se pokouší zobrazit hesla. Tato akce vyžaduje zadání hesla systému Windows.</translation>
 <translation id="8290100596633877290">Ouha! Prohlížeč Google Chrome spadl. Restartovat?</translation>
diff --git a/chrome/app/resources/google_chrome_strings_en-GB.xtb b/chrome/app/resources/google_chrome_strings_en-GB.xtb
index dee6996..20df56f 100644
--- a/chrome/app/resources/google_chrome_strings_en-GB.xtb
+++ b/chrome/app/resources/google_chrome_strings_en-GB.xtb
@@ -259,6 +259,7 @@
 <translation id="8179874765710681175">Install Chrome on your phone. We’ll send an SMS to your phone.</translation>
 <translation id="8183957050892517584">Chrome will securely store your personal details so that you don't need to type them again.</translation>
 <translation id="8226081633851087288">{0,plural, =0{Chrome OS will restart now}=1{Chrome OS will restart in 1 second}other{Chrome OS will restart in # seconds}}</translation>
+<translation id="825412236959742607">This page uses too much memory, so Chrome removed some content.</translation>
 <translation id="8255190535488645436">Google Chrome is using your camera and microphone.</translation>
 <translation id="8286862437124483331">Google Chrome is trying to show passwords. Type your Windows password to allow this.</translation>
 <translation id="8290100596633877290">Whoa! Google Chrome has crashed. Relaunch now?</translation>
diff --git a/chrome/app/resources/google_chrome_strings_id.xtb b/chrome/app/resources/google_chrome_strings_id.xtb
index 5cf839c..28da3198 100644
--- a/chrome/app/resources/google_chrome_strings_id.xtb
+++ b/chrome/app/resources/google_chrome_strings_id.xtb
@@ -148,7 +148,7 @@
 <translation id="4891791193823137474">Jalankan Google Chrome di latar belakang</translation>
 <translation id="4895437082222824641">Buka link di &amp;tab Chrome baru</translation>
 <translation id="4921569541910214635">Membagikan komputer? Kini Anda dapat menyiapkan Chrome seperti yang Anda inginkan.</translation>
-<translation id="4945319281866068441">{0,plural, =1{Mulai ulang Chrome OS dalam 1 hari}other{Mulai ulang Chrome OS dalam # hari}}</translation>
+<translation id="4945319281866068441">{0,plural, =1{Mulai ulang Chrome OS dalam satu hari}other{Mulai ulang Chrome OS dalam # hari}}</translation>
 <translation id="4953650215774548573">Setel Google Chrome sebagai browser default Anda</translation>
 <translation id="495931528404527476">Di Chrome</translation>
 <translation id="4990567037958725628">Google Chrome Canary</translation>
diff --git a/chrome/app/resources/google_chrome_strings_it.xtb b/chrome/app/resources/google_chrome_strings_it.xtb
index d887653..cad2948 100644
--- a/chrome/app/resources/google_chrome_strings_it.xtb
+++ b/chrome/app/resources/google_chrome_strings_it.xtb
@@ -30,7 +30,7 @@
 <translation id="1682634494516646069">Google Chrome non è in grado di leggere e scrivere nella directory dei dati: <ph name="USER_DATA_DIRECTORY" /></translation>
 <translation id="1698376642261615901">Google Chrome è un browser web che apre pagine e applicazioni web con grande velocità. È veloce, stabile e facile da utilizzare. Esplora il Web in modo più sicuro grazie alla protezione contro malware e phishing incorporata in Google Chrome.</translation>
 <translation id="1718131156967340976">Seleziona <ph name="SMALL_PRODUCT_LOGO" /> <ph name="BEGIN_BOLD" />Google Chrome<ph name="END_BOLD" /></translation>
-<translation id="1734234790201236882">Chrome salverà la password nel tuo account Google affinché tu non debba memorizzarla.</translation>
+<translation id="1734234790201236882">Chrome salverà la password nel tuo Account Google affinché tu non debba memorizzarla.</translation>
 <translation id="174539241580958092">Impossibile sincronizzare i dati in Google Chrome a causa di un errore durante l'accesso.</translation>
 <translation id="1759842336958782510">Chrome</translation>
 <translation id="1773601347087397504">Ricevi assistenza per l'utilizzo di Chrome OS</translation>
@@ -251,7 +251,7 @@
 <translation id="8129812357326543296">Informazioni su &amp;Google Chrome</translation>
 <translation id="8179874765710681175">Installa Chrome sul telefono. Invieremo un SMS al tuo telefono.</translation>
 <translation id="8183957050892517584">Chrome memorizzerà in sicurezza i tuoi dati personali affinché tu non debba digitarli di nuovo.</translation>
-<translation id="8226081633851087288">{0,plural, =0{Chrome OS verrà riavviato ora}=1{Chrome OS verrà riavviato tra 1 secondo}other{Chrome OS verrà riavviato tra # secondi}}</translation>
+<translation id="8226081633851087288">{0,plural, =0{Chrome OS verrà riavviato ora}=1{Chrome OS verrà riavviato tra un secondo}other{Chrome OS verrà riavviato tra # secondi}}</translation>
 <translation id="8255190535488645436">Google Chrome sta utilizzando videocamera e microfono.</translation>
 <translation id="8286862437124483331">Google Chrome sta cercando di visualizzare le password. Per consentire la visualizzazione, digita la tua password Windows.</translation>
 <translation id="8290100596633877290">Spiacenti, si è verificato un arresto anomalo di Google Chrome. Riavviarlo ora?</translation>
@@ -274,7 +274,7 @@
 <translation id="870251953148363156">Aggiorna &amp;Google Chrome</translation>
 <translation id="873133009373065397">Google Chrome non è in grado di determinare o impostare il browser predefinito</translation>
 <translation id="8736674169840206667">L'amministratore richiede il riavvio di Chrome per installare un aggiornamento</translation>
-<translation id="8791470158600044404">{0,plural, =1{Chrome OS verrà riavviato tra 1 minuto}other{Chrome OS verrà riavviato tra # minuti}}</translation>
+<translation id="8791470158600044404">{0,plural, =1{Chrome OS verrà riavviato tra un minuto}other{Chrome OS verrà riavviato tra # minuti}}</translation>
 <translation id="8796108026289707191">Google Chrome deve essere riavviato adesso.</translation>
 <translation id="8823341990149967727">Chrome non è aggiornato</translation>
 <translation id="884296878221830158">Controlla anche la pagina visualizzata all'avvio di Chrome o quando fai clic sul pulsante Pagina iniziale.</translation>
diff --git a/chrome/app/resources/google_chrome_strings_lt.xtb b/chrome/app/resources/google_chrome_strings_lt.xtb
index 64a6993..3cb42a3 100644
--- a/chrome/app/resources/google_chrome_strings_lt.xtb
+++ b/chrome/app/resources/google_chrome_strings_lt.xtb
@@ -260,6 +260,7 @@
 <translation id="8179874765710681175">Įdiekite „Chrome“ telefone. Išsiųsime SMS pranešimą jūsų telefonu.</translation>
 <translation id="8183957050892517584">„Chrome“ patikimai saugos jūsų išsamią informaciją, kad nebereikėtų jos vėl įvesti.</translation>
 <translation id="8226081633851087288">{0,plural, =0{„Chrome“ OS bus paleista iš naujo dabar}=1{„Chrome“ OS bus paleista iš naujo po 1 sekundės}one{„Chrome“ OS bus paleista iš naujo po # sekundės}few{„Chrome“ OS bus paleista iš naujo po # sekundžių}many{„Chrome“ OS bus paleista iš naujo po # sekundės}other{„Chrome“ OS bus paleista iš naujo po # sekundžių}}</translation>
+<translation id="825412236959742607">Šis puslapis naudoja per daug atminties, todėl „Chrome“ pašalino šiek tiek turinio.</translation>
 <translation id="8255190535488645436">„Google Chrome“ naudoja jūsų fotoaparatą ir mikrofoną.</translation>
 <translation id="8286862437124483331">„Google Chrome“ bando rodyti slaptažodžius. Įveskite „Windows“ slaptažodį, kad tai leistumėte.</translation>
 <translation id="8290100596633877290">Oi! „Google Chrome“ užstrigo. Paleisti iš naujo dabar?</translation>
diff --git a/chrome/app/resources/google_chrome_strings_ml.xtb b/chrome/app/resources/google_chrome_strings_ml.xtb
index 23e37ca7..c73e3a4 100644
--- a/chrome/app/resources/google_chrome_strings_ml.xtb
+++ b/chrome/app/resources/google_chrome_strings_ml.xtb
@@ -34,7 +34,7 @@
 <ph name="USER_DATA_DIRECTORY" /></translation>
 <translation id="1698376642261615901">Google Chrome എന്നത് അതിവേഗത്തില്‍‌ വെബ്‌പേജുകളും അപ്ലിക്കേഷനുകളും പ്രവര്‍‌ത്തിപ്പിക്കുന്ന ഒരു വെബ് ബ്രൗസറാണ്. അത് വേഗതയുള്ളതും, സ്ഥിരതയുള്ളതും ഉപയോഗിക്കാൻ എളുപ്പവുമാണ്. Google Chrome-ലേക്ക് ബില്‍‌റ്റുചെയ്‌ത ക്ഷുദ്രവെയര്‍‌, ഫിഷിംഗ് പരിരക്ഷണം ഉപയോഗിച്ച് വെബ് കൂടുതൽ‍‌ സുരക്ഷിതമായി ബ്രൗസുചെയ്യുക.</translation>
 <translation id="1718131156967340976"><ph name="SMALL_PRODUCT_LOGO" /> <ph name="BEGIN_BOLD" />Google Chrome<ph name="END_BOLD" /> തിരഞ്ഞെടുക്കുക</translation>
-<translation id="1734234790201236882">നിങ്ങളുടെ Google അക്കൗണ്ടിൽ Chrome ഈ പാസ്‌വേഡ് സംരക്ഷിക്കും. നിങ്ങൾ അത് ഓർത്ത് വയ്ക്കേണ്ടതില്ല.</translation>
+<translation id="1734234790201236882">നിങ്ങളുടെ Google അക്കൗണ്ടിൽ Chrome ഈ പാസ്‌വേഡ് സംരക്ഷിക്കും. നിങ്ങൾ അത് ഓർത്ത് വയ്‌ക്കേണ്ടതില്ല.</translation>
 <translation id="174539241580958092">സൈൻ ഇൻ ചെയ്യുന്നതിലെ ഒരു പിശകിനാൽ Google Chrome-ന് നിങ്ങളുടെ ഡാറ്റ സമന്വയിപ്പിക്കാനായില്ല.</translation>
 <translation id="1759842336958782510">Chrome</translation>
 <translation id="1773601347087397504">Chrome OS ഉപയോഗിച്ച് സഹായം തേടുക</translation>
@@ -152,7 +152,7 @@
 <translation id="4891791193823137474">പശ്ചാത്തലത്തില്‍ Google Chrome പ്രവര്‍ത്തിക്കാന്‍ അനുവദിക്കുക</translation>
 <translation id="4895437082222824641">പുതിയ Chrome &amp;ടാബിൽ ലിങ്ക് തുറക്കുക</translation>
 <translation id="4921569541910214635">ഒരു കമ്പ്യൂട്ടർ പങ്കിടണോ? ഇപ്പോൾ നിങ്ങൾക്ക് ഇഷ്‌ടപ്പെട്ട രീതിയിൽ Chrome സജ്ജീകരിക്കാനാകും.</translation>
-<translation id="4945319281866068441">{0,plural, =1{ ഒരു ദിവസത്തിനുള്ളിൽ Chrome OS പുനഃരാരംഭിക്കും}other{# ദിവസത്തിനുള്ളിൽ Chrome OS പുനഃരാരംഭിക്കും}}</translation>
+<translation id="4945319281866068441">{0,plural, =1{ ഒരു ദിവസത്തിനുള്ളിൽ Chrome OS പുനഃരാരംഭിക്കുക}other{# ദിവസത്തിനുള്ളിൽ Chrome OS പുനഃരാരംഭിക്കുക}}</translation>
 <translation id="4953650215774548573">Google Chrome-നെ നിങ്ങളുടെ സ്ഥിര ബ്രൗസറായി സജ്ജീകരിക്കുക</translation>
 <translation id="495931528404527476">Chrome-ൽ</translation>
 <translation id="4990567037958725628">Google Chrome Canary</translation>
@@ -290,7 +290,7 @@
 <translation id="9026991721384951619">നിങ്ങളുടെ അക്കൗണ്ടിന്റെ സൈൻ ഇൻ വിശദാംശങ്ങൾ കാലഹരണപ്പെട്ടതിനാൽ Chrome OS-ന് നിങ്ങളുടെ ഡാറ്റ സമന്വയിപ്പിക്കാനായില്ല.</translation>
 <translation id="9067395829937117663">Google Chrome-ന് Windows 7 അല്ലെങ്കിൽ അതിനുശേഷമുള്ള പതിപ്പ് ആവശ്യമാണ്.</translation>
 <translation id="9084668267983921457">സർട്ടിഫിക്കറ്റ് പരിശോധിച്ചുറപ്പിക്കൽ പരാജയപ്പെട്ടു. Google Chrome-ൽ വീണ്ടും സൈൻ ഇൻ ചെയ്യുക അല്ലെങ്കിൽ കൂടുതൽ വിവരങ്ങൾക്ക് നിങ്ങളുടെ അഡ്‌മിനിസ്‌ട്രേറ്ററെ ബന്ധപ്പെടുക. <ph name="ADDITIONAL_EXPLANATION" /></translation>
-<translation id="909149346112452267">{0,plural, =1{ഒരു മണിക്കൂറിൽ Chrome വീണ്ടും പുനഃരാരംഭിക്കും}other{Chrome OS # മണിക്കൂറിൽ പുനഃരാരംഭിക്കും}}</translation>
+<translation id="909149346112452267">{0,plural, =1{ഒരു മണിക്കൂറിൽ Chrome OS പുനഃരാരംഭിക്കും}other{Chrome OS # മണിക്കൂറിൽ പുനഃരാരംഭിക്കും}}</translation>
 <translation id="911206726377975832">നിങ്ങളുടെ ബ്രൌസിംഗ് ഡാറ്റയും ഇതോടൊപ്പം ഇല്ലാതാക്കണോ?</translation>
 <translation id="919706545465235479">സമന്വയിപ്പിക്കാൻ തുടങ്ങുന്നതിന് Chrome അപ്‌ഡേറ്റുചെയ്യുക</translation>
 <translation id="924447568950697217">Chrome-നെ നിങ്ങളുടെ ഡിഫോൾട്ട് ബ്രൗസറാക്കുക</translation>
diff --git a/chrome/app/resources/google_chrome_strings_mr.xtb b/chrome/app/resources/google_chrome_strings_mr.xtb
index 0f828ec9..a572814d 100644
--- a/chrome/app/resources/google_chrome_strings_mr.xtb
+++ b/chrome/app/resources/google_chrome_strings_mr.xtb
@@ -256,6 +256,7 @@
 <translation id="8179874765710681175">आपल्या फोनवर Chrome इंस्टॉल करा. आम्ही आपल्या फोनवर एक SMS पाठवू.</translation>
 <translation id="8183957050892517584">Chrome आपले वैयक्तिक तपशील सुरक्षितपणे संचयित करेल जेणेकरून आपल्याला ते पुन्हा टाइप करण्याची आवश्यकता नसेल.</translation>
 <translation id="8226081633851087288">{0,plural, =0{Chrome OS आता रीस्टार्ट होईल}=1{Chrome OS एका सेकंदात रीस्टार्ट होईल}one{Chrome OS # सेकंदात रीस्टार्ट होईल}other{Chrome OS # सेकंदामध्ये रीस्टार्ट होईल}}</translation>
+<translation id="825412236959742607">हे पेज खूप जास्त मेमरी वापरत असल्यामुळे, Chrome ने काही आशय काढून टाकला आहे.</translation>
 <translation id="8255190535488645436">Google Chrome आपला कॅमेरा आणि मायक्रोफोन वापरत आहे.</translation>
 <translation id="8286862437124483331">Google Chrome संकेतशब्‍द दर्शविण्‍याचा प्रयत्‍न करत आहे. यास अनुमती देण्‍यासाठी आपला Windows संकेतशब्‍द टाइप करा.</translation>
 <translation id="8290100596633877290">अरेरे! Google Chrome क्रॅश झाला. त्वरित पुन्हा लाँच करायचा?</translation>
diff --git a/chrome/app/resources/google_chrome_strings_sk.xtb b/chrome/app/resources/google_chrome_strings_sk.xtb
index 9167c208..38dc077 100644
--- a/chrome/app/resources/google_chrome_strings_sk.xtb
+++ b/chrome/app/resources/google_chrome_strings_sk.xtb
@@ -257,6 +257,7 @@
 <translation id="8179874765710681175">Nainštalujte si Chrome do telefónu. Pošleme vám doň správu SMS.</translation>
 <translation id="8183957050892517584">Chrome vaše osobné údaje bezpečne uloží, aby ste ich už nemuseli znova zadávať.</translation>
 <translation id="8226081633851087288">{0,plural, =0{Chrome OS sa teraz reštartuje}=1{Chrome OS sa reštartuje o sekundu}few{Chrome OS sa reštartuje o # sekundy}many{Chrome OS sa reštartuje o # sekundy}other{Chrome OS sa reštartuje o # sekúnd}}</translation>
+<translation id="825412236959742607">Táto stránka využíva príliš veľa pamäte, a preto Chrome odstránil niektorý obsah.</translation>
 <translation id="8255190535488645436">Google Chrome používa vašu kameru a mikrofón.</translation>
 <translation id="8286862437124483331">Prehliadač Google Chrome sa pokúša zobraziť heslá. Ak to chcete povoliť, zadajte svoje heslo systému Windows.</translation>
 <translation id="8290100596633877290">Google Chrome zlyhal. Chcete ho spustiť znova?</translation>
diff --git a/chrome/app/resources/google_chrome_strings_sw.xtb b/chrome/app/resources/google_chrome_strings_sw.xtb
index a5384e1..772a512 100644
--- a/chrome/app/resources/google_chrome_strings_sw.xtb
+++ b/chrome/app/resources/google_chrome_strings_sw.xtb
@@ -52,7 +52,7 @@
 <translation id="216054706567564023">Sakinisha Chrome kwenye simu yako. Tutatuma SMS kwenye nambari yako ya simu ya mbinu za kurejesha uwezo wa kufikia akaunti.</translation>
 <translation id="2246246234298806438">Google Chrome haiwezi kuonyesha uhakiki wa kuchapisha wakati kionyeshi kilichojengewa ndani cha PDF kinakosekana.</translation>
 <translation id="2252923619938421629">Saidia kuboresha Google Chrome kwa kuripoti mipangilio ya sasa</translation>
-<translation id="225363235161345695">{0,plural, =1{Chrome itaanza upya baada ya saa}other{Chrome itaanza upya baada ya saa #}}</translation>
+<translation id="225363235161345695">{0,plural, =1{Chrome itafunguka upya baada ya saa}other{Chrome itafunguka upya baada ya saa #}}</translation>
 <translation id="2286950485307333924">Sasa umeingia katika Chrome</translation>
 <translation id="2290014774651636340">Funguo za API za Google zinakosekana. Baadhi ya utendaji wa Google Chrome utazimwa.</translation>
 <translation id="2290095356545025170">Je, una hakika kuwa ungependa kusanidua Google Chrome?</translation>
@@ -172,7 +172,7 @@
 <translation id="5430073640787465221">Faili yako ya mapendekezo imeharibika au ni batili.
 
 Google Chrome haiwezi kufufua mipangilio yako.</translation>
-<translation id="5483595757826856374">{0,plural, =0{Chrome itawaka upya sasa hivi}=1{Chrome itawaka upya baada ya sekunde 1}other{Chrome itawaka upya baada ya sekunde #}}</translation>
+<translation id="5483595757826856374">{0,plural, =0{Chrome itafunguka upya sasa hivi}=1{Chrome itafunguka upya baada ya sekunde 1}other{Chrome itafunguka upya baada ya sekunde #}}</translation>
 <translation id="5514308096618405748">Mfumo wa uendeshaji wa Chrome unawezeshwa na <ph name="BEGIN_LINK_CROS_OSS" />programu huria<ph name="END_LINK_CROS_OSS" /> za ziada, kama vile Linux (Beta).</translation>
 <translation id="556024056938947818">Google Chrome inajaribu kuonyesha manenosiri.</translation>
 <translation id="5566025111015594046">Google Chrome (mDNS-In)</translation>
@@ -216,7 +216,7 @@
 <translation id="6970811910055250180">Inasasisha kifaa chako...</translation>
 <translation id="6982337800632491844"><ph name="DOMAIN" /> huhitaji usome na ukubali Sheria na Masharti yafuatayo kabla ya kutumia kifaa hiki. Masharti haya hayapanui, kurekebisha au kupunguza Masharti ya Google Chrome OS.</translation>
 <translation id="6989339256997917931">Google Chrome imesasishwa, lakini hujaitumia kwa angalau siku 30.</translation>
-<translation id="7023651421574588884">Msimamizi wako anakushauri uzime kisha uwashe mfumo wa uendeshaji wa Chrome ili utumie sasisho</translation>
+<translation id="7023651421574588884">Msimamizi wako anakushauri uzime kisha uwashe mfumo wa uendeshaji wa Chrome ili uweke sasisho</translation>
 <translation id="7098166902387133879">Google Chrome inatumia maikrofoni yako.</translation>
 <translation id="7106741999175697885">Kidhibiti cha Shughuli - Google Chrome</translation>
 <translation id="7164397146364144019">Unaweza kusaidia kuifanya Chrome kuwa salama na rahisi zaidi kutumia kwa kuripoti maelezo ya uwezekano wa matukio yasiyo salama kwa Google kiotomatiki.</translation>
@@ -232,7 +232,7 @@
 <translation id="7486227612705979895">Chrome itafikia Hifadhi yako ili kutoa mapendekezo kwenye sehemu ya anwani</translation>
 <translation id="7535429826459677826">Google Chrome Dev</translation>
 <translation id="7552219221109926349">Onyesha Mfumo wa Uendeshaji wa Chrome katika lugha hii</translation>
-<translation id="7561940363513215021">{0,plural, =1{Chrome itawaka upya baada ya dakika 1}other{Chrome itawaka upya baada ya dakika #}}</translation>
+<translation id="7561940363513215021">{0,plural, =1{Chrome itafunguka upya baada ya dakika 1}other{Chrome itafunguka upya baada ya dakika #}}</translation>
 <translation id="7589360514048265910">Kompyuta hii haitapokea tena masasisho ya Google Chrome kwa sababu Mac OS X 10.9 haitumiki tena.</translation>
 <translation id="7592736734348559088">Google Chrome haikuweza kusawazisha data yako kwa sababu maelezo yako ya kuingia katika akaunti yanahitaji kusasishwa.</translation>
 <translation id="7626032353295482388">Karibu kwenye Chrome</translation>
@@ -254,7 +254,7 @@
 <translation id="8008534537613507642">Sakinisha Chrome Upya</translation>
 <translation id="8030318113982266900">Inasasisha kituo chako kwenda kituo cha <ph name="CHANNEL_NAME" />...</translation>
 <translation id="8032142183999901390">Baada ya kuiondoa akaunti yako kwenye Chrome, huenda ukahitajika kupakia upya vichupo vyako vilivyo wazi ili kuanza kufanya kazi.</translation>
-<translation id="8077352834923175128">Msimamizi wako anakuomba uzime kisha uwashe mfumo wa uendeshaji wa Chrome ili utumie sasisho hili</translation>
+<translation id="8077352834923175128">Msimamizi wako anakuomba uzime kisha uwashe mfumo wa uendeshaji wa Chrome ili uweke sasisho hili</translation>
 <translation id="8129812357326543296">Kuhusu &amp;Google Chrome</translation>
 <translation id="8179874765710681175">Sakinisha Chrome kwenye simu yako. Tutakutumia SMS kwenye simu yako.</translation>
 <translation id="8183957050892517584">Chrome itahifadhi salama maelezo yako ya kibinafsi ili usihitaji kuyaandika tena.</translation>
diff --git a/chrome/app/resources/google_chrome_strings_ta.xtb b/chrome/app/resources/google_chrome_strings_ta.xtb
index 1eab2a75..af4e1d3 100644
--- a/chrome/app/resources/google_chrome_strings_ta.xtb
+++ b/chrome/app/resources/google_chrome_strings_ta.xtb
@@ -13,7 +13,7 @@
 <translation id="123620459398936149">உங்கள் தரவை Chrome OS ஆல் ஒத்திசைக்க முடியவில்லை. உங்கள் கடவுச்சொற்றொடரைப் புதுப்பிக்கவும்.</translation>
 <translation id="127345590676626841">Chrome தானாகவே புதுப்பித்துக்கொள்வதால், எப்போதுமே புதிய பதிப்பைப் பெறுவீர்கள். இந்தப் பதிவிறக்கம் முடியும்போது, Chrome மறுதொடக்கம் செய்யும், பிறகு உங்கள் பணியைத் தொடரலாம்.</translation>
 <translation id="1302523850133262269">சமீபத்திய முறைமை புதுப்பிப்புகளை Chrome நிறுவும் வரை காத்திருக்கவும்.</translation>
-<translation id="1312676208694947750">{0,plural, =0{Chrome OS புதுப்பிப்பு உள்ளது}=1{Chrome OS புதுப்பிப்பு உள்ளது}other{Chrome OS புதுப்பிப்பு # நாட்களுக்குக் கிடைக்கிறது}}</translation>
+<translation id="1312676208694947750">{0,plural, =0{Chrome OS புதுப்பிப்பு உள்ளது}=1{Chrome OS புதுப்பிப்பு உள்ளது}other{Chrome OS புதுப்பிப்பு # நாட்களாக இருக்கிறது}}</translation>
 <translation id="137466361146087520">Google Chrome பீட்டா</translation>
 <translation id="1393853151966637042">Chrome ஐப் பயன்படுத்தி உதவி பெறுக</translation>
 <translation id="1399397803214730675">Google Chrome இன் மிகச் சமீபத்திய பதிப்பு இந்தக் கணினியில் ஏற்கனவே உள்ளது. மென்பொருள் பணிபுரியவில்லை என்றால், Google Chrome சட்டகத்தினை நிறுவல்நீக்கம் செய்துவிட்டு, மீண்டும் முயற்சி செய்க.</translation>
@@ -154,7 +154,7 @@
 <translation id="4990567037958725628">Google Chrome Canary</translation>
 <translation id="5028489144783860647">உங்கள் தரவை Google Chrome ஆல் ஒத்திசைக்க முடியவில்லை. உங்கள் கடவுச்சொற்றொடரைப் புதுப்பிக்கவும்.</translation>
 <translation id="5062123544085870375">Chrome OSஸை மீண்டும் தொடங்கவும்</translation>
-<translation id="5090044601776247154">Google Chrome 70 பதிப்பு முதல், மேற்பார்வையிடப்படும் பயனர் சுயவிவரங்கள் கிடைக்காது.</translation>
+<translation id="5090044601776247154">Google Chrome 70 பதிப்பு முதல், மேற்பார்வையிடப்படும் பயனர் கணக்குகள் கிடைக்காது.</translation>
 <translation id="5132929315877954718">Google Chrome க்கான சிறந்த பயன்பாடுகள், கேம்ஸ், நீட்டிப்புகள் மற்றும் தீம்களைக் கண்டறியவும்.</translation>
 <translation id="5166975452760862670">Google Chrome இந்த மொழியில் காட்டப்படுகிறது</translation>
 <translation id="5170938038195470297">Google Chrome இன் புத்தம் புதிய பதிப்பு என்பதால், உங்கள் சுயவிவரத்தைப் பயன்படுத்த முடியாது. சில அம்சங்கள் கிடைக்காமல் போகலாம். வேறு சுயவிவர கோப்பகத்தைக் குறிப்பிடுக அல்லது Chrome இன் புதிய பதிப்பைப் பயன்படுத்துக.</translation>
@@ -165,7 +165,7 @@
 <translation id="5386244825306882791">Chrome ஐத் தொடங்கும்போது அல்லது சர்வபுலத்திலிருந்து தேடலை மேற்கொள்ளும்போது காண்பிக்கப்படும் பக்கத்தையும் இது கட்டுப்படுத்துகிறது.</translation>
 <translation id="5430073640787465221">உங்கள் விருப்பத்தேர்வுகளின் கோப்பு சிதைவடைந்துள்ளது அல்லது தவறானது. உங்கள் அமைப்புகளை Google Chrome ஆல் மீட்டெடுக்க முடியவில்லை.</translation>
 <translation id="5483595757826856374">{0,plural, =0{இப்போது Chrome மீண்டும் தொடங்கும்}=1{1 வினாடியில் Chrome மீண்டும் தொடங்கும்}other{# வினாடிகளில் Chrome மீண்டும் தொடங்கும்}}</translation>
-<translation id="5514308096618405748">Chrome OS, Linuxஸை (பீட்டா) போலவே கூடுதல் <ph name="BEGIN_LINK_CROS_OSS" />ஓப்பன் சோர்ஸ் மென்பொருளால்<ph name="END_LINK_CROS_OSS" /> உருவாக்கப்பட்டுள்ளது.</translation>
+<translation id="5514308096618405748">Chrome OS, Linuxஸைப் (பீட்டா) போலவே கூடுதல் <ph name="BEGIN_LINK_CROS_OSS" />ஓப்பன் சோர்ஸ் மென்பொருளால்<ph name="END_LINK_CROS_OSS" /> உருவாக்கப்பட்டுள்ளது.</translation>
 <translation id="556024056938947818">Google Chrome ஆனது கடவுச்சொற்களைக் காட்ட முயற்சிக்கிறது.</translation>
 <translation id="5566025111015594046">Google Chrome (mDNS-In)</translation>
 <translation id="565744775970812598"><ph name="FILE_NAME" /> ஆபத்தானதாக இருக்கலாம் என்பதால் Chrome அதைத் தடுத்துள்ளது.</translation>
@@ -203,7 +203,7 @@
 <translation id="683440813066116847">mDNS ட்ராஃபிக்கை அனுமதிப்பதற்கான, Google Chrome Canary க்கான உள்வரும் விதி.</translation>
 <translation id="686561893457936865">Chromeஐ எல்லா இடங்களுக்கும் எடுத்துச் செல்லலாம்</translation>
 <translation id="6885412569789873916">Chrome பீட்டா பயன்பாடுகள்</translation>
-<translation id="6964107240822114422">{0,plural, =0{Chrome புதுப்பிப்பு உள்ளது}=1{Chrome புதுப்பிப்பு உள்ளது}other{Chrome புதுப்பிப்பு # நாட்களுக்குக் கிடைக்கிறது}}</translation>
+<translation id="6964107240822114422">{0,plural, =0{Chrome புதுப்பிப்பு உள்ளது}=1{Chrome புதுப்பிப்பு உள்ளது}other{Chrome புதுப்பிப்பு # நாட்களாக இருக்கிறது}}</translation>
 <translation id="6967962315388095737">mDNS ட்ராஃபிக்கை அனுமதிக்க, Google Chrome பீட்டாவுக்கான உள்வரும் விதி.</translation>
 <translation id="6970811910055250180">உங்கள் சாதனம் புதுப்பிக்கப்படுகிறது...</translation>
 <translation id="6982337800632491844">இந்தச் சாதனத்தைப் பயன்படுத்துவதற்கு முன் பின்வரும் சேவை விதிமுறைகளைப் படித்து ஏற்றுக்கொள்வது <ph name="DOMAIN" /> க்கு அவசியமாகும். இந்த விதிமுறைகளானது Google Chrome OS விதிமுறைகளை விரிவாக்கவோ, மாற்றவோ அல்லது கட்டுப்படுத்தவோ செய்யாது.</translation>
diff --git a/chrome/app/resources/google_chrome_strings_te.xtb b/chrome/app/resources/google_chrome_strings_te.xtb
index f6a5fa6..39f42c9 100644
--- a/chrome/app/resources/google_chrome_strings_te.xtb
+++ b/chrome/app/resources/google_chrome_strings_te.xtb
@@ -240,6 +240,7 @@
 <translation id="8286862437124483331">Google Chrome పాస్‌వర్డ్‌లను చూపడానికి ప్రయత్నిస్తోంది. దీన్ని అనుమతించడానికి మీ Windows పాస్‌వర్డ్‌ను టైప్ చేయండి.</translation>
 <translation id="8290100596633877290">ఆపండి! Google Chrome క్రాష్ అయ్యింది. ఇప్పుడు మళ్ళీ ప్రారంభించాల?</translation>
 <translation id="8342675569599923794">ఈ ఫైల్ అపాయకరమైనది, కాబట్టి Chrome దీన్ని బ్లాక్ చేసింది.</translation>
+<translation id="8380166467911888159">అక్షరక్రమ లోపాలను పరిష్కరించడానికి, Chrome మీరు వచన ఫీల్డ్‌లలో టైప్ చేసే వచనాన్ని Googleకి పంపుతుంది</translation>
 <translation id="840084489713044809">Google Chrome మీ పాస్‌వర్డ్‌లను ఎగుమతి చేయాలనుకుంటోంది.</translation>
 <translation id="8406086379114794905">Chromeని మెరుగుపరచడంలో సహాయపడండి</translation>
 <translation id="8455999171311319804">ఐచ్ఛికం: విశ్లేషణ మరియు వినియోగ డేటాను స్వయంచాలకంగా Googleకి పంపడం ద్వారా Chrome OSని మెరుగుపరచడంలో సహాయం అందించండి.</translation>
diff --git a/chrome/app/resources/google_chrome_strings_th.xtb b/chrome/app/resources/google_chrome_strings_th.xtb
index 72c5520c..868b00c 100644
--- a/chrome/app/resources/google_chrome_strings_th.xtb
+++ b/chrome/app/resources/google_chrome_strings_th.xtb
@@ -166,7 +166,7 @@
 <translation id="5386244825306882791">อีกทั้งยังควบคุมหน้าที่จะแสดงเมื่อคุณเปิด Chrome หรือทำการค้นหาจากแถบอเนกประสงค์ด้วย</translation>
 <translation id="5430073640787465221">ไฟล์ค่ากำหนดของคุณขัดข้องหรือไม่ถูกต้อง Google Chrome ไม่สามารถกู้คืนการตั้งค่าของคุณได้</translation>
 <translation id="5483595757826856374">{0,plural, =0{Chrome จะเปิดขึ้นมาใหม่ตอนนี้}=1{Chrome จะเปิดขึ้นมาใหม่ใน 1 วินาที}other{Chrome จะเปิดขึ้นมาใหม่ใน # วินาที}}</translation>
-<translation id="5514308096618405748">Chrome OS เกิดขึ้นได้ด้วยการสนับสนุนจาก<ph name="BEGIN_LINK_CROS_OSS" />ซอฟต์แวร์โอเพนซอร์<ph name="END_LINK_CROS_OSS" />เพิ่มเติม ซึ่งก็คือ Linux (เบต้า)</translation>
+<translation id="5514308096618405748">Chrome OS เกิดขึ้นได้ด้วยการสนับสนุนจาก<ph name="BEGIN_LINK_CROS_OSS" />ซอฟต์แวร์โอเพนซอร์ส<ph name="END_LINK_CROS_OSS" />เพิ่มเติม เช่นเดียวกับ Linux (เบต้า)</translation>
 <translation id="556024056938947818">Google Chrome กำลังพยายามแสดงรหัสผ่าน</translation>
 <translation id="5566025111015594046">Google Chrome (mDNS-In)</translation>
 <translation id="565744775970812598"><ph name="FILE_NAME" /> อาจเป็นอันตราย Chrome จึงบล็อกไว้</translation>
@@ -251,7 +251,7 @@
 <translation id="8129812357326543296">เกี่ยวกับ &amp;Google Chrome</translation>
 <translation id="8179874765710681175">ติดตั้ง Chrome ในโทรศัพท์ เราจะส่ง SMS ไปยังโทรศัพท์ของคุณ</translation>
 <translation id="8183957050892517584">Chrome จะเก็บรายละเอียดส่วนตัวของคุณอย่างปลอดภัย คุณจึงไม่จำเป็นต้องพิมพ์รายละเอียดเหล่านั้นอีกครั้ง</translation>
-<translation id="8226081633851087288">{0,plural, =0{Chrome OS กำลังจะรีสตาร์ท}=1{Chrome OS จะรีสตาร์ทใน 1 วินาที}other{Chrome OS จะรีสตาร์ทใน # วินาที}}</translation>
+<translation id="8226081633851087288">{0,plural, =0{Chrome OS จะรีสตาร์ทตอนนี้}=1{Chrome OS จะรีสตาร์ทใน 1 วินาที}other{Chrome OS จะรีสตาร์ทใน # วินาที}}</translation>
 <translation id="8255190535488645436">Google Chrome ใช้กล้องและไมโครโฟนของคุณอยู่</translation>
 <translation id="8286862437124483331">Google Chrome กำลังพยายามแสดงรหัสผ่าน พิมพ์รหัสผ่าน Windows ของคุณเพื่อแสดงรหัสผ่าน</translation>
 <translation id="8290100596633877290">อ๊ะ! Google Chrome ขัดข้อง เปิดใหม่ตอนนี้หรือไม่</translation>
diff --git a/chrome/app/resources/google_chrome_strings_tr.xtb b/chrome/app/resources/google_chrome_strings_tr.xtb
index ee61739..df55029 100644
--- a/chrome/app/resources/google_chrome_strings_tr.xtb
+++ b/chrome/app/resources/google_chrome_strings_tr.xtb
@@ -149,12 +149,12 @@
 <translation id="4891791193823137474">Google Chrome'un arka planda çalışmasına izin ver</translation>
 <translation id="4895437082222824641">Bağlantıyı yeni Chrome &amp;sekmesinde aç</translation>
 <translation id="4921569541910214635">Bilgisayarı paylaşıyor musunuz? Artık Chrome'u tam olarak istediğiniz gibi kurabilirsiniz.</translation>
-<translation id="4945319281866068441">{0,plural, =1{Chrome OS'u bir gün içinde yeniden başlatın}other{Chrome OS'u # gün içinde yeniden başlatın}}</translation>
+<translation id="4945319281866068441">{0,plural, =1{Chrome OS'i bir gün içinde yeniden başlatın}other{Chrome OS'i # gün içinde yeniden başlatın}}</translation>
 <translation id="4953650215774548573">Google Chrome'u varsayılan tarayıcım olarak ayarla</translation>
 <translation id="495931528404527476">Chrome'da</translation>
 <translation id="4990567037958725628">Google Chrome Canary</translation>
 <translation id="5028489144783860647">Google Chrome, verilerinizi senkronize edemedi. Lütfen Senkronizasyon parolanızı güncelleyin.</translation>
-<translation id="5062123544085870375">Chrome OS'u yeniden başlat</translation>
+<translation id="5062123544085870375">Chrome OS'i yeniden başlat</translation>
 <translation id="5090044601776247154">Google Chrome 70 sürümünden itibaren denetlenen kullanıcı profilleri artık kullanılmayacaktır.</translation>
 <translation id="5132929315877954718">Google Chrome'a özgü harika uygulamaları, oyunları, uzantıları ve temaları keşfedin.</translation>
 <translation id="5166975452760862670">Google Chrome bu dilde görüntüleniyor</translation>
@@ -211,7 +211,7 @@
 <translation id="6970811910055250180">Cihazınız güncelleniyor...</translation>
 <translation id="6982337800632491844"><ph name="DOMAIN" />, bu cihazı kullanmadan önce aşağıdaki Hizmet Şartları'nı okuyup kabul etmenizi gerektirmektedir. Bu şartlar, Google Chromium OS Şartları'nın kapsamını genişletmez, değiştirmez veya sınırlamaz.</translation>
 <translation id="6989339256997917931">Google Chrome güncellendi, ancak programı en azından 30 gündür kullanmıyorsunuz.</translation>
-<translation id="7023651421574588884">Bir güncellemeyi uygulamak için yöneticiniz Chrome OS'u yeniden başlatmanızı gerektiriyor</translation>
+<translation id="7023651421574588884">Bir güncellemeyi uygulamak için yöneticiniz Chrome OS'i yeniden başlatmanızı gerektiriyor</translation>
 <translation id="7098166902387133879">Google Chrome mikrofonunuzu kullanıyor.</translation>
 <translation id="7106741999175697885">Görev Yöneticisi - Google Chrome</translation>
 <translation id="7164397146364144019">Olası güvenlik olaylarının ayrıntılarını Google'a otomatik olarak bildirerek Chrome'un daha güvenli ve kullanımı kolay bir hale gelmesine yardımcı olabilirsiniz.</translation>
@@ -249,7 +249,7 @@
 <translation id="8008534537613507642">Chrome'u yeniden yükle</translation>
 <translation id="8030318113982266900">Cihazınız <ph name="CHANNEL_NAME" /> kanalına güncelleniyor...</translation>
 <translation id="8032142183999901390">Hesabınızı Chrome'dan kaldırdıktan sonra, geçerli olması için açık sekmelerinizi yeniden yüklemeniz gerekebilir.</translation>
-<translation id="8077352834923175128">Yöneticiniz bu güncellemeyi uygulamak için Chrome OS'u yeniden başlatmanızı istiyor</translation>
+<translation id="8077352834923175128">Yöneticiniz bu güncellemeyi uygulamak için Chrome OS'i yeniden başlatmanızı istiyor</translation>
 <translation id="8129812357326543296">&amp;Google Chrome hakkında</translation>
 <translation id="8179874765710681175">Chrome'u telefonunuza yükleyin. Telefonunuza SMS göndereceğiz.</translation>
 <translation id="8183957050892517584">Chrome, kişisel bilgilerinizi güvenli şekilde depoladığından bu bilgileri tekrar yazmak zorunda kalmazsınız.</translation>
diff --git a/chrome/app/resources/google_chrome_strings_zh-TW.xtb b/chrome/app/resources/google_chrome_strings_zh-TW.xtb
index 51a475bd..ee55edf 100644
--- a/chrome/app/resources/google_chrome_strings_zh-TW.xtb
+++ b/chrome/app/resources/google_chrome_strings_zh-TW.xtb
@@ -210,7 +210,7 @@
 <translation id="6970811910055250180">正在更新裝置...</translation>
 <translation id="6982337800632491844">在使用這個裝置前,<ph name="DOMAIN" /> 要求你詳讀並接受下列《服務條款》。這些條款不會擴展、修改或限制《Chrome 作業系統條款》。</translation>
 <translation id="6989339256997917931">Google Chrome 已更新,但你至少已有 30 天未使用該瀏覽器。</translation>
-<translation id="7023651421574588884">管理員要求你重新啟動 Chrome 作業系統,以套用更新</translation>
+<translation id="7023651421574588884">你的管理員要求你重新啟動 Chrome 作業系統,以套用更新</translation>
 <translation id="7098166902387133879">Google Chrome 正在使用你的麥克風。</translation>
 <translation id="7106741999175697885">工作管理員 - Google Chrome</translation>
 <translation id="7164397146364144019">你可以自動將疑似安全性事件的詳細資料傳送給 Google,協助我們讓 Chrome 變得更安全、更好用。</translation>
@@ -248,7 +248,7 @@
 <translation id="8008534537613507642">重新安裝 Chrome</translation>
 <translation id="8030318113982266900">正在將裝置更新至「<ph name="CHANNEL_NAME" />」...</translation>
 <translation id="8032142183999901390">將你的帳戶從 Chrome 移除後,可能需要重新載入開啟的分頁,操作才會生效。</translation>
-<translation id="8077352834923175128">管理員請你重新啟動 Chrome 作業系統,以套用這項更新</translation>
+<translation id="8077352834923175128">你的管理員請你重新啟動 Chrome 作業系統,以套用這項更新</translation>
 <translation id="8129812357326543296">關於 Google Chrome(&amp;G)</translation>
 <translation id="8179874765710681175">在手機上安裝 Chrome。我們會傳送簡訊到你的手機。</translation>
 <translation id="8183957050892517584">Chrome 會妥善保存您的個人詳細資料,讓您不必重複輸入相同的資料。</translation>
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 9164beab..7a6aedc 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -435,10 +435,10 @@
       Linux (Beta)
     </message>
     <message name="IDS_SETTINGS_CROSTINI_LABEL" desc="The text associated with the primary section setting.">
-      Linux (Beta)
+      Linux
     </message>
     <message name="IDS_SETTINGS_CROSTINI_SUBTEXT" desc="Description for the section for enabling and managing Crostini.">
-      Run Linux tools, editors, and IDEs on your Chromebook.
+      Run Linux tools, editors, and IDEs on your Chromebook. &lt;a target="_blank" href="<ph name="URL">$1<ex>https://google.com/</ex></ph>"&gt;Learn more&lt;/a&gt;
     </message>
     <message name="IDS_SETTINGS_CROSTINI_REMOVE" desc="Label for the row to open a dialog confirming removal of Crostini.">
       Remove Linux Apps for Chromebook
diff --git a/chrome/app/vector_icons/picture_in_picture_alt.icon b/chrome/app/vector_icons/picture_in_picture_alt.icon
index eeaa252..1bf1d9fa 100644
--- a/chrome/app/vector_icons/picture_in_picture_alt.icon
+++ b/chrome/app/vector_icons/picture_in_picture_alt.icon
@@ -4,25 +4,21 @@
 
 // Set to Google Blue 600
 PATH_COLOR_ARGB, 0xFF, 0x1A, 0x73, 0xE8,
-MOVE_TO, 38, 22,
-H_LINE_TO, 22,
-R_V_LINE_TO, 11.99f,
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 16, 14,
+H_LINE_TO, 0,
+V_LINE_TO, 2,
 R_H_LINE_TO, 16,
-V_LINE_TO, 22,
+R_V_LINE_TO, 12,
 CLOSE,
-R_MOVE_TO, 8, 16,
-V_LINE_TO, 9.96f,
-CUBIC_TO, 46, 7.76f, 44.2f, 6, 42, 6,
-H_LINE_TO, 6,
-CUBIC_TO, 3.8f, 6, 2, 7.76f, 2, 9.96f,
-V_LINE_TO, 38,
-R_CUBIC_TO, 0, 2.2f, 1.8f, 4, 4, 4,
-R_H_LINE_TO, 36,
-R_CUBIC_TO, 2.2f, 0, 4, -1.8f, 4, -4,
+MOVE_TO, 1, 13,
+R_H_LINE_TO, 14,
+V_LINE_TO, 3,
+H_LINE_TO, 1,
+R_V_LINE_TO, 10,
 CLOSE,
-R_MOVE_TO, -4, 0.04f,
-H_LINE_TO, 6,
-V_LINE_TO, 9.94f,
-R_H_LINE_TO, 36,
-R_V_LINE_TO, 28.1f,
+MOVE_TO, 8, 8,
+R_H_LINE_TO, 6,
+R_V_LINE_TO, 4,
+H_LINE_TO, 8,
 CLOSE
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 2e94d4cb..68e0d67 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1640,7 +1640,7 @@
     "//build/config/compiler:wexit_time_destructors",
     "//build/config:precompiled_headers",
   ]
-  defines = []
+  defines = [ "ZLIB_CONST" ]
   libs = []
   ldflags = []
 
@@ -2097,6 +2097,8 @@
       "android/explore_sites/catalog.h",
       "android/explore_sites/explore_sites_bridge.cc",
       "android/explore_sites/explore_sites_bridge.h",
+      "android/explore_sites/explore_sites_feature.cc",
+      "android/explore_sites/explore_sites_feature.h",
       "android/explore_sites/ntp_json_fetcher.cc",
       "android/explore_sites/ntp_json_fetcher.h",
       "android/explore_sites/url_util.cc",
@@ -2386,6 +2388,7 @@
       "password_manager/generated_password_saved_infobar_delegate_android.h",
       "password_manager/password_accessory_controller.cc",
       "password_manager/password_accessory_controller.h",
+      "password_manager/password_accessory_metrics_util.h",
       "password_manager/password_accessory_view_interface.h",
       "password_manager/password_generation_dialog_view_interface.h",
       "password_manager/password_manager_infobar_delegate_android.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2e8327a..854c587 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -144,6 +144,7 @@
 #if defined(OS_ANDROID)
 #include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/browser/android/download/new_net_error_page_feature.h"
+#include "chrome/browser/android/explore_sites/explore_sites_feature.h"
 #else  // OS_ANDROID
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "ui/message_center/public/cpp/features.h"
@@ -1206,6 +1207,12 @@
     {"Content Preview", &kNewNetErrorPageUIContentPreview, 1, nullptr},
     {"Content Preview + Auto download", &kNewNetErrorPageUIContentPreviewAutoDL,
      1, nullptr}};
+
+const FeatureEntry::FeatureParam kExploreSitesExperimental = {
+    chrome::android::explore_sites::kExploreSitesVariationParameterName,
+    chrome::android::explore_sites::kExploreSitesVariationExperimental};
+const FeatureEntry::FeatureVariation kExploreSitesVariations[] = {
+    {"Experimental", &kExploreSitesExperimental, 1, nullptr}};
 #endif  // defined(OS_ANDROID)
 
 // RECORDING USER METRICS FOR FLAGS:
@@ -1380,7 +1387,9 @@
      FEATURE_VALUE_TYPE(chrome::android::kContextualSearchUnityIntegration)},
     {"explore-sites", flag_descriptions::kExploreSitesName,
      flag_descriptions::kExploreSitesDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kExploreSites)},
+     FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kExploreSites,
+                                    kExploreSitesVariations,
+                                    "ExploreSites")},
 #endif  // OS_ANDROID
     {"show-autofill-type-predictions",
      flag_descriptions::kShowAutofillTypePredictionsName,
@@ -3743,6 +3752,11 @@
                                     kUnifiedConsentVariations,
                                     "UnifiedConsentVariations")},
 
+    {"force-unified-consent-bump",
+     flag_descriptions::kForceUnifiedConsentBumpName,
+     flag_descriptions::kForceUnifiedConsentBumpDescription, kOsAll,
+     FEATURE_VALUE_TYPE(unified_consent::kForceUnifiedConsentBump)},
+
     {"simplify-https-indicator", flag_descriptions::kSimplifyHttpsIndicatorName,
      flag_descriptions::kSimplifyHttpsIndicatorDescription, kOsDesktop,
      FEATURE_WITH_PARAMS_VALUE_TYPE(toolbar::features::kSimplifyHttpsIndicator,
@@ -4202,6 +4216,12 @@
      flag_descriptions::kLongPressBackNewDesignDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kLongPressBackNewDesign)},
 #endif
+
+    {"sync-standalone-transport",
+     flag_descriptions::kSyncStandaloneTransportName,
+     flag_descriptions::kSyncStandaloneTransportDescription, kOsAll,
+     FEATURE_VALUE_TYPE(switches::kSyncStandaloneTransport)},
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
index 39f36fa..d26c60e7 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
@@ -167,6 +167,9 @@
 
   SetDiscourseContextAndAddToHeader(*context_);
 
+  // Disable cookies for this request.
+  search_term_fetcher_->SetAllowCredentials(false);
+
   search_term_fetcher_->Start();
 }
 
@@ -382,18 +385,17 @@
   if (!sync_service)
     return false;
 
-  // Check whether the user has enabled *personalized* URL-keyed data collection
+  // Check whether the user has enabled anonymous URL-keyed data collection
   // from the unified consent service.
-  bool is_unified_consent_enabled = IsUnifiedConsentEnabled(profile);
   std::unique_ptr<UrlKeyedDataCollectionConsentHelper>
-      personalized_unified_consent_url_helper =
+      anonymized_unified_consent_url_helper =
           UrlKeyedDataCollectionConsentHelper::
-              NewPersonalizedDataCollectionConsentHelper(
-                  is_unified_consent_enabled, sync_service);
-  // TODO(donnd): if not enabled, check if *anonymous* URL-keyed data collection
-  // is enabled and do what's needed to send it but not logging it.
-  // See https://crbug.com.com/865104 for details.
-  return personalized_unified_consent_url_helper->IsEnabled();
+              NewAnonymizedDataCollectionConsentHelper(
+                  IsUnifiedConsentEnabled(profile),
+                  ProfileManager::GetActiveUserProfile()->GetPrefs(),
+                  sync_service);
+  // If they have, then allow sending of the URL.
+  return anonymized_unified_consent_url_helper->IsEnabled();
 }
 
 // Gets the target language from the translate service using the user's profile.
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature.cc b/chrome/browser/android/explore_sites/explore_sites_feature.cc
new file mode 100644
index 0000000..d86fb77
--- /dev/null
+++ b/chrome/browser/android/explore_sites/explore_sites_feature.cc
@@ -0,0 +1,33 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/explore_sites/explore_sites_feature.h"
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "chrome/browser/android/chrome_feature_list.h"
+
+namespace chrome {
+namespace android {
+namespace explore_sites {
+
+const char kExploreSitesVariationParameterName[] = "variation";
+
+const char kExploreSitesVariationExperimental[] = "experiment";
+
+ExploreSitesVariation GetExploreSitesVariation() {
+  if (base::FeatureList::IsEnabled(kExploreSites)) {
+    if (base::GetFieldTrialParamValueByFeature(
+            kExploreSites, kExploreSitesVariationParameterName) ==
+        kExploreSitesVariationExperimental) {
+      return ExploreSitesVariation::EXPERIMENT;
+    }
+    return ExploreSitesVariation::ENABLED;
+  }
+  return ExploreSitesVariation::DISABLED;
+}
+
+}  // namespace explore_sites
+}  // namespace android
+}  // namespace chrome
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature.h b/chrome/browser/android/explore_sites/explore_sites_feature.h
new file mode 100644
index 0000000..121815a4f
--- /dev/null
+++ b/chrome/browser/android/explore_sites/explore_sites_feature.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_EXPLORE_SITES_EXPLORE_SITES_FEATURE_H_
+#define CHROME_BROWSER_ANDROID_EXPLORE_SITES_EXPLORE_SITES_FEATURE_H_
+
+namespace chrome {
+namespace android {
+namespace explore_sites {
+
+extern const char kExploreSitesVariationParameterName[];
+
+extern const char kExploreSitesVariationExperimental[];
+
+enum class ExploreSitesVariation { ENABLED, EXPERIMENT, DISABLED };
+
+ExploreSitesVariation GetExploreSitesVariation();
+
+}  // namespace explore_sites
+}  // namespace android
+}  // namespace chrome
+
+#endif  // CHROME_BROWSER_ANDROID_EXPLORE_SITES_EXPLORE_SITES_FEATURE_H_
diff --git a/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc b/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc
new file mode 100644
index 0000000..39d984f
--- /dev/null
+++ b/chrome/browser/android/explore_sites/explore_sites_feature_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/explore_sites/explore_sites_feature.h"
+
+#include <map>
+#include <string>
+
+#include "base/feature_list.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/android/chrome_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome {
+namespace android {
+namespace explore_sites {
+
+TEST(ExploreSitesFeatureTest, ExploreSitesEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kExploreSites);
+  EXPECT_EQ(ExploreSitesVariation::ENABLED, GetExploreSitesVariation());
+}
+
+TEST(ExploreSitesFeatureTest, ExploreSitesDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kExploreSites);
+  EXPECT_EQ(ExploreSitesVariation::DISABLED, GetExploreSitesVariation());
+}
+
+TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithExperiment) {
+  std::map<std::string, std::string> parameters;
+  parameters[kExploreSitesVariationParameterName] =
+      kExploreSitesVariationExperimental;
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(kExploreSites,
+                                                         parameters);
+  EXPECT_EQ(ExploreSitesVariation::EXPERIMENT, GetExploreSitesVariation());
+}
+
+TEST(ExploreSitesFeatureTest, ExploreSitesEnabledWithBogus) {
+  const char bogusParamValue[] = "bogus";
+  std::map<std::string, std::string> parameters;
+  parameters[kExploreSitesVariationParameterName] = bogusParamValue;
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(kExploreSites,
+                                                         parameters);
+  EXPECT_EQ(ExploreSitesVariation::ENABLED, GetExploreSitesVariation());
+}
+
+}  // namespace explore_sites
+}  // namespace android
+}  // namespace chrome
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index 8b90919..94c21c92 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -19,7 +19,11 @@
     "android_vsync_helper.h",
     "autocomplete_controller.cc",
     "autocomplete_controller.h",
+    "controller_delegate_for_testing.cc",
+    "controller_delegate_for_testing.h",
     "gl_browser_interface.h",
+    "gvr_controller_delegate.cc",
+    "gvr_controller_delegate.h",
     "gvr_keyboard_delegate.cc",
     "gvr_keyboard_delegate.h",
     "gvr_keyboard_shim.cc",
diff --git a/chrome/browser/android/vr/controller_delegate_for_testing.cc b/chrome/browser/android/vr/controller_delegate_for_testing.cc
new file mode 100644
index 0000000..8ad6b7d0
--- /dev/null
+++ b/chrome/browser/android/vr/controller_delegate_for_testing.cc
@@ -0,0 +1,133 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr/controller_delegate_for_testing.h"
+
+#include "chrome/browser/vr/input_event.h"
+#include "chrome/browser/vr/test/constants.h"
+#include "chrome/browser/vr/ui_interface.h"
+#include "chrome/browser/vr/ui_scene_constants.h"
+#include "chrome/browser/vr/ui_test_input.h"
+
+namespace {
+
+// Laser origin relative to the center of the controller.
+constexpr gfx::Point3F kLaserOriginOffset = {0.0f, 0.0f, -0.05f};
+
+// We position the controller in a fixed position (no arm model).
+// The location constants are approximations that allow us to have the
+// controller and the laser visible on the screenshots.
+void SetOriginAndTransform(vr::ControllerModel* model) {
+  gfx::Transform mat;
+  mat.Translate3d(vr::kStartControllerPosition);
+  mat.PreconcatTransform(gfx::Transform(
+      gfx::Quaternion(vr::kForwardVector, model->laser_direction)));
+  model->transform = mat;
+  model->laser_origin = kLaserOriginOffset;
+  mat.TransformPoint(&model->laser_origin);
+}
+
+}  // namespace
+
+namespace vr {
+
+ControllerDelegateForTesting::ControllerDelegateForTesting(UiInterface* ui)
+    : ui_(ui) {
+  cached_controller_model_.laser_direction = kForwardVector;
+  SetOriginAndTransform(&cached_controller_model_);
+}
+
+ControllerDelegateForTesting::~ControllerDelegateForTesting() = default;
+
+void ControllerDelegateForTesting::QueueControllerActionForTesting(
+    ControllerTestInput controller_input) {
+  DCHECK_NE(controller_input.action,
+            VrControllerTestAction::kRevertToRealController);
+  ControllerModel controller_model;
+  auto target_point = ui_->GetTargetPointForTesting(
+      controller_input.element_name, controller_input.position);
+  auto direction = (target_point - kStartControllerPosition) - kOrigin;
+  direction.GetNormalized(&controller_model.laser_direction);
+  SetOriginAndTransform(&controller_model);
+
+  switch (controller_input.action) {
+    case VrControllerTestAction::kClick:
+      // Add in the button down action.
+      controller_model.touchpad_button_state =
+          UiInputManager::ButtonState::DOWN;
+      controller_model_queue_.push(controller_model);
+      // Add in the button up action.
+      controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
+      controller_model_queue_.push(controller_model);
+      break;
+    case VrControllerTestAction::kHover:
+      FALLTHROUGH;
+    case VrControllerTestAction::kClickUp:
+      controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
+      controller_model_queue_.push(controller_model);
+      break;
+    case VrControllerTestAction::kClickDown:
+      controller_model.touchpad_button_state =
+          UiInputManager::ButtonState::DOWN;
+      controller_model_queue_.push(controller_model);
+      break;
+    case VrControllerTestAction::kMove:
+      // Use whatever the last button state is.
+      if (!IsQueueEmpty()) {
+        controller_model.touchpad_button_state =
+            controller_model_queue_.back().touchpad_button_state;
+      } else {
+        controller_model.touchpad_button_state =
+            cached_controller_model_.touchpad_button_state;
+      }
+      controller_model_queue_.push(controller_model);
+      break;
+    default:
+      NOTREACHED() << "Given unsupported controller action";
+  }
+}
+
+bool ControllerDelegateForTesting::IsQueueEmpty() const {
+  return controller_model_queue_.empty();
+}
+
+void ControllerDelegateForTesting::UpdateController(
+    const RenderInfo& render_info,
+    base::TimeTicks current_time,
+    bool is_webxr_frame) {
+  if (!controller_model_queue_.empty()) {
+    cached_controller_model_ = controller_model_queue_.front();
+    controller_model_queue_.pop();
+  }
+  cached_controller_model_.last_orientation_timestamp = current_time;
+  cached_controller_model_.last_button_timestamp = current_time;
+}
+
+ControllerModel ControllerDelegateForTesting::GetModel(
+    const RenderInfo& render_info) {
+  return cached_controller_model_;
+}
+
+InputEventList ControllerDelegateForTesting::GetGestures(
+    base::TimeTicks current_time) {
+  return InputEventList();
+}
+
+device::mojom::XRInputSourceStatePtr
+ControllerDelegateForTesting::GetInputSourceState() {
+  auto state = device::mojom::XRInputSourceState::New();
+  state->description = device::mojom::XRInputSourceDescription::New();
+  state->source_id = 1;
+  state->description->target_ray_mode =
+      device::mojom::XRTargetRayMode::POINTING;
+  state->description->emulated_position = true;
+
+  return state;
+}
+
+void ControllerDelegateForTesting::OnResume() {}
+
+void ControllerDelegateForTesting::OnPause() {}
+
+}  // namespace vr
diff --git a/chrome/browser/android/vr/controller_delegate_for_testing.h b/chrome/browser/android/vr/controller_delegate_for_testing.h
new file mode 100644
index 0000000..15f96181
--- /dev/null
+++ b/chrome/browser/android/vr/controller_delegate_for_testing.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_VR_CONTROLLER_DELEGATE_FOR_TESTING_H_
+#define CHROME_BROWSER_ANDROID_VR_CONTROLLER_DELEGATE_FOR_TESTING_H_
+
+#include <queue>
+
+#include "base/macros.h"
+#include "chrome/browser/vr/controller_delegate.h"
+#include "chrome/browser/vr/model/controller_model.h"
+
+namespace vr {
+
+class UiInterface;
+struct ControllerTestInput;
+
+class ControllerDelegateForTesting : public ControllerDelegate {
+ public:
+  explicit ControllerDelegateForTesting(UiInterface* ui);
+  ~ControllerDelegateForTesting() override;
+
+  void QueueControllerActionForTesting(ControllerTestInput controller_input);
+  bool IsQueueEmpty() const;
+
+  // ControllerDelegate implementation.
+  void UpdateController(const RenderInfo& render_info,
+                        base::TimeTicks current_time,
+                        bool is_webxr_frame) override;
+  ControllerModel GetModel(const RenderInfo& render_info) override;
+  InputEventList GetGestures(base::TimeTicks current_time) override;
+  device::mojom::XRInputSourceStatePtr GetInputSourceState() override;
+  void OnResume() override;
+  void OnPause() override;
+
+ private:
+  UiInterface* ui_;
+  std::queue<ControllerModel> controller_model_queue_;
+  ControllerModel cached_controller_model_;
+
+  DISALLOW_COPY_AND_ASSIGN(ControllerDelegateForTesting);
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_ANDROID_VR_CONTROLLER_DELEGATE_FOR_TESTING_H_
diff --git a/chrome/browser/android/vr/gvr_controller_delegate.cc b/chrome/browser/android/vr/gvr_controller_delegate.cc
new file mode 100644
index 0000000..232546b
--- /dev/null
+++ b/chrome/browser/android/vr/gvr_controller_delegate.cc
@@ -0,0 +1,149 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/vr/gvr_controller_delegate.h"
+
+#include <utility>
+
+#include "chrome/browser/android/vr/gl_browser_interface.h"
+#include "chrome/browser/vr/input_event.h"
+#include "chrome/browser/vr/model/controller_model.h"
+#include "chrome/browser/vr/pose_util.h"
+#include "chrome/browser/vr/ui_renderer.h"
+
+namespace {
+constexpr gfx::Vector3dF kForwardVector = {0.0f, 0.0f, -1.0f};
+}
+
+namespace vr {
+
+GvrControllerDelegate::GvrControllerDelegate(
+    std::unique_ptr<VrController> controller,
+    GlBrowserInterface* browser)
+    : controller_(std::move(controller)), browser_(browser) {}
+
+GvrControllerDelegate::~GvrControllerDelegate() = default;
+
+void GvrControllerDelegate::UpdateController(const RenderInfo& render_info,
+                                             base::TimeTicks current_time,
+                                             bool is_webxr_frame) {
+  controller_->UpdateState(render_info.head_pose);
+
+  device::GvrGamepadData controller_data = controller_->GetGamepadData();
+  if (!is_webxr_frame)
+    controller_data.connected = false;
+  browser_->UpdateGamepadData(controller_data);
+}
+
+ControllerModel GvrControllerDelegate::GetModel(const RenderInfo& render_info) {
+  gfx::Vector3dF head_direction = GetForwardVector(render_info.head_pose);
+
+  gfx::Vector3dF controller_direction;
+  gfx::Quaternion controller_quat;
+  if (!controller_->IsConnected()) {
+    // No controller detected, set up a gaze cursor that tracks the forward
+    // direction.
+    controller_direction = kForwardVector;
+    controller_quat = gfx::Quaternion(kForwardVector, head_direction);
+  } else {
+    controller_direction = {0.0f, -sin(kErgoAngleOffset),
+                            -cos(kErgoAngleOffset)};
+    controller_quat = controller_->Orientation();
+  }
+  gfx::Transform(controller_quat).TransformVector(&controller_direction);
+
+  ControllerModel controller_model;
+  controller_->GetTransform(&controller_model.transform);
+  controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
+  DCHECK(!(controller_->ButtonUpHappened(PlatformController::kButtonSelect) &&
+           controller_->ButtonDownHappened(PlatformController::kButtonSelect)))
+      << "Cannot handle a button down and up event within one frame.";
+  if (controller_->ButtonState(gvr::kControllerButtonClick)) {
+    controller_model.touchpad_button_state = UiInputManager::ButtonState::DOWN;
+  }
+  controller_model.app_button_state =
+      controller_->ButtonState(gvr::kControllerButtonApp)
+          ? UiInputManager::ButtonState::DOWN
+          : UiInputManager::ButtonState::UP;
+  controller_model.home_button_state =
+      controller_->ButtonState(gvr::kControllerButtonHome)
+          ? UiInputManager::ButtonState::DOWN
+          : UiInputManager::ButtonState::UP;
+  controller_model.opacity = controller_->GetOpacity();
+  controller_model.laser_direction = controller_direction;
+  controller_model.laser_origin = controller_->GetPointerStart();
+  controller_model.handedness = controller_->GetHandedness();
+  controller_model.recentered = controller_->GetRecentered();
+  controller_model.touching_touchpad = controller_->IsTouchingTrackpad();
+  controller_model.touchpad_touch_position =
+      controller_->GetPositionInTrackpad();
+  controller_model.last_orientation_timestamp =
+      controller_->GetLastOrientationTimestamp();
+  controller_model.last_button_timestamp =
+      controller_->GetLastButtonTimestamp();
+  controller_model.battery_level = controller_->GetBatteryLevel();
+  return controller_model;
+}
+
+InputEventList GvrControllerDelegate::GetGestures(
+    base::TimeTicks current_time) {
+  if (!controller_->IsConnected())
+    return {};
+  return gesture_detector_.DetectGestures(*controller_, current_time);
+}
+
+device::mojom::XRInputSourceStatePtr
+GvrControllerDelegate::GetInputSourceState() {
+  device::mojom::XRInputSourceStatePtr state =
+      device::mojom::XRInputSourceState::New();
+  state->description = device::mojom::XRInputSourceDescription::New();
+
+  // Only one controller is supported, so the source id can be static.
+  state->source_id = 1;
+
+  // It's a handheld pointing device.
+  state->description->target_ray_mode =
+      device::mojom::XRTargetRayMode::POINTING;
+
+  // Controller uses an arm model.
+  state->description->emulated_position = true;
+
+  if (controller_->IsConnected()) {
+    // Set the primary button state.
+    bool select_button_down =
+        controller_->IsButtonDown(PlatformController::kButtonSelect);
+    state->primary_input_pressed = select_button_down;
+    state->primary_input_clicked =
+        was_select_button_down_ && !select_button_down;
+    was_select_button_down_ = select_button_down;
+
+    // Set handedness.
+    state->description->handedness =
+        controller_->GetHandedness() == PlatformController::kRightHanded
+            ? device::mojom::XRHandedness::RIGHT
+            : device::mojom::XRHandedness::LEFT;
+
+    // Get the grip transform
+    gfx::Transform grip;
+    controller_->GetTransform(&grip);
+    state->grip = grip;
+
+    // Set the pointer offset from the grip transform.
+    gfx::Transform pointer;
+    controller_->GetRelativePointerTransform(&pointer);
+    state->description->pointer_offset = pointer;
+  }
+
+  return state;
+}
+
+void GvrControllerDelegate::OnResume() {
+  controller_->OnResume();
+}
+
+void GvrControllerDelegate::OnPause() {
+  controller_->OnPause();
+}
+
+}  // namespace vr
diff --git a/chrome/browser/android/vr/gvr_controller_delegate.h b/chrome/browser/android/vr/gvr_controller_delegate.h
new file mode 100644
index 0000000..932ef92
--- /dev/null
+++ b/chrome/browser/android/vr/gvr_controller_delegate.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_VR_GVR_CONTROLLER_DELEGATE_H_
+#define CHROME_BROWSER_ANDROID_VR_GVR_CONTROLLER_DELEGATE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/android/vr/vr_controller.h"
+#include "chrome/browser/vr/controller_delegate.h"
+
+namespace vr {
+
+class GestureDetector;
+class GlBrowserInterface;
+
+class GvrControllerDelegate : public ControllerDelegate {
+ public:
+  GvrControllerDelegate(std::unique_ptr<VrController> controller,
+                        GlBrowserInterface* browser);
+  ~GvrControllerDelegate() override;
+
+  // ControllerDelegate implementation.
+  void UpdateController(const RenderInfo& render_info,
+                        base::TimeTicks current_time,
+                        bool is_webxr_frame) override;
+  ControllerModel GetModel(const RenderInfo& render_info) override;
+  InputEventList GetGestures(base::TimeTicks current_time) override;
+  device::mojom::XRInputSourceStatePtr GetInputSourceState() override;
+  void OnResume() override;
+  void OnPause() override;
+
+ private:
+  std::unique_ptr<VrController> controller_;
+  GestureDetector gesture_detector_;
+  GlBrowserInterface* browser_;
+
+  bool was_select_button_down_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(GvrControllerDelegate);
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_ANDROID_VR_GVR_CONTROLLER_DELEGATE_H_
diff --git a/chrome/browser/android/vr/vr_controller.cc b/chrome/browser/android/vr/vr_controller.cc
index cb127286..0e9803a 100644
--- a/chrome/browser/android/vr/vr_controller.cc
+++ b/chrome/browser/android/vr/vr_controller.cc
@@ -26,10 +26,6 @@
 constexpr float kFadeDistanceFromFace = 0.34f;
 constexpr float kDeltaAlpha = 3.0f;
 
-// Small deadzone for testing that prevents the controller's head offset from
-// being updated every frame on 3DOF devices.
-constexpr float kHeadOffsetDeadzone = 0.0005f;
-
 void ClampTouchpadPosition(gfx::PointF* position) {
   position->set_x(base::ClampToRange(position->x(), 0.0f, 1.0f));
   position->set_y(base::ClampToRange(position->y(), 0.0f, 1.0f));
@@ -126,54 +122,6 @@
   return pad;
 }
 
-device::mojom::XRInputSourceStatePtr VrController::GetInputSourceState() {
-  device::mojom::XRInputSourceStatePtr state =
-      device::mojom::XRInputSourceState::New();
-
-  // Only one controller is supported, so the source id can be static.
-  state->source_id = 1;
-
-  // Set the primary button state.
-  state->primary_input_pressed = ButtonState(GVR_CONTROLLER_BUTTON_CLICK);
-
-  if (ButtonUpHappened(PlatformController::kButtonSelect))
-    state->primary_input_clicked = true;
-
-  state->description = device::mojom::XRInputSourceDescription::New();
-
-  // It's a handheld pointing device.
-  state->description->target_ray_mode =
-      device::mojom::XRTargetRayMode::POINTING;
-
-  // Controller uses an arm model.
-  state->description->emulated_position = true;
-
-  // Set handedness.
-  switch (handedness_) {
-    case GVR_CONTROLLER_LEFT_HANDED:
-      state->description->handedness = device::mojom::XRHandedness::LEFT;
-      break;
-    case GVR_CONTROLLER_RIGHT_HANDED:
-      state->description->handedness = device::mojom::XRHandedness::RIGHT;
-      break;
-    default:
-      state->description->handedness = device::mojom::XRHandedness::NONE;
-      break;
-  }
-
-  // Get the grip transform
-  gfx::Transform grip;
-  GetTransform(&grip);
-  state->grip = grip;
-
-  // Set the pointer offset from the grip transform.
-  gfx::Transform pointer;
-  GetRelativePointerTransform(&pointer);
-  state->description->pointer_offset = pointer;
-
-  return state;
-}
-
 bool VrController::IsButtonDown(ButtonType type) const {
   return controller_state_->GetButtonState(PlatformToGvrButton(type));
 }
@@ -299,25 +247,12 @@
   return controller_state_->GetConnectionState() == gvr::kControllerConnected;
 }
 
-void VrController::EnableDeadzoneForTesting() {
-  enable_deadzone_ = true;
-}
-
 void VrController::UpdateState(const gfx::Transform& head_pose) {
   gfx::Transform inv_pose;
   if (head_pose.GetInverse(&inv_pose)) {
     auto current_head_offset = gfx::Point3F();
     inv_pose.TransformPoint(&current_head_offset);
-    // TODO(https://crbug.com/861807): Remove this once the controller can be
-    // dirty without necessarily affecting quiescence.
-    if (enable_deadzone_) {
-      if (head_offset_.SquaredDistanceTo(current_head_offset) >
-          kHeadOffsetDeadzone) {
-        head_offset_ = current_head_offset;
-      }
-    } else {
-      head_offset_ = current_head_offset;
-    }
+    head_offset_ = current_head_offset;
   }
 
   gvr::Mat4f gvr_head_pose;
diff --git a/chrome/browser/android/vr/vr_controller.h b/chrome/browser/android/vr/vr_controller.h
index b6b4bf0..9c16ecba 100644
--- a/chrome/browser/android/vr/vr_controller.h
+++ b/chrome/browser/android/vr/vr_controller.h
@@ -14,7 +14,6 @@
 #include "chrome/browser/vr/gesture_detector.h"
 #include "chrome/browser/vr/platform_controller.h"
 #include "device/vr/android/gvr/gvr_gamepad_data_provider.h"
-#include "device/vr/public/mojom/vr_service.mojom.h"
 #include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_types.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/quaternion.h"
@@ -48,7 +47,6 @@
   void OnPause();
 
   device::GvrGamepadData GetGamepadData();
-  device::mojom::XRInputSourceStatePtr GetInputSourceState();
 
   // Called once per frame to update controller state.
   void UpdateState(const gfx::Transform& head_pose);
@@ -86,7 +84,6 @@
   int GetBatteryLevel() const override;
 
  private:
-
   bool GetButtonLongPressFromButtonInfo();
 
   void UpdateTimestamps();
@@ -125,8 +122,6 @@
 
   float alpha_value_ = 1.0f;
 
-  bool enable_deadzone_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(VrController);
 };
 
diff --git a/chrome/browser/android/vr/vr_shell_gl.cc b/chrome/browser/android/vr/vr_shell_gl.cc
index 52970eb..a38af50 100644
--- a/chrome/browser/android/vr/vr_shell_gl.cc
+++ b/chrome/browser/android/vr/vr_shell_gl.cc
@@ -21,7 +21,9 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event_argument.h"
+#include "chrome/browser/android/vr/controller_delegate_for_testing.h"
 #include "chrome/browser/android/vr/gl_browser_interface.h"
+#include "chrome/browser/android/vr/gvr_controller_delegate.h"
 #include "chrome/browser/android/vr/gvr_util.h"
 #include "chrome/browser/android/vr/mailbox_to_surface_bridge.h"
 #include "chrome/browser/android/vr/metrics_util_android.h"
@@ -201,8 +203,8 @@
                      bool start_in_web_vr_mode,
                      bool pause_content,
                      bool low_density)
-    : low_density_(low_density),
-      ui_(std::move(ui)),
+    : RenderLoop(std::move(ui)),
+      low_density_(low_density),
       web_vr_mode_(start_in_web_vr_mode),
       surfaceless_rendering_(reprojected_rendering),
       daydream_support_(daydream_support),
@@ -222,6 +224,8 @@
       ui_controller_update_time_(kWebVRSlidingAverageSize),
       weak_ptr_factory_(this) {
   GvrInit(gvr_api);
+  controller_delegate_ = std::make_unique<GvrControllerDelegate>(
+      std::make_unique<VrController>(gvr_api), browser_);
 }
 
 VrShellGl::~VrShellGl() {
@@ -332,7 +336,7 @@
 
   if (reinitializing && mailbox_bridge_) {
     mailbox_bridge_ = nullptr;
-    mailbox_bridge_ready_ = false;
+    webxr_->set_mailbox_bridge_ready(false);
     CreateOrResizeWebVRSurface(webvr_surface_size_);
   }
 
@@ -341,45 +345,14 @@
     OnVSync(base::TimeTicks::Now());
 }
 
-bool VrShellGl::WebVrCanProcessFrame() {
-  if (!mailbox_bridge_ready_) {
-    // Can't copy onto the transfer surface without mailbox_bridge_.
-    DVLOG(2) << __FUNCTION__ << ": waiting for mailbox bridge";
-    return false;
-  }
-
-  if (webxr_->HaveProcessingFrame()) {
-    DVLOG(2) << __FUNCTION__ << ": waiting for previous processing frame";
-    return false;
-  }
-
-  return true;
-}
-
-void VrShellGl::WebVrTryDeferredProcessing() {
-  if (!webxr_->HaveAnimatingFrame())
-    return;
-
-  WebXrFrame* animating_frame = webxr_->GetAnimatingFrame();
-  if (!animating_frame || !animating_frame->deferred_start_processing ||
-      !WebVrCanProcessFrame()) {
-    return;
-  }
-
-  DVLOG(2) << "Running deferred SubmitFrame";
-  // Run synchronously, not via PostTask, to ensure we don't
-  // get a new SendVSync scheduling in between.
-  base::ResetAndReturn(&animating_frame->deferred_start_processing).Run();
-}
-
 void VrShellGl::OnGpuProcessConnectionReady() {
   DVLOG(1) << __FUNCTION__;
   CHECK(mailbox_bridge_);
 
-  mailbox_bridge_ready_ = true;
+  webxr_->set_mailbox_bridge_ready(true);
   // We might have a deferred submit that was waiting for
-  // mailbox_bridge_ready_.
-  WebVrTryDeferredProcessing();
+  // mailbox_bridge_ready.
+  webxr_->TryDeferredProcessing();
 
   // See if we can send a VSync.
   WebVrTryStartAnimatingFrame(false);
@@ -387,7 +360,7 @@
 
 void VrShellGl::CreateSurfaceBridge(gl::SurfaceTexture* surface_texture) {
   DCHECK(!mailbox_bridge_);
-  mailbox_bridge_ready_ = false;
+  webxr_->set_mailbox_bridge_ready(false);
   mailbox_bridge_ = std::make_unique<MailboxToSurfaceBridge>();
   if (surface_texture) {
     mailbox_bridge_->CreateSurface(surface_texture);
@@ -482,7 +455,7 @@
   TRACE_EVENT0("gpu", __FUNCTION__);
 
   DVLOG(2) << __FUNCTION__ << ": size=" << size.width() << "x" << size.height();
-  CHECK(mailbox_bridge_ready_);
+  CHECK(webxr_->mailbox_bridge_ready());
   CHECK(webxr_->HaveAnimatingFrame());
 
   WebXrSharedBuffer* buffer;
@@ -571,7 +544,7 @@
   // Renderer didn't submit a frame. Wait for the sync token to ensure
   // that any mailbox_bridge_ operations for the next frame happen after
   // whatever drawing the Renderer may have done before exiting.
-  if (mailbox_bridge_ready_)
+  if (webxr_->mailbox_bridge_ready())
     mailbox_bridge_->WaitSyncToken(sync_token);
 
   DVLOG(2) << __FUNCTION__ << ": recycle unused animating frame";
@@ -624,22 +597,14 @@
   if (!SubmitFrameCommon(frame_index, time_waited))
     return;
 
-  if (WebVrCanProcessFrame()) {
-    ProcessWebVrFrameFromGMB(frame_index, sync_token);
-  } else {
-    DVLOG(2) << "Deferring processing frame, not ready";
-    WebXrFrame* animating_frame = webxr_->GetAnimatingFrame();
-    DCHECK(!animating_frame->deferred_start_processing);
-    animating_frame->deferred_start_processing =
-        base::BindOnce(&VrShellGl::ProcessWebVrFrameFromGMB,
-                       weak_ptr_factory_.GetWeakPtr(), frame_index, sync_token);
-  }
+  webxr_->ProcessOrDefer(base::BindOnce(&VrShellGl::ProcessWebVrFrameFromGMB,
+                                        weak_ptr_factory_.GetWeakPtr(),
+                                        frame_index, sync_token));
 }
 
 void VrShellGl::ProcessWebVrFrameFromGMB(int16_t frame_index,
                                          const gpu::SyncToken& sync_token) {
   TRACE_EVENT0("gpu", __FUNCTION__);
-  webxr_->TransitionFrameAnimatingToProcessing();
 
   mailbox_bridge_->CreateGpuFence(
       sync_token, base::BindOnce(&VrShellGl::OnWebVRTokenSignaled,
@@ -656,23 +621,15 @@
   if (!SubmitFrameCommon(frame_index, time_waited))
     return;
 
-  if (WebVrCanProcessFrame()) {
-    ProcessWebVrFrameFromMailbox(frame_index, mailbox);
-  } else {
-    DVLOG(2) << "Deferring processing frame, not ready";
-    WebXrFrame* animating_frame = webxr_->GetAnimatingFrame();
-    DCHECK(!animating_frame->deferred_start_processing);
-    animating_frame->deferred_start_processing =
-        base::BindOnce(&VrShellGl::ProcessWebVrFrameFromMailbox,
-                       weak_ptr_factory_.GetWeakPtr(), frame_index, mailbox);
-  }
+  webxr_->ProcessOrDefer(
+      base::BindOnce(&VrShellGl::ProcessWebVrFrameFromMailbox,
+                     weak_ptr_factory_.GetWeakPtr(), frame_index, mailbox));
 }
 
 void VrShellGl::ProcessWebVrFrameFromMailbox(
     int16_t frame_index,
     const gpu::MailboxHolder& mailbox) {
   TRACE_EVENT0("gpu", __FUNCTION__);
-  webxr_->TransitionFrameAnimatingToProcessing();
 
   // LIFECYCLE: pending_frames_ should be empty when there's no processing
   // frame. It gets one element here, and then is emptied again before leaving
@@ -682,7 +639,7 @@
   DCHECK(pending_frames_.empty());
 
   // LIFECYCLE: We shouldn't have gotten here unless mailbox_bridge_ is ready.
-  DCHECK(mailbox_bridge_ready_);
+  DCHECK(webxr_->mailbox_bridge_ready());
 
   // Don't allow any state changes for this processing frame until it
   // arrives on the Surface. See OnWebVRFrameAvailable.
@@ -893,9 +850,8 @@
     DVLOG(1) << __FUNCTION__ << ": discarding frame, "
              << (web_vr_mode_ ? "UI is active" : "not presenting");
     WebVrCancelProcessingFrameAfterTransfer();
-    // We're no longer in processing state, unblock WebVrCanProcessFrame which
-    // may be waiting for this.
-    WebVrTryDeferredProcessing();
+    // We're no longer in processing state, unblock pending processing frames.
+    webxr_->TryDeferredProcessing();
   }
 }
 
@@ -947,7 +903,6 @@
 
 void VrShellGl::GvrInit(gvr_context* gvr_api) {
   gvr_api_ = gvr::GvrApi::WrapNonOwned(gvr_api);
-  controller_.reset(new VrController(gvr_api));
 
   MetricsUtilAndroid::LogVrViewerType(gvr_api_->GetViewerType());
 
@@ -1102,101 +1057,6 @@
   content_underlay_viewport_.SetSourceUv(kContentUv);
 }
 
-void VrShellGl::UpdateController(const RenderInfo& render_info,
-                                 base::TimeTicks current_time) {
-  TRACE_EVENT0("gpu", "VrShellGl::UpdateController");
-  controller_->UpdateState(render_info.head_pose);
-  gfx::Point3F laser_origin = controller_->GetPointerStart();
-
-  device::GvrGamepadData controller_data = controller_->GetGamepadData();
-  if (!ShouldDrawWebVr())
-    controller_data.connected = false;
-  browser_->UpdateGamepadData(controller_data);
-
-  if (ShouldDrawWebVr()) {
-    auto gestures = controller_->DetectGestures();
-    ui_->HandleMenuButtonEvents(&gestures);
-  } else {
-    HandleControllerInput(laser_origin, render_info, current_time);
-  }
-}
-
-void VrShellGl::HandleControllerInput(const gfx::Point3F& laser_origin,
-                                      const RenderInfo& render_info,
-                                      base::TimeTicks current_time) {
-  gfx::Vector3dF head_direction = GetForwardVector(render_info.head_pose);
-  gfx::Vector3dF ergo_neutral_pose;
-  if (!controller_->IsConnected()) {
-    // No controller detected, set up a gaze cursor that tracks the
-    // forward direction.
-    ergo_neutral_pose = {0.0f, 0.0f, -1.0f};
-    controller_quat_ =
-        gfx::Quaternion(gfx::Vector3dF(0.0f, 0.0f, -1.0f), head_direction);
-  } else {
-    ergo_neutral_pose = {0.0f, -sin(kErgoAngleOffset), -cos(kErgoAngleOffset)};
-    controller_quat_ = controller_->Orientation();
-  }
-
-  gfx::Transform mat(controller_quat_);
-  gfx::Vector3dF controller_direction = ergo_neutral_pose;
-  mat.TransformVector(&controller_direction);
-
-  ControllerModel controller_model;
-  controller_->GetTransform(&controller_model.transform);
-  auto input_event_list = controller_->DetectGestures();
-  controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
-  DCHECK(!(controller_->ButtonUpHappened(PlatformController::kButtonSelect) &&
-           controller_->ButtonDownHappened(PlatformController::kButtonSelect)))
-      << "Cannot handle a button down and up event within one frame.";
-  if (controller_->ButtonState(gvr::kControllerButtonClick)) {
-    controller_model.touchpad_button_state = UiInputManager::ButtonState::DOWN;
-  }
-  controller_model.app_button_state =
-      controller_->ButtonState(gvr::kControllerButtonApp)
-          ? UiInputManager::ButtonState::DOWN
-          : UiInputManager::ButtonState::UP;
-  controller_model.home_button_state =
-      controller_->ButtonState(gvr::kControllerButtonHome)
-          ? UiInputManager::ButtonState::DOWN
-          : UiInputManager::ButtonState::UP;
-  controller_model.opacity = controller_->GetOpacity();
-  controller_model.laser_direction = controller_direction;
-  controller_model.laser_origin = laser_origin;
-  controller_model.handedness = controller_->GetHandedness();
-  controller_model.recentered = controller_->GetRecentered();
-  controller_model.touching_touchpad = controller_->IsTouchingTrackpad();
-  controller_model.touchpad_touch_position =
-      controller_->GetPositionInTrackpad();
-  controller_model.last_orientation_timestamp =
-      controller_->GetLastOrientationTimestamp();
-  controller_model.last_touch_timestamp = controller_->GetLastTouchTimestamp();
-  controller_model.last_button_timestamp =
-      controller_->GetLastButtonTimestamp();
-  controller_model.battery_level = controller_->GetBatteryLevel();
-
-  if (!test_controller_model_queue_.empty()) {
-    cached_test_controller_model_ = test_controller_model_queue_.front();
-    test_controller_model_queue_.pop();
-  }
-  if (using_test_controller_model_) {
-    // We need to copy the timestamps, otherwise we hit a DCHECK when submitting
-    // motion events due to invalid timestamps.
-    cached_test_controller_model_.last_orientation_timestamp =
-        controller_model.last_orientation_timestamp;
-    cached_test_controller_model_.last_touch_timestamp =
-        controller_model.last_touch_timestamp;
-    cached_test_controller_model_.last_button_timestamp =
-        controller_model.last_button_timestamp;
-    controller_model = cached_test_controller_model_;
-  }
-  controller_model_ = controller_model;
-
-  ReticleModel reticle_model;
-  ui_->HandleInput(current_time, render_info, controller_model, &reticle_model,
-                   &input_event_list);
-  ui_->OnControllerUpdated(controller_model, reticle_model);
-}
-
 bool VrShellGl::ResizeForWebVR(int16_t frame_index) {
   // Process all pending_bounds_ changes targeted for before this frame, being
   // careful of wrapping frame indices.
@@ -1257,7 +1117,7 @@
 }
 
 void VrShellGl::UpdateEyeInfos(const gfx::Transform& head_pose,
-                               Viewport& viewport,
+                               const Viewport& viewport,
                                const gfx::Size& render_size,
                                RenderInfo* out_render_info) {
   for (auto eye : {GVR_LEFT_EYE, GVR_RIGHT_EYE}) {
@@ -1373,7 +1233,8 @@
   if (!is_webvr_frame) {
     TRACE_EVENT0("gpu", "Controller");
     base::TimeTicks controller_start = base::TimeTicks::Now();
-    UpdateController(render_info_, current_time);
+    ProcessControllerInput(render_info_, current_time,
+                           false /* is_web_xr_frame */);
     controller_time = base::TimeTicks::Now() - controller_start;
     ui_controller_update_time_.AddSample(controller_time);
   }
@@ -1635,7 +1496,7 @@
   TRACE_EVENT1("gpu", "VrShellGl::DrawFrameSubmitWhenReady", "frame",
                frame_index);
   DVLOG(2) << __FUNCTION__ << ": frame=" << static_cast<int>(frame_index);
-  bool use_polling = mailbox_bridge_ready_ &&
+  bool use_polling = webxr_->mailbox_bridge_ready() &&
                      mailbox_bridge_->IsGpuWorkaroundEnabled(
                          gpu::DONT_USE_EGLCLIENTWAITSYNC_WITH_TIMEOUT);
   if (fence) {
@@ -1800,7 +1661,7 @@
   if (is_webvr_frame) {
     // We finished processing a frame, this may make pending WebVR
     // work eligible to proceed.
-    WebVrTryDeferredProcessing();
+    webxr_->TryDeferredProcessing();
   }
 
   if (ShouldDrawWebVr()) {
@@ -1872,7 +1733,7 @@
 void VrShellGl::OnPause() {
   paused_ = true;
   vsync_helper_.CancelVSyncRequest();
-  controller_->OnPause();
+  controller_delegate_->OnPause();
   ui_->OnPause();
   gvr_api_->PauseTracking();
   webvr_frame_timeout_.Cancel();
@@ -1884,7 +1745,7 @@
   gvr_api_->RefreshViewerProfile();
   viewports_need_updating_ = true;
   gvr_api_->ResumeTracking();
-  controller_->OnResume();
+  controller_delegate_->OnResume();
   if (!ready_to_draw_)
     return;
   vsync_helper_.CancelVSyncRequest();
@@ -2007,11 +1868,11 @@
     return false;
   }
 
-  if (webxr_use_shared_buffer_draw_ && !mailbox_bridge_ready_) {
+  if (webxr_use_shared_buffer_draw_ && !webxr_->mailbox_bridge_ready()) {
     // For exclusive scheduling, we need the mailbox bridge before the first
     // frame so that we can place a sync token. For shared buffer draw, we
     // need it to set up buffers before starting client rendering.
-    DVLOG(2) << __FUNCTION__ << ": waiting for mailbox_bridge_ready_";
+    DVLOG(2) << __FUNCTION__ << ": waiting for mailbox_bridge_ready";
     return false;
   }
 
@@ -2102,9 +1963,9 @@
     base::TimeTicks controller_start = base::TimeTicks::Now();
     device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_.get(),
                                                  &render_info_.head_pose);
-    UpdateController(render_info_, frame_time);
+    ProcessControllerInput(render_info_, frame_time, true /* is_webxr_frame */);
 
-    input_states_.push_back(controller_->GetInputSourceState());
+    input_states_.push_back(controller_delegate_->GetInputSourceState());
 
     ui_controller_update_time_.AddSample(base::TimeTicks::Now() -
                                          controller_start);
@@ -2278,7 +2139,7 @@
   }
 
   if (webxr_use_shared_buffer_draw_) {
-    CHECK(mailbox_bridge_ready_);
+    CHECK(webxr_->mailbox_bridge_ready());
     CHECK(webxr_->HaveAnimatingFrame());
     WebXrSharedBuffer* buffer =
         webxr_->GetAnimatingFrame()->shared_buffer.get();
@@ -2382,27 +2243,32 @@
   ui_test_state_ = std::make_unique<UiTestState>();
   ui_test_state_->quiescence_timeout_ms =
       base::TimeDelta::FromMilliseconds(ui_expectation.quiescence_timeout_ms);
-  // The controller is pretty much perpetually dirty due to noise, even during
-  // tests, so we need to apply a deadzone to it. We can't apply this deadzone
-  // all the time since it results in grid-like pointer movement.
-  // TODO(https://crbug.com/861807): Remove this workaround once the controller
-  // can be dirty with affecting quiescence.
-  controller_->EnableDeadzoneForTesting();
 }
 
 void VrShellGl::PerformControllerActionForTesting(
     ControllerTestInput controller_input) {
   if (controller_input.action ==
       VrControllerTestAction::kRevertToRealController) {
-    DCHECK(test_controller_model_queue_.empty()) << "Attempted to revert to "
-                                                    "using real controller "
-                                                    "with actions still queued";
-    using_test_controller_model_ = false;
+    if (using_controller_delegate_for_testing_) {
+      DCHECK(
+          static_cast<ControllerDelegateForTesting*>(controller_delegate_.get())
+              ->IsQueueEmpty())
+          << "Attempted to revert to using real controller with actions still "
+             "queued";
+      using_controller_delegate_for_testing_ = false;
+      controller_delegate_for_testing_.swap(controller_delegate_);
+    }
     return;
   }
-  ui_->PerformControllerActionForTesting(controller_input,
-                                         test_controller_model_queue_);
-  using_test_controller_model_ = true;
+  if (!using_controller_delegate_for_testing_) {
+    using_controller_delegate_for_testing_ = true;
+    if (!controller_delegate_for_testing_)
+      controller_delegate_for_testing_ =
+          std::make_unique<ControllerDelegateForTesting>(ui_.get());
+    controller_delegate_for_testing_.swap(controller_delegate_);
+  }
+  static_cast<ControllerDelegateForTesting*>(controller_delegate_.get())
+      ->QueueControllerActionForTesting(controller_input);
 }
 
 void VrShellGl::ReportUiStatusForTesting(const base::TimeTicks& current_time,
diff --git a/chrome/browser/android/vr/vr_shell_gl.h b/chrome/browser/android/vr/vr_shell_gl.h
index e5837f2..572d094 100644
--- a/chrome/browser/android/vr/vr_shell_gl.h
+++ b/chrome/browser/android/vr/vr_shell_gl.h
@@ -22,6 +22,7 @@
 #include "chrome/browser/vr/content_input_delegate.h"
 #include "chrome/browser/vr/fps_meter.h"
 #include "chrome/browser/vr/model/controller_model.h"
+#include "chrome/browser/vr/render_loop.h"
 #include "chrome/browser/vr/sliding_average.h"
 #include "chrome/browser/vr/ui_input_manager.h"
 #include "chrome/browser/vr/ui_renderer.h"
@@ -67,7 +68,6 @@
 class ScopedGpuTrace;
 class SlidingTimeDeltaAverage;
 class UiInterface;
-class VrController;
 class VrShell;
 
 struct WebVrBounds {
@@ -97,7 +97,8 @@
 
 // This class manages all GLThread owned objects and GL rendering for VrShell.
 // It is not threadsafe and must only be used on the GL thread.
-class VrShellGl : public device::mojom::XRPresentationProvider,
+class VrShellGl : public RenderLoop,
+                  public device::mojom::XRPresentationProvider,
                   public device::mojom::XRFrameDataProvider {
  public:
   VrShellGl(GlBrowserInterface* browser_interface,
@@ -176,7 +177,7 @@
   bool ResizeForWebVR(int16_t frame_index);
   void UpdateSamples();
   void UpdateEyeInfos(const gfx::Transform& head_pose,
-                      Viewport& viewport,
+                      const Viewport& viewport,
                       const gfx::Size& render_size,
                       RenderInfo* out_render_info);
   void UpdateContentViewportTransforms(const gfx::Transform& head_pose);
@@ -258,13 +259,6 @@
   // becoming true.
   void WebVrTryStartAnimatingFrame(bool is_from_onvsync);
 
-  // Checks if we're in a valid state for processing the current animating
-  // frame. Invalid states include mailbox_bridge_ready_ being false, or an
-  // already existing processing frame that's not done yet.
-  bool WebVrCanProcessFrame();
-  // Call this after state changes that could result in WebVrCanProcessFrame
-  // becoming true.
-  void WebVrTryDeferredProcessing();
   // Transition a frame from animating to processing.
   void ProcessWebVrFrameFromGMB(int16_t frame_index,
                                 const gpu::SyncToken& sync_token);
@@ -288,6 +282,8 @@
                                 bool ui_updated);
   void ReportUiActivityResultForTesting(VrUiTestActivityResult result);
 
+  std::unique_ptr<ControllerDelegate> controller_delegate_for_testing_;
+
   // samplerExternalOES texture data for WebVR content image.
   int webvr_texture_id_ = 0;
   int content_texture_id_ = 0;
@@ -322,7 +318,6 @@
       WebVrBounds(gfx::RectF(), gfx::RectF(), gfx::Size());
   base::queue<uint16_t> pending_frames_;
   std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
-  bool mailbox_bridge_ready_ = false;
 
   // A fence used to avoid overstuffed GVR buffers in WebVR mode.
   std::unique_ptr<gl::GLFenceAndroidNativeFenceSync>
@@ -349,15 +344,12 @@
   int webvr_unstuff_ratelimit_frames_ = 0;
 
   bool cardboard_ = false;
-  gfx::Quaternion controller_quat_;
 
   gfx::Size content_tex_buffer_size_ = {0, 0};
   gfx::Size webvr_surface_size_ = {0, 0};
 
   std::unique_ptr<WebXrPresentationState> webxr_ = nullptr;
 
-  std::unique_ptr<UiInterface> ui_;
-
   bool web_vr_mode_ = false;
   bool ready_to_draw_ = false;
   bool paused_ = true;
@@ -367,7 +359,6 @@
   bool cardboard_trigger_pressed_ = false;
   bool cardboard_trigger_clicked_ = false;
 
-  std::unique_ptr<VrController> controller_;
   std::vector<device::mojom::XRInputSourceStatePtr> input_states_;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
@@ -442,9 +433,7 @@
   std::unique_ptr<PlatformUiInputDelegate> vr_dialog_input_delegate_;
   bool showing_vr_dialog_ = false;
   std::unique_ptr<UiTestState> ui_test_state_;
-  std::queue<ControllerModel> test_controller_model_queue_;
-  ControllerModel cached_test_controller_model_;
-  bool using_test_controller_model_ = false;
+  bool using_controller_delegate_for_testing_ = false;
 
   base::WeakPtrFactory<VrShellGl> weak_ptr_factory_;
 
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.cc b/chrome/browser/android/vr/web_xr_presentation_state.cc
index ba6fee5f..9ca763a 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.cc
+++ b/chrome/browser/android/vr/web_xr_presentation_state.cc
@@ -138,4 +138,40 @@
   last_ui_allows_sending_vsync = false;
 }
 
+bool WebXrPresentationState::CanProcessFrame() const {
+  if (!mailbox_bridge_ready_) {
+    DVLOG(2) << __FUNCTION__ << ": waiting for mailbox bridge";
+    return false;
+  }
+  if (processing_frame_) {
+    DVLOG(2) << __FUNCTION__ << ": waiting for previous processing frame";
+    return false;
+  }
+
+  return true;
+}
+
+void WebXrPresentationState::ProcessOrDefer(base::OnceClosure callback) {
+  DCHECK(animating_frame_ && !animating_frame_->deferred_start_processing);
+  if (CanProcessFrame()) {
+    TransitionFrameAnimatingToProcessing();
+    base::ResetAndReturn(&callback).Run();
+  } else {
+    DVLOG(2) << "Deferring processing frame, not ready";
+    animating_frame_->deferred_start_processing = std::move(callback);
+  }
+}
+
+void WebXrPresentationState::TryDeferredProcessing() {
+  if (!animating_frame_ || !animating_frame_->deferred_start_processing ||
+      !CanProcessFrame()) {
+    return;
+  }
+  DVLOG(2) << "Running deferred SubmitFrame";
+  // Run synchronously, not via PostTask, to ensure we don't
+  // get a new SendVSync scheduling in between.
+  TransitionFrameAnimatingToProcessing();
+  base::ResetAndReturn(&animating_frame_->deferred_start_processing).Run();
+}
+
 }  // namespace vr
diff --git a/chrome/browser/android/vr/web_xr_presentation_state.h b/chrome/browser/android/vr/web_xr_presentation_state.h
index 8839a5f..702f250 100644
--- a/chrome/browser/android/vr/web_xr_presentation_state.h
+++ b/chrome/browser/android/vr/web_xr_presentation_state.h
@@ -162,6 +162,11 @@
   void RecycleUnusedAnimatingFrame();
   bool RecycleProcessingFrameIfPossible();
 
+  void ProcessOrDefer(base::OnceClosure callback);
+  // Call this after state changes that could result in CanProcessFrame
+  // becoming true.
+  void TryDeferredProcessing();
+
   bool HaveAnimatingFrame() { return animating_frame_; }
   WebXrFrame* GetAnimatingFrame();
   bool HaveProcessingFrame() { return processing_frame_; }
@@ -169,6 +174,9 @@
   bool HaveRenderingFrame() { return rendering_frame_; }
   WebXrFrame* GetRenderingFrame();
 
+  void set_mailbox_bridge_ready(bool ready) { mailbox_bridge_ready_ = ready; }
+  bool mailbox_bridge_ready() const { return mailbox_bridge_ready_; }
+
   // Used by WebVrCanAnimateFrame() to detect when ui_->CanSendWebVrVSync()
   // transitions from false to true, as part of starting the incoming frame
   // timeout.
@@ -181,6 +189,10 @@
   base::OnceClosure end_presentation_callback;
 
  private:
+  // Checks if we're in a valid state for processing the current animating
+  // frame. Invalid states include mailbox_bridge_ready_ being false, or an
+  // already existing processing frame that's not done yet.
+  bool CanProcessFrame() const;
   std::unique_ptr<WebXrFrame> frames_storage_[kWebXrFrameCount];
 
   // Index of the next animating WebXR frame.
@@ -191,6 +203,8 @@
   WebXrFrame* rendering_frame_ = nullptr;
   base::queue<WebXrFrame*> idle_frames_;
 
+  bool mailbox_bridge_ready_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(WebXrPresentationState);
 };
 
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 3a7c862..c03eec9 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -246,6 +246,10 @@
 // Given |webContents|, extracts a GURL to be used for Handoff. This may return
 // the empty GURL.
 - (GURL)handoffURLFromWebContents:(content::WebContents*)webContents;
+
+// Return false if Chrome startup is paused by dialog and AppController is
+// called without any initialized Profile.
+- (BOOL)isProfileReady;
 @end
 
 class AppControllerProfileObserver : public ProfileAttributesStorage::Observer {
@@ -433,6 +437,8 @@
 }
 
 - (void)applicationWillHide:(NSNotification*)notification {
+  if (![self isProfileReady])
+    return;
   apps::ExtensionAppShimHandler::OnChromeWillHide();
 }
 
@@ -613,7 +619,7 @@
 }
 
 - (void)windowDidResignMain:(NSNotification*)notify {
-  if (chrome::GetTotalBrowserCount() == 0) {
+  if (chrome::GetTotalBrowserCount() == 0 && [self isProfileReady]) {
     [self windowChangedToProfile:
         g_browser_process->profile_manager()->GetLastUsedProfile()];
   }
@@ -1012,7 +1018,7 @@
   // Ignore commands during session restore's browser creation.  It uses a
   // nested run loop and commands dispatched during this operation cause
   // havoc.
-  if (SessionRestore::IsRestoring(lastProfile) &&
+  if (lastProfile && SessionRestore::IsRestoring(lastProfile) &&
       base::RunLoop::IsNestedOnCurrentThread()) {
     return;
   }
@@ -1322,16 +1328,20 @@
   [app registerServicesMenuSendTypes:types returnTypes:types];
 }
 
+// Return null if Chrome is not ready or there is no ProfileManager.
 - (Profile*)lastProfile {
   // Return the profile of the last-used Browser, if available.
   if (lastProfile_)
     return lastProfile_;
 
+  if (![self isProfileReady])
+    return nullptr;
+
   // On first launch, use the logic that ChromeBrowserMain uses to determine
   // the initial profile.
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   if (!profile_manager)
-    return NULL;
+    return nullptr;
 
   return profile_manager->GetProfile(
       GetStartupProfilePath(profile_manager->user_data_dir(),
@@ -1341,6 +1351,9 @@
 - (Profile*)safeLastProfileForNewWindows {
   Profile* profile = [self lastProfile];
 
+  if (!profile)
+    return nullptr;
+
   // Guest sessions must always be OffTheRecord. Use that when opening windows.
   if (profile->IsGuestSession())
     return profile->GetOffTheRecordProfile();
@@ -1705,6 +1718,12 @@
   return webContents->GetVisibleURL();
 }
 
+- (BOOL)isProfileReady {
+  return !g_browser_process->browser_policy_connector()
+              ->machine_level_user_cloud_policy_controller()
+              ->IsEnterpriseStartupDialogShowing();
+}
+
 #pragma mark - HandoffActiveURLObserverBridgeDelegate
 
 - (void)handoffActiveURLChanged:(content::WebContents*)webContents {
diff --git a/chrome/browser/apps/guest_view/web_view_browsertest.cc b/chrome/browser/apps/guest_view/web_view_browsertest.cc
index 1775209..a874c0e 100644
--- a/chrome/browser/apps/guest_view/web_view_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_browsertest.cc
@@ -4352,10 +4352,17 @@
   }
 };
 
+// Flaky on Linux. See: https://crbug.com/870604.
+#if defined(OS_LINUX)
+#define MAYBE_TouchFocusesEmbedder DISABLED_TouchFocusesEmbedder
+#else
+#define MAYBE_TouchFocusesEmbedder TouchFocusesEmbedder
+#endif
+
 // The following test verifies that a views::WebView hosting an embedder
 // gains focus on touchstart.
 IN_PROC_BROWSER_TEST_F(WebViewFocusBrowserPluginSpecificTest,
-                       TouchFocusesEmbedder) {
+                       MAYBE_TouchFocusesEmbedder) {
   LoadAppWithGuest("web_view/accept_touch_events");
 
   content::WebContents* web_contents = GetEmbedderWebContents();
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index bc99655..091419e8 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -44,6 +44,7 @@
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/host_zoom_map.h"
+#include "content/public/browser/picture_in_picture_window_controller.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/test/browser_test_utils.h"
@@ -1400,4 +1401,28 @@
   ASSERT_TRUE(RunPlatformAppTest("platform_apps/new_window_about_blank"));
 }
 
+// Tests that platform apps can enter and exit Picture-in-Picture.
+IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, PictureInPicture) {
+  LoadAndLaunchPlatformApp("picture_in_picture", "Launched");
+
+  WebContents* web_contents = GetFirstAppWindowWebContents();
+  ASSERT_TRUE(web_contents);
+  content::PictureInPictureWindowController* window_controller =
+      content::PictureInPictureWindowController::GetOrCreateForWebContents(
+          web_contents);
+  ASSERT_TRUE(window_controller->GetWindowForTesting());
+  EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
+
+  bool result = false;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+      web_contents, "enterPictureInPicture();", &result));
+  EXPECT_TRUE(result);
+  EXPECT_TRUE(window_controller->GetWindowForTesting()->IsVisible());
+
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+      web_contents, "exitPictureInPicture();", &result));
+  EXPECT_TRUE(result);
+  EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
index 3287bbc..749d404 100644
--- a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
@@ -7,10 +7,7 @@
 #include "base/command_line.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
-#include "base/files/file_util.h"
 #include "base/guid.h"
-#include "base/json/json_reader.h"
-#include "base/json/json_string_value_serializer.h"
 #include "base/macros.h"
 #include "base/path_service.h"
 #include "base/strings/string16.h"
@@ -23,17 +20,17 @@
 #include "build/build_config.h"
 #include "chrome/browser/autofill/autofill_uitest.h"
 #include "chrome/browser/autofill/autofill_uitest_util.h"
-#include "chrome/browser/browser_process.h"
+#include "chrome/browser/autofill/captured_sites_test_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/test_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/autofill/core/browser/autofill_manager_test_delegate.h"
@@ -44,10 +41,7 @@
 #include "components/autofill/core/browser/state_names.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_switches.h"
-#include "content/public/browser/browsing_data_remover.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test_utils.h"
-#include "content/public/test/browsing_data_remover_test_util.h"
+#include "components/autofill/core/common/autofill_util.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/test_utils.h"
 #include "services/network/public/cpp/network_switches.h"
@@ -55,72 +49,18 @@
 
 namespace {
 
-const base::TimeDelta default_action_timeout = base::TimeDelta::FromSeconds(30);
-const base::TimeDelta paint_event_check_interval =
-    base::TimeDelta::FromMilliseconds(500);
-const int autofill_action_num_retries = 5;
-
-// PageActivityObserver waits until Chrome finishes loading a page and stops
-// making visual updates to the page.
-class PageActivityObserver : public content::WebContentsObserver {
- public:
-  explicit PageActivityObserver(content::WebContents* web_contents)
-      : content::WebContentsObserver(web_contents) {}
-  ~PageActivityObserver() override = default;
-
-  // Wait until Chrome finishes loading a page and updating the page's visuals.
-  // If Chrome finishes loading a page but continues to paint every half
-  // second, exit after |continuous_paint_timeout| expires since Chrome
-  // finished loading the page.
-  void WaitTillPageIsIdle(
-      base::TimeDelta continuous_paint_timeout = default_action_timeout) {
-    base::TimeTicks finished_load_time = base::TimeTicks::Now();
-    bool page_is_loading = false;
-    do {
-      paint_occurred_during_last_loop_ = false;
-      base::RunLoop heart_beat;
-      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-          FROM_HERE, heart_beat.QuitClosure(), paint_event_check_interval);
-      heart_beat.Run();
-      page_is_loading =
-          web_contents()->IsWaitingForResponse() || web_contents()->IsLoading();
-      if (page_is_loading) {
-        finished_load_time = base::TimeTicks::Now();
-      } else if (base::TimeTicks::Now() - finished_load_time >
-                 continuous_paint_timeout) {
-        // |continuous_paint_timeout| has expired since Chrome loaded the page.
-        // During this period of time, Chrome has been continuously painting
-        // the page. In this case, the page is probably idle, but a bug, a
-        // blinking caret or a persistent animation is making Chrome paint at
-        // regular intervals. Exit.
-        break;
-      }
-    } while (page_is_loading || paint_occurred_during_last_loop_);
-  }
-
- private:
-  void DidCommitAndDrawCompositorFrame() override {
-    paint_occurred_during_last_loop_ = true;
-  }
-
-  bool paint_occurred_during_last_loop_ = false;
-
-  DISALLOW_COPY_AND_ASSIGN(PageActivityObserver);
-};
-
-std::string FilePathToUTF8(const base::FilePath::StringType& str) {
-#if defined(OS_WIN)
-  return base::WideToUTF8(str);
-#else
-  return str;
-#endif
-}
+const base::TimeDelta autofill_wait_for_action_interval =
+    base::TimeDelta::FromSeconds(5);
 
 base::FilePath GetReplayFilesDirectory() {
   base::FilePath src_dir;
-  CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-  return src_dir.Append(
-      FILE_PATH_LITERAL("chrome/test/data/autofill/captured_sites"));
+  if (base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) {
+    return src_dir.Append(
+        FILE_PATH_LITERAL("chrome/test/data/autofill/captured_sites"));
+  } else {
+    src_dir.clear();
+    return src_dir;
+  }
 }
 
 // Iterate through Autofill's Web Page Replay capture file directory to look
@@ -138,7 +78,8 @@
     // has the '.test' extension.
     if (file.Extension().empty() &&
         base::PathExists(file.AddExtension(FILE_PATH_LITERAL(".test")))) {
-      sites.push_back(FilePathToUTF8(file.BaseName().value()));
+      sites.push_back(
+          captured_sites_test_utils::FilePathToUTF8(file.BaseName().value()));
     }
   }
   std::sort(sites.begin(), sites.end());
@@ -151,33 +92,78 @@
 
 class AutofillCapturedSitesInteractiveTest
     : public AutofillUiTest,
+      public captured_sites_test_utils::
+          TestRecipeReplayChromeFeatureActionExecutor,
       public ::testing::WithParamInterface<std::string> {
+ public:
+  // TestRecipeReplayChromeFeatureActionExecutor
+  bool AutofillForm(content::WebContents* web_contents,
+                    const std::string& focus_element_css_selector,
+                    int attempts = 1) override {
+    AutofillManager* autofill_manager =
+        ContentAutofillDriverFactory::FromWebContents(web_contents)
+            ->DriverForFrame(web_contents->GetMainFrame())
+            ->autofill_manager();
+    autofill_manager->SetTestDelegate(test_delegate());
+
+    int tries = 0;
+    while (tries < attempts) {
+      tries++;
+      autofill_manager->client()->HideAutofillPopup();
+
+      if (!ShowAutofillSuggestion(focus_element_css_selector)) {
+        LOG(WARNING) << "Failed to bring up the autofill suggestion drop down.";
+        continue;
+      }
+
+      if (!ShouldAutoselectFirstSuggestionOnArrowDown()) {
+        // Press the down key to highlight the first choice in the autofill
+        // suggestion drop down.
+        test_delegate()->Reset();
+        SendKeyToPopup(ui::DomKey::ARROW_DOWN);
+        if (!test_delegate()->Wait({ObservedUiEvents::kPreviewFormData},
+                                   autofill_wait_for_action_interval)) {
+          LOG(WARNING) << "Failed to select an option from the "
+                       << "autofill suggestion drop down.";
+          continue;
+        }
+      }
+
+      // Press the enter key to invoke autofill using the first suggestion.
+      test_delegate()->Reset();
+      SendKeyToPopup(ui::DomKey::ENTER);
+      if (!test_delegate()->Wait({ObservedUiEvents::kFormDataFilled},
+                                 autofill_wait_for_action_interval)) {
+        LOG(WARNING) << "Failed to fill the form.";
+        continue;
+      }
+
+      return true;
+    }
+
+    autofill_manager->client()->HideAutofillPopup();
+    return false;
+  }
+
  protected:
   AutofillCapturedSitesInteractiveTest()
       : profile_(test::GetFullProfile()),
         card_(CreditCard(base::GenerateGUID(), "http://www.example.com")) {}
+
   ~AutofillCapturedSitesInteractiveTest() override {}
 
   // InProcessBrowserTest:
   void SetUpOnMainThread() override {
     AutofillUiTest::SetUpOnMainThread();
     SetupTestProfile();
-    EXPECT_TRUE(InstallWebPageReplayServerRootCert())
-        << "Cannot install the root certificate "
-        << "for the local web page replay server.";
-    CleanupSiteData();
+    recipe_replayer_ =
+        std::make_unique<captured_sites_test_utils::TestRecipeReplayer>(
+            browser(), this);
+    recipe_replayer()->Setup();
   }
 
   void TearDownOnMainThread() override {
-    // If there are still cookies at the time the browser test shuts down,
-    // Chrome's SQL lite persistent cookie store will crash.
-    CleanupSiteData();
-    EXPECT_TRUE(StopWebPageReplayServer())
-        << "Cannot stop the local Web Page Replay server.";
-    EXPECT_TRUE(RemoveWebPageReplayServerRootCert())
-        << "Cannot remove the root certificate "
-        << "for the local Web Page Replay server.";
-
+    recipe_replayer()->Cleanup();
     AutofillUiTest::TearDownOnMainThread();
   }
 
@@ -188,120 +174,12 @@
     // elements in a form to determine if the form is ready for interaction.
     feature_list_.InitAndEnableFeature(features::kAutofillShowTypePredictions);
     command_line->AppendSwitch(switches::kShowAutofillTypePredictions);
-
-    // Direct traffic to the Web Page Replay server.
-    command_line->AppendSwitchASCII(
-        network::switches::kHostResolverRules,
-        base::StringPrintf(
-            "MAP *:80 127.0.0.1:%d,"
-            "MAP *:443 127.0.0.1:%d,"
-            // Uncomment to use the live autofill prediction server.
-            // "EXCLUDE clients1.google.com,"
-            "EXCLUDE localhost",
-            host_http_port_, host_https_port_));
+    captured_sites_test_utils::TestRecipeReplayer::SetUpCommandLine(
+        command_line);
   }
 
-  bool StartWebPageReplayServer(const std::string& replay_file) {
-    std::vector<std::string> args;
-    base::FilePath src_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-    args.push_back(base::StringPrintf("--http_port=%d", host_http_port_));
-    args.push_back(base::StringPrintf("--https_port=%d", host_https_port_));
-    args.push_back(base::StringPrintf(
-        "--inject_scripts=%s,%s",
-        FilePathToUTF8(src_dir
-                           .Append(FILE_PATH_LITERAL(
-                               "third_party/catapult/web_page_replay_go"))
-                           .Append(FILE_PATH_LITERAL("deterministic.js"))
-                           .value())
-            .c_str(),
-        FilePathToUTF8(
-            src_dir
-                .Append(FILE_PATH_LITERAL(
-                    "chrome/test/data/web_page_replay_go_helper_scripts"))
-                .Append(FILE_PATH_LITERAL("automation_helper.js"))
-                .value())
-            .c_str()));
-
-    // Specify the replay file.
-    args.push_back(base::StringPrintf(
-        "%s", FilePathToUTF8(
-                  GetReplayFilesDirectory().AppendASCII(replay_file).value())
-                  .c_str()));
-
-    web_page_replay_server_ = RunWebPageReplayCmd("replay", args);
-
-    // Sleep 20 seconds to wait for the web page replay server to start.
-    // TODO(bug 847910): create a process std stream reader class to use the
-    // process output to determine when the server is ready.
-    base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(20));
-
-    return web_page_replay_server_.IsValid();
-  }
-
-  bool StopWebPageReplayServer() {
-    if (web_page_replay_server_.IsValid())
-      return web_page_replay_server_.Terminate(0, true);
-    // The test server hasn't started, no op.
-    return true;
-  }
-
-  bool ReplayTestRecipe(const char* recipe_file_name) {
-    // Read the text of the recipe file.
-    base::FilePath src_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-    base::FilePath recipe_file_path = GetReplayFilesDirectory().AppendASCII(
-        base::StringPrintf("%s.test", recipe_file_name));
-    base::ThreadRestrictions::SetIOAllowed(true);
-    std::string json_text;
-    CHECK(base::ReadFileToString(recipe_file_path, &json_text));
-
-    // Convert the file text into a json object.
-    std::unique_ptr<base::DictionaryValue> recipe =
-        base::DictionaryValue::From(base::JSONReader().ReadToValue(json_text));
-    if (!recipe) {
-      ADD_FAILURE() << "Failed to deserialize json text!";
-      return false;
-    }
-
-    InitializeBrowserToExecuteRecipe(recipe);
-
-    // Iterate through and execute each action in the recipe.
-    base::Value* action_list_container = recipe->FindKey("actions");
-    CHECK(action_list_container);
-    CHECK_EQ(base::Value::Type::LIST, action_list_container->type());
-    base::Value::ListStorage& action_list = action_list_container->GetList();
-
-    for (base::ListValue::iterator it_action = action_list.begin();
-         it_action != action_list.end(); ++it_action) {
-      base::DictionaryValue* action;
-      CHECK(it_action->GetAsDictionary(&action));
-
-      base::Value* type_container = action->FindKey("type");
-      CHECK(type_container);
-      CHECK_EQ(base::Value::Type::STRING, type_container->type());
-      std::string type = type_container->GetString();
-
-      if (base::CompareCaseInsensitiveASCII(type, "waitFor") == 0) {
-        ExecuteWaitForStateAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "execute") == 0) {
-        ExecuteRunCommandAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "click") == 0) {
-        ExecuteClickAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "type") == 0) {
-        ExecuteTypeAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "select") == 0) {
-        ExecuteSelectDropdownAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "autofill") == 0) {
-        ExecuteAutofillAction(action);
-      } else if (base::CompareCaseInsensitiveASCII(type, "validateField") ==
-                 0) {
-        ExecuteValidateFieldValueAction(action);
-      } else {
-        ADD_FAILURE() << "Unrecognized action type: " << type;
-      }
-    }    // end foreach action
-    return true;
+  captured_sites_test_utils::TestRecipeReplayer* recipe_replayer() {
+    return recipe_replayer_.get();
   }
 
   const CreditCard credit_card() { return card_; }
@@ -319,369 +197,56 @@
     AddTestAutofillData(browser(), profile_, card_);
   }
 
-  bool InstallWebPageReplayServerRootCert() {
-    return RunWebPageReplayCmdAndWaitForExit("installroot",
-                                             std::vector<std::string>());
-  }
-
-  bool RemoveWebPageReplayServerRootCert() {
-    return RunWebPageReplayCmdAndWaitForExit("removeroot",
-                                             std::vector<std::string>());
-  }
-
-  bool RunWebPageReplayCmdAndWaitForExit(
-      const std::string& cmd,
-      const std::vector<std::string>& args,
-      const base::TimeDelta& timeout = base::TimeDelta::FromSeconds(5)) {
-    base::Process process = RunWebPageReplayCmd(cmd, args);
-    if (process.IsValid()) {
-      int exit_code;
-      if (process.WaitForExitWithTimeout(timeout, &exit_code))
-        return (exit_code == 0);
-    }
-    return false;
-  }
-
-  base::Process RunWebPageReplayCmd(const std::string& cmd,
-                                    const std::vector<std::string>& args) {
-    base::LaunchOptions options = base::LaunchOptionsForTest();
-    base::FilePath exe_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &exe_dir));
-    base::FilePath web_page_replay_binary_dir =
-        exe_dir.Append(FILE_PATH_LITERAL(
-            "third_party/catapult/telemetry/telemetry/internal/bin"));
-    options.current_directory = web_page_replay_binary_dir;
-
-#if defined(OS_WIN)
-    std::string wpr_executable_binary = "win/x86_64/wpr";
-#elif defined(OS_MACOSX)
-    std::string wpr_executable_binary = "mac/x86_64/wpr";
-#elif defined(OS_POSIX)
-    std::string wpr_executable_binary = "linux/x86_64/wpr";
-#else
-#error Plaform is not supported.
-#endif
-    base::CommandLine full_command(
-        web_page_replay_binary_dir.AppendASCII(wpr_executable_binary));
-    full_command.AppendArg(cmd);
-
-    // Ask web page replay to use the custom certifcate and key files used to
-    // make the web page captures.
-    // The capture files used in these browser tests are also used on iOS to
-    // test autofill.
-    // The custom cert and key files are different from those of the offical
-    // WPR releases. The custom files are made to work on iOS.
-    base::FilePath src_dir;
-    CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
-    base::FilePath web_page_replay_support_file_dir =
-        src_dir.Append(FILE_PATH_LITERAL(
-            "components/test/data/autofill/web_page_replay_support_files"));
-    full_command.AppendArg(base::StringPrintf(
-        "--https_cert_file=%s",
-        FilePathToUTF8(web_page_replay_support_file_dir
-                           .Append(FILE_PATH_LITERAL("wpr_cert.pem"))
-                           .value())
-            .c_str()));
-    full_command.AppendArg(base::StringPrintf(
-        "--https_key_file=%s",
-        FilePathToUTF8(web_page_replay_support_file_dir
-                           .Append(FILE_PATH_LITERAL("wpr_key.pem"))
-                           .value())
-            .c_str()));
-
-    for (auto const& arg : args)
-      full_command.AppendArg(arg);
-
-    return base::LaunchProcess(full_command, options);
-  }
-
-  bool WaitForStateChange(
-      const std::vector<std::string>& state_assertions,
-      const base::TimeDelta& timeout = default_action_timeout) {
-    const base::TimeTicks start_time = base::TimeTicks::Now();
-    while (!AllAssertionsPassed(state_assertions)) {
-      if (base::TimeTicks::Now() - start_time > timeout) {
-        ADD_FAILURE() << "State change hasn't completed within timeout.";
-        return false;
-      }
-      base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
-    }
-    return true;
-  }
-
-  bool AllAssertionsPassed(const std::vector<std::string>& assertions)
-      WARN_UNUSED_RESULT {
-    for (std::string const& assertion : assertions) {
-      bool assertion_passed = false;
-      EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
-          GetWebContents(),
-          base::StringPrintf("window.domAutomationController.send("
-                             "    (function() {"
-                             "      try {"
-                             "        %s"
-                             "      } catch (ex) {}"
-                             "      return false;"
-                             "    })());",
-                             assertion.c_str()),
-          &assertion_passed));
-      if (!assertion_passed) {
-        LOG(ERROR) << "'" << assertion << "' failed!";
-        return false;
-      }
-    }
-    return true;
-  }
-
-  void CleanupSiteData() {
-    // Navigate to about:blank, then clear the browser cache.
-    // Navigating to about:blank before clearing the cache ensures that
-    // the cleanup is thorough and nothing is held.
-    ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
-    content::BrowsingDataRemover* remover =
-        content::BrowserContext::GetBrowsingDataRemover(browser()->profile());
-    content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
-    remover->RemoveAndReply(
-        base::Time(), base::Time::Max(),
-        content::BrowsingDataRemover::DATA_TYPE_COOKIES,
-        content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
-        &completion_observer);
-    completion_observer.BlockUntilCompletion();
-  }
-
-  bool ExecuteJavaScriptOnElementByXpath(
-      const std::string& element_xpath,
-      const std::string& execute_function_body,
-      const base::TimeDelta& time_to_wait_for_element =
-          default_action_timeout) {
-    std::string js(base::StringPrintf(
+  bool ShowAutofillSuggestion(const std::string& focus_element_xpath) {
+    const std::string js(base::StringPrintf(
         "try {"
         "  var element = automation_helper.getElementByXpath(`%s`);"
-        "  (function(target) { %s })(element);"
+        "  while (document.activeElement !== element) {"
+        "    element.focus();"
+        "  }"
         "} catch(ex) {}",
-        element_xpath.c_str(), execute_function_body.c_str()));
-    return content::ExecuteScript(GetWebContents(), js);
-  }
+        focus_element_xpath.c_str()));
+    if (content::ExecuteScript(GetWebContents(), js)) {
+      test_delegate()->Reset();
 
-  bool ExpectElementPropertyEquals(
-      const std::string& element_xpath,
-      const std::string& get_property_function_body,
-      const std::string& expected_value,
-      bool ignoreCase = false) {
-    std::string value;
-    if (content::ExecuteScriptAndExtractString(
-            GetWebContents(),
-            base::StringPrintf(
-                "window.domAutomationController.send("
-                "    (function() {"
-                "      try {"
-                "        var element = function() {"
-                "          return automation_helper.getElementByXpath(`%s`);"
-                "        }();"
-                "        return function(target){%s}(element);"
-                "      } catch (ex) {}"
-                "      return 'Exception encountered';"
-                "    })());",
-                element_xpath.c_str(), get_property_function_body.c_str()),
-            &value)) {
-      if (ignoreCase) {
-        EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(expected_value, value))
-            << "Field xpath: `" << element_xpath << "`, "
-            << "Expected: " << expected_value << ", actual: " << value;
+      if (ShouldAutoselectFirstSuggestionOnArrowDown()) {
+        SendKeyToPage(ui::DomKey::ARROW_DOWN);
+        return test_delegate()->Wait({ObservedUiEvents::kSuggestionShown,
+                                      ObservedUiEvents::kPreviewFormData},
+                                     autofill_wait_for_action_interval);
       } else {
-        EXPECT_EQ(expected_value, value)
-            << "Field xpath: `" << element_xpath << "`, ";
+        SendKeyToPage(ui::DomKey::ARROW_DOWN);
+        return test_delegate()->Wait({ObservedUiEvents::kSuggestionShown},
+                                     autofill_wait_for_action_interval);
       }
-      return true;
     }
-    LOG(ERROR) << element_xpath << ", " << get_property_function_body;
     return false;
   }
 
-  // Functions for deserializing and executing actions from the test recipe
-  // JSON object.
-  void InitializeBrowserToExecuteRecipe(
-      std::unique_ptr<base::DictionaryValue>& recipe) {
-    // Extract the starting URL from the test recipe.
-    base::Value* starting_url_container = recipe->FindKey("startingURL");
-    CHECK(starting_url_container);
-    CHECK_EQ(base::Value::Type::STRING, starting_url_container->type());
-    LOG(INFO) << "Navigating to " << starting_url_container->GetString();
-
-    // Navigate to the starting URL, wait for the page to complete loading.
-    PageActivityObserver page_activity_observer(GetWebContents());
-    CHECK(content::ExecuteScript(
-        GetWebContents(),
-        base::StringPrintf("window.location.href = '%s';",
-                           starting_url_container->GetString().c_str())));
-    page_activity_observer.WaitTillPageIsIdle();
-  }
-
-  void ExecuteWaitForStateAction(base::DictionaryValue* action) {
-    // Extract the list of JavaScript assertions into a vector.
-    std::vector<std::string> state_assertions;
-    base::Value* assertions_list_container = action->FindKey("assertions");
-    CHECK(assertions_list_container);
-    CHECK_EQ(base::Value::Type::LIST, assertions_list_container->type());
-    base::Value::ListStorage& assertions_list =
-        assertions_list_container->GetList();
-    for (base::ListValue::iterator it_assertion = assertions_list.begin();
-         it_assertion != assertions_list.end(); ++it_assertion) {
-      CHECK_EQ(base::Value::Type::STRING, it_assertion->type());
-      state_assertions.push_back(it_assertion->GetString());
-    }
-    LOG(INFO) << "Waiting for page to reach a state.";
-
-    // Wait for all of the assertions to become true on the current page.
-    CHECK(WaitForStateChange(state_assertions, default_action_timeout));
-  }
-
-  void ExecuteRunCommandAction(base::DictionaryValue* action) {
-    // Extract the list of JavaScript commands into a vector.
-    std::vector<std::string> commands;
-    base::Value* commands_list_container = action->FindKey("commands");
-    CHECK(commands_list_container);
-    CHECK_EQ(base::Value::Type::LIST, commands_list_container->type());
-    base::Value::ListStorage& commands_list =
-        commands_list_container->GetList();
-
-    for (base::ListValue::iterator it_command = commands_list.begin();
-         it_command != commands_list.end(); ++it_command) {
-      CHECK_EQ(base::Value::Type::STRING, it_command->type());
-      commands.push_back(it_command->GetString());
-    }
-    LOG(INFO) << "Running JavaScript commands on the page.";
-
-    // Execute the commands.
-    PageActivityObserver page_activity_observer(GetWebContents());
-    for (const std::string& command : commands) {
-      CHECK(content::ExecuteScript(GetWebContents(), command));
-      // Wait in case the JavaScript command triggers page load or layout
-      // changes.
-      page_activity_observer.WaitTillPageIsIdle();
-    }
-  }
-
-  void ExecuteClickAction(base::DictionaryValue* action) {
-    std::string xpath = GetTargetHTMLElementXpathFromAction(action);
-    WaitForElemementToBeReady(xpath);
-
-    LOG(INFO) << "Left mouse clicking `" << xpath << "`.";
-    PageActivityObserver page_activity_observer(GetWebContents());
-    CHECK(ExecuteJavaScriptOnElementByXpath(xpath, "target.click();"));
-    page_activity_observer.WaitTillPageIsIdle();
-  }
-
-  void ExecuteTypeAction(base::DictionaryValue* action) {
-    base::Value* value_container = action->FindKey("value");
-    CHECK(value_container);
-    CHECK_EQ(base::Value::Type::STRING, value_container->type());
-    std::string value = value_container->GetString();
-
-    std::string xpath = GetTargetHTMLElementXpathFromAction(action);
-    WaitForElemementToBeReady(xpath);
-
-    LOG(INFO) << "Typing '" << value << "' inside `" << xpath << "`.";
-    PageActivityObserver page_activity_observer(GetWebContents());
-    CHECK(ExecuteJavaScriptOnElementByXpath(
-        xpath, base::StringPrintf(
-                   "automation_helper.setInputElementValue(target, `%s`);",
-                   value.c_str())));
-    page_activity_observer.WaitTillPageIsIdle();
-  }
-
-  void ExecuteSelectDropdownAction(base::DictionaryValue* action) {
-    base::Value* index_container = action->FindKey("index");
-    CHECK(index_container);
-    CHECK_EQ(base::Value::Type::INTEGER, index_container->type());
-    int index = index_container->GetInt();
-
-    std::string xpath = GetTargetHTMLElementXpathFromAction(action);
-    WaitForElemementToBeReady(xpath);
-
-    LOG(INFO) << "Select option '" << index << "' from `" << xpath << "`.";
-    PageActivityObserver page_activity_observer(GetWebContents());
-    CHECK(ExecuteJavaScriptOnElementByXpath(
-        xpath, base::StringPrintf(
-                   "automation_helper"
-                   "  .selectOptionFromDropDownElementByIndex(target, %d);",
-                   index_container->GetInt())));
-    page_activity_observer.WaitTillPageIsIdle();
-  }
-
-  void ExecuteAutofillAction(base::DictionaryValue* action) {
-    std::string xpath = GetTargetHTMLElementXpathFromAction(action);
-    WaitForElemementToBeReady(xpath);
-
-    LOG(INFO) << "Invoking Chrome Autofill on `" << xpath << "`.";
-    PageActivityObserver page_activity_observer(GetWebContents());
-    // Clear the input box first, in case a previous value is there.
-    // If the text input box is not clear, pressing the down key will not
-    // bring up the autofill suggestion box.
-    // This can happen on sites that requires the user to sign in. After
-    // signing in, the site fills the form with the user's profile
-    // information.
-    CHECK(ExecuteJavaScriptOnElementByXpath(
-        xpath, "automation_helper.setInputElementValue(target, ``);"));
-    CHECK(TryFillForm(xpath, autofill_action_num_retries));
-    page_activity_observer.WaitTillPageIsIdle();
-  }
-
-  void ExecuteValidateFieldValueAction(base::DictionaryValue* action) {
-    base::Value* autofill_prediction_container =
-        action->FindKey("expectedAutofillType");
-    CHECK(autofill_prediction_container);
-    CHECK_EQ(base::Value::Type::STRING, autofill_prediction_container->type());
-    std::string expected_autofill_prediction_type =
-        autofill_prediction_container->GetString();
-
-    base::Value* expected_value_container = action->FindKey("expectedValue");
-    CHECK(expected_value_container);
-    CHECK_EQ(base::Value::Type::STRING, expected_value_container->type());
-    std::string expected_value = expected_value_container->GetString();
-
-    std::string xpath = GetTargetHTMLElementXpathFromAction(action);
-    WaitForElemementToBeReady(xpath);
-
-    LOG(INFO) << "Checking the field `" << xpath << "`.";
-    ExpectElementPropertyEquals(
-        xpath.c_str(), "return target.getAttribute('autofill-prediction');",
-        expected_autofill_prediction_type, true);
-    ExpectElementPropertyEquals(xpath.c_str(), "return target.value;",
-                                expected_value);
-  }
-
-  std::string GetTargetHTMLElementXpathFromAction(
-      base::DictionaryValue* action) {
-    base::Value* xpath_container = action->FindKey("selector");
-    CHECK(xpath_container);
-    CHECK_EQ(base::Value::Type::STRING, xpath_container->type());
-    return xpath_container->GetString();
-  }
-
-  void WaitForElemementToBeReady(std::string xpath) {
-    std::vector<std::string> state_assertions;
-    state_assertions.push_back(base::StringPrintf(
-        "return automation_helper.isElementWithXpathReady(`%s`);",
-        xpath.c_str()));
-    CHECK(WaitForStateChange(state_assertions, default_action_timeout));
-  }
-
-  // The Web Page Replay server that will be serving the captured sites
-  base::Process web_page_replay_server_;
-  const int host_http_port_ = 8080;
-  const int host_https_port_ = 8081;
-
   AutofillProfile profile_;
   CreditCard card_;
+  std::unique_ptr<captured_sites_test_utils::TestRecipeReplayer>
+      recipe_replayer_;
 
   base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_P(AutofillCapturedSitesInteractiveTest, Recipe) {
   // Prints the path of the test to be executed.
-  LOG(INFO) << GetParam();
-  ASSERT_TRUE(StartWebPageReplayServer(GetParam()));
-  ASSERT_TRUE(ReplayTestRecipe(GetParam().c_str()));
+  VLOG(1) << GetParam();
+
+  // Craft the capture file path.
+  base::FilePath src_dir;
+  ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
+  base::FilePath capture_file_path =
+      GetReplayFilesDirectory().AppendASCII(GetParam().c_str());
+
+  // Craft the recipe file path.
+  base::FilePath recipe_file_path = GetReplayFilesDirectory().AppendASCII(
+      base::StringPrintf("%s.test", GetParam().c_str()));
+
+  ASSERT_TRUE(
+      recipe_replayer()->ReplayTest(capture_file_path, recipe_file_path));
 }
 
 struct GetParamAsString {
diff --git a/chrome/browser/autofill/autofill_uitest.cc b/chrome/browser/autofill/autofill_uitest.cc
index cd72f32..ac10be6b 100644
--- a/chrome/browser/autofill/autofill_uitest.cc
+++ b/chrome/browser/autofill/autofill_uitest.cc
@@ -89,70 +89,6 @@
   test::ReenableSystemServices();
 }
 
-bool AutofillUiTest::TryFillForm(const std::string& focus_element_xpath,
-                                 const int attempts) {
-  content::WebContents* web_contents = GetWebContents();
-  AutofillManager* autofill_manager =
-      ContentAutofillDriverFactory::FromWebContents(web_contents)
-          ->DriverForFrame(web_contents->GetMainFrame())
-          ->autofill_manager();
-
-  int tries = 0;
-  while (tries < attempts) {
-    tries++;
-    autofill_manager->client()->HideAutofillPopup();
-
-    if (!ShowAutofillSuggestion(focus_element_xpath)) {
-      LOG(WARNING) << "Failed to bring up the autofill suggestion drop down.";
-      continue;
-    }
-
-    // Press the down key again to highlight the first choice in the autofill
-    // suggestion drop down.
-    test_delegate()->Reset();
-    SendKeyToPopup(ui::DomKey::ARROW_DOWN);
-    if (!test_delegate()->Wait({ObservedUiEvents::kPreviewFormData},
-                               base::TimeDelta::FromSeconds(5))) {
-      LOG(WARNING) << "Failed to select an option from the"
-                   << " autofill suggestion drop down.";
-      continue;
-    }
-
-    // Press the enter key to invoke autofill using the first suggestion.
-    test_delegate()->Reset();
-    SendKeyToPopup(ui::DomKey::ENTER);
-    if (!test_delegate()->Wait({ObservedUiEvents::kFormDataFilled},
-                               base::TimeDelta::FromSeconds(5))) {
-      LOG(WARNING) << "Failed to fill the form.";
-      continue;
-    }
-
-    return true;
-  }
-
-  autofill_manager->client()->HideAutofillPopup();
-  return false;
-}
-
-bool AutofillUiTest::ShowAutofillSuggestion(
-    const std::string& focus_element_xpath) {
-  const std::string js(base::StringPrintf(
-      "try {"
-      "  var element = automation_helper.getElementByXpath(`%s`);"
-      "  while (document.activeElement !== element) {"
-      "    element.focus();"
-      "  }"
-      "} catch(ex) {}",
-      focus_element_xpath.c_str()));
-  if (content::ExecuteScript(GetWebContents(), js)) {
-    test_delegate()->Reset();
-    SendKeyToPage(ui::DomKey::ARROW_DOWN);
-    return test_delegate()->Wait({ObservedUiEvents::kSuggestionShown},
-                                 base::TimeDelta::FromSeconds(5));
-  }
-  return false;
-}
-
 void AutofillUiTest::SendKeyToPage(ui::DomKey key) {
   ui::KeyboardCode key_code = ui::NonPrintableDomKeyToKeyboardCode(key);
   ui::DomCode code = ui::UsLayoutKeyboardCodeToDomCode(key_code);
diff --git a/chrome/browser/autofill/autofill_uitest.h b/chrome/browser/autofill/autofill_uitest.h
index d0b0980..ac78ecd 100644
--- a/chrome/browser/autofill/autofill_uitest.h
+++ b/chrome/browser/autofill/autofill_uitest.h
@@ -64,10 +64,6 @@
   void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
 
-  bool TryFillForm(const std::string& focus_element_xpath,
-                   const int attempts = 1);
-  bool ShowAutofillSuggestion(const std::string& focus_element_xpath);
-
   void SendKeyToPage(ui::DomKey key);
   void SendKeyToPageAndWait(ui::DomKey key,
                             std::list<ObservedUiEvents> expected_events);
@@ -75,6 +71,7 @@
                             ui::DomCode code,
                             ui::KeyboardCode key_code,
                             std::list<ObservedUiEvents> expected_events);
+  void SendKeyToPopup(ui::DomKey key);
   // Send key to the render host view's widget if |widget| is null.
   void SendKeyToPopupAndWait(ui::DomKey key,
                              std::list<ObservedUiEvents> expected_events,
@@ -98,8 +95,6 @@
   content::RenderWidgetHost::KeyPressEventCallback key_press_event_sink();
 
  private:
-  void SendKeyToPopup(ui::DomKey key);
-
   AutofillManagerTestDelegateImpl test_delegate_;
 
   // KeyPressEventCallback that serves as a sink to ensure that every key press
diff --git a/chrome/browser/autofill/captured_sites_test_utils.cc b/chrome/browser/autofill/captured_sites_test_utils.cc
new file mode 100644
index 0000000..cfbc19cb
--- /dev/null
+++ b/chrome/browser/autofill/captured_sites_test_utils.cc
@@ -0,0 +1,618 @@
+// Copyright 2018 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/autofill/captured_sites_test_utils.h"
+
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/test_utils.h"
+#include "ipc/ipc_channel_factory.h"
+#include "ipc/ipc_logging.h"
+#include "ipc/ipc_message_macros.h"
+#include "ipc/ipc_sync_message.h"
+
+namespace {
+// The maximum amount of time to wait for Chrome to finish autofilling a form.
+const base::TimeDelta kAutofillActionWaitForVisualUpdateTimeout =
+    base::TimeDelta::FromSeconds(3);
+
+// The number of tries the TestRecipeReplayer should perform when executing an
+// Chrome Autofill action.
+// Chrome Autofill can be flaky on some real-world pages. The Captured Site
+// Automation Framework will retry an autofill action a couple times before
+// concluding that Chrome Autofill does not work.
+const int kAutofillActionNumRetries = 5;
+}  // namespace
+
+namespace captured_sites_test_utils {
+
+constexpr base::TimeDelta PageActivityObserver::kPaintEventCheckInterval;
+
+std::string FilePathToUTF8(const base::FilePath::StringType& str) {
+#if defined(OS_WIN)
+  return base::WideToUTF8(str);
+#else
+  return str;
+#endif
+}
+
+// PageActivityObserver -------------------------------------------------------
+PageActivityObserver::PageActivityObserver(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+PageActivityObserver::PageActivityObserver(content::RenderFrameHost* frame)
+    : content::WebContentsObserver(
+          content::WebContents::FromRenderFrameHost(frame)) {}
+
+void PageActivityObserver::WaitTillPageIsIdle(
+    base::TimeDelta continuous_paint_timeout) {
+  base::TimeTicks finished_load_time = base::TimeTicks::Now();
+  bool page_is_loading = false;
+  do {
+    paint_occurred_during_last_loop_ = false;
+    base::RunLoop heart_beat;
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE, heart_beat.QuitClosure(), kPaintEventCheckInterval);
+    heart_beat.Run();
+    page_is_loading =
+        web_contents()->IsWaitingForResponse() || web_contents()->IsLoading();
+    if (page_is_loading) {
+      finished_load_time = base::TimeTicks::Now();
+    } else if (base::TimeTicks::Now() - finished_load_time >
+               continuous_paint_timeout) {
+      // |continuous_paint_timeout| has expired since Chrome loaded the page.
+      // During this period of time, Chrome has been continuously painting
+      // the page. In this case, the page is probably idle, but a bug, a
+      // blinking caret or a persistent animation is making Chrome paint at
+      // regular intervals. Exit.
+      break;
+    }
+  } while (page_is_loading || paint_occurred_during_last_loop_);
+}
+
+void PageActivityObserver::DidCommitAndDrawCompositorFrame() {
+  paint_occurred_during_last_loop_ = true;
+}
+
+// TestRecipeReplayer ---------------------------------------------------------
+TestRecipeReplayer::TestRecipeReplayer(
+    Browser* browser,
+    TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor)
+    : browser_(browser), feature_action_executor_(feature_action_executor) {}
+
+TestRecipeReplayer::~TestRecipeReplayer(){};
+
+bool TestRecipeReplayer::ReplayTest(const base::FilePath capture_file_path,
+                                    const base::FilePath recipe_file_path) {
+  if (StartWebPageReplayServer(capture_file_path)) {
+    return ReplayRecordedActions(recipe_file_path);
+  }
+  return false;
+}
+
+// static
+void TestRecipeReplayer::SetUpCommandLine(base::CommandLine* command_line) {
+  // Direct traffic to the Web Page Replay server.
+  command_line->AppendSwitchASCII(
+      network::switches::kHostResolverRules,
+      base::StringPrintf(
+          "MAP *:80 127.0.0.1:%d,"
+          "MAP *:443 127.0.0.1:%d,"
+          // Uncomment to use the live autofill prediction server.
+          //"EXCLUDE clients1.google.com,"
+          "EXCLUDE localhost",
+          kHostHttpPort, kHostHttpsPort));
+}
+
+void TestRecipeReplayer::Setup() {
+  EXPECT_TRUE(InstallWebPageReplayServerRootCert())
+      << "Cannot install the root certificate "
+      << "for the local web page replay server.";
+  CleanupSiteData();
+}
+
+void TestRecipeReplayer::Cleanup() {
+  // If there are still cookies at the time the browser test shuts down,
+  // Chrome's SQL lite persistent cookie store will crash.
+  CleanupSiteData();
+  EXPECT_TRUE(StopWebPageReplayServer())
+      << "Cannot stop the local Web Page Replay server.";
+  EXPECT_TRUE(RemoveWebPageReplayServerRootCert())
+      << "Cannot remove the root certificate "
+      << "for the local Web Page Replay server.";
+}
+
+TestRecipeReplayChromeFeatureActionExecutor*
+TestRecipeReplayer::feature_action_executor() {
+  return feature_action_executor_;
+}
+
+content::WebContents* TestRecipeReplayer::GetWebContents() {
+  return browser_->tab_strip_model()->GetActiveWebContents();
+}
+
+void TestRecipeReplayer::CleanupSiteData() {
+  // Navigate to about:blank, then clear the browser cache.
+  // Navigating to about:blank before clearing the cache ensures that
+  // the cleanup is thorough and nothing is held.
+  ui_test_utils::NavigateToURL(browser_, GURL(url::kAboutBlankURL));
+  content::BrowsingDataRemover* remover =
+      content::BrowserContext::GetBrowsingDataRemover(browser_->profile());
+  content::BrowsingDataRemoverCompletionObserver completion_observer(remover);
+  remover->RemoveAndReply(
+      base::Time(), base::Time::Max(),
+      content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+      content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
+      &completion_observer);
+  completion_observer.BlockUntilCompletion();
+}
+
+bool TestRecipeReplayer::StartWebPageReplayServer(
+    const base::FilePath& capture_file_path) {
+  std::vector<std::string> args;
+  base::FilePath src_dir;
+  if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
+    return false;
+
+  args.push_back(base::StringPrintf("--http_port=%d", kHostHttpPort));
+  args.push_back(base::StringPrintf("--https_port=%d", kHostHttpsPort));
+  args.push_back(base::StringPrintf(
+      "--inject_scripts=%s,%s",
+      FilePathToUTF8(
+          src_dir.AppendASCII("third_party/catapult/web_page_replay_go")
+              .AppendASCII("deterministic.js")
+              .value())
+          .c_str(),
+      FilePathToUTF8(
+          src_dir
+              .AppendASCII("chrome/test/data/web_page_replay_go_helper_scripts")
+              .AppendASCII("automation_helper.js")
+              .value())
+          .c_str()));
+
+  // Specify the capture file.
+  args.push_back(base::StringPrintf(
+      "%s", FilePathToUTF8(capture_file_path.value()).c_str()));
+  if (!RunWebPageReplayCmd("replay", args, &web_page_replay_server_))
+    return false;
+
+  // Sleep 20 seconds to wait for the web page replay server to start.
+  // TODO(crbug.com/847910): create a process std stream reader class to use the
+  // process output to determine when the server is ready
+  base::RunLoop wpr_launch_waiter;
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, wpr_launch_waiter.QuitClosure(),
+      base::TimeDelta::FromSeconds(20));
+  wpr_launch_waiter.Run();
+
+  return web_page_replay_server_.IsValid();
+}
+
+bool TestRecipeReplayer::StopWebPageReplayServer() {
+  if (web_page_replay_server_.IsValid())
+    return web_page_replay_server_.Terminate(0, true);
+  // The test server hasn't started, no op.
+  return true;
+}
+
+bool TestRecipeReplayer::InstallWebPageReplayServerRootCert() {
+  return RunWebPageReplayCmdAndWaitForExit("installroot",
+                                           std::vector<std::string>());
+}
+
+bool TestRecipeReplayer::RemoveWebPageReplayServerRootCert() {
+  return RunWebPageReplayCmdAndWaitForExit("removeroot",
+                                           std::vector<std::string>());
+}
+
+bool TestRecipeReplayer::RunWebPageReplayCmdAndWaitForExit(
+    const std::string& cmd,
+    const std::vector<std::string>& args,
+    const base::TimeDelta& timeout) {
+  base::Process process;
+  if (!RunWebPageReplayCmd(cmd, args, &process))
+    return false;
+  if (process.IsValid()) {
+    int exit_code;
+    if (process.WaitForExitWithTimeout(timeout, &exit_code))
+      return (exit_code == 0);
+  }
+  return false;
+}
+
+bool TestRecipeReplayer::RunWebPageReplayCmd(
+    const std::string& cmd,
+    const std::vector<std::string>& args,
+    base::Process* process) {
+  base::LaunchOptions options = base::LaunchOptionsForTest();
+  base::FilePath exe_dir;
+  if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &exe_dir))
+    return false;
+
+  base::FilePath web_page_replay_binary_dir = exe_dir.AppendASCII(
+      "third_party/catapult/telemetry/telemetry/internal/bin");
+  options.current_directory = web_page_replay_binary_dir;
+
+#if defined(OS_WIN)
+  std::string wpr_executable_binary = "win/x86_64/wpr";
+#elif defined(OS_MACOSX)
+  std::string wpr_executable_binary = "mac/x86_64/wpr";
+#elif defined(OS_POSIX)
+  std::string wpr_executable_binary = "linux/x86_64/wpr";
+#else
+#error Plaform is not supported.
+#endif
+  base::CommandLine full_command(
+      web_page_replay_binary_dir.AppendASCII(wpr_executable_binary));
+  full_command.AppendArg(cmd);
+
+  // Ask web page replay to use the custom certifcate and key files used to
+  // make the web page captures.
+  // The capture files used in these browser tests are also used on iOS to
+  // test autofill.
+  // The custom cert and key files are different from those of the offical
+  // WPR releases. The custom files are made to work on iOS.
+  base::FilePath src_dir;
+  if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
+    return false;
+
+  base::FilePath web_page_replay_support_file_dir = src_dir.AppendASCII(
+      "components/test/data/autofill/web_page_replay_support_files");
+  full_command.AppendArg(base::StringPrintf(
+      "--https_cert_file=%s",
+      FilePathToUTF8(
+          web_page_replay_support_file_dir.AppendASCII("wpr_cert.pem").value())
+          .c_str()));
+  full_command.AppendArg(base::StringPrintf(
+      "--https_key_file=%s",
+      FilePathToUTF8(
+          web_page_replay_support_file_dir.AppendASCII("wpr_key.pem").value())
+          .c_str()));
+
+  for (const auto arg : args)
+    full_command.AppendArg(arg);
+
+  *process = base::LaunchProcess(full_command, options);
+  return true;
+}
+
+bool TestRecipeReplayer::ReplayRecordedActions(
+    const base::FilePath recipe_file_path) {
+  // Read the text of the recipe file.
+  base::ThreadRestrictions::SetIOAllowed(true);
+  std::string json_text;
+  if (!base::ReadFileToString(recipe_file_path, &json_text))
+    return false;
+
+  // Convert the file text into a json object.
+  std::unique_ptr<base::DictionaryValue> recipe =
+      base::DictionaryValue::From(base::JSONReader().ReadToValue(json_text));
+  if (!recipe) {
+    ADD_FAILURE() << "Failed to deserialize json text!";
+    return false;
+  }
+
+  InitializeBrowserToExecuteRecipe(recipe);
+
+  // Iterate through and execute each action in the recipe.
+  base::Value* action_list_container = recipe->FindKey("actions");
+  if (!action_list_container)
+    return false;
+  if (base::Value::Type::LIST != action_list_container->type())
+    return false;
+  base::Value::ListStorage& action_list = action_list_container->GetList();
+
+  for (base::ListValue::iterator it_action = action_list.begin();
+       it_action != action_list.end(); ++it_action) {
+    base::DictionaryValue* action;
+    if (!it_action->GetAsDictionary(&action))
+      return false;
+
+    base::Value* type_container = action->FindKey("type");
+    if (!type_container)
+      return false;
+    if (base::Value::Type::STRING != type_container->type())
+      return false;
+    std::string type = type_container->GetString();
+
+    if (base::CompareCaseInsensitiveASCII(type, "autofill") == 0) {
+      ExecuteAutofillAction(action);
+    } else if (base::CompareCaseInsensitiveASCII(type, "click") == 0) {
+      ExecuteClickAction(action);
+    } else if (base::CompareCaseInsensitiveASCII(type, "select") == 0) {
+      ExecuteSelectDropdownAction(action);
+    } else if (base::CompareCaseInsensitiveASCII(type, "type") == 0) {
+      ExecuteTypeAction(action);
+    } else if (base::CompareCaseInsensitiveASCII(type, "validateField") == 0) {
+      ExecuteValidateFieldValueAction(action);
+    } else if (base::CompareCaseInsensitiveASCII(type, "waitFor") == 0) {
+      ExecuteWaitForStateAction(action);
+    } else {
+      ADD_FAILURE() << "Unrecognized action type: " << type;
+    }
+  }  // end foreach action
+  return true;
+}
+
+// Functions for deserializing and executing actions from the test recipe
+// JSON object.
+void TestRecipeReplayer::InitializeBrowserToExecuteRecipe(
+    std::unique_ptr<base::DictionaryValue>& recipe) {
+  // Extract the starting URL from the test recipe.
+  base::Value* starting_url_container = recipe->FindKey("startingURL");
+  ASSERT_TRUE(starting_url_container);
+  ASSERT_EQ(base::Value::Type::STRING, starting_url_container->type());
+
+  // Navigate to the starting URL, wait for the page to complete loading.
+  PageActivityObserver page_activity_observer(GetWebContents());
+  ASSERT_TRUE(content::ExecuteScript(
+      GetWebContents(),
+      base::StringPrintf("window.location.href = '%s';",
+                         starting_url_container->GetString().c_str())));
+  page_activity_observer.WaitTillPageIsIdle();
+}
+
+void TestRecipeReplayer::ExecuteAutofillAction(base::DictionaryValue* action) {
+  std::string xpath;
+  ASSERT_TRUE(GetTargetHTMLElementXpathFromAction(action, &xpath));
+  WaitForElemementToBeReady(xpath);
+
+  VLOG(1) << "Invoking Chrome Autofill on `" << xpath << "`.";
+  PageActivityObserver page_activity_observer(GetWebContents());
+  // Clear the input box first, in case a previous value is there.
+  // If the text input box is not clear, pressing the down key will not
+  // bring up the autofill suggestion box.
+  // This can happen on sites that requires the user to sign in. After
+  // signing in, the site fills the form with the user's profile
+  // information.
+  ASSERT_TRUE(ExecuteJavaScriptOnElementByXpath(
+      xpath, "automation_helper.setInputElementValue(target, ``);"));
+  ASSERT_TRUE(feature_action_executor()->AutofillForm(
+      GetWebContents(), xpath, kAutofillActionNumRetries));
+  page_activity_observer.WaitTillPageIsIdle(
+      kAutofillActionWaitForVisualUpdateTimeout);
+}
+
+void TestRecipeReplayer::ExecuteClickAction(base::DictionaryValue* action) {
+  std::string xpath;
+  ASSERT_TRUE(GetTargetHTMLElementXpathFromAction(action, &xpath));
+  WaitForElemementToBeReady(xpath);
+
+  VLOG(1) << "Left mouse clicking `" << xpath << "`.";
+  PageActivityObserver page_activity_observer(GetWebContents());
+  ASSERT_TRUE(ExecuteJavaScriptOnElementByXpath(xpath, "target.click();"));
+  page_activity_observer.WaitTillPageIsIdle();
+}
+
+void TestRecipeReplayer::ExecuteSelectDropdownAction(
+    base::DictionaryValue* action) {
+  base::Value* index_container = action->FindKey("index");
+  ASSERT_TRUE(index_container);
+  ASSERT_EQ(base::Value::Type::INTEGER, index_container->type());
+  int index = index_container->GetInt();
+
+  std::string xpath;
+  ASSERT_TRUE(GetTargetHTMLElementXpathFromAction(action, &xpath));
+  WaitForElemementToBeReady(xpath);
+
+  VLOG(1) << "Select option '" << index << "' from `" << xpath << "`.";
+  PageActivityObserver page_activity_observer(GetWebContents());
+  ASSERT_TRUE(ExecuteJavaScriptOnElementByXpath(
+      xpath, base::StringPrintf(
+                 "automation_helper"
+                 "  .selectOptionFromDropDownElementByIndex(target, %d);",
+                 index_container->GetInt())));
+  page_activity_observer.WaitTillPageIsIdle();
+}
+
+void TestRecipeReplayer::ExecuteTypeAction(base::DictionaryValue* action) {
+  base::Value* value_container = action->FindKey("value");
+  ASSERT_TRUE(value_container);
+  ASSERT_EQ(base::Value::Type::STRING, value_container->type());
+  std::string value = value_container->GetString();
+
+  std::string xpath;
+  ASSERT_TRUE(GetTargetHTMLElementXpathFromAction(action, &xpath));
+  WaitForElemementToBeReady(xpath);
+
+  VLOG(1) << "Typing '" << value << "' inside `" << xpath << "`.";
+  PageActivityObserver page_activity_observer(GetWebContents());
+  ASSERT_TRUE(ExecuteJavaScriptOnElementByXpath(
+      xpath, base::StringPrintf(
+                 "automation_helper.setInputElementValue(target, `%s`);",
+                 value.c_str())));
+  page_activity_observer.WaitTillPageIsIdle();
+}
+
+void TestRecipeReplayer::ExecuteValidateFieldValueAction(
+    base::DictionaryValue* action) {
+  std::string xpath;
+  ASSERT_TRUE(GetTargetHTMLElementXpathFromAction(action, &xpath));
+  WaitForElemementToBeReady(xpath);
+
+  base::Value* autofill_prediction_container =
+      action->FindKey("expectedAutofillType");
+  if (autofill_prediction_container) {
+    ASSERT_EQ(base::Value::Type::STRING, autofill_prediction_container->type());
+    std::string expected_autofill_prediction_type =
+        autofill_prediction_container->GetString();
+    VLOG(1) << "Checking the field `" << xpath << "` has the autofill type '"
+            << expected_autofill_prediction_type << "'";
+    ExpectElementPropertyEquals(
+        xpath.c_str(), "return target.getAttribute('autofill-prediction');",
+        expected_autofill_prediction_type, true);
+  }
+
+  base::Value* expected_value_container = action->FindKey("expectedValue");
+  ASSERT_TRUE(expected_value_container);
+  ASSERT_EQ(base::Value::Type::STRING, expected_value_container->type());
+  std::string expected_value = expected_value_container->GetString();
+
+  VLOG(1) << "Checking the field `" << xpath << "`.";
+  ExpectElementPropertyEquals(xpath.c_str(), "return target.value;",
+                              expected_value);
+}
+
+void TestRecipeReplayer::ExecuteWaitForStateAction(
+    base::DictionaryValue* action) {
+  // Extract the list of JavaScript assertions into a vector.
+  std::vector<std::string> state_assertions;
+  base::Value* assertions_list_container = action->FindKey("assertions");
+  ASSERT_TRUE(assertions_list_container);
+  ASSERT_EQ(base::Value::Type::LIST, assertions_list_container->type());
+  base::Value::ListStorage& assertions_list =
+      assertions_list_container->GetList();
+  for (base::ListValue::iterator it_assertion = assertions_list.begin();
+       it_assertion != assertions_list.end(); ++it_assertion) {
+    ASSERT_EQ(base::Value::Type::STRING, it_assertion->type());
+    state_assertions.push_back(it_assertion->GetString());
+  }
+
+  VLOG(1) << "Waiting for page to reach a state.";
+
+  // Wait for all of the assertions to become true on the current page.
+  ASSERT_TRUE(WaitForStateChange(state_assertions, default_action_timeout));
+}
+
+bool TestRecipeReplayer::GetTargetHTMLElementXpathFromAction(
+    base::DictionaryValue* action,
+    std::string* xpath) {
+  xpath->clear();
+  base::Value* xpath_container = action->FindKey("selector");
+  if (!xpath_container)
+    return false;
+  if (base::Value::Type::STRING != xpath_container->type())
+    return false;
+  *xpath = xpath_container->GetString();
+  return true;
+}
+
+void TestRecipeReplayer::WaitForElemementToBeReady(std::string xpath) {
+  std::vector<std::string> state_assertions;
+  state_assertions.push_back(base::StringPrintf(
+      "return automation_helper.isElementWithXpathReady(`%s`);",
+      xpath.c_str()));
+  ASSERT_TRUE(WaitForStateChange(state_assertions, default_action_timeout));
+}
+
+bool TestRecipeReplayer::WaitForStateChange(
+    const std::vector<std::string>& state_assertions,
+    const base::TimeDelta& timeout) {
+  const base::TimeTicks start_time = base::TimeTicks::Now();
+  PageActivityObserver page_activity_observer(GetWebContents());
+  while (!AllAssertionsPassed(state_assertions)) {
+    if (base::TimeTicks::Now() - start_time > timeout) {
+      ADD_FAILURE() << "State change hasn't completed within timeout.";
+      return false;
+    }
+    page_activity_observer.WaitTillPageIsIdle();
+  }
+  return true;
+}
+
+bool TestRecipeReplayer::AllAssertionsPassed(
+    const std::vector<std::string>& assertions) {
+  for (const std::string& assertion : assertions) {
+    bool assertion_passed = false;
+    EXPECT_TRUE(ExecuteScriptAndExtractBool(
+        GetWebContents(),
+        base::StringPrintf("window.domAutomationController.send("
+                           "    (function() {"
+                           "      try {"
+                           "        %s"
+                           "      } catch (ex) {}"
+                           "      return false;"
+                           "    })());",
+                           assertion.c_str()),
+        &assertion_passed));
+    if (!assertion_passed) {
+      VLOG(1) << "'" << assertion << "' failed!";
+      return false;
+    }
+  }
+  return true;
+}
+
+bool TestRecipeReplayer::ExecuteJavaScriptOnElementByXpath(
+    const std::string& element_xpath,
+    const std::string& execute_function_body,
+    const base::TimeDelta& time_to_wait_for_element) {
+  std::string js(base::StringPrintf(
+      "try {"
+      "  var element = automation_helper.getElementByXpath(`%s`);"
+      "  (function(target) { %s })(element);"
+      "} catch(ex) {}",
+      element_xpath.c_str(), execute_function_body.c_str()));
+  return ExecuteScript(GetWebContents(), js);
+}
+
+bool TestRecipeReplayer::ExpectElementPropertyEquals(
+    const std::string& element_xpath,
+    const std::string& get_property_function_body,
+    const std::string& expected_value,
+    bool ignoreCase) {
+  std::string value;
+  if (ExecuteScriptAndExtractString(
+          GetWebContents(),
+          base::StringPrintf(
+              "window.domAutomationController.send("
+              "    (function() {"
+              "      try {"
+              "        var element = function() {"
+              "          return automation_helper.getElementByXpath(`%s`);"
+              "        }();"
+              "        return function(target){%s}(element);"
+              "      } catch (ex) {}"
+              "      return 'Exception encountered';"
+              "    })());",
+              element_xpath.c_str(), get_property_function_body.c_str()),
+          &value)) {
+    if (ignoreCase) {
+      EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(expected_value, value))
+          << "Field xpath: `" << element_xpath << "`, "
+          << "Expected: " << expected_value << ", actual: " << value;
+    } else {
+      EXPECT_EQ(expected_value, value)
+          << "Field xpath: `" << element_xpath << "`, ";
+    }
+    return true;
+  }
+  VLOG(1) << element_xpath << ", " << get_property_function_body;
+  return false;
+}
+
+// TestRecipeReplayChromeFeatureActionExecutor --------------------------------
+TestRecipeReplayChromeFeatureActionExecutor::
+    TestRecipeReplayChromeFeatureActionExecutor() {}
+TestRecipeReplayChromeFeatureActionExecutor::
+    ~TestRecipeReplayChromeFeatureActionExecutor() {}
+
+bool TestRecipeReplayChromeFeatureActionExecutor::AutofillForm(
+    content::WebContents* web_contents,
+    const std::string& focus_element_css_selector,
+    const int attempts) {
+  return false;
+}
+
+}  // namespace captured_sites_test_utils
diff --git a/chrome/browser/autofill/captured_sites_test_utils.h b/chrome/browser/autofill/captured_sites_test_utils.h
new file mode 100644
index 0000000..10e1e12
--- /dev/null
+++ b/chrome/browser/autofill/captured_sites_test_utils.h
@@ -0,0 +1,201 @@
+// Copyright 2018 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_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
+#define CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "chrome/browser/ui/browser.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "services/network/public/cpp/network_switches.h"
+
+namespace captured_sites_test_utils {
+
+// The amount of time to wait for an action to complete, or for a page element
+// to appear. The Captured Site Automation Framework uses this timeout to break
+// out of wait loops in the event that
+// 1. A page load error occurred and the page does not have a page element
+//    the test expects. Test should stop waiting.
+// 2. A page contains persistent animation (such as a flash sale count down
+//    timer) that causes the framework's paint-based PageActivityObserver to
+//    wait indefinitely. Test should stop waiting if a sufficiently large time
+//    has expired for the page to load or for the page to respond to the last
+//    user action.
+const base::TimeDelta default_action_timeout = base::TimeDelta::FromSeconds(30);
+
+std::string FilePathToUTF8(const base::FilePath::StringType& str);
+
+// PageActivityObserver
+//
+// PageActivityObserver is a universal wait-for-page-ready object that ensures
+// the current web page finishes responding to user input. The Captured Site
+// Automation Framework, specifically the TestRecipeReplayer class, uses
+// PageActivityObserver to time delays between two test actions. Without the
+// delay, a test may break itself by performing a page action before the page
+// is ready to receive the action.
+//
+// For example, the Amazon.com checkout page runs background scripts after
+// loading. While running the background scripts, the checkout page displays
+// a spinner. If a user clicks on a link while the spinner is present,
+// Amazon.com will dispatch the user to an error page.
+//
+// Page readiness is hard to determine because on real-world sites, page
+// readiness does not correspond to page load events. In the above Amazon.com
+// example, the checkout page starts the background scripts after the page
+// finishes loading.
+//
+// The PageActivityObserver defines page ready as the absence of Chrome paint
+// events. On real-world sites, if a page is busy loading, the Chrome tab
+// should be busy and Chrome should continuously make layout changes and
+// repaint the page. If a site is busy doing background work, most pages
+// typically display some form of persistent animation such as a progress bar
+// or a spinner to tell the user that the page is not ready. Therefore, it
+// is reasonable to assume that a page is ready if Chrome finished painting.
+class PageActivityObserver : public content::WebContentsObserver {
+ public:
+  explicit PageActivityObserver(content::WebContents* web_contents);
+  explicit PageActivityObserver(content::RenderFrameHost* frame);
+  ~PageActivityObserver() override = default;
+
+  // Wait until Chrome finishes loading a page and updating the page's visuals.
+  // If Chrome finishes loading a page but continues to paint every half
+  // second, exit after |continuous_paint_timeout| expires since Chrome
+  // finished loading the page.
+  void WaitTillPageIsIdle(
+      base::TimeDelta continuous_paint_timeout = default_action_timeout);
+
+ private:
+  // PageActivityObserver determines if Chrome stopped painting by checking if
+  // Chrome hasn't painted for a specific amount of time.
+  // kPaintEventCheckInterval defines this amount of time.
+  static constexpr base::TimeDelta kPaintEventCheckInterval =
+      base::TimeDelta::FromMilliseconds(500);
+
+  // content::WebContentsObserver:
+  void DidCommitAndDrawCompositorFrame() override;
+
+  bool paint_occurred_during_last_loop_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(PageActivityObserver);
+};
+
+// TestRecipeReplayChromeFeatureActionExecutor
+//
+// TestRecipeReplayChromeFeatureActionExecutor is a helper interface. A
+// TestRecipeReplayChromeFeatureActionExecutor class implements functions
+// that automate Chrome feature behavior. TestRecipeReplayer calls
+// TestRecipeReplayChromeFeatureActionExecutor functions to execute actions
+// that involves a Chrome feature - such as Chrome Autofill or Chrome
+// Password Manager. Executing a Chrome feature action typically require
+// using private or protected hooks defined inside that feature's
+// InProcessBrowserTest class. By implementing this interface an
+// InProcessBrowserTest exposes its feature to captured site automation.
+class TestRecipeReplayChromeFeatureActionExecutor {
+ public:
+  // Chrome Autofill feature methods.
+  // Triggers Chrome Autofill in the specified input element on the specified
+  // document.
+  virtual bool AutofillForm(content::WebContents* web_contents,
+                            const std::string& focus_element_css_selector,
+                            int attempts = 1);
+
+ protected:
+  TestRecipeReplayChromeFeatureActionExecutor();
+  ~TestRecipeReplayChromeFeatureActionExecutor();
+
+  DISALLOW_COPY_AND_ASSIGN(TestRecipeReplayChromeFeatureActionExecutor);
+};
+
+// TestRecipeReplayer
+//
+// The TestRecipeReplayer object drives Captured Site Automation by
+// 1. Providing a set of functions that help an InProcessBrowserTest to
+//    configure, start and stop a Web Page Replay (WPR) server. A WPR server
+//    is a local server that intercepts and responds to Chrome requests with
+//    pre-recorded traffic. Using a captured site archive file, WPR can
+//    mimick the site server and provide the test with deterministic site
+//    behaviors.
+// 2. Providing a function that deserializes and replays a Test Recipe. A Test
+//    Recipe is a JSON formatted file containing instructions on how to run a
+//    Chrome test against a live or captured site. These instructions include
+//    the starting URL for the test, and a list of user actions (clicking,
+//    typing) that drives the test. One may sample some example Test Recipes
+//    under the src/chrome/test/data/autofill/captured_sites directory.
+class TestRecipeReplayer {
+ public:
+  static const int kHostHttpPort = 8080;
+  static const int kHostHttpsPort = 8081;
+
+  TestRecipeReplayer(
+      Browser* browser,
+      TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor);
+  ~TestRecipeReplayer();
+  void Setup();
+  void Cleanup();
+
+  // Replay a test by:
+  // 1. Starting a WPR server using the specified capture file.
+  // 2. Replaying the specified Test Recipe file.
+  bool ReplayTest(const base::FilePath capture_file_path,
+                  const base::FilePath recipe_file_path);
+
+  static void SetUpCommandLine(base::CommandLine* command_line);
+
+ private:
+  TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor();
+  content::WebContents* GetWebContents();
+  void CleanupSiteData();
+  bool StartWebPageReplayServer(const base::FilePath& capture_file_path);
+  bool StopWebPageReplayServer();
+  bool InstallWebPageReplayServerRootCert();
+  bool RemoveWebPageReplayServerRootCert();
+  bool RunWebPageReplayCmdAndWaitForExit(
+      const std::string& cmd,
+      const std::vector<std::string>& args,
+      const base::TimeDelta& timeout = base::TimeDelta::FromSeconds(5));
+  bool RunWebPageReplayCmd(const std::string& cmd,
+                           const std::vector<std::string>& args,
+                           base::Process* process);
+  bool ReplayRecordedActions(const base::FilePath recipe_file_path);
+  void InitializeBrowserToExecuteRecipe(
+      std::unique_ptr<base::DictionaryValue>& recipe);
+  void ExecuteAutofillAction(base::DictionaryValue* action);
+  void ExecuteClickAction(base::DictionaryValue* action);
+  void ExecuteSelectDropdownAction(base::DictionaryValue* action);
+  void ExecuteTypeAction(base::DictionaryValue* action);
+  void ExecuteValidateFieldValueAction(base::DictionaryValue* action);
+  void ExecuteWaitForStateAction(base::DictionaryValue* action);
+  bool GetTargetHTMLElementXpathFromAction(base::DictionaryValue* action,
+                                           std::string* xpath);
+  void WaitForElemementToBeReady(std::string xpath);
+  bool WaitForStateChange(
+      const std::vector<std::string>& state_assertions,
+      const base::TimeDelta& timeout = default_action_timeout);
+  bool AllAssertionsPassed(const std::vector<std::string>& assertions);
+  bool ExecuteJavaScriptOnElementByXpath(
+      const std::string& element_xpath,
+      const std::string& execute_function_body,
+      const base::TimeDelta& time_to_wait_for_element = default_action_timeout);
+  bool ExpectElementPropertyEquals(
+      const std::string& element_xpath,
+      const std::string& get_property_function_body,
+      const std::string& expected_value,
+      bool ignoreCase = false);
+
+  Browser* browser_;
+  TestRecipeReplayChromeFeatureActionExecutor* feature_action_executor_;
+
+  // The Web Page Replay server that serves the captured sites.
+  base::Process web_page_replay_server_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestRecipeReplayer);
+};
+
+}  // namespace captured_sites_test_utils
+
+#endif  // CHROME_BROWSER_AUTOFILL_CAPTURED_SITES_TEST_UTILS_H_
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index 295b9dac..2db7f349 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -472,6 +472,7 @@
   blink::mojom::AppBannerController* controller_ptr = controller.get();
   controller_ptr->BannerPromptRequest(
       std::move(banner_proxy), mojo::MakeRequest(&event_), {GetBannerType()},
+      IsExperimentalAppBannersEnabled(),
       base::BindOnce(&AppBannerManager::OnBannerPromptReply, GetWeakPtr(),
                      std::move(controller)));
 }
@@ -717,12 +718,7 @@
   UpdateState(State::COMPLETE);
 }
 
-void AppBannerManager::DisplayAppBanner(bool user_gesture) {
-  if (IsExperimentalAppBannersEnabled() && !user_gesture) {
-    Stop(NO_GESTURE);
-    return;
-  }
-
+void AppBannerManager::DisplayAppBanner() {
   if (state_ == State::PENDING_PROMPT) {
     ShowBanner();
   } else if (state_ == State::SENDING_EVENT) {
diff --git a/chrome/browser/banners/app_banner_manager.h b/chrome/browser/banners/app_banner_manager.h
index edeb654..682b67c 100644
--- a/chrome/browser/banners/app_banner_manager.h
+++ b/chrome/browser/banners/app_banner_manager.h
@@ -326,7 +326,7 @@
   // blink::mojom::AppBannerService overrides.
   // Called when Blink has prevented a banner from being shown, and is now
   // requesting that it be shown later.
-  void DisplayAppBanner(bool user_gesture) override;
+  void DisplayAppBanner() override;
 
   // Returns an InstallableStatusCode indicating whether a banner should be
   // shown.
diff --git a/chrome/browser/banners/app_banner_manager_browsertest.cc b/chrome/browser/banners/app_banner_manager_browsertest.cc
index 451433e9..df73526 100644
--- a/chrome/browser/banners/app_banner_manager_browsertest.cc
+++ b/chrome/browser/banners/app_banner_manager_browsertest.cc
@@ -594,37 +594,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
-                       ExperimentalFlowWebAppBannerPromptNeedsGesture) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kExperimentalAppBanners);
-  std::unique_ptr<AppBannerManagerTest> manager(
-      CreateAppBannerManager(browser()));
-  base::HistogramTester histograms;
-
-  SiteEngagementService* service =
-      SiteEngagementService::Get(browser()->profile());
-  GURL test_url = GetBannerURLWithAction("stash_event");
-  service->ResetBaseScoreForURL(test_url, 10);
-
-  // Navigate to page and get the pipeline started.
-  TriggerBannerFlowWithNavigation(browser(), manager.get(), test_url,
-                                  false /* expected_will_show */,
-                                  State::PENDING_PROMPT);
-
-  // Now let the page call prompt without a gesture, an error should be
-  // generated.
-  TriggerBannerFlow(
-      browser(), manager.get(),
-      base::BindOnce(&ExecuteScript, browser(), "callStashedPrompt();",
-                     false /* with_gesture */),
-      false /* expected_will_show */, State::COMPLETE);
-
-  histograms.ExpectTotalCount(banners::kMinutesHistogram, 0);
-  histograms.ExpectUniqueSample(banners::kInstallableStatusCodeHistogram,
-                                NO_GESTURE, 1);
-}
-
-IN_PROC_BROWSER_TEST_F(AppBannerManagerBrowserTest,
                        ExperimentalFlowWebAppBannerPromptWithGesture) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kExperimentalAppBanners);
diff --git a/chrome/browser/chrome_browser_application_mac.mm b/chrome/browser/chrome_browser_application_mac.mm
index a6ae6a34..b26345d 100644
--- a/chrome/browser/chrome_browser_application_mac.mm
+++ b/chrome/browser/chrome_browser_application_mac.mm
@@ -8,6 +8,7 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/mac/call_with_eh_frame.h"
+#include "base/observer_list.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/trace_event/trace_event.h"
@@ -18,6 +19,8 @@
 #include "components/crash/core/common/crash_key.h"
 #import "components/crash/core/common/objc_zombie.h"
 #include "content/public/browser/browser_accessibility_state.h"
+#include "content/public/browser/native_event_processor_mac.h"
+#include "content/public/browser/native_event_processor_observer_mac.h"
 
 namespace chrome_browser_application_mac {
 
@@ -97,6 +100,11 @@
 - (void)_cycleWindowsReversed:(BOOL)arg1;
 @end
 
+@interface BrowserCrApplication ()<NativeEventProcessor> {
+  base::ObserverList<content::NativeEventProcessorObserver> observers_;
+}
+@end
+
 @implementation BrowserCrApplication
 
 + (void)initialize {
@@ -325,6 +333,8 @@
 
       default: {
         base::mac::ScopedSendingEvent sendingEventScoper;
+        content::ScopedNotifyNativeEventProcessorObserver
+            scopedObserverNotifier(&observers_, event);
         [super sendEvent:event];
       }
     }
@@ -353,4 +363,14 @@
   return cyclingWindows_;
 }
 
+- (void)addNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer {
+  observers_.AddObserver(observer);
+}
+
+- (void)removeNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer {
+  observers_.RemoveObserver(observer);
+}
+
 @end
diff --git a/chrome/browser/chrome_security_exploit_browsertest.cc b/chrome/browser/chrome_security_exploit_browsertest.cc
index 6289244..a6440a0 100644
--- a/chrome/browser/chrome_security_exploit_browsertest.cc
+++ b/chrome/browser/chrome_security_exploit_browsertest.cc
@@ -435,14 +435,10 @@
       CreateMemoryBackedBlob(payload, payload_type);
   std::string blob_id = blob->GetUUID();
 
-  // Note: a well-behaved renderer would always send the following message here,
-  // but it's actually not necessary for the original attack to succeed, so we
-  // omit it. As a result there are some log warnings from the quota observer.
-  //
-  // IPC::IpcSecurityTestUtil::PwnMessageReceived(
-  //     rfh->GetProcess()->GetChannel(),
-  //     FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin),
-  //                                      storage::kFileSystemTypeTemporary));
+  // Note: a well-behaved renderer would always call Open first before calling
+  // Create and Write, but it's actually not necessary for the original attack
+  // to succeed, so we omit it. As a result there are some log warnings from the
+  // quota observer.
 
   GURL target_url =
       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index 09a53bf..2a14382 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -444,6 +444,7 @@
   base::WeakPtrFactory<AccessibilityManager> weak_ptr_factory_;
 
   friend class DictationTest;
+  friend class SwitchAccessTest;
   DISALLOW_COPY_AND_ASSIGN(AccessibilityManager);
 };
 
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.cc
index 63e8102..fedcbb6 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_event_rewriter_delegate.cc
@@ -33,11 +33,6 @@
 void SpokenFeedbackEventRewriterDelegate::DispatchKeyEventToChromeVox(
     std::unique_ptr<ui::Event> event,
     bool capture) {
-  if (!ShouldDispatchKeyEventToChromeVox(event.get())) {
-    OnUnhandledSpokenFeedbackEvent(std::move(event));
-    return;
-  }
-
   extensions::ExtensionHost* host = chromeos::GetAccessibilityExtensionHost(
       extension_misc::kChromeVoxExtensionId);
   // Listen for any unhandled keyboard events from ChromeVox's background page
diff --git a/chrome/browser/chromeos/accessibility/switch_access_browsertest.cc b/chrome/browser/chromeos/accessibility/switch_access_browsertest.cc
new file mode 100644
index 0000000..ff01338
--- /dev/null
+++ b/chrome/browser/chromeos/accessibility/switch_access_browsertest.cc
@@ -0,0 +1,64 @@
+// Copyright 2018 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 "base/command_line.h"
+#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chromeos/chromeos_switches.h"
+#include "content/public/test/browser_test_utils.h"
+
+namespace chromeos {
+
+class SwitchAccessTest : public InProcessBrowserTest {
+ public:
+  void SendVirtualKeyPress(ui::KeyboardCode key) {
+    ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+        nullptr, key, false, false, false, false)));
+  }
+
+  void EnableSwitchAccess(const std::set<int>& key_codes = {'1', '2', '3',
+                                                            '4'}) {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        chromeos::switches::kEnableExperimentalAccessibilityFeatures);
+
+    AccessibilityManager* manager = AccessibilityManager::Get();
+    manager->SetSwitchAccessEnabled(true);
+    manager->SetSwitchAccessKeys(key_codes);
+
+    EXPECT_TRUE(manager->IsSwitchAccessEnabled());
+  }
+
+ protected:
+  SwitchAccessTest() = default;
+  ~SwitchAccessTest() override = default;
+
+  void SetUpOnMainThread() override {}
+};
+
+IN_PROC_BROWSER_TEST_F(SwitchAccessTest, IgnoresVirtualKeyEvents) {
+  EnableSwitchAccess({'1', '2', '3', '4'});
+
+  // Load a webpage with a text box
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;charset=utf-8,<input type=text id=in>"));
+
+  // Put focus in the text box
+  SendVirtualKeyPress(ui::KeyboardCode::VKEY_TAB);
+
+  // Send a virtual key event for one of the keys taken by switch access
+  SendVirtualKeyPress(ui::KeyboardCode::VKEY_1);
+
+  // Check that the text field received the keystroke
+  std::string output;
+  std::string script =
+      "window.domAutomationController.send("
+      "document.getElementById('in').value)";
+  ASSERT_TRUE(ExecuteScriptAndExtractString(
+      browser()->tab_strip_model()->GetWebContentsAt(0), script, &output));
+  EXPECT_STREQ(output.c_str(), "1");
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/switch_access_event_handler.cc b/chrome/browser/chromeos/accessibility/switch_access_event_handler.cc
index c9b9cde..4f92eb98 100644
--- a/chrome/browser/chromeos/accessibility/switch_access_event_handler.cc
+++ b/chrome/browser/chromeos/accessibility/switch_access_event_handler.cc
@@ -32,6 +32,9 @@
 void SwitchAccessEventHandler::OnKeyEvent(ui::KeyEvent* event) {
   DCHECK(event);
 
+  if (!event->HasNativeEvent())
+    return;
+
   ui::KeyboardCode key_code = event->key_code();
   if (captured_keys_.find(key_code) != captured_keys_.end()) {
     CancelEvent(event);
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
index f37e168..473f9c3d 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_browsertest.cc
@@ -69,7 +69,7 @@
                        PreferenceChange) {
   // TODO(penghuang): Re-enable once the EXO+Viz work is done and Arc can be
   // supported. https://crbug.com/807465
-  if (features::IsVizDisplayCompositorEnabled())
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
     return;
 
   ASSERT_EQ(mojom::AccessibilityFilterType::OFF,
diff --git a/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_arc_home_service_browsertest.cc b/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_arc_home_service_browsertest.cc
index 9cd93bd..d597a82 100644
--- a/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_arc_home_service_browsertest.cc
+++ b/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_arc_home_service_browsertest.cc
@@ -142,8 +142,16 @@
   ASSERT_EQ(base::UTF16ToUTF8(child->children[0]->text), "1");
 }
 
+// Flaky on chromeos: http://crbug.com/870319
+#if defined(OS_CHROMEOS)
+#define MAYBE_VoiceInteractionStructureMultipleSelectionTest \
+  DISABLED_VoiceInteractionStructureMultipleSelectionTest
+#else
+#define MAYBE_VoiceInteractionStructureMultipleSelectionTest \
+  VoiceInteractionStructureMultipleSelectionTest
+#endif
 IN_PROC_BROWSER_TEST_F(ArcVoiceInteractionArcHomeServiceTest,
-                       VoiceInteractionStructureMultipleSelectionTest) {
+                       MAYBE_VoiceInteractionStructureMultipleSelectionTest) {
   auto result = GetVoiceInteractionStructure(
       "<html>"
       "  <body>"
diff --git a/chrome/browser/chromeos/drive/drive_integration_service.cc b/chrome/browser/chromeos/drive/drive_integration_service.cc
index f6d6e41e..cb63896 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service.cc
+++ b/chrome/browser/chromeos/drive/drive_integration_service.cc
@@ -15,6 +15,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/sys_info.h"
 #include "base/task_scheduler/post_task.h"
+#include "base/timer/timer.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
@@ -288,7 +289,9 @@
         on_drivefs_unmounted_(std::move(on_drivefs_unmounted)),
         test_drivefs_mojo_connection_delegate_factory_(
             std::move(test_drivefs_mojo_connection_delegate_factory)),
-        drivefs_host_(profile_->GetPath(), this) {}
+        drivefs_host_(profile_->GetPath(),
+                      this,
+                      std::make_unique<base::OneShotTimer>()) {}
 
   drivefs::DriveFsHost* drivefs_host() { return &drivefs_host_; }
 
@@ -666,6 +669,7 @@
   storage::ExternalMountPoints* const mount_points =
       storage::ExternalMountPoints::GetSystemInstance();
   DCHECK(mount_points);
+  drivefs_consecutive_failures_count_ = 0;
 
   bool success = mount_points->RegisterFileSystem(
       mount_point_name_,
@@ -713,13 +717,32 @@
 
   RemoveDriveMountPoint();
 
-  if (remount_delay) {
-    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&DriveIntegrationService::AddDriveMountPoint,
-                       weak_ptr_factory_.GetWeakPtr()),
-        remount_delay.value());
+  if (!remount_delay) {
+    // If DriveFs didn't specify retry time it's likely unexpected error, e.g.
+    // crash. Use limited exponential backoff for retry.
+    ++drivefs_consecutive_failures_count_;
+    ++drivefs_total_failures_count_;
+    if (drivefs_total_failures_count_ > 10) {
+      logger_->Log(logging::LOG_ERROR,
+                   "DriveFs is too crashy. Leaving it alone.");
+      return;
+    }
+    if (drivefs_consecutive_failures_count_ > 3) {
+      logger_->Log(logging::LOG_ERROR,
+                   "DriveFs keeps failing at start. Giving up.");
+      return;
+    }
+    remount_delay = base::TimeDelta::FromSeconds(
+        5 * (1 << (drivefs_consecutive_failures_count_ - 1)));
+    logger_->Log(logging::LOG_WARNING, "DriveFs died, retry in %d seconds",
+                 static_cast<int>(remount_delay.value().InSeconds()));
   }
+
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&DriveIntegrationService::AddDriveMountPoint,
+                     weak_ptr_factory_.GetWeakPtr()),
+      remount_delay.value());
 }
 
 void DriveIntegrationService::Initialize() {
diff --git a/chrome/browser/chromeos/drive/drive_integration_service.h b/chrome/browser/chromeos/drive/drive_integration_service.h
index 20934a9..a41f2470 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service.h
+++ b/chrome/browser/chromeos/drive/drive_integration_service.h
@@ -233,6 +233,8 @@
       profile_notification_registrar_;
 
   std::unique_ptr<DriveFsHolder> drivefs_holder_;
+  int drivefs_total_failures_count_ = 0;
+  int drivefs_consecutive_failures_count_ = 0;
 
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
diff --git a/chrome/browser/chromeos/login/reset_browsertest.cc b/chrome/browser/chromeos/login/reset_browsertest.cc
index 1c1650f..d7dc948 100644
--- a/chrome/browser/chromeos/login/reset_browsertest.cc
+++ b/chrome/browser/chromeos/login/reset_browsertest.cc
@@ -216,7 +216,8 @@
   RegisterSomeUser();
 }
 
-IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, RollbackUnavailable) {
+// Disabled due to flakiness (crbug.com/870284)
+IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, DISABLED_RollbackUnavailable) {
   update_engine_client_->set_can_rollback_check_result(false);
 
   InvokeResetScreen();
diff --git a/chrome/browser/chromeos/login/ui/fake_login_display_host.cc b/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
index ebee6661..0a413b2 100644
--- a/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
+++ b/chrome/browser/chromeos/login/ui/fake_login_display_host.cc
@@ -142,6 +142,8 @@
 
 void FakeLoginDisplayHost::ShowResetScreen() {}
 
+void FakeLoginDisplayHost::ShowAccountAccessHelpApp() {}
+
 void FakeLoginDisplayHost::ShowDialogForCaptivePortal() {}
 
 void FakeLoginDisplayHost::HideDialogForCaptivePortal() {}
diff --git a/chrome/browser/chromeos/login/ui/fake_login_display_host.h b/chrome/browser/chromeos/login/ui/fake_login_display_host.h
index a6415c54..7c09a82d 100644
--- a/chrome/browser/chromeos/login/ui/fake_login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/fake_login_display_host.h
@@ -66,6 +66,7 @@
   void ResyncUserData() override;
   void ShowFeedback() override;
   void ShowResetScreen() override;
+  void ShowAccountAccessHelpApp() override;
   void ShowDialogForCaptivePortal() override;
   void HideDialogForCaptivePortal() override;
   void UpdateAddUserButtonStatus() override;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host.h b/chrome/browser/chromeos/login/ui/login_display_host.h
index ba21d944..00272c98 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host.h
@@ -197,6 +197,9 @@
   // Shows the powerwash dialog.
   virtual void ShowResetScreen() = 0;
 
+  // Start up the help application for trouble signin in.
+  virtual void ShowAccountAccessHelpApp() = 0;
+
   // In the views case, make the OobeUIDialogDelegate visible so that Captive
   // Portal web modal can be seen. In webui login, this should be a no-op.
   virtual void ShowDialogForCaptivePortal() = 0;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
index 1fcc680..6cdad38 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.cc
@@ -317,6 +317,12 @@
   GetOobeUI()->ForwardAccelerator(kAccelReset);
 }
 
+void LoginDisplayHostMojo::ShowAccountAccessHelpApp() {
+  scoped_refptr<HelpAppLauncher>(new HelpAppLauncher(GetNativeWindow()))
+      ->ShowHelpTopic(HelpAppLauncher::HELP_CANT_ACCESS_ACCOUNT);
+  dialog_->Show();
+}
+
 void LoginDisplayHostMojo::UpdateAddUserButtonStatus() {
   DCHECK(GetOobeUI());
   LoginScreenClient::Get()->login_screen()->SetAddUserButtonEnabled(
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
index 1591ac01..332ae3ea 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_mojo.h
@@ -93,6 +93,7 @@
   void OnCancelPasswordChangedFlow() override;
   void ShowFeedback() override;
   void ShowResetScreen() override;
+  void ShowAccountAccessHelpApp() override;
   void ShowDialogForCaptivePortal() override;
   void HideDialogForCaptivePortal() override;
   void UpdateAddUserButtonStatus() override;
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index adb7f006..ccda02c 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -1177,6 +1177,10 @@
   NOTREACHED();
 }
 
+void LoginDisplayHostWebUI::ShowAccountAccessHelpApp() {
+  NOTREACHED();
+}
+
 // This is handled differently in webui.
 void LoginDisplayHostWebUI::ShowDialogForCaptivePortal() {}
 
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.h b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
index 1fb0dbfd4..07e385d 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
@@ -81,6 +81,7 @@
   const user_manager::UserList GetUsers() override;
   void ShowFeedback() override;
   void ShowResetScreen() override;
+  void ShowAccountAccessHelpApp() override;
   void ShowDialogForCaptivePortal() override;
   void HideDialogForCaptivePortal() override;
   void UpdateAddUserButtonStatus() override;
diff --git a/chrome/browser/chromeos/login/ui/mock_login_display_host.h b/chrome/browser/chromeos/login/ui/mock_login_display_host.h
index dbaa6e4..d7a2a39e 100644
--- a/chrome/browser/chromeos/login/ui/mock_login_display_host.h
+++ b/chrome/browser/chromeos/login/ui/mock_login_display_host.h
@@ -73,6 +73,7 @@
   MOCK_METHOD0(ResyncUserData, void());
   MOCK_METHOD0(ShowFeedback, void());
   MOCK_METHOD0(ShowResetScreen, void());
+  MOCK_METHOD0(ShowAccountAccessHelpApp, void());
   MOCK_METHOD0(OnCancelPasswordChangedFlow, void());
   MOCK_METHOD0(ShowDialogForCaptivePortal, void());
   MOCK_METHOD0(HideDialogForCaptivePortal, void());
diff --git a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
index 0c23dfd..ddb6cdf 100644
--- a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
@@ -24,6 +24,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_path_override.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/chromeos/app_mode/arc/arc_kiosk_app_manager.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
@@ -34,6 +35,7 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
 #include "chrome/browser/chromeos/settings/stub_install_attributes.h"
+#include "chrome/common/chrome_content_client.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
@@ -64,6 +66,7 @@
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_type.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/common/content_client.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/test_utils.h"
 #include "storage/browser/fileapi/external_mount_points.h"
@@ -321,6 +324,9 @@
     ChromeUnitTestSuite::InitializeProviders();
     ChromeUnitTestSuite::InitializeResourceBundle();
 
+    content::SetContentClient(&content_client_);
+    content::SetBrowserClientForTesting(&browser_content_client_);
+
     // Run this test with a well-known timezone so that Time::LocalMidnight()
     // returns the same values on all machines.
     std::unique_ptr<base::Environment> env(base::Environment::Create());
@@ -557,6 +563,8 @@
   TestingBrowserProcessInitializer initializer_;
   content::TestBrowserThreadBundle test_browser_thread_bundle_;
 
+  ChromeContentClient content_client_;
+  ChromeContentBrowserClient browser_content_client_;
   chromeos::ScopedStubInstallAttributes install_attributes_;
   chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
   DiskMountManager::MountPointMap mount_point_map_;
diff --git a/chrome/browser/conflicts/incompatible_applications_browsertest.cc b/chrome/browser/conflicts/incompatible_applications_browsertest.cc
index 9915277d..12666d3d 100644
--- a/chrome/browser/conflicts/incompatible_applications_browsertest.cc
+++ b/chrome/browser/conflicts/incompatible_applications_browsertest.cc
@@ -86,7 +86,7 @@
     // TODO(crbug.com/850517): Don't do test-specific setup if the test isn't
     // going to do anything. It seems to conflict with the VizDisplayCompositor
     // feature.
-    if (!features::IsVizDisplayCompositorEnabled()) {
+    if (!base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
       ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
 
       ASSERT_NO_FATAL_FAILURE(
@@ -199,7 +199,7 @@
     return;
 
   // TODO(crbug.com/850517) This fails in viz_browser_tests in official builds.
-  if (features::IsVizDisplayCompositorEnabled())
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
     return;
 
   ModuleDatabase* module_database = ModuleDatabase::GetInstance();
diff --git a/chrome/browser/devtools/devtools_eye_dropper.cc b/chrome/browser/devtools/devtools_eye_dropper.cc
index 45f8146..b82f19a 100644
--- a/chrome/browser/devtools/devtools_eye_dropper.cc
+++ b/chrome/browser/devtools/devtools_eye_dropper.cc
@@ -34,7 +34,7 @@
       last_cursor_y_(-1),
       host_(nullptr),
       use_video_capture_api_(
-          features::IsVizDisplayCompositorEnabled() ||
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
           base::FeatureList::IsEnabled(
               features::kUseVideoCaptureApiForDevToolsSnapshots)),
       weak_factory_(this) {
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
index afb0efe..83532ae9 100644
--- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
+++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -423,6 +423,9 @@
         kWifi1ServicePath, shill::kConnectableProperty, base::Value(true));
     service_test_->SetServiceProperty(kWifi1ServicePath, shill::kDeviceProperty,
                                       base::Value(kWifiDevicePath));
+    service_test_->SetServiceProperty(
+        kWifi1ServicePath, shill::kTetheringProperty,
+        base::Value(shill::kTetheringNotDetectedState));
     base::DictionaryValue static_ipconfig;
     static_ipconfig.SetKey(shill::kAddressProperty, base::Value("1.2.3.4"));
     service_test_->SetServiceProperty(
@@ -444,6 +447,9 @@
         kWifi2ServicePath, shill::kSignalStrengthProperty, base::Value(80));
     service_test_->SetServiceProperty(
         kWifi2ServicePath, shill::kConnectableProperty, base::Value(true));
+    service_test_->SetServiceProperty(
+        kWifi2ServicePath, shill::kTetheringProperty,
+        base::Value(shill::kTetheringNotDetectedState));
 
     AddService("stub_wimax", "wimax", shill::kTypeWimax, shill::kStateOnline);
     service_test_->SetServiceProperty(
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
index 583b91e..cfdeeeb 100644
--- a/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
+++ b/chrome/browser/extensions/api/web_navigation/web_navigation_apitest.cc
@@ -321,7 +321,13 @@
   ASSERT_TRUE(RunExtensionTest("webnavigation/filtered")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, UserAction) {
+// Flaky on Windows. See http://crbug.com/662160.
+#if defined(OS_WIN)
+#define MAYBE_UserAction DISABLED_UserAction
+#else
+#define MAYBE_UserAction UserAction
+#endif
+IN_PROC_BROWSER_TEST_F(WebNavigationApiTest, MAYBE_UserAction) {
   content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
   ASSERT_TRUE(StartEmbeddedTestServer());
 
diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h
index 894dde70..681d741 100644
--- a/chrome/browser/extensions/crx_installer.h
+++ b/chrome/browser/extensions/crx_installer.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/compiler_specific.h"
@@ -248,6 +249,7 @@
  private:
   friend class ::ExtensionServiceTest;
   friend class ExtensionUpdaterTest;
+  friend class BookmarkAppInstallerTest;
 
   CrxInstaller(base::WeakPtr<ExtensionService> service_weak,
                std::unique_ptr<ExtensionInstallPrompt> client,
diff --git a/chrome/browser/extensions/extension_tabs_apitest.cc b/chrome/browser/extensions/extension_tabs_apitest.cc
index c1bf367..cc0593b5 100644
--- a/chrome/browser/extensions/extension_tabs_apitest.cc
+++ b/chrome/browser/extensions/extension_tabs_apitest.cc
@@ -50,7 +50,13 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(ExtensionApiNewTabTest, Tabs) {
+// Flaky on chromeos: http://crbug.com/870322
+#if defined(OS_CHROMEOS)
+#define MAYBE_Tabs DISABLED_Tabs
+#else
+#define MAYBE_Tabs Tabs
+#endif
+IN_PROC_BROWSER_TEST_F(ExtensionApiNewTabTest, MAYBE_Tabs) {
   // The test creates a tab and checks that the URL of the new tab
   // is that of the new tab page.  Make sure the pref that controls
   // this is set.
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8524982..bd834b47 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1762,6 +1762,12 @@
     "Match Autofill suggestions based on substrings (token prefixes) rather "
     "than just prefixes.";
 
+const char kSyncStandaloneTransportName[] = "Allow Sync standalone transport";
+const char kSyncStandaloneTransportDescription[] =
+    "If enabled, allows Chrome Sync to start in standalone transport mode. In "
+    "this mode, the Sync machinery can start without user opt-in, but only a "
+    "subset of data types are supported.";
+
 const char kSyncSandboxName[] = "Use Chrome Sync sandbox";
 const char kSyncSandboxDescription[] =
     "Connects to the testing server for Chrome Sync.";
@@ -1916,6 +1922,11 @@
     "features. This includes new confirmation screens and improved settings "
     "pages.";
 
+const char kForceUnifiedConsentBumpName[] = "Force Unified Consent Bump";
+const char kForceUnifiedConsentBumpDescription[] =
+    "Force the unified consent bump UI to be shown on every start-up. This "
+    "flag is for debug purpose, to test the UI.";
+
 const char kUiPartialSwapName[] = "Partial swap";
 const char kUiPartialSwapDescription[] = "Sets partial swap behavior.";
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index aaa1440..58a193c 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1079,6 +1079,9 @@
 extern const char kSyncSandboxName[];
 extern const char kSyncSandboxDescription[];
 
+extern const char kSyncStandaloneTransportName[];
+extern const char kSyncStandaloneTransportDescription[];
+
 extern const char kSysInternalsName[];
 extern const char kSysInternalsDescription[];
 
@@ -1160,6 +1163,9 @@
 extern const char kUnifiedConsentName[];
 extern const char kUnifiedConsentDescription[];
 
+extern const char kForceUnifiedConsentBumpName[];
+extern const char kForceUnifiedConsentBumpDescription[];
+
 extern const char kUiPartialSwapName[];
 extern const char kUiPartialSwapDescription[];
 
diff --git a/chrome/browser/installable/installable_logging.cc b/chrome/browser/installable/installable_logging.cc
index 0df3d1c..4a016f7 100644
--- a/chrome/browser/installable/installable_logging.cc
+++ b/chrome/browser/installable/installable_logging.cc
@@ -66,8 +66,6 @@
 static const char kInIncognitoMessage[] =
     "the page is loaded in an incognito window";
 static const char kNotOfflineCapable[] = "the page does not work offline";
-static const char kNoGesture[] =
-    "beforeinstallpromptevent.prompt() was not called with a user gesture";
 }  // namespace
 
 void LogErrorToConsole(content::WebContents* web_contents,
@@ -90,6 +88,7 @@
     case FAILED_TO_CREATE_BANNER:
     case WAITING_FOR_MANIFEST:
     case WAITING_FOR_INSTALLABLE_CHECK:
+    case NO_GESTURE:
     case WAITING_FOR_NATIVE_DATA:
     case SHOWING_APP_INSTALLATION_DIALOG:
     case MAX_ERROR_CODE:
@@ -164,9 +163,6 @@
     case NOT_OFFLINE_CAPABLE:
       message = kNotOfflineCapable;
       break;
-    case NO_GESTURE:
-      message = kNoGesture;
-      break;
   }
 
   web_contents->GetMainFrame()->AddMessageToConsole(
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
index 8f05d4ca..18cc871f 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager.cc
@@ -66,8 +66,6 @@
 
 }  // namespace
 
-const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
-
 WebRtcEventLogManager* WebRtcEventLogManager::g_webrtc_event_log_manager =
     nullptr;
 
@@ -386,6 +384,25 @@
   return enabled;
 }
 
+std::unique_ptr<LogFileWriter::Factory>
+WebRtcEventLogManager::CreateRemoteLogFileWriterFactory() {
+#if defined(OS_ANDROID)
+  NOTREACHED();
+  return nullptr;  // Appease compiler.
+#else
+  if (remote_log_file_writer_factory_for_testing_) {
+    return std::move(remote_log_file_writer_factory_for_testing_);
+  } else if (base::FeatureList::IsEnabled(
+                 features::kWebRtcRemoteEventLogGzipped)) {
+    return std::make_unique<GzippedLogFileWriterFactory>(
+        std::make_unique<GzipLogCompressorFactory>(
+            std::make_unique<DefaultGzippedSizeEstimator::Factory>()));
+  } else {
+    return std::make_unique<BaseLogFileWriterFactory>();
+  }
+#endif
+}
+
 void WebRtcEventLogManager::RenderProcessExited(
     RenderProcessHost* host,
     const content::ChildProcessTerminationInfo& info) {
@@ -508,6 +525,9 @@
       g_browser_process->system_request_context();
   DCHECK(url_request_context_getter);
 
+  auto log_file_writer_factory = CreateRemoteLogFileWriterFactory();
+  DCHECK(log_file_writer_factory);
+
   // * |url_request_context_getter| is owned by IOThread. The internal task
   //   runner that uses it (|task_runner_|) stops before IOThread dies, so we
   //   can trust that |url_request_context_getter| will not be used after
@@ -520,18 +540,23 @@
       base::BindOnce(
           &WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal,
           base::Unretained(this), base::Unretained(network_connection_tracker),
-          base::Unretained(url_request_context_getter)));
+          base::Unretained(url_request_context_getter),
+          std::move(log_file_writer_factory)));
 }
 
 void WebRtcEventLogManager::OnFirstBrowserContextLoadedInternal(
     content::NetworkConnectionTracker* network_connection_tracker,
-    net::URLRequestContextGetter* url_request_context_getter) {
+    net::URLRequestContextGetter* url_request_context_getter,
+    std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   DCHECK(network_connection_tracker);
   DCHECK(url_request_context_getter);
+  DCHECK(log_file_writer_factory);
   DCHECK(remote_logs_manager_);
   remote_logs_manager_->SetNetworkConnectionTracker(network_connection_tracker);
   remote_logs_manager_->SetUrlRequestContextGetter(url_request_context_getter);
+  remote_logs_manager_->SetLogFileWriterFactory(
+      std::move(log_file_writer_factory));
 }
 
 void WebRtcEventLogManager::EnableForBrowserContextInternal(
@@ -763,6 +788,14 @@
                                 std::move(uploader_factory), std::move(reply)));
 }
 
+void WebRtcEventLogManager::SetRemoteLogFileWriterFactoryForTesting(
+    std::unique_ptr<LogFileWriter::Factory> factory) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!first_browser_context_initializations_done_) << "Too late.";
+  DCHECK(!remote_log_file_writer_factory_for_testing_) << "Already called.";
+  remote_log_file_writer_factory_for_testing_ = std::move(factory);
+}
+
 void WebRtcEventLogManager::UploadConditionsHoldForTesting(
     base::OnceCallback<void(bool)> callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager.h b/chrome/browser/media/webrtc/webrtc_event_log_manager.h
index 2c1392c..5604a9b 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <memory>
-#include <type_traits>
 #include <utility>
 
 #include "base/callback.h"
@@ -172,6 +171,10 @@
   // Checks whether remote-bound logging is enabled.
   bool IsRemoteLoggingEnabled() const;
 
+  // Determines the exact subclass of LogFileWriter::Factory to be used for
+  // producing remote-bound logs.
+  std::unique_ptr<LogFileWriter::Factory> CreateRemoteLogFileWriterFactory();
+
   // RenderProcessHostObserver implementation.
   void RenderProcessExited(
       content::RenderProcessHost* host,
@@ -198,12 +201,15 @@
   // network_connection_tracker() and system_request_context() are not available
   // during instantiation; we get them when the first profile is loaded, which
   // is also the earliest time when they could be needed.
+  // The LogFileWriter::Factory is similarly deferred, but for a different
+  // reason - it makes it easier to allow unit tests to inject their own.
   // OnFirstBrowserContextLoaded() is on the UI thread.
   // OnFirstBrowserContextLoadedInternal() is the task sent to |task_runner_|.
   void OnFirstBrowserContextLoaded();
   void OnFirstBrowserContextLoadedInternal(
       content::NetworkConnectionTracker* network_connection_tracker,
-      net::URLRequestContextGetter* url_request_context_getter);
+      net::URLRequestContextGetter* url_request_context_getter,
+      std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
 
   void EnableForBrowserContextInternal(
       BrowserContextId browser_context_id,
@@ -268,6 +274,13 @@
       std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory,
       base::OnceClosure reply);
 
+  // Sets a LogFileWriter factory for remote-bound files.
+  // Only usable in tests.
+  // Must be called before the first browser context is enabled.
+  // Effective immediately.
+  void SetRemoteLogFileWriterFactoryForTesting(
+      std::unique_ptr<LogFileWriter::Factory> factory);
+
   // It is not always feasible to check in unit tests that uploads do not occur
   // at a certain time, because that's (sometimes) racy with the event that
   // suppresses the upload. We therefore allow unit tests to glimpse into the
@@ -299,6 +312,9 @@
 
   // Manages remote-bound logs - logs which will be sent to a remote server.
   // This is only possible when a command line flag is present.
+  // Creation and destruction (or reset) only on the UI thread, and therefore
+  // checking for emptiness allowed on the UI thread.
+  // Everything else (dereferencing and calling methods) only on |task_runner_|.
   // TODO(eladalon): Remove the command-line flag and the unique_ptr.
   // https://crbug.com/775415
   std::unique_ptr<WebRtcRemoteEventLogManager> remote_logs_manager_;
@@ -324,6 +340,11 @@
   // This member must only be accessed on the UI thread.
   bool first_browser_context_initializations_done_;
 
+  // May only be set for tests, in which case, it will be passed to
+  // |remote_logs_manager_| when (and if) produced.
+  std::unique_ptr<LogFileWriter::Factory>
+      remote_log_file_writer_factory_for_testing_;
+
   // The main logic will run sequentially on this runner, on which blocking
   // tasks are allowed.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
index 652d94d..e97969b 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.cc
@@ -8,19 +8,23 @@
 
 #include "base/files/file_util.h"
 #include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
+#include "third_party/zlib/zlib.h"
 
 using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
-using LogFilesMap = std::map<WebRtcEventLogPeerConnectionKey, LogFile>;
+
+const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
 
 const char kStartRemoteLoggingFailureFeatureDisabled[] = "Feature disabled.";
 const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[] =
     "Unlimited size disallowed.";
+const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
 const char kStartRemoteLoggingFailureMaxSizeTooLarge[] =
     "Excessively large max log size.";
-const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
 const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[] =
     "Unknown or inactive peer connection.";
 const char kStartRemoteLoggingFailureAlreadyLogging[] = "Already logging.";
@@ -29,71 +33,757 @@
 const BrowserContextId kNullBrowserContextId =
     reinterpret_cast<BrowserContextId>(nullptr);
 
-LogFile::LogFile(const base::FilePath& path,
-                 base::File file,
-                 size_t max_file_size_bytes)
-    : path_(path),
-      file_(std::move(file)),
-      max_file_size_bytes_(max_file_size_bytes),
-      file_size_bytes_(0) {}
+namespace {
 
-LogFile::LogFile(LogFile&& other)
-    : path_(std::move(other.path_)),
-      file_(std::move(other.file_)),
-      max_file_size_bytes_(other.max_file_size_bytes_),
-      file_size_bytes_(other.file_size_bytes_) {}
+constexpr int kDefaultMemLevel = 8;
 
-LogFile::~LogFile() = default;
+constexpr size_t kGzipHeaderBytes = 15;
+constexpr size_t kGzipFooterBytes = 10;
 
-bool LogFile::MaxSizeReached() const {
-  if (max_file_size_bytes_ == kWebRtcEventLogManagerUnlimitedFileSize) {
-    return false;
-  }
-  DCHECK_LE(file_size_bytes_, max_file_size_bytes_);
-  return file_size_bytes_ >= max_file_size_bytes_;
-}
+// Tracks budget over a resource (such as bytes allowed in a file, etc.).
+// Allows an unlimited budget.
+class Budget {
+ public:
+  // If !max.has_value(), the budget is unlimited.
+  explicit Budget(base::Optional<size_t> max) : max_(max), current_(0) {}
 
-bool LogFile::Write(const std::string& message) {
-  DCHECK_LE(message.length(),
-            static_cast<size_t>(std::numeric_limits<int>::max()));
+  // Check whether the budget allows consuming an additional |consumed| of
+  // the resource.
+  bool ConsumeAllowed(size_t consumed) const {
+    if (!max_.has_value()) {
+      return true;
+    }
 
-  // Observe the file size limit, if any. Note that base::File's interface does
-  // not allow writing more than numeric_limits<int>::max() bytes at a time.
-  int message_len = static_cast<int>(message.length());  // DCHECKed above.
-  if (max_file_size_bytes_ != kWebRtcEventLogManagerUnlimitedFileSize) {
-    DCHECK_LT(file_size_bytes_, max_file_size_bytes_);
-    const bool size_will_wrap_around =
-        file_size_bytes_ + message.length() < file_size_bytes_;
-    const bool size_limit_will_be_exceeded =
-        file_size_bytes_ + message.length() > max_file_size_bytes_;
-    if (size_will_wrap_around || size_limit_will_be_exceeded) {
-      return false;
+    DCHECK_LE(current_, max_.value());
+
+    const size_t after_consumption = current_ + consumed;
+
+    if (after_consumption < current_) {
+      return false;  // Wrap-around.
+    } else if (after_consumption > max_.value()) {
+      return false;  // Budget exceeded.
+    } else {
+      return true;
     }
   }
 
-  int written = file_.WriteAtCurrentPos(message.c_str(), message_len);
-  if (written != message_len) {
-    LOG(WARNING) << "WebRTC event log message couldn't be written to the "
-                    "locally stored file in its entirety.";
+  // Checks whether the budget has been completely used up.
+  bool Exhausted() const { return !ConsumeAllowed(0); }
+
+  // Consume an additional |consumed| of the resource.
+  void Consume(size_t consumed) {
+    DCHECK(ConsumeAllowed(consumed));
+    current_ += consumed;
+  }
+
+ private:
+  const base::Optional<size_t> max_;
+  size_t current_;
+};
+
+// Writes a log to a file while observing a maximum size.
+class BaseLogFileWriter : public LogFileWriter {
+ public:
+  // If !max_file_size_bytes.has_value(), an unlimited writer is created.
+  // If it has a value, it must be at least MinFileSizeBytes().
+  BaseLogFileWriter(const base::FilePath& path,
+                    base::Optional<size_t> max_file_size_bytes);
+
+  ~BaseLogFileWriter() override;
+
+  bool Init() override;
+
+  const base::FilePath& path() const override;
+
+  bool MaxSizeReached() const override;
+
+  bool Write(const std::string& input) override;
+
+  bool Close() override;
+
+  void Delete() override;
+
+ protected:
+  // * Logs are created PRE_INIT.
+  // * If Init() is successful (potentially writing some header to the log),
+  //   the log becomes ACTIVE.
+  // * Any error puts the log into an unrecoverable ERRORED state. When an
+  //   errored file is Close()-ed, it is deleted.
+  // * If Write() is ever denied because of budget constraintss, the file
+  //   becomes FULL. Only metadata is then allowed (subject to its own budget).
+  // * Closing an ACTIVE or FULL file puts it into CLOSED, at which point the
+  //   file may be used. (Note that closing itself might also yield an error,
+  //   which would put the file into ERRORED, then deleted.)
+  // * Closed files may be DELETED.
+  enum class State { PRE_INIT, ACTIVE, FULL, CLOSED, ERRORED, DELETED };
+
+  // Setter/getter for |state_|.
+  void SetState(State state);
+  State state() const { return state_; }
+
+  // Checks whether the budget allows writing an additional |bytes|.
+  bool WithinBudget(size_t bytes) const;
+
+  // Writes |input| to the file.
+  // May only be called on ACTIVE or FULL files (for FULL files, only metadata
+  // such as compression footers, etc., may be written; the budget must still
+  // be respected).
+  // It's up to the caller to respect the budget; this will DCHECK on it.
+  // Returns |true| if writing was successful. |false| indicates an
+  // unrecoverable error; the file must be discarded.
+  bool WriteInternal(const std::string& input, bool metadata);
+
+  // Finalizes the file (writes metadata such as compression footer, if any).
+  // Reports whether the file was successfully finalized. Those which weren't
+  // should be discarded.
+  virtual bool Finalize();
+
+ private:
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  const base::FilePath path_;
+  base::File file_;  // Populated by Init().
+  State state_;
+  Budget budget_;
+};
+
+BaseLogFileWriter::BaseLogFileWriter(const base::FilePath& path,
+                                     base::Optional<size_t> max_file_size_bytes)
+    : task_runner_(base::SequencedTaskRunnerHandle::Get()),
+      path_(path),
+      state_(State::PRE_INIT),
+      budget_(max_file_size_bytes) {}
+
+BaseLogFileWriter::~BaseLogFileWriter() {
+  if (!task_runner_->RunsTasksInCurrentSequence()) {
+    // Chrome shut-down. The original task_runner_ is no longer running, so
+    // no risk of concurrent access or races.
+    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+    task_runner_ = base::SequencedTaskRunnerHandle::Get();
+  }
+
+  if (state() != State::CLOSED && state() != State::DELETED) {
+    Close();
+  }
+}
+
+bool BaseLogFileWriter::Init() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_EQ(state(), State::PRE_INIT);
+
+  // TODO(crbug.com/775415): Use a temporary filename which will indicate
+  // incompletion, and rename to something that is eligible for upload only
+  // on an orderly and successful Close().
+
+  // Attempt to create the file.
+  constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
+                             base::File::FLAG_EXCLUSIVE_WRITE;
+  file_.Initialize(path_, file_flags);
+  if (!file_.IsValid() || !file_.created()) {
+    LOG(WARNING) << "Couldn't create remote-bound WebRTC event log file.";
+    if (!base::DeleteFile(path_, /*recursive=*/false)) {
+      LOG(ERROR) << "Failed to delete " << path_ << ".";
+    }
+    SetState(State::ERRORED);
     return false;
   }
 
-  file_size_bytes_ += static_cast<size_t>(written);
-  DCHECK(max_file_size_bytes_ == kWebRtcEventLogManagerUnlimitedFileSize ||
-         file_size_bytes_ <= max_file_size_bytes_);
+  SetState(State::ACTIVE);
 
   return true;
 }
 
-void LogFile::Close() {
-  file_.Flush();
-  file_.Close();
+const base::FilePath& BaseLogFileWriter::path() const {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  return path_;
 }
 
-void LogFile::Delete() {
+bool BaseLogFileWriter::MaxSizeReached() const {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_EQ(state(), State::ACTIVE);
+  return !WithinBudget(1);
+}
+
+bool BaseLogFileWriter::Write(const std::string& input) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_EQ(state(), State::ACTIVE);
+  DCHECK(!MaxSizeReached());
+
+  if (input.empty()) {
+    return true;
+  }
+
+  if (!WithinBudget(input.length())) {
+    SetState(State::FULL);
+    return false;
+  }
+
+  const bool did_write = WriteInternal(input, /*metadata=*/false);
+  if (!did_write) {
+    SetState(State::ERRORED);
+  }
+  return did_write;
+}
+
+bool BaseLogFileWriter::Close() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_NE(state(), State::CLOSED);
+  DCHECK_NE(state(), State::DELETED);
+
+  const bool result = ((state() != State::ERRORED) && Finalize());
+
+  if (result) {
+    file_.Flush();
+    file_.Close();
+    SetState(State::CLOSED);
+  } else {
+    Delete();  // Changes the state to DELETED.
+  }
+
+  return result;
+}
+
+void BaseLogFileWriter::Delete() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_NE(state(), State::DELETED);
+
+  // The file should be closed before deletion. However, we do not want to go
+  // through Finalize() and any potential production of a compression footer,
+  // etc., since we'll be discarding the file anyway.
+  if (state() != State::CLOSED) {
+    file_.Close();
+  }
+
   if (!base::DeleteFile(path_, /*recursive=*/false)) {
     LOG(ERROR) << "Failed to delete " << path_ << ".";
   }
+
+  SetState(State::DELETED);
+}
+
+void BaseLogFileWriter::SetState(State state) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  state_ = state;
+}
+
+bool BaseLogFileWriter::WithinBudget(size_t bytes) const {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  return budget_.ConsumeAllowed(bytes);
+}
+
+bool BaseLogFileWriter::WriteInternal(const std::string& input, bool metadata) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(state() == State::ACTIVE || (state() == State::FULL && metadata));
+  DCHECK(WithinBudget(input.length()));
+
+  // base::File's interface does not allow writing more than
+  // numeric_limits<int>::max() bytes at a time.
+  DCHECK_LE(input.length(),
+            static_cast<size_t>(std::numeric_limits<int>::max()));
+  const int input_len = static_cast<int>(input.length());
+
+  int written = file_.WriteAtCurrentPos(input.c_str(), input_len);
+  if (written != input_len) {
+    LOG(WARNING) << "WebRTC event log couldn't be written to the "
+                    "locally stored file in its entirety.";
+    return false;
+  }
+
+  budget_.Consume(static_cast<size_t>(written));
+
+  return true;
+}
+
+bool BaseLogFileWriter::Finalize() {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK_NE(state(), State::CLOSED);
+  DCHECK_NE(state(), State::DELETED);
+  DCHECK_NE(state(), State::ERRORED);
+  return true;
+}
+
+// Writes a GZIP-compressed log to a file while observing a maximum size.
+class GzippedLogFileWriter : public BaseLogFileWriter {
+ public:
+  GzippedLogFileWriter(const base::FilePath& path,
+                       base::Optional<size_t> max_file_size_bytes,
+                       std::unique_ptr<LogCompressor> compressor);
+
+  ~GzippedLogFileWriter() override = default;
+
+  bool Init() override;
+
+  bool MaxSizeReached() const override;
+
+  bool Write(const std::string& input) override;
+
+ protected:
+  bool Finalize() override;
+
+ private:
+  std::unique_ptr<LogCompressor> compressor_;
+};
+
+GzippedLogFileWriter::GzippedLogFileWriter(
+    const base::FilePath& path,
+    base::Optional<size_t> max_file_size_bytes,
+    std::unique_ptr<LogCompressor> compressor)
+    : BaseLogFileWriter(path, max_file_size_bytes),
+      compressor_(std::move(compressor)) {
+  // Factory validates size before instantiation.
+  DCHECK(!max_file_size_bytes.has_value() ||
+         max_file_size_bytes.value() >= kGzipOverheadBytes);
+}
+
+bool GzippedLogFileWriter::Init() {
+  if (!BaseLogFileWriter::Init()) {
+    // Super-class should SetState on its own.
+    return false;
+  }
+
+  std::string header;
+  compressor_->CreateHeader(&header);
+
+  const bool result = WriteInternal(header, /*metadata=*/true);
+  if (!result) {
+    SetState(State::ERRORED);
+  }
+
+  return result;
+}
+
+bool GzippedLogFileWriter::MaxSizeReached() const {
+  DCHECK_EQ(state(), State::ACTIVE);
+
+  // Note that the overhead used (footer only) assumes state() is State::ACTIVE,
+  // as DCHECKed above.
+  return !WithinBudget(1 + kGzipFooterBytes);
+}
+
+bool GzippedLogFileWriter::Write(const std::string& input) {
+  DCHECK_EQ(state(), State::ACTIVE);
+  DCHECK(!MaxSizeReached());
+
+  if (input.empty()) {
+    return true;
+  }
+
+  std::string compressed_input;
+  const auto result = compressor_->Compress(input, &compressed_input);
+
+  switch (result) {
+    case LogCompressor::Result::OK: {
+      // |compressor_| guarantees |compressed_input| is within-budget.
+      bool did_write = WriteInternal(compressed_input, /*metadata=*/false);
+      if (!did_write) {
+        SetState(State::ERRORED);
+      }
+      return did_write;
+    }
+    case LogCompressor::Result::DISALLOWED: {
+      SetState(State::FULL);
+      return false;
+    }
+    case LogCompressor::Result::ERROR_ENCOUNTERED: {
+      SetState(State::ERRORED);
+      return false;
+    }
+  }
+
+  NOTREACHED();
+  return false;  // Appease compiler.
+}
+
+bool GzippedLogFileWriter::Finalize() {
+  DCHECK_NE(state(), State::CLOSED);
+  DCHECK_NE(state(), State::DELETED);
+  DCHECK_NE(state(), State::ERRORED);
+
+  std::string footer;
+  if (!compressor_->CreateFooter(&footer)) {
+    LOG(WARNING) << "Compression footer could not be produced.";
+    SetState(State::ERRORED);
+    return false;
+  }
+
+  // |compressor_| guarantees |footer| is within-budget.
+  if (!WriteInternal(footer, /*metadata=*/true)) {
+    LOG(WARNING) << "Footer could not be written.";
+    SetState(State::ERRORED);
+    return false;
+  }
+
+  return true;
+}
+
+// Concrete implementation of LogCompressor using GZIP.
+class GzipLogCompressor : public LogCompressor {
+ public:
+  GzipLogCompressor(
+      base::Optional<size_t> max_size_bytes,
+      std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator);
+
+  ~GzipLogCompressor() override;
+
+  void CreateHeader(std::string* output) override;
+
+  Result Compress(const std::string& input, std::string* output) override;
+
+  bool CreateFooter(std::string* output) override;
+
+ private:
+  // * A compressed log starts out empty (PRE_HEADER).
+  // * Once the header is produced, the stream is ACTIVE.
+  // * If it is ever detected that compressing the next input would exceed the
+  //   budget, that input is NOT compressed, and the state becomes FULL, from
+  //   which only writing the footer or discarding the file are allowed.
+  // * Writing the footer is allowed on an ACTIVE or FULL stream. Then, the
+  //   stream is effectively closed.
+  // * Any error puts the stream into ERRORED. An errored stream can only
+  //   be discarded.
+  enum class State { PRE_HEADER, ACTIVE, FULL, POST_FOOTER, ERRORED };
+
+  // Returns the budget left after reserving the GZIP overhead.
+  // Optionals without a value, both in the parameters as well as in the
+  // return value of the function, signal an unlimited amount.
+  static base::Optional<size_t> SizeAfterOverheadReservation(
+      base::Optional<size_t> max_size_bytes);
+
+  // Compresses |input| into |output|, while observing the budget (unless
+  // !budgeted). If |last|, also closes the stream.
+  Result CompressInternal(const std::string& input,
+                          std::string* output,
+                          bool budgeted,
+                          bool last);
+
+  // Compresses the input data already in |stream_| into |output|.
+  bool Deflate(int flush, std::string* output);
+
+  State state_;
+  Budget budget_;
+  std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator_;
+  z_stream stream_;
+};
+
+GzipLogCompressor::GzipLogCompressor(
+    base::Optional<size_t> max_size_bytes,
+    std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)
+    : state_(State::PRE_HEADER),
+      budget_(SizeAfterOverheadReservation(max_size_bytes)),
+      compressed_size_estimator_(std::move(compressed_size_estimator)) {
+  memset(&stream_, 0, sizeof(z_stream));
+  // Using (MAX_WBITS + 16) triggers the creation of a GZIP header.
+  const int result =
+      deflateInit2(&stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16,
+                   kDefaultMemLevel, Z_DEFAULT_STRATEGY);
+  DCHECK_EQ(result, Z_OK);
+}
+
+GzipLogCompressor::~GzipLogCompressor() {
+  const int result = deflateEnd(&stream_);
+  // Z_DATA_ERROR reports that the stream was not properly terminated,
+  // but nevertheless correctly released. That happens when we don't
+  // write the footer.
+  DCHECK(result == Z_OK ||
+         (result == Z_DATA_ERROR && state_ != State::POST_FOOTER));
+}
+
+void GzipLogCompressor::CreateHeader(std::string* output) {
+  DCHECK(output);
+  DCHECK(output->empty());
+  DCHECK_EQ(state_, State::PRE_HEADER);
+
+  const Result result =
+      CompressInternal("", output, /*budgeted=*/false, /*last=*/false);
+  DCHECK_EQ(result, Result::OK);
+  DCHECK_EQ(output->size(), kGzipHeaderBytes);
+
+  state_ = State::ACTIVE;
+}
+
+LogCompressor::Result GzipLogCompressor::Compress(const std::string& input,
+                                                  std::string* output) {
+  DCHECK_EQ(state_, State::ACTIVE);
+
+  if (input.empty()) {
+    return Result::OK;
+  }
+
+  const auto result =
+      CompressInternal(input, output, /*budgeted=*/true, /*last=*/false);
+
+  switch (result) {
+    case Result::OK:
+      return result;
+    case Result::DISALLOWED:
+      state_ = State::FULL;
+      return result;
+    case Result::ERROR_ENCOUNTERED:
+      state_ = State::ERRORED;
+      return result;
+  }
+
+  NOTREACHED();
+  return Result::ERROR_ENCOUNTERED;  // Appease compiler.
+}
+
+bool GzipLogCompressor::CreateFooter(std::string* output) {
+  DCHECK(output);
+  DCHECK(output->empty());
+  DCHECK(state_ == State::ACTIVE || state_ == State::FULL);
+
+  const Result result =
+      CompressInternal("", output, /*budgeted=*/false, /*last=*/true);
+  if (result != Result::OK) {  // !budgeted -> Result::DISALLOWED impossible.
+    DCHECK_EQ(result, Result::ERROR_ENCOUNTERED);
+    // An error message was logged by CompressInternal().
+    state_ = State::ERRORED;
+    return false;
+  }
+
+  if (output->length() != kGzipFooterBytes) {
+    LOG(ERROR) << "Incorrect footer size (" << output->length() << ").";
+    state_ = State::ERRORED;
+    return false;
+  }
+
+  state_ = State::POST_FOOTER;
+
+  return true;
+}
+
+base::Optional<size_t> GzipLogCompressor::SizeAfterOverheadReservation(
+    base::Optional<size_t> max_size_bytes) {
+  if (!max_size_bytes.has_value()) {
+    return base::Optional<size_t>();
+  } else {
+    DCHECK_GE(max_size_bytes.value(), kGzipHeaderBytes + kGzipFooterBytes);
+    return max_size_bytes.value() - (kGzipHeaderBytes + kGzipFooterBytes);
+  }
+}
+
+LogCompressor::Result GzipLogCompressor::CompressInternal(
+    const std::string& input,
+    std::string* output,
+    bool budgeted,
+    bool last) {
+  DCHECK(output);
+  DCHECK(output->empty());
+  DCHECK(state_ == State::PRE_HEADER || state_ == State::ACTIVE ||
+         (!budgeted && state_ == State::FULL));
+
+  // Avoid writing to |output| unless the return value is OK.
+  std::string temp_output;
+
+  if (budgeted) {
+    const size_t estimated_compressed_size =
+        compressed_size_estimator_->EstimateCompressedSize(input);
+    if (!budget_.ConsumeAllowed(estimated_compressed_size)) {
+      return Result::DISALLOWED;
+    }
+  }
+
+  if (last) {
+    DCHECK(input.empty());
+    stream_.next_in = nullptr;
+  } else {
+    stream_.next_in = reinterpret_cast<z_const Bytef*>(input.c_str());
+  }
+
+  DCHECK_LE(input.length(),
+            static_cast<size_t>(std::numeric_limits<uInt>::max()));
+  stream_.avail_in = static_cast<uInt>(input.length());
+
+  const bool result = Deflate(last ? Z_FINISH : Z_SYNC_FLUSH, &temp_output);
+
+  stream_.next_in = nullptr;  // Avoid dangling pointers.
+
+  if (!result) {
+    // An error message was logged by Deflate().
+    return Result::ERROR_ENCOUNTERED;
+  }
+
+  if (budgeted) {
+    if (!budget_.ConsumeAllowed(temp_output.length())) {
+      LOG(WARNING) << "Compressed size was above estimate and unexpectedly "
+                      "exceeded the budget.";
+      return Result::ERROR_ENCOUNTERED;
+    }
+    budget_.Consume(temp_output.length());
+  }
+
+  std::swap(*output, temp_output);
+  return Result::OK;
+}
+
+bool GzipLogCompressor::Deflate(int flush, std::string* output) {
+  DCHECK((flush != Z_FINISH && stream_.next_in != nullptr) ||
+         (flush == Z_FINISH && stream_.next_in == nullptr));
+  DCHECK(output->empty());
+
+  bool success = true;  // Result of this method.
+  int z_result;         // Result of the zlib function.
+
+  size_t total_compressed_size = 0;
+
+  do {
+    // Allocate some additional buffer.
+    constexpr uInt kCompressionBuffer = 4 * 1024;
+    output->resize(total_compressed_size + kCompressionBuffer);
+
+    // This iteration should write directly beyond previous iterations' last
+    // written byte.
+    stream_.next_out =
+        reinterpret_cast<uint8_t*>(&((*output)[total_compressed_size]));
+    stream_.avail_out = kCompressionBuffer;
+
+    z_result = deflate(&stream_, flush);
+
+    DCHECK_GE(kCompressionBuffer, stream_.avail_out);
+    const size_t compressed_size = kCompressionBuffer - stream_.avail_out;
+
+    if (flush != Z_FINISH) {
+      if (z_result != Z_OK) {
+        LOG(ERROR) << "Compression failed (" << z_result << ").";
+        success = false;
+        break;
+      }
+    } else {  // flush == Z_FINISH
+      // End of the stream; we expect the footer to be exactly the size which
+      // we've set aside for it.
+      if (z_result != Z_STREAM_END || compressed_size != kGzipFooterBytes) {
+        LOG(ERROR) << "Compression failed (" << z_result << ", "
+                   << compressed_size << ").";
+        success = false;
+        break;
+      }
+    }
+
+    total_compressed_size += compressed_size;
+  } while (stream_.avail_out == 0 && z_result != Z_STREAM_END);
+
+  stream_.next_out = nullptr;  // Avoid dangling pointers.
+
+  if (success) {
+    output->resize(total_compressed_size);
+  } else {
+    output->clear();
+  }
+
+  return success;
+}
+
+}  // namespace
+
+const size_t kGzipOverheadBytes = kGzipHeaderBytes + kGzipFooterBytes;
+
+const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[] =
+    FILE_PATH_LITERAL("log");
+const base::FilePath::CharType kWebRtcEventLogGzippedExtension[] =
+    FILE_PATH_LITERAL("log.gz");
+
+size_t BaseLogFileWriterFactory::MinFileSizeBytes() const {
+  // No overhead incurred; data written straight to the file without metadata.
+  return 0;
+}
+
+base::FilePath::StringPieceType BaseLogFileWriterFactory::Extension() const {
+  return kWebRtcEventLogUncompressedExtension;
+}
+
+std::unique_ptr<LogFileWriter> BaseLogFileWriterFactory::Create(
+    const base::FilePath& path,
+    base::Optional<size_t> max_file_size_bytes) const {
+  if (max_file_size_bytes.has_value() &&
+      max_file_size_bytes.value() < MinFileSizeBytes()) {
+    LOG(WARNING) << "Max size (" << max_file_size_bytes.value()
+                 << ") below minimum size (" << MinFileSizeBytes() << ").";
+    return nullptr;
+  }
+
+  auto result = std::make_unique<BaseLogFileWriter>(path, max_file_size_bytes);
+
+  if (!result->Init()) {
+    // Error logged by Init.
+    result.reset();  // Destructor deletes errored files.
+  }
+
+  return result;
+}
+
+std::unique_ptr<CompressedSizeEstimator>
+DefaultGzippedSizeEstimator::Factory::Create() const {
+  return std::make_unique<DefaultGzippedSizeEstimator>();
+}
+
+size_t DefaultGzippedSizeEstimator::EstimateCompressedSize(
+    const std::string& input) const {
+  // This estimation is not tight. Since we expect to produce logs of
+  // several MBs, overshooting the estimation by one KB should be
+  // very safe and still relatively efficient.
+  constexpr size_t kOverheadOverUncompressedSizeBytes = 1000;
+  return input.length() + kOverheadOverUncompressedSizeBytes;
+}
+
+GzipLogCompressorFactory::GzipLogCompressorFactory(
+    std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)
+    : estimator_factory_(std::move(estimator_factory)) {}
+
+GzipLogCompressorFactory::~GzipLogCompressorFactory() = default;
+
+size_t GzipLogCompressorFactory::MinSizeBytes() const {
+  return kGzipOverheadBytes;
+}
+
+std::unique_ptr<LogCompressor> GzipLogCompressorFactory::Create(
+    base::Optional<size_t> max_size_bytes) const {
+  if (max_size_bytes.has_value() && max_size_bytes.value() < MinSizeBytes()) {
+    LOG(WARNING) << "Max size (" << max_size_bytes.value()
+                 << ") below minimum size (" << MinSizeBytes() << ").";
+    return nullptr;
+  }
+  return std::make_unique<GzipLogCompressor>(max_size_bytes,
+                                             estimator_factory_->Create());
+}
+
+GzippedLogFileWriterFactory::GzippedLogFileWriterFactory(
+    std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)
+    : gzip_compressor_factory_(std::move(gzip_compressor_factory)) {}
+
+GzippedLogFileWriterFactory::~GzippedLogFileWriterFactory() = default;
+
+size_t GzippedLogFileWriterFactory::MinFileSizeBytes() const {
+  // Only the compression's own overhead is incurred.
+  return gzip_compressor_factory_->MinSizeBytes();
+}
+
+base::FilePath::StringPieceType GzippedLogFileWriterFactory::Extension() const {
+  return kWebRtcEventLogGzippedExtension;
+}
+
+std::unique_ptr<LogFileWriter> GzippedLogFileWriterFactory::Create(
+    const base::FilePath& path,
+    base::Optional<size_t> max_file_size_bytes) const {
+  if (max_file_size_bytes.has_value() &&
+      max_file_size_bytes.value() < MinFileSizeBytes()) {
+    LOG(WARNING) << "Size below allowed minimum.";
+    return nullptr;
+  }
+
+  auto gzip_compressor = gzip_compressor_factory_->Create(max_file_size_bytes);
+  if (!gzip_compressor) {
+    // The factory itself will have logged an error.
+    return nullptr;
+  }
+
+  auto result = std::make_unique<GzippedLogFileWriter>(
+      path, max_file_size_bytes, std::move(gzip_compressor));
+
+  if (!result->Init()) {
+    // Error logged by Init.
+    result.reset();  // Destructor deletes errored files.
+  }
+
+  return result;
 }
 
 BrowserContextId GetBrowserContextId(
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
index a3b6595..bd83517 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_common.h
@@ -5,12 +5,11 @@
 #ifndef CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
 #define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_COMMON_H_
 
-#include <map>
+#include <memory>
 #include <string>
-#include <tuple>
 
-#include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 
@@ -44,13 +43,19 @@
 // limit is applied per browser context.
 extern const size_t kMaxPendingRemoteBoundWebRtcEventLogs;
 
+// Overhead incurred by GZIP due to its header and footer.
+extern const size_t kGzipOverheadBytes;
+
 // Remote-bound log files' names will be of the format [prefix]_[log_id].[ext],
 // where |prefix| is equal to kRemoteBoundWebRtcEventLogFileNamePrefix,
 // |log_id| is composed of 32 random characters from '0'-'9' and 'A'-'F',
-// and |ext| is kRemoteBoundWebRtcEventLogExtension.
+// and |ext| is the extension determined by the used LogCompressor::Factory,
+// which will be either kWebRtcEventLogUncompressedExtension or
+// kWebRtcEventLogGzippedExtension.
 extern const base::FilePath::CharType
     kRemoteBoundWebRtcEventLogFileNamePrefix[];
-extern const base::FilePath::CharType kRemoteBoundWebRtcEventLogExtension[];
+extern const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[];
+extern const base::FilePath::CharType kWebRtcEventLogGzippedExtension[];
 
 // Remote-bound event logs will not be uploaded if the time since their last
 // modification (meaning the time when they were completed) exceeds this value.
@@ -65,8 +70,8 @@
 // These are made globally visible so that unit tests may check for them.
 extern const char kStartRemoteLoggingFailureFeatureDisabled[];
 extern const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[];
-extern const char kStartRemoteLoggingFailureMaxSizeTooLarge[];
 extern const char kStartRemoteLoggingFailureMaxSizeTooSmall[];
+extern const char kStartRemoteLoggingFailureMaxSizeTooLarge[];
 extern const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[];
 extern const char kStartRemoteLoggingFailureAlreadyLogging[];
 extern const char kStartRemoteLoggingFailureGeneric[];
@@ -188,36 +193,214 @@
   virtual ~WebRtcRemoteEventLogsObserver() = default;
 };
 
-class LogFile {
+// Writes a log to a file while observing a maximum size.
+class LogFileWriter {
  public:
-  LogFile(const base::FilePath& path,
-          base::File file,
-          size_t max_file_size_bytes);
+  class Factory {
+   public:
+    virtual ~Factory() = default;
 
-  LogFile(LogFile&& other);
+    // The smallest size a log file of this type may assume.
+    virtual size_t MinFileSizeBytes() const = 0;
 
-  ~LogFile();
+    // The extension type associated with this type of log files.
+    virtual base::FilePath::StringPieceType Extension() const = 0;
 
-  bool MaxSizeReached() const;
+    // Instantiate and initialize a LogFileWriter.
+    // If creation or initialization fail, an empty unique_ptr will be returned,
+    // and it will be guaranteed that the file itself is not created. (If |path|
+    // had pointed to an existing file, that file will be deleted.)
+    // If !max_file_size_bytes.has_value(), the LogFileWriter is unlimited.
+    virtual std::unique_ptr<LogFileWriter> Create(
+        const base::FilePath& path,
+        base::Optional<size_t> max_file_size_bytes) const = 0;
+  };
 
-  // Writes to the log file, while respecting the file's size limit.
+  virtual ~LogFileWriter() = default;
+
+  // Init() must be called on each LogFileWriter exactly once, before it's used.
+  // If initialization fails, no further actions may be performed on the object
+  // other than Close() and Delete().
+  virtual bool Init() = 0;
+
+  // Getter for the path of the file |this| wraps.
+  virtual const base::FilePath& path() const = 0;
+
+  // Whether the maximum file size was reached.
+  virtual bool MaxSizeReached() const = 0;
+
+  // Writes to the log file while respecting the file's size limit.
   // True is returned if and only if the message was written to the file in
-  // it entirety.
-  // The function does *not* close the file, neither on errors nor when the
-  // maximum size is reached.
-  bool Write(const std::string& message);
+  // it entirety. That is, |false| is returned either if a genuine error
+  // occurs, or when the budget does not allow the next write.
+  // If |false| is ever returned, only Close() and Delete() may subsequently
+  // be called.
+  // The function does *not* close the file.
+  // The function may not be called if MaxSizeReached().
+  virtual bool Write(const std::string& input) = 0;
 
-  void Close();
+  // If the file was successfully closed, true is returned, and the file may
+  // now be used. Otherwise, the file is deleted, and false is returned.
+  virtual bool Close() = 0;
 
-  void Delete();
+  // Delete the file from disk.
+  virtual void Delete() = 0;
+};
 
-  const base::FilePath& path() const { return path_; }
+// Produces LogFileWriter instances that perform no compression.
+class BaseLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+  ~BaseLogFileWriterFactory() override = default;
+
+  size_t MinFileSizeBytes() const override;
+
+  base::FilePath::StringPieceType Extension() const override;
+
+  std::unique_ptr<LogFileWriter> Create(
+      const base::FilePath& path,
+      base::Optional<size_t> max_file_size_bytes) const override;
+};
+
+// Interface for a class that provides compression of a stream, while attempting
+// to observe a limit on the size.
+//
+// One should note that:
+// * For compressors that use a footer, to guarantee proper decompression,
+//   the footer must be written to the file.
+// * In such a case, usually, nothing can be omitted from the file, or the
+//   footer's CRC (if used) would be wrong.
+// * Determining a string's size pre-compression, without performing the actual
+//   compression, is heuristic in nature.
+//
+// Therefore, compression might terminate (FULL) earlier than it
+// must, or even in theory (which we attempt to avoid in practice) exceed the
+// size allowed it, in which case the file will be discarded (ERROR).
+class LogCompressor {
+ public:
+  // By subclassing this factory, concrete implementations of LogCompressor can
+  // be produced by unit tests, while keeping their definition in the .cc file.
+  // (Only the factory needs to be declared in the header.)
+  class Factory {
+   public:
+    virtual ~Factory() = default;
+
+    // The smallest size a log file of this type may assume.
+    virtual size_t MinSizeBytes() const = 0;
+
+    // Returns a LogCompressor if the parameters are valid and all
+    // initializations are successful; en empty unique_ptr otherwise.
+    // If !max_size_bytes.has_value(), an unlimited compressor is created.
+    virtual std::unique_ptr<LogCompressor> Create(
+        base::Optional<size_t> max_size_bytes) const = 0;
+  };
+
+  // Result of a call to Compress().
+  // * OK and ERROR_ENCOUNTERED are self-explanatory.
+  // * DISALLOWED means that, due to budget constraints, the input could
+  //   not be compressed. The stream is still in a legal state, but only
+  //   a call to CreateFooter() is now allowed.
+  enum class Result { OK, DISALLOWED, ERROR_ENCOUNTERED };
+
+  virtual ~LogCompressor() = default;
+
+  // Produces a compression header and writes it to |output|.
+  // The size does not count towards the max size limit.
+  // Guaranteed not to fail (nothing can realistically go wrong).
+  virtual void CreateHeader(std::string* output) = 0;
+
+  // Compresses |input| into |output|.
+  // * If compression succeeded, and the budget was observed, OK is returned.
+  // * If the compressor thinks the string, once compressed, will exceed the
+  //   maximum size (when combined with previously compressed strings),
+  //   compression will not be done, and DISALLOWED will be returned.
+  //   This allows producing a valid footer without exceeding the size limit.
+  // * Unexpected errors in the underlying compressor (e.g. zlib, etc.),
+  //   or unexpectedly getting a compressed string which exceeds the budget,
+  //   will return ERROR_ENCOUNTERED.
+  // This function may not be called again if DISALLOWED or ERROR_ENCOUNTERED
+  // were ever returned before, or after CreateFooter() was called.
+  virtual Result Compress(const std::string& input, std::string* output) = 0;
+
+  // Produces a compression footer and writes it to |output|.
+  // The footer does not count towards the max size limit.
+  // May not be called more than once, or if Compress() returned ERROR.
+  virtual bool CreateFooter(std::string* output) = 0;
+};
+
+// Estimates the compressed size, without performing compression (except in
+// unit tests, where performance is of lesser importance).
+// This interface allows unit tests to simulate specific cases, such as
+// over/under-estimation, and show that the code using the LogCompressor
+// deals with them correctly. (E.g., if the estimation expects the compression
+// to not go over-budget, but then it does.)
+// The estimator is expected to be stateful. That is, the order of calls to
+// EstimateCompressedSize() should correspond to the order of calls
+// to Compress().
+class CompressedSizeEstimator {
+ public:
+  class Factory {
+   public:
+    virtual ~Factory() = default;
+    virtual std::unique_ptr<CompressedSizeEstimator> Create() const = 0;
+  };
+
+  virtual ~CompressedSizeEstimator() = default;
+
+  virtual size_t EstimateCompressedSize(const std::string& input) const = 0;
+};
+
+// Provides a conservative estimation of the number of bytes required to
+// compress a string using GZIP. This estimation is not expected to ever
+// be overly optimistic, but the code using it should nevertheless be prepared
+// to deal with that theoretical possibility.
+class DefaultGzippedSizeEstimator : public CompressedSizeEstimator {
+ public:
+  class Factory : public CompressedSizeEstimator::Factory {
+   public:
+    ~Factory() override = default;
+
+    std::unique_ptr<CompressedSizeEstimator> Create() const override;
+  };
+
+  ~DefaultGzippedSizeEstimator() override = default;
+
+  size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Interface for producing LogCompressorGzip objects.
+class GzipLogCompressorFactory : public LogCompressor::Factory {
+ public:
+  explicit GzipLogCompressorFactory(
+      std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory);
+  ~GzipLogCompressorFactory() override;
+
+  size_t MinSizeBytes() const override;
+
+  std::unique_ptr<LogCompressor> Create(
+      base::Optional<size_t> max_size_bytes) const override;
 
  private:
-  const base::FilePath path_;
-  base::File file_;
-  const size_t max_file_size_bytes_;
-  size_t file_size_bytes_;
+  std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory_;
+};
+
+// Produces LogFileWriter instances that perform compression using GZIP.
+class GzippedLogFileWriterFactory : public LogFileWriter::Factory {
+ public:
+  explicit GzippedLogFileWriterFactory(
+      std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory);
+
+  ~GzippedLogFileWriterFactory() override;
+
+  size_t MinFileSizeBytes() const override;
+
+  base::FilePath::StringPieceType Extension() const override;
+
+  std::unique_ptr<LogFileWriter> Create(
+      const base::FilePath& path,
+      base::Optional<size_t> max_file_size_bytes) const override;
+
+ private:
+  std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory_;
 };
 
 // Translate a BrowserContext into an ID. This lets us associate PeerConnections
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
new file mode 100644
index 0000000..e1f64fc
--- /dev/null
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright 2018 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/media/webrtc/webrtc_event_log_manager_common.h"
+
+#include <memory>
+#include <numeric>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/optional.h"
+#include "base/rand_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace {
+constexpr LogCompressor::Result OK = LogCompressor::Result::OK;
+constexpr LogCompressor::Result DISALLOWED = LogCompressor::Result::DISALLOWED;
+constexpr LogCompressor::Result ERROR_ENCOUNTERED =
+    LogCompressor::Result::ERROR_ENCOUNTERED;
+}  // namespace
+
+// Tests for GzipLogCompressor.
+// Note that these tests may not use GzippedSize(), or they would be assuming
+// what they set out to prove. (Subsequent tests may use it, though.)
+class GzipLogCompressorTest : public ::testing::Test {
+ public:
+  ~GzipLogCompressorTest() override = default;
+
+  void Init(
+      std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory) {
+    DCHECK(!compressor_factory_);
+    DCHECK(estimator_factory);
+    compressor_factory_ = std::make_unique<GzipLogCompressorFactory>(
+        std::move(estimator_factory));
+  }
+
+  std::string Decompress(const std::string& input) {
+    std::string output;
+    EXPECT_TRUE(compression::GzipUncompress(input, &output));
+    return output;
+  }
+
+  std::unique_ptr<GzipLogCompressorFactory> compressor_factory_;
+};
+
+TEST_F(GzipLogCompressorTest,
+       GzipLogCompressorFactoryCreatesCompressorIfMinimalSizeOrAbove) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+  const size_t min_size = compressor_factory_->MinSizeBytes();
+  auto compressor = compressor_factory_->Create(min_size);
+  EXPECT_TRUE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest,
+       GzipLogCompressorFactoryDoesNotCreateCompressorIfBelowMinimalSize) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+  const size_t min_size = compressor_factory_->MinSizeBytes();
+  ASSERT_GE(min_size, 1u);
+  auto compressor = compressor_factory_->Create(min_size - 1);
+  EXPECT_FALSE(compressor);
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamReasonableMaxSize) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const std::string simulated_file = header + footer;
+  EXPECT_EQ(Decompress(simulated_file), "");
+}
+
+TEST_F(GzipLogCompressorTest, EmptyStreamMinimalSize) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  const size_t min_size = compressor_factory_->MinSizeBytes();
+  auto compressor = compressor_factory_->Create(min_size);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const std::string simulated_file = header + footer;
+  EXPECT_EQ(Decompress(simulated_file), "");
+}
+
+TEST_F(GzipLogCompressorTest, SingleCallToCompress) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  const std::string input = "Some random text.";
+  std::string log;
+  ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const std::string simulated_file = header + log + footer;
+  EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, MultipleCallsToCompress) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  const std::vector<std::string> inputs = {
+      "Some random text.",
+      "This text is also random. I give you my word for it. 100% random.",
+      "nejnnc pqmnx0981 mnl<D@ikjed90~~,z."};
+
+  std::vector<std::string> logs(inputs.size());
+  for (size_t i = 0; i < inputs.size(); i++) {
+    ASSERT_EQ(compressor->Compress(inputs[i], &logs[i]), OK);
+  }
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const auto input = std::accumulate(begin(inputs), end(inputs), std::string());
+  const auto log = std::accumulate(begin(logs), end(logs), std::string());
+
+  const std::string simulated_file = header + log + footer;
+  EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, UnlimitedBudgetSanity) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  auto compressor = compressor_factory_->Create(base::Optional<size_t>());
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  const std::string input = "Some random text.";
+  std::string log;
+  ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const std::string simulated_file = header + log + footer;
+  EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+// Test once with a big input, to provide coverage over inputs that could
+// exceed the size of some local buffers in the UUT.
+TEST_F(GzipLogCompressorTest, CompressionBigInput) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  auto compressor = compressor_factory_->Create(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  constexpr size_t kRealisticSizeBytes = 1000 * 1000;
+  const std::string input = base::RandBytesAsString(kRealisticSizeBytes);
+  std::string log;
+  ASSERT_EQ(compressor->Compress(input, &log), OK);
+
+  std::string footer;
+  ASSERT_TRUE(compressor->CreateFooter(&footer));
+
+  const std::string simulated_file = header + log + footer;
+  EXPECT_EQ(Decompress(simulated_file), input);
+}
+
+TEST_F(GzipLogCompressorTest, BudgetExceededByFirstCompressYieldsEmptyFile) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  const std::string input = "This won't fit.";
+
+  auto compressor = compressor_factory_->Create(GzippedSize(input) - 1);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  // Focal point #1 - Compress() returns DISALLOWED.
+  std::string log;
+  EXPECT_EQ(compressor->Compress(input, &log), DISALLOWED);
+
+  // Focal point #2 - CreateFooter() still succeeds;
+  std::string footer;
+  EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+  // Focal point #3 - the resulting log is parsable, and contains only those
+  // logs for which Compress() was successful.
+  // Note that |log| is not supposed to be written to the file, because
+  // Compress() has disallowed it.
+  const std::string simulated_file = header + footer;
+  EXPECT_EQ(Decompress(simulated_file), "");
+}
+
+TEST_F(GzipLogCompressorTest,
+       BudgetExceededByNonFirstCompressYieldsPartialFile) {
+  Init(std::make_unique<PerfectGzipEstimator::Factory>());
+
+  const std::string short_input = "short";
+  const std::string long_input = "A somewhat longer input string. @$%^&*()!!2";
+
+  // Allocate enough budget that |short_input| would be produced, and not yet
+  // exhaust the budget, but |long_input| won't fit.
+  auto compressor = compressor_factory_->Create(GzippedSize(short_input) + 1);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  std::string short_log;
+  ASSERT_EQ(compressor->Compress(short_input, &short_log), OK);
+
+  // Focal point #1 - Compress() returns DISALLOWED.
+  std::string long_log;
+  EXPECT_EQ(compressor->Compress(long_input, &long_log), DISALLOWED);
+  EXPECT_TRUE(long_log.empty());
+
+  // Focal point #2 - CreateFooter() still succeeds;
+  std::string footer;
+  EXPECT_TRUE(compressor->CreateFooter(&footer));
+
+  // Focal point #3 - the resulting log is parsable, and contains only those
+  // logs for which Compress() was successful.
+  // Note that |long_log| is not supposed to be written to the file, because
+  // Compress() has disallowed it.
+  const std::string simulated_file = header + short_log + footer;
+  EXPECT_EQ(Decompress(simulated_file), short_input);
+}
+
+TEST_F(GzipLogCompressorTest,
+       ExceedingBudgetDueToOverlyOptimisticEstimationYieldsError) {
+  // Use an estimator that will always be overly optimistic.
+  Init(std::make_unique<NullEstimator::Factory>());
+
+  // Set a budget that will easily be exceeded.
+  auto compressor = compressor_factory_->Create(kGzipOverheadBytes + 5);
+  ASSERT_TRUE(compressor);
+
+  std::string header;
+  compressor->CreateHeader(&header);
+
+  // Prepare to compress an input that is guaranteed to exceed the budget.
+  const std::string input = "A string that would not fit in five bytes.";
+
+  // The estimation allowed the compression, but then the compressed output
+  // ended up being over-budget.
+  std::string compressed;
+  EXPECT_EQ(compressor->Compress(input, &compressed), ERROR_ENCOUNTERED);
+  EXPECT_TRUE(compressed.empty());
+}
+
+// Tests relevant to all LogFileWriter subclasses.
+class LogFileWriterTest
+    : public ::testing::Test,
+      public ::testing::WithParamInterface<WebRtcEventLogCompression> {
+ public:
+  LogFileWriterTest() { EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+  ~LogFileWriterTest() override {}
+
+  void Init(WebRtcEventLogCompression compression) {
+    DCHECK(!compression_.has_value()) << "Must only be called once.";
+    compression_ = compression;
+    log_file_writer_factory_ = CreateLogFileWriterFactory(compression);
+    path_ = temp_dir_.GetPath()
+                .Append(FILE_PATH_LITERAL("arbitrary_filename"))
+                .AddExtension(log_file_writer_factory_->Extension());
+  }
+
+  std::unique_ptr<LogFileWriter> CreateWriter(base::Optional<size_t> max_size) {
+    return log_file_writer_factory_->Create(path_, max_size);
+  }
+
+  void ExpectFileContents(const base::FilePath& file_path,
+                          const std::string& expected_contents) {
+    DCHECK(compression_.has_value()) << "Must call Init().";
+
+    std::string file_contents;
+    ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
+
+    switch (compression_.value()) {
+      case WebRtcEventLogCompression::NONE: {
+        EXPECT_EQ(file_contents, expected_contents);
+        break;
+      }
+      case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+      case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION: {
+        std::string uncompressed;
+        ASSERT_TRUE(compression::GzipUncompress(file_contents, &uncompressed));
+        EXPECT_EQ(uncompressed, expected_contents);
+        break;
+      }
+      default: { NOTREACHED(); }
+    }
+  }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  base::Optional<WebRtcEventLogCompression> compression_;  // Set in Init().
+  base::ScopedTempDir temp_dir_;
+  base::FilePath path_;
+  std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+};
+
+TEST_P(LogFileWriterTest, FactoryCreatesLogFileWriter) {
+  Init(GetParam());
+  EXPECT_TRUE(CreateWriter(log_file_writer_factory_->MinFileSizeBytes()));
+}
+
+#if defined(OS_POSIX)
+TEST_P(LogFileWriterTest, FactoryReturnsEmptyUniquePtrIfCantCreateFile) {
+  Init(GetParam());
+  RemoveWritePermissions(temp_dir_.GetPath());
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  EXPECT_FALSE(writer);
+}
+#endif  // defined(OS_POSIX)
+
+TEST_P(LogFileWriterTest, CloseSucceedsWhenNoErrorsOccurred) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  EXPECT_TRUE(writer->Close());
+}
+
+// Other tests check check the case of compression where the estimation is
+// close to the file's capacity, reaches or exceeds it.
+TEST_P(LogFileWriterTest, CallToWriteSuccedsWhenCapacityFarOff) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  const std::string log = "log";
+  EXPECT_TRUE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, CallToWriteWithEmptyStringSucceeds) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  const std::string log = "";
+  EXPECT_TRUE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, UnlimitedBudgetSanity) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(base::Optional<size_t>());
+  ASSERT_TRUE(writer);
+
+  const std::string log = "log";
+  EXPECT_TRUE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log);
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesUnclosedFile) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  writer->Delete();
+  EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_P(LogFileWriterTest, DeleteRemovesClosedFile) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  EXPECT_TRUE(writer->Close());
+
+  writer->Delete();
+  EXPECT_FALSE(base::PathExists(path_));
+}
+
+#if !defined(OS_WIN)  // Deleting the open file does not work on Windows.
+TEST_P(LogFileWriterTest, WriteDoesNotCrashIfFileRemovedExternally) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+  ASSERT_FALSE(base::PathExists(path_));  // Sanity on the test itself.
+
+  // It's up to the OS whether this will succeed or fail, but it must not crash.
+  writer->Write("log");
+}
+
+TEST_P(LogFileWriterTest, CloseDoesNotCrashIfFileRemovedExternally) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+  ASSERT_FALSE(base::PathExists(path_));  // Sanity on the test itself.
+
+  // It's up to the OS whether this will succeed or fail, but it must not crash.
+  writer->Close();
+}
+
+TEST_P(LogFileWriterTest, DeleteDoesNotCrashIfFileRemovedExternally) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(base::DeleteFile(path_, /*recursive=*/false));
+  ASSERT_FALSE(base::PathExists(path_));  // Sanity on the test itself.
+
+  // It's up to the OS whether this will succeed or fail, but it must not crash.
+  writer->Delete();
+}
+#endif  // !defined(OS_WIN)
+
+TEST_P(LogFileWriterTest, PathReturnsTheCorrectPath) {
+  Init(GetParam());
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  ASSERT_EQ(writer->path(), path_);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    Compression,
+    LogFileWriterTest,
+    ::testing::Values(WebRtcEventLogCompression::NONE,
+                      WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION));
+
+// Tests for UncompressedLogFileWriterTest only.
+class UncompressedLogFileWriterTest : public LogFileWriterTest {
+ public:
+  ~UncompressedLogFileWriterTest() override = default;
+};
+
+TEST_F(UncompressedLogFileWriterTest,
+       MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+  Init(WebRtcEventLogCompression::NONE);
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  const std::string log = "log";
+  ASSERT_TRUE(writer->Write(log));
+
+  EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+  Init(WebRtcEventLogCompression::NONE);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(log.size());
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(writer->Write(log));  // (CallToWriteSuccedsWhenCapacityReached)
+
+  EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+  Init(WebRtcEventLogCompression::NONE);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(log.size());
+  ASSERT_TRUE(writer);
+
+  EXPECT_TRUE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log);
+}
+
+TEST_F(UncompressedLogFileWriterTest, CallToWriteFailsWhenCapacityExceeded) {
+  Init(WebRtcEventLogCompression::NONE);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(log.size() - 1);
+  ASSERT_TRUE(writer);
+
+  EXPECT_FALSE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, "");
+}
+
+TEST_F(UncompressedLogFileWriterTest, WriteCompleteMessagesOnly) {
+  Init(WebRtcEventLogCompression::NONE);
+
+  const std::string log1 = "01234";
+  const std::string log2 = "56789";
+
+  auto writer = CreateWriter(log1.size() + log2.size() - 1);
+  ASSERT_TRUE(writer);
+
+  EXPECT_TRUE(writer->Write(log1));
+
+  EXPECT_FALSE(writer->Write(log2));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log1);
+}
+
+// Tests for GzippedLogFileWriterTest only.
+class GzippedLogFileWriterTest : public LogFileWriterTest {
+ public:
+  ~GzippedLogFileWriterTest() override = default;
+};
+
+TEST_F(GzippedLogFileWriterTest, FactoryDeletesFileIfMaxSizeBelowMin) {
+  Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+  const size_t min_size = log_file_writer_factory_->MinFileSizeBytes();
+  ASSERT_GE(min_size, 1u);
+
+  auto writer = CreateWriter(min_size - 1);
+  ASSERT_FALSE(writer);
+
+  EXPECT_FALSE(base::PathExists(path_));
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsFalseWhenMaxNotReached) {
+  Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+  auto writer = CreateWriter(kMaxRemoteLogFileSizeBytes);
+  ASSERT_TRUE(writer);
+
+  const std::string log = "log";
+  ASSERT_TRUE(writer->Write(log));
+  EXPECT_FALSE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, MaxSizeReachedReturnsTrueWhenMaxReached) {
+  // By using a 0 estimation, we allow the compressor to keep going to
+  // the point of budget saturation.
+  Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(GzippedSize(log));
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(writer->Write(log));  // (CallToWriteSuccedsWhenCapacityReached)
+  EXPECT_TRUE(writer->MaxSizeReached());
+}
+
+TEST_F(GzippedLogFileWriterTest, CallToWriteSuccedsWhenCapacityReached) {
+  Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(GzippedSize(log));
+  ASSERT_TRUE(writer);
+
+  EXPECT_TRUE(writer->Write(log));
+
+  ASSERT_TRUE(writer->Close());
+  ExpectFileContents(path_, log);
+}
+
+// Also tests the scenario WriteCompleteMessagesOnly.
+TEST_F(GzippedLogFileWriterTest,
+       CallToWriteFailsWhenCapacityWouldBeExceededButEstimationPreventedWrite) {
+  Init(WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION);
+
+  const std::string log1 = "abcde";
+  const std::string log2 = "fghij";
+  const std::vector<std::string> logs = {log1, log2};
+
+  // Find out the size necessary for compressing log1 and log2 in two calls.
+  const size_t compressed_len = GzippedSize(logs);  // Vector version.
+
+  auto writer = CreateWriter(compressed_len - 1);
+  ASSERT_TRUE(writer);
+
+  ASSERT_TRUE(writer->Write(log1));
+
+  EXPECT_FALSE(writer->Write(log2));
+
+  // The second write was succesfully prevented; no error should have occurred,
+  // and it should be possible to produce a meaningful gzipped log file.
+  EXPECT_TRUE(writer->Close());
+
+  ExpectFileContents(path_, log1);  // Only the in-budget part was written.
+}
+
+// This tests the case when the estimation fails to warn us of a pending
+// over-budget write, which leaves us unable to produce a valid compression
+// footer for the truncated file. This forces us to discard the file.
+TEST_F(GzippedLogFileWriterTest,
+       CallToWriteFailsWhenCapacityExceededDespiteEstimationAllowingIt) {
+  // By using a 0 estimation, we allow the compressor to keep going to
+  // the point of budget saturation.
+  Init(WebRtcEventLogCompression::GZIP_NULL_ESTIMATION);
+
+  const std::string log = "log";
+
+  auto writer = CreateWriter(GzippedSize(log) - 1);
+  ASSERT_TRUE(writer);
+
+  EXPECT_FALSE(writer->Write(log));
+
+  EXPECT_FALSE(writer->Close());
+  EXPECT_FALSE(base::PathExists(path_));  // Errored files deleted by Close().
+}
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
index 4046605..458210b8 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_local.cc
@@ -90,7 +90,11 @@
   DCHECK_EQ(log_files_.size(), 0u);
 
   base_path_ = base_path;
-  max_log_file_size_bytes_ = max_file_size_bytes;
+
+  max_log_file_size_bytes_ =
+      (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize)
+          ? base::Optional<size_t>()
+          : base::Optional<size_t>(max_file_size_bytes);
 
   for (const PeerConnectionKey& peer_connection : active_peer_connections_) {
     if (log_files_.size() >= kMaxNumberLocalWebRtcEventLogFiles) {
@@ -127,9 +131,9 @@
     return false;
   }
 
-  const bool write_successful = it->second.Write(message);
+  const bool write_successful = it->second->Write(message);
 
-  if (!write_successful || it->second.MaxSizeReached()) {
+  if (!write_successful || it->second->MaxSizeReached()) {
     CloseLogFile(it);
   }
 
@@ -169,6 +173,7 @@
 
 void WebRtcLocalEventLogManager::StartLogFile(const PeerConnectionKey& key) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
+  DCHECK(log_files_.find(key) == log_files_.end());
 
   // Add some information to the name given by the caller.
   base::FilePath file_path = GetFilePath(base_path_, key);
@@ -182,30 +187,27 @@
     return;  // No available file path was found.
   } else if (unique_number != 0) {
     // The filename is taken, but a unique number was found.
-    // TODO(eladalon): Fix the way the unique number is used.
-    // https://crbug.com/785333
+    // TODO(crbug.com/785333): Fix the way the unique number is used.
     file_path = file_path.InsertBeforeExtension(FILE_PATH_LITERAL(" (") +
                                                 IntToStringType(unique_number) +
                                                 FILE_PATH_LITERAL(")"));
   }
 
-  // Attempt to create the file.
-  constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
-                             base::File::FLAG_EXCLUSIVE_WRITE;
-  base::File file(file_path, file_flags);
-  if (!file.IsValid() || !file.created()) {
+  auto log_file =
+      log_file_writer_factory_.Create(file_path, max_log_file_size_bytes_);
+  if (!log_file) {
     LOG(WARNING) << "Couldn't create and/or open local WebRTC event log file.";
     return;
   }
 
-  // If the file was successfully created, it's now ready to be written to.
-  DCHECK(log_files_.find(key) == log_files_.end());
-  log_files_.emplace(
-      key, LogFile(file_path, std::move(file), max_log_file_size_bytes_));
+  const auto it = log_files_.emplace(key, std::move(log_file));
+  DCHECK(it.second);
 
   // The observer needs to be able to run on any TaskQueue.
   if (observer_) {
-    observer_->OnLocalLogStarted(key, file_path);
+    LogFilesMap::iterator map_iter = it.first;
+    // map_iter->second is a std::unique_ptr<LogFileWriter>.
+    observer_->OnLocalLogStarted(key, map_iter->second->path());
   }
 }
 
@@ -215,7 +217,7 @@
 
   const PeerConnectionKey peer_connection = it->first;
 
-  it->second.Close();
+  it->second->Close();
   it = log_files_.erase(it);
 
   if (observer_) {
@@ -237,7 +239,7 @@
     base::Time::Now().LocalExplode(&now);
   }
 
-  // [user_defined]_[date]_[time]_[render_process_id]_[lid].log
+  // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
   char stamp[100];
   int written =
       base::snprintf(stamp, arraysize(stamp), "%04d%02d%02d_%02d%02d_%d_%d",
@@ -247,6 +249,6 @@
   CHECK_LT(static_cast<size_t>(written), arraysize(stamp));
 
   return base_path.InsertBeforeExtension(FILE_PATH_LITERAL("_"))
-      .InsertBeforeExtensionASCII(base::StringPiece(stamp))
-      .AddExtension(FILE_PATH_LITERAL("log"));
+      .AddExtension(log_file_writer_factory_.Extension())
+      .InsertBeforeExtensionASCII(base::StringPiece(stamp));
 }
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
index 7b70e75..7887b85 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_local.h
@@ -9,14 +9,15 @@
 #include <set>
 #include <string>
 
-#include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/time/clock.h"
 #include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
 
 class WebRtcLocalEventLogManager final {
-  using LogFilesMap = std::map<WebRtcEventLogPeerConnectionKey, LogFile>;
+  using LogFilesMap =
+      std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
   using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
 
  public:
@@ -48,7 +49,7 @@
   LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it);
 
   // Derives the name of a local log file. The format is:
-  // [user_defined]_[date]_[time]_[render_process_id]_[lid].log
+  // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
   base::FilePath GetFilePath(const base::FilePath& base_path,
                              const PeerConnectionKey& key) const;
 
@@ -56,6 +57,9 @@
   // but live on its owner's internal, IO-capable task queue.
   SEQUENCE_CHECKER(io_task_sequence_checker_);
 
+  // Produces LogFileWriter instances, for writing the logs to files.
+  BaseLogFileWriterFactory log_file_writer_factory_;
+
   // Observer which will be informed whenever a local log file is started or
   // stopped. Through this, the owning WebRtcEventLogManager can be informed,
   // and decide whether it wants to turn notifications from WebRTC on/off.
@@ -80,10 +84,9 @@
   // to this directory.
   base::FilePath base_path_;
 
-  // The maximum size for local logs, in bytes. Note that
-  // kWebRtcEventLogManagerUnlimitedFileSize is a sentinel value (with a
-  // self-explanatory name).
-  size_t max_log_file_size_bytes_;
+  // The maximum size for local logs, in bytes.
+  // If !has_value(), the value is unlimited.
+  base::Optional<size_t> max_log_file_size_bytes_;
 
   DISALLOW_COPY_AND_ASSIGN(WebRtcLocalEventLogManager);
 };
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
index 547b28f..7087ec0 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.cc
@@ -8,7 +8,6 @@
 #include <iterator>
 #include <limits>
 
-#include "base/big_endian.h"
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/files/file.h"
@@ -16,7 +15,6 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/logging.h"
-#include "base/macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/threading/sequenced_task_runner_handle.h"
@@ -35,23 +33,6 @@
 const base::TimeDelta kDefaultWebRtcRemoteEventLogUploadDelay =
     base::TimeDelta::FromSeconds(30);
 
-bool AreLogParametersValid(size_t max_file_size_bytes,
-                           std::string* error_message) {
-  if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
-    LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
-    *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
-    return false;
-  }
-
-  if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
-    LOG(WARNING) << "File size exceeds maximum allowed.";
-    *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
-    return false;
-  }
-
-  return true;
-}
-
 base::TimeDelta GetProactivePruningDelta() {
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           ::switches::kWebRtcRemoteEventLogProactivePruningDelta)) {
@@ -133,8 +114,6 @@
 
 const base::FilePath::CharType kRemoteBoundWebRtcEventLogFileNamePrefix[] =
     FILE_PATH_LITERAL("webrtc_event_log_");
-const base::FilePath::CharType kRemoteBoundWebRtcEventLogExtension[] =
-    FILE_PATH_LITERAL("log");
 
 WebRtcRemoteEventLogManager::WebRtcRemoteEventLogManager(
     WebRtcRemoteEventLogsObserver* observer,
@@ -212,11 +191,22 @@
       std::make_unique<WebRtcEventLogUploaderImpl::Factory>(context_getter);
 }
 
+void WebRtcRemoteEventLogManager::SetLogFileWriterFactory(
+    std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(log_file_writer_factory);
+  DCHECK(!log_file_writer_factory_);
+  log_file_writer_factory_ = std::move(log_file_writer_factory);
+}
+
 void WebRtcRemoteEventLogManager::EnableForBrowserContext(
     BrowserContextId browser_context_id,
     const base::FilePath& browser_context_dir) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(network_connection_tracker_)
+      << "SetNetworkConnectionTracker not called.";
   DCHECK(uploader_factory_) << "SetUrlRequestContextGetter() not called.";
+  DCHECK(log_file_writer_factory_) << "SetLogFileWriterFactory() not called.";
   DCHECK(!BrowserContextEnabled(browser_context_id)) << "Already enabled.";
 
   const base::FilePath remote_bound_logs_dir =
@@ -344,8 +334,8 @@
   // May not restart active remote logs.
   auto it = active_logs_.find(key);
   if (it != active_logs_.end()) {
-    LOG(ERROR) << "Remote logging already underway for ("
-               << key.render_process_id << ", " << key.lid << ").";
+    LOG(ERROR) << "Remote logging already underway for " << peer_connection_id
+               << ".";
     *error_message = kStartRemoteLoggingFailureAlreadyLogging;
     return false;
   }
@@ -375,9 +365,9 @@
     return false;
   }
 
-  const bool write_successful = it->second.Write(message);
-
-  if (!write_successful || it->second.MaxSizeReached()) {
+  const bool write_successful = it->second->Write(message);
+  if (!write_successful || it->second->MaxSizeReached()) {
+    // Note: If the file is invalid, CloseLogFile() will discard it.
     CloseLogFile(it, /*make_pending=*/true);
     ManageUploadSchedule();
   }
@@ -455,6 +445,32 @@
       base::BindOnce(std::move(callback), UploadConditionsHold()));
 }
 
+bool WebRtcRemoteEventLogManager::AreLogParametersValid(
+    size_t max_file_size_bytes,
+    std::string* error_message) const {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+
+  if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
+    LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
+    *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
+    return false;
+  }
+
+  if (max_file_size_bytes < log_file_writer_factory_->MinFileSizeBytes()) {
+    LOG(WARNING) << "File size below minimum allowed.";
+    *error_message = kStartRemoteLoggingFailureMaxSizeTooSmall;
+    return false;
+  }
+
+  if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
+    LOG(WARNING) << "File size exceeds maximum allowed.";
+    *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
+    return false;
+  }
+
+  return true;
+}
+
 bool WebRtcRemoteEventLogManager::BrowserContextEnabled(
     BrowserContextId browser_context_id) const {
   const auto it = enabled_browser_contexts_.find(browser_context_id);
@@ -468,17 +484,24 @@
 
   const PeerConnectionKey peer_connection = it->first;  // Copy, not reference.
 
-  it->second.Close();
+  bool valid_file = it->second->Close();  // !valid_file -> Close() deletes.
+  if (valid_file) {
+    if (make_pending) {
+      // The current time is a good enough approximation of the file's last
+      // modification time.
+      const base::Time last_modified = base::Time::Now();
 
-  if (make_pending) {
-    // The current time is a good enough approximation of the file's last
-    // modification time.
-    const base::Time last_modified = base::Time::Now();
-
-    // The stopped log becomes a pending log.
-    const auto emplace_result = pending_logs_.emplace(
-        peer_connection.browser_context_id, it->second.path(), last_modified);
-    DCHECK(emplace_result.second);  // No pre-existing entry.
+      // The stopped log becomes a pending log.
+      const auto emplace_result =
+          pending_logs_.emplace(peer_connection.browser_context_id,
+                                it->second->path(), last_modified);
+      DCHECK(emplace_result.second);  // No pre-existing entry.
+    } else {
+      const base::FilePath log_file_path = it->second->path();
+      if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
+        LOG(ERROR) << "Failed to delete " << log_file_path << ".";
+      }
+    }
   }
 
   it = active_logs_.erase(it);
@@ -514,14 +537,19 @@
     const base::FilePath& remote_bound_logs_dir) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
-  base::FilePath::StringType pattern =
-      base::FilePath::StringType(FILE_PATH_LITERAL("*")) +
-      base::FilePath::kExtensionSeparator + kRemoteBoundWebRtcEventLogExtension;
   base::FileEnumerator enumerator(remote_bound_logs_dir,
                                   /*recursive=*/false,
-                                  base::FileEnumerator::FILES, pattern);
+                                  base::FileEnumerator::FILES);
 
   for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
+    const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+    const base::FilePath::StringType extension = info.GetName().Extension();
+    const auto separator =
+        base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
+    if (extension != separator + kWebRtcEventLogUncompressedExtension &&
+        extension != separator + kWebRtcEventLogGzippedExtension) {
+      continue;
+    }
     const auto last_modified = enumerator.GetInfo().GetLastModifiedTime();
     auto it = pending_logs_.emplace(browser_context_id, path, last_modified);
     DCHECK(it.second);  // No pre-existing entry.
@@ -549,27 +577,23 @@
       GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
   const base::FilePath file_path =
       base_path.Append(kRemoteBoundWebRtcEventLogFileNamePrefix)
-          .InsertBeforeExtensionASCII(id)
-          .AddExtension(kRemoteBoundWebRtcEventLogExtension);
+          .AddExtension(log_file_writer_factory_->Extension())
+          .InsertBeforeExtensionASCII(id);
 
-  // Attempt to create the file.
-  constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
-                             base::File::FLAG_EXCLUSIVE_WRITE;
-  base::File file(file_path, file_flags);
-  if (!file.IsValid() || !file.created()) {
-    LOG(WARNING) << "Couldn't create and/or open remote WebRTC event log file.";
-    // Intentionally using a generic error; look for other places where it's
-    // set for an explanation why.
+  // The log is now ACTIVE.
+  DCHECK_NE(max_file_size_bytes, kWebRtcEventLogManagerUnlimitedFileSize);
+  auto log_file =
+      log_file_writer_factory_->Create(file_path, max_file_size_bytes);
+  if (!log_file) {
+    // TODO(crbug.com/775415): Add UMA for exact failure type.
+    LOG(WARNING) << "Failed to initialize remote-bound WebRTC event log file.";
     *error_message = kStartRemoteLoggingFailureGeneric;
     return false;
   }
-
-  // The log is now ACTIVE.
-  LogFile log_file(file_path, std::move(file), max_file_size_bytes);
   const auto it = active_logs_.emplace(key, std::move(log_file));
   DCHECK(it.second);
 
-  observer_->OnRemoteLogStarted(key, it.first->second.path());
+  observer_->OnRemoteLogStarted(key, it.first->second->path());
 
   *log_id = id;
   return true;
@@ -646,11 +670,7 @@
     // Since the file is active, assume it's still being modified.
     if (LogFileMatchesFilter(it->first.browser_context_id, base::Time::Now(),
                              browser_context_id, delete_begin, delete_end)) {
-      const base::FilePath log_file_path = it->second.path();
       it = CloseLogFile(it, /*make_pending=*/false);
-      if (!base::DeleteFile(log_file_path, /*recursive=*/false)) {
-        LOG(ERROR) << "Failed to delete " << log_file_path << ".";
-      }
     } else {
       ++it;
     }
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
index c914772..d8fa61a 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_remote.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include <set>
-#include <vector>
 
 #include "base/optional.h"
 #include "base/sequenced_task_runner.h"
@@ -25,7 +24,8 @@
 class WebRtcRemoteEventLogManager final
     : public content::NetworkConnectionTracker::NetworkConnectionObserver {
   using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
-  using LogFilesMap = std::map<WebRtcEventLogPeerConnectionKey, LogFile>;
+  using LogFilesMap =
+      std::map<WebRtcEventLogPeerConnectionKey, std::unique_ptr<LogFileWriter>>;
   using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
 
  public:
@@ -46,6 +46,12 @@
   // Must be called before any call to EnableForBrowserContext().
   void SetUrlRequestContextGetter(net::URLRequestContextGetter* context_getter);
 
+  // Sets a LogFileWriter factory.
+  // Must not be called more than once.
+  // Must be called before any call to EnableForBrowserContext().
+  void SetLogFileWriterFactory(
+      std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory);
+
   // Enables remote-bound logging for a given BrowserContext. Logs stored during
   // previous sessions become eligible for upload, and recording of new logs for
   // peer connections associated with this BrowserContext, in the
@@ -148,12 +154,21 @@
   void UploadConditionsHoldForTesting(base::OnceCallback<void(bool)> callback);
 
  private:
+  // Validates log parameters (at the moment, only max file size).
+  // If valid, returns true. Otherwise, false, and |error_message| gets
+  // a relevant error.
+  bool AreLogParametersValid(size_t max_file_size_bytes,
+                             std::string* error_message) const;
+
   // Checks whether a browser context has already been enabled via a call to
   // EnableForBrowserContext(), and not yet disabled using a call to
   // DisableForBrowserContext().
   bool BrowserContextEnabled(BrowserContextId browser_context_id) const;
 
-  // Closes an active log file, changing its state from ACTIVE to PENDING.
+  // Closes an active log file.
+  // If |make_pending| is true, closing the file changes its state from ACTIVE
+  // to PENDING. If |make_pending| is false, or if the file couldn't be closed
+  // correctly, the file will be deleted.
   // Returns an iterator to the next ACTIVE file.
   LogFilesMap::iterator CloseLogFile(LogFilesMap::iterator it,
                                      bool make_pending);
@@ -324,8 +339,11 @@
   // are not considered active, regardless of whether they have been torn down.
   std::map<PeerConnectionKey, const std::string> active_peer_connections_;
 
+  // Creates LogFileWriter instances (compressed/uncompressed, etc.).
+  std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory_;
+
   // Remote-bound logs which we're currently in the process of writing to disk.
-  std::map<PeerConnectionKey, LogFile> active_logs_;
+  LogFilesMap active_logs_;
 
   // Remote-bound logs which have been written to disk before (either during
   // this Chrome session or during an earlier one), and which are no waiting to
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
index ba89a8ab..48caeef 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest.cc
@@ -21,10 +21,10 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
-#include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
+#include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/synchronization/waitable_event.h"
@@ -36,6 +36,7 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/testing_browser_process.h"
@@ -47,6 +48,7 @@
 #include "net/url_request/url_request_test_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
 
 // TODO(crbug.com/775415): Add unit tests for incognito mode.
 // TODO(crbug.com/775415): Migrate to being based on Profiles rather than on
@@ -68,6 +70,8 @@
 using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
 using RenderProcessHost = content::RenderProcessHost;
 
+using Compression = WebRtcEventLogCompression;
+
 namespace {
 
 #if !defined(OS_ANDROID)
@@ -238,6 +242,25 @@
     LoadProfiles();
   }
 
+  void CreateWebRtcEventLogManager(
+      base::Optional<Compression> remote = base::Optional<Compression>()) {
+    DCHECK(!event_log_manager_);
+
+    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+
+    local_log_extension_ = kWebRtcEventLogUncompressedExtension;
+
+    if (remote.has_value()) {
+      auto factory = CreateLogFileWriterFactory(remote.value());
+      remote_log_extension_ = factory->Extension();
+      event_log_manager_->SetRemoteLogFileWriterFactoryForTesting(
+          std::move(factory));
+    } else {
+      // kWebRtcRemoteEventLogGzipped is turned on by default.
+      remote_log_extension_ = kWebRtcEventLogGzippedExtension;
+    }
+  }
+
   void LoadProfiles() {
     testing_profile_manager_ = std::make_unique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
@@ -550,7 +573,17 @@
                                 const std::string& expected_event_log) {
     std::string file_contents;
     ASSERT_TRUE(base::ReadFileToString(file_path, &file_contents));
-    EXPECT_EQ(file_contents, expected_event_log);
+
+    if (remote_log_extension_ == kWebRtcEventLogUncompressedExtension) {
+      EXPECT_EQ(file_contents, expected_event_log);
+    } else if (remote_log_extension_ == kWebRtcEventLogGzippedExtension) {
+      std::string uncompressed_log;
+      ASSERT_TRUE(
+          compression::GzipUncompress(file_contents, &uncompressed_log));
+      EXPECT_EQ(uncompressed_log, expected_event_log);
+    } else {
+      NOTREACHED();
+    }
   }
 
   // When the peer connection's ID is not the focus of the test, this allows
@@ -598,6 +631,11 @@
   // Unit under test.
   std::unique_ptr<WebRtcEventLogManager> event_log_manager_;
 
+  // Extensions associated with local/remote-bound event logs. Depends on
+  // whether they're compressed.
+  base::FilePath::StringPieceType local_log_extension_;
+  base::FilePath::StringPieceType remote_log_extension_;
+
   // The directory which will contain all profiles.
   base::ScopedTempDir profiles_dir_;
 
@@ -664,14 +702,17 @@
     // Use a low delay, or the tests would run for quite a long time.
     scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
         ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
-
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
   }
 
+  ~WebRtcEventLogManagerTest() override = default;
+
   void SetUp() override {
+    CreateWebRtcEventLogManager(Compression::GZIP_PERFECT_ESTIMATION);
+
     auto tracker = std::make_unique<content::MockNetworkConnectionTracker>(
         true, network::mojom::ConnectionType::CONNECTION_ETHERNET);
     WebRtcEventLogManagerTestBase::SetUp(std::move(tracker));
+
     SetWebRtcEventLogUploaderFactoryForTesting(
         std::make_unique<NullWebRtcEventLogUploader::Factory>(false));
   }
@@ -763,7 +804,7 @@
       scoped_feature_list_.InitAndDisableFeature(
           features::kWebRtcRemoteEventLog);
     }
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+    CreateWebRtcEventLogManager();
   }
 
   ~WebRtcEventLogManagerTestWithRemoteLoggingDisabledOrNotEnabled() override =
@@ -783,7 +824,7 @@
     scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
         ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
 
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+    CreateWebRtcEventLogManager();
   }
 
   ~WebRtcEventLogManagerTestUploadSuppressionDisablingFlag() override = default;
@@ -806,7 +847,7 @@
     scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
         ::switches::kWebRtcRemoteEventLogUploadDelayMs, "100");
 
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+    CreateWebRtcEventLogManager();
   }
 
   ~WebRtcEventLogManagerTestForNetworkConnectivity() override = default;
@@ -831,7 +872,7 @@
     const base::FilePath file_path =
         remote_logs_dir.Append(kRemoteBoundWebRtcEventLogFileNamePrefix)
             .InsertBeforeExtensionASCII("01234567890123456789012345678901")
-            .AddExtension(kRemoteBoundWebRtcEventLogExtension);
+            .AddExtension(remote_log_extension_);
     constexpr int file_flags = base::File::FLAG_CREATE |
                                base::File::FLAG_WRITE |
                                base::File::FLAG_EXCLUSIVE_WRITE;
@@ -867,7 +908,7 @@
         ::switches::kWebRtcRemoteEventLogUploadDelayMs,
         std::to_string(upload_delay_ms));
 
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+    CreateWebRtcEventLogManager();
 
     WebRtcEventLogManagerTestBase::SetUp();
   }
@@ -885,6 +926,33 @@
   static const size_t kIntentionallyExcessiveDelayMs = 1000 * 1000 * 1000;
 };
 
+// For testing compression issues.
+class WebRtcEventLogManagerTestCompression
+    : public WebRtcEventLogManagerTestBase {
+ public:
+  WebRtcEventLogManagerTestCompression() {
+    scoped_feature_list_.InitAndEnableFeature(features::kWebRtcRemoteEventLog);
+
+    scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
+        ::switches::kWebRtcRemoteEventLogUploadDelayMs, "0");
+  }
+
+  ~WebRtcEventLogManagerTestCompression() override = default;
+
+  void SetUp() override {
+    // Defer until Init(), which will allow the test body more control.
+  }
+
+  void Init(base::Optional<WebRtcEventLogCompression> remote_compression =
+                base::Optional<WebRtcEventLogCompression>()) {
+    CreateWebRtcEventLogManager(remote_compression);
+
+    auto tracker = std::make_unique<content::MockNetworkConnectionTracker>(
+        true, network::mojom::ConnectionType::CONNECTION_ETHERNET);
+    WebRtcEventLogManagerTestBase::SetUp(std::move(tracker));
+  }
+};
+
 namespace {
 
 class PeerConnectionTrackerProxyForTesting
@@ -905,23 +973,11 @@
   WebRtcEventLogManagerTestBase* const test_;
 };
 
-#if defined(OS_POSIX)
-void RemoveWritePermissionsFromDirectory(const base::FilePath& path) {
-  int permissions;
-  ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
-  constexpr int write_permissions = base::FILE_PERMISSION_WRITE_BY_USER |
-                                    base::FILE_PERMISSION_WRITE_BY_GROUP |
-                                    base::FILE_PERMISSION_WRITE_BY_OTHERS;
-  permissions &= ~write_permissions;
-  ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
-}
-#endif  // defined(OS_POSIX)
-
-// The factory for the following fake uploader produces a sequence of uploaders
-// which fail the test if given a file other than that which they expect. The
-// factory itself likewise fails the test if destroyed before producing all
-// expected uploaders, or if it's asked for more uploaders than it expects to
-// create. This allows us to test for sequences of uploads.
+// The factory for the following fake uploader produces a sequence of
+// uploaders which fail the test if given a file other than that which they
+// expect. The factory itself likewise fails the test if destroyed before
+// producing all expected uploaders, or if it's asked for more uploaders than
+// it expects to create. This allows us to test for sequences of uploads.
 class FileListExpectingWebRtcEventLogUploader : public WebRtcEventLogUploader {
  public:
   class Factory : public WebRtcEventLogUploader::Factory {
@@ -1470,7 +1526,7 @@
 
 #if defined(OS_POSIX)
 TEST_F(WebRtcEventLogManagerTest, LocalLogLegalPathWithoutPermissionsSanity) {
-  RemoveWritePermissionsFromDirectory(local_logs_base_dir_.GetPath());
+  RemoveWritePermissions(local_logs_base_dir_.GetPath());
 
   // Since the log file won't be properly opened, these will not be called.
   EXPECT_CALL(local_observer_, OnLocalLogStarted(_, _)).Times(0);
@@ -1556,7 +1612,7 @@
   ASSERT_TRUE(file_path);
   ASSERT_FALSE(file_path->empty());
 
-  // [user_defined]_[date]_[time]_[render_process_id]_[lid].log
+  // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
   const StringType date = FILE_PATH_LITERAL("20170906");
   const StringType time = FILE_PATH_LITERAL("1043");
   base::FilePath expected_path = local_logs_base_path;
@@ -1564,7 +1620,7 @@
       FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
       FILE_PATH_LITERAL("_") + IntToStringType(rph_->GetID()) +
       FILE_PATH_LITERAL("_") + IntToStringType(kLid));
-  expected_path = expected_path.AddExtension(FILE_PATH_LITERAL("log"));
+  expected_path = expected_path.AddExtension(local_log_extension_);
 
   EXPECT_EQ(file_path, expected_path);
 }
@@ -1603,7 +1659,7 @@
   ASSERT_TRUE(file_path_1);
   ASSERT_FALSE(file_path_1->empty());
 
-  // [user_defined]_[date]_[time]_[render_process_id]_[lid].log
+  // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
   const StringType date = FILE_PATH_LITERAL("20170906");
   const StringType time = FILE_PATH_LITERAL("1043");
   base::FilePath expected_path_1 = local_logs_base_path;
@@ -1611,7 +1667,7 @@
       FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
       FILE_PATH_LITERAL("_") + IntToStringType(rph_->GetID()) +
       FILE_PATH_LITERAL("_") + IntToStringType(kLid));
-  expected_path_1 = expected_path_1.AddExtension(FILE_PATH_LITERAL("log"));
+  expected_path_1 = expected_path_1.AddExtension(local_log_extension_);
 
   ASSERT_EQ(file_path_1, expected_path_1);
 
@@ -1730,6 +1786,21 @@
 }
 
 TEST_F(WebRtcEventLogManagerTest,
+       StartRemoteLoggingReturnsFalseIfFileSizeToSmall) {
+  const size_t min_size =
+      CreateLogFileWriterFactory(Compression::GZIP_NULL_ESTIMATION)
+          ->MinFileSizeBytes();
+
+  const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+  const std::string id = "id";  // For explicitness' sake.
+  ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid, id));
+  std::string error_message;
+  EXPECT_FALSE(StartRemoteLogging(key.render_process_id, id, min_size - 1,
+                                  nullptr, &error_message));
+  EXPECT_EQ(error_message, kStartRemoteLoggingFailureMaxSizeTooSmall);
+}
+
+TEST_F(WebRtcEventLogManagerTest,
        StartRemoteLoggingReturnsFalseIfExcessivelyLargeFileSize) {
   const auto key = GetPeerConnectionKey(rph_.get(), kLid);
   const std::string id = "id";  // For explicitness' sake.
@@ -1797,7 +1868,7 @@
   const base::FilePath expected_filename =
       base::FilePath(kRemoteBoundWebRtcEventLogFileNamePrefix)
           .InsertBeforeExtensionASCII(log_id)
-          .AddExtension(kRemoteBoundWebRtcEventLogExtension);
+          .AddExtension(remote_log_extension_);
   EXPECT_EQ(file_path->BaseName(), expected_filename);
 }
 
@@ -1811,6 +1882,9 @@
   ASSERT_TRUE(PeerConnectionAdded(rph_->GetID(), kLid));
   ASSERT_TRUE(StartRemoteLogging(rph_->GetID(), GetUniqueId(key)));
 
+  // Close file before examining its contents.
+  ASSERT_TRUE(PeerConnectionRemoved(key.render_process_id, key.lid));
+
   ExpectRemoteFileContents(*file_path, "");
 }
 
@@ -1842,7 +1916,7 @@
   }
 
   // All log files must be created in their own context's directory.
-  for (size_t i = 0; i < arraysize(browser_contexts); ++i) {
+  for (size_t i = 0; i < base::size(browser_contexts); ++i) {
     ASSERT_TRUE(file_paths[i]);
     EXPECT_TRUE(browser_contexts[i]->GetPath().IsParent(*file_paths[i]));
   }
@@ -1895,6 +1969,9 @@
   EXPECT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, log),
             std::make_pair(false, true));
 
+  // Close file before examining its contents.
+  ASSERT_TRUE(PeerConnectionRemoved(key.render_process_id, key.lid));
+
   ExpectRemoteFileContents(*file_path, log);
 }
 
@@ -1957,18 +2034,18 @@
       std::accumulate(std::begin(logs), std::end(logs), std::string()));
 }
 
-TEST_F(WebRtcEventLogManagerTest, RemoteLogFileSizeLimitNotExceeded) {
+TEST_F(WebRtcEventLogManagerTest,
+       RemoteLogFileSizeLimitNotExceededSingleWrite) {
   const auto key = GetPeerConnectionKey(rph_.get(), kLid);
   base::Optional<base::FilePath> file_path;
   ON_CALL(remote_observer_, OnRemoteLogStarted(key, _))
       .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
 
   const std::string log = "tpyo";
-  const size_t file_size_limit_bytes = log.length() / 2;
 
   ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid));
   ASSERT_TRUE(StartRemoteLogging(key.render_process_id, GetUniqueId(key),
-                                 file_size_limit_bytes));
+                                 GzippedSize(log) - 1));
 
   // Failure is reported, because not everything could be written. The file
   // will also be closed.
@@ -1976,11 +2053,38 @@
   ASSERT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, log),
             std::make_pair(false, false));
 
-  // Additional calls to Write() have no effect.
-  ASSERT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, "ignored"),
+  // Make sure the file would be closed, so that we could safely read it.
+  ASSERT_TRUE(PeerConnectionRemoved(key.render_process_id, key.lid));
+
+  // No partial writes occurred.
+  ExpectRemoteFileContents(*file_path, "");
+}
+
+TEST_F(WebRtcEventLogManagerTest,
+       RemoteLogFileSizeLimitNotExceededMultipleWrites) {
+  const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+  base::Optional<base::FilePath> file_path;
+  ON_CALL(remote_observer_, OnRemoteLogStarted(key, _))
+      .WillByDefault(Invoke(SaveFilePathTo(&file_path)));
+
+  const std::string log1 = "abcabc";
+  const std::string log2 = "defghijklmnopqrstuvwxyz";
+
+  ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid));
+  ASSERT_TRUE(StartRemoteLogging(key.render_process_id, GetUniqueId(key),
+                                 1 + GzippedSize(log1)));
+
+  // First write works.
+  ASSERT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, log1),
+            std::make_pair(false, true));
+
+  // On the second write, failure is reported, because not everything could be
+  // written. The file will also be closed.
+  EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
+  ASSERT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, log2),
             std::make_pair(false, false));
 
-  ExpectRemoteFileContents(*file_path, "");
+  ExpectRemoteFileContents(*file_path, log1);
 }
 
 TEST_F(WebRtcEventLogManagerTest,
@@ -2065,9 +2169,9 @@
 
 TEST_F(WebRtcEventLogManagerTest, DifferentRemoteLogsMayHaveDifferentMaximums) {
   const std::string logs[2] = {"abra", "cadabra"};
-  std::vector<base::Optional<base::FilePath>> file_paths(arraysize(logs));
+  std::vector<base::Optional<base::FilePath>> file_paths(base::size(logs));
   std::vector<PeerConnectionKey> keys;
-  for (size_t i = 0; i < arraysize(logs); ++i) {
+  for (size_t i = 0; i < base::size(logs); ++i) {
     keys.push_back(GetPeerConnectionKey(rph_.get(), i));
     ON_CALL(remote_observer_, OnRemoteLogStarted(keys[i], _))
         .WillByDefault(Invoke(SaveFilePathTo(&file_paths[i])));
@@ -2076,7 +2180,7 @@
   for (size_t i = 0; i < keys.size(); ++i) {
     ASSERT_TRUE(PeerConnectionAdded(keys[i].render_process_id, keys[i].lid));
     ASSERT_TRUE(StartRemoteLogging(keys[i].render_process_id,
-                                   GetUniqueId(keys[i]), logs[i].length()));
+                                   GetUniqueId(keys[i]), GzippedSize(logs[i])));
   }
 
   for (size_t i = 0; i < keys.size(); ++i) {
@@ -2101,7 +2205,7 @@
 
   ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid));
   ASSERT_TRUE(StartRemoteLogging(key.render_process_id, GetUniqueId(key),
-                                 log.length()));
+                                 GzippedSize(log)));
   ASSERT_TRUE(file_path);
 
   EXPECT_CALL(remote_observer_, OnRemoteLogStopped(key)).Times(1);
@@ -2121,7 +2225,7 @@
   // permissions from it, thereby preventing it from being created again.
   UnloadProfiles();
   ASSERT_TRUE(base::DeleteFile(remote_logs_path, /*recursive=*/true));
-  RemoveWritePermissionsFromDirectory(browser_context_dir);
+  RemoveWritePermissions(browser_context_dir);
 
   // Graceful handling by BrowserContext::EnableForBrowserContext, despite
   // failing to create the remote logs' directory..
@@ -2161,7 +2265,7 @@
   // Remove write permissions from the directory.
   const base::FilePath remote_logs_path = RemoteBoundLogsDir(browser_context_);
   ASSERT_TRUE(base::DirectoryExists(remote_logs_path));
-  RemoveWritePermissionsFromDirectory(remote_logs_path);
+  RemoveWritePermissions(remote_logs_path);
 
   // StartRemoteLogging() will now fail.
   const auto key = GetPeerConnectionKey(rph_.get(), kLid);
@@ -2203,7 +2307,7 @@
     ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid));
     EXPECT_CALL(remote_observer_, OnRemoteLogStarted(key, _)).Times(1);
     ASSERT_TRUE(StartRemoteLogging(key.render_process_id, GetUniqueId(key),
-                                   log.length()));
+                                   GzippedSize(log)));
   }
 
   // By writing to one of the logs until it reaches capacity, we fill it,
@@ -2318,9 +2422,55 @@
   ASSERT_TRUE(CreateDirectory(remote_logs_dir));
 
   for (size_t i = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+    const base::FilePath file_path = remote_logs_dir.Append(IntToStringType(i))
+                                         .AddExtension(remote_log_extension_);
+    constexpr int file_flags = base::File::FLAG_CREATE |
+                               base::File::FLAG_WRITE |
+                               base::File::FLAG_EXCLUSIVE_WRITE;
+    base::File file(file_path, file_flags);
+    ASSERT_TRUE(file.IsValid() && file.created());
+    expected_files.emplace_back(browser_context_id_, file_path,
+                                GetLastModificationTime(file_path));
+  }
+
+  // This factory enforces the expectation that the files will be uploaded,
+  // all of them, only them, and in the order expected.
+  base::RunLoop run_loop;
+  SetWebRtcEventLogUploaderFactoryForTesting(
+      std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+          &expected_files, true, &run_loop));
+
+  LoadProfiles();
+  ASSERT_EQ(browser_context_->GetPath(), browser_context_path);
+
+  WaitForPendingTasks(&run_loop);
+}
+
+// It is possible for remote-bound logs to be compressed or uncompressed.
+// We show that logs from a previous session are captured even if they are
+// different, with regards to compression, compared to last time.
+TEST_F(WebRtcEventLogManagerTest,
+       LogsCapturedPreviouslyMadePendingEvenIfDifferentExtensionUsed) {
+  // Unload the profile, but remember where it stores its files.
+  const base::FilePath browser_context_path = browser_context_->GetPath();
+  const base::FilePath remote_logs_dir = RemoteBoundLogsDir(browser_context_);
+  UnloadProfiles();
+
+  // Seed the remote logs' directory with log files, simulating the
+  // creation of logs in a previous session.
+  std::list<WebRtcLogFileInfo> expected_files;
+  ASSERT_TRUE(CreateDirectory(remote_logs_dir));
+
+  base::FilePath::StringPieceType extensions[] = {
+      kWebRtcEventLogUncompressedExtension, kWebRtcEventLogGzippedExtension};
+  ASSERT_LE(base::size(extensions), kMaxPendingRemoteBoundWebRtcEventLogs)
+      << "Lacking test coverage.";
+
+  for (size_t i = 0, ext = 0; i < kMaxPendingRemoteBoundWebRtcEventLogs; ++i) {
+    const auto& extension = extensions[ext];
+    ext = (ext + 1) % base::size(extensions);
     const base::FilePath file_path =
-        remote_logs_dir.Append(IntToStringType(i))
-            .AddExtension(kRemoteBoundWebRtcEventLogExtension);
+        remote_logs_dir.Append(IntToStringType(i)).AddExtension(extension);
     constexpr int file_flags = base::File::FLAG_CREATE |
                                base::File::FLAG_WRITE |
                                base::File::FLAG_EXCLUSIVE_WRITE;
@@ -2460,7 +2610,7 @@
   for (size_t i = 0; i < kProfilesNum; ++i) {
     ASSERT_TRUE(base::DirectoryExists(remote_logs_dirs[i]));
     file_paths[i] = remote_logs_dirs[i].AppendASCII("file").AddExtension(
-        kRemoteBoundWebRtcEventLogExtension);
+        remote_log_extension_);
     ASSERT_TRUE(!base::PathExists(file_paths[i]));
     constexpr int file_flags = base::File::FLAG_CREATE |
                                base::File::FLAG_WRITE |
@@ -2522,7 +2672,7 @@
   base::FilePath file_paths[2];
   for (size_t i = 0; i < 2; ++i) {
     file_paths[i] = remote_logs_dir.Append(IntToStringType(i))
-                        .AddExtension(kRemoteBoundWebRtcEventLogExtension);
+                        .AddExtension(remote_log_extension_);
     constexpr int file_flags = base::File::FLAG_CREATE |
                                base::File::FLAG_WRITE |
                                base::File::FLAG_EXCLUSIVE_WRITE;
@@ -2609,7 +2759,7 @@
   const base::FilePath permissions_lacking_remote_logs_path =
       RemoteBoundLogsDir(browser_contexts[without_permissions]);
   ASSERT_TRUE(base::DirectoryExists(permissions_lacking_remote_logs_path));
-  RemoveWritePermissionsFromDirectory(permissions_lacking_remote_logs_path);
+  RemoveWritePermissions(permissions_lacking_remote_logs_path);
 
   // Fail to start a log associated with the permission-lacking directory.
   const auto without_permissions_key =
@@ -3588,6 +3738,42 @@
   WaitForPendingTasks(&run_loop);
 }
 
+TEST_F(WebRtcEventLogManagerTestCompression,
+       ErroredFilesDueToBadEstimationDeletedRatherThanUploaded) {
+  Init(Compression::GZIP_NULL_ESTIMATION);
+
+  const std::string log = "It's better than bad; it's good.";
+
+  const auto key = GetPeerConnectionKey(rph_.get(), kLid);
+  base::Optional<base::FilePath> log_file;
+  ON_CALL(remote_observer_, OnRemoteLogStarted(key, _))
+      .WillByDefault(Invoke(SaveFilePathTo(&log_file)));
+  ASSERT_TRUE(PeerConnectionAdded(key.render_process_id, key.lid));
+  ASSERT_TRUE(StartRemoteLogging(key.render_process_id, GetUniqueId(key),
+                                 GzippedSize(log) - 1));
+  ASSERT_TRUE(log_file);
+
+  std::list<WebRtcLogFileInfo> empty_list;
+  base::RunLoop run_loop;
+  SetWebRtcEventLogUploaderFactoryForTesting(
+      std::make_unique<FileListExpectingWebRtcEventLogUploader::Factory>(
+          &empty_list, true, &run_loop));
+
+  // Writing fails because the budget is exceeded.
+  EXPECT_EQ(OnWebRtcEventLogWrite(key.render_process_id, key.lid, log),
+            std::make_pair(false, false));
+
+  // The file was deleted due to the error we've instigated (by using an
+  // intentionally over-optimistic estimation).
+  EXPECT_FALSE(base::PathExists(*log_file));
+
+  // If the file is incorrectly still eligible for an upload, this will trigger
+  // the upload (which will be a test failure).
+  ASSERT_TRUE(PeerConnectionRemoved(key.render_process_id, key.lid));
+
+  WaitForPendingTasks(&run_loop);
+}
+
 #else  // defined(OS_ANDROID)
 
 class WebRtcEventLogManagerTestOnMobileDevices
@@ -3597,7 +3783,7 @@
     // features::kWebRtcRemoteEventLog not defined on mobile, and can therefore
     // not be forced on. This test is here to make sure that when the feature
     // is changed to be on by default, it will still be off for mobile devices.
-    event_log_manager_ = WebRtcEventLogManager::CreateSingletonInstance();
+    CreateWebRtcEventLogManager();
   }
 };
 
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
new file mode 100644
index 0000000..b5d6c42
--- /dev/null
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2018 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/media/webrtc/webrtc_event_log_manager_unittest_helpers.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+    WebRtcEventLogCompression compression) {
+  switch (compression) {
+    case WebRtcEventLogCompression::NONE:
+      return std::make_unique<BaseLogFileWriterFactory>();
+    case WebRtcEventLogCompression::GZIP_NULL_ESTIMATION:
+      return std::make_unique<GzippedLogFileWriterFactory>(
+          std::make_unique<GzipLogCompressorFactory>(
+              std::make_unique<NullEstimator::Factory>()));
+    case WebRtcEventLogCompression::GZIP_PERFECT_ESTIMATION:
+      return std::make_unique<GzippedLogFileWriterFactory>(
+          std::make_unique<GzipLogCompressorFactory>(
+              std::make_unique<PerfectGzipEstimator::Factory>()));
+  }
+  NOTREACHED();
+  return nullptr;  // Appease compiler.
+}
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path) {
+  int permissions;
+  ASSERT_TRUE(base::GetPosixFilePermissions(path, &permissions));
+  constexpr int write_permissions = base::FILE_PERMISSION_WRITE_BY_USER |
+                                    base::FILE_PERMISSION_WRITE_BY_GROUP |
+                                    base::FILE_PERMISSION_WRITE_BY_OTHERS;
+  permissions &= ~write_permissions;
+  ASSERT_TRUE(base::SetPosixFilePermissions(path, permissions));
+}
+#endif  // defined(OS_POSIX)
+
+std::unique_ptr<CompressedSizeEstimator> NullEstimator::Factory::Create()
+    const {
+  return std::make_unique<NullEstimator>();
+}
+
+size_t NullEstimator::EstimateCompressedSize(const std::string& input) const {
+  return 0;
+}
+
+std::unique_ptr<CompressedSizeEstimator> PerfectGzipEstimator::Factory::Create()
+    const {
+  return std::make_unique<PerfectGzipEstimator>();
+}
+
+PerfectGzipEstimator::PerfectGzipEstimator() {
+  // This factory will produce an optimistic compressor that will always
+  // think it can compress additional inputs, which will therefore allow
+  // us to find out what the real compressed size it, since compression
+  // will never be suppressed.
+  GzipLogCompressorFactory factory(std::make_unique<NullEstimator::Factory>());
+
+  compressor_ = factory.Create(base::Optional<size_t>());
+  DCHECK(compressor_);
+
+  std::string ignored;
+  compressor_->CreateHeader(&ignored);
+}
+
+PerfectGzipEstimator::~PerfectGzipEstimator() = default;
+
+size_t PerfectGzipEstimator::EstimateCompressedSize(
+    const std::string& input) const {
+  std::string output;
+  EXPECT_EQ(compressor_->Compress(input, &output), LogCompressor::Result::OK);
+  return output.length();
+}
+
+size_t GzippedSize(const std::string& uncompressed) {
+  PerfectGzipEstimator perfect_estimator;
+  return kGzipOverheadBytes +
+         perfect_estimator.EstimateCompressedSize(uncompressed);
+}
+
+size_t GzippedSize(const std::vector<std::string>& uncompressed) {
+  PerfectGzipEstimator perfect_estimator;
+
+  size_t result = kGzipOverheadBytes;
+  for (const std::string& str : uncompressed) {
+    result += perfect_estimator.EstimateCompressedSize(str);
+  }
+
+  return result;
+}
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
new file mode 100644
index 0000000..1bc3301d
--- /dev/null
+++ b/chrome/browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2018 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_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+#define CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
+
+// Which type of compression, if any, LogFileWriterTest should use.
+enum class WebRtcEventLogCompression {
+  NONE,
+  GZIP_NULL_ESTIMATION,
+  GZIP_PERFECT_ESTIMATION
+};
+
+// Produce a LogFileWriter::Factory object.
+std::unique_ptr<LogFileWriter::Factory> CreateLogFileWriterFactory(
+    WebRtcEventLogCompression compression);
+
+#if defined(OS_POSIX)
+void RemoveWritePermissions(const base::FilePath& path);
+#endif  // defined(OS_POSIX)
+
+// Always estimates strings to be compressed to zero bytes.
+class NullEstimator : public CompressedSizeEstimator {
+ public:
+  class Factory : public CompressedSizeEstimator::Factory {
+   public:
+    ~Factory() override = default;
+
+    std::unique_ptr<CompressedSizeEstimator> Create() const override;
+  };
+
+  ~NullEstimator() override = default;
+
+  size_t EstimateCompressedSize(const std::string& input) const override;
+};
+
+// Provides a perfect estimation of the compressed size by cheating - performing
+// actual compression, then reporting the resulting size.
+// This class is stateful; the number, nature and order of calls to
+// EstimateCompressedSize() is important.
+class PerfectGzipEstimator : public CompressedSizeEstimator {
+ public:
+  class Factory : public CompressedSizeEstimator::Factory {
+   public:
+    ~Factory() override = default;
+
+    std::unique_ptr<CompressedSizeEstimator> Create() const override;
+  };
+
+  PerfectGzipEstimator();
+
+  ~PerfectGzipEstimator() override;
+
+  size_t EstimateCompressedSize(const std::string& input) const override;
+
+ private:
+  // This compressor allows EstimateCompressedSize to return an exact estimate.
+  // EstimateCompressedSize is normally const, but here we fake it, so we set
+  // it as mutable.
+  mutable std::unique_ptr<LogCompressor> compressor_;
+};
+
+// Check the gzipped size of |uncompressed|, including header and footer,
+// assuming it were gzipped on its own.
+size_t GzippedSize(const std::string& uncompressed);
+
+// Same as other version, but with elements compressed in sequence.
+size_t GzippedSize(const std::vector<std::string>& uncompressed);
+
+#endif  // CHROME_BROWSER_MEDIA_WEBRTC_WEBRTC_EVENT_LOG_MANAGER_UNITTEST_HELPERS_H_
diff --git a/chrome/browser/media/webrtc/webrtc_event_log_uploader.h b/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
index b40b3ad5..368bea9 100644
--- a/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
+++ b/chrome/browser/media/webrtc/webrtc_event_log_uploader.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/files/file_path.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/sequenced_task_runner.h"
 #include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
 #include "net/url_request/url_fetcher.h"
diff --git a/chrome/browser/metrics/chrome_metrics_service_accessor.h b/chrome/browser/metrics/chrome_metrics_service_accessor.h
index f2f3e45..f6ee3ad 100644
--- a/chrome/browser/metrics/chrome_metrics_service_accessor.h
+++ b/chrome/browser/metrics/chrome_metrics_service_accessor.h
@@ -139,6 +139,7 @@
   friend class ChromeMetricsServiceClient;
   friend class ChromePasswordManagerClient;
   friend class NavigationMetricsRecorder;
+  friend class ChromeUnifiedConsentServiceClient;
 
   // Testing related friends.
   friend class MetricsReportingStateTest;
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 4a05da8..199cec2 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -46,6 +46,7 @@
 #include "chrome/browser/metrics/subprocess_metrics_provider.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/safe_browsing/certificate_reporting_metrics_provider.h"
+#include "chrome/browser/signin/unified_consent_helper.h"
 #include "chrome/browser/sync/chrome_sync_client.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/translate/translate_ranker_metrics_provider.h"
@@ -906,12 +907,13 @@
 
   ObserveServiceForDeletions(history_service);
 
-  browser_sync::ProfileSyncService* sync =
-      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
+  syncer::SyncService* sync =
+      ProfileSyncServiceFactory::GetSyncServiceForBrowserContext(profile);
   if (!sync) {
     return false;
   }
-  ObserveServiceForSyncDisables(static_cast<syncer::SyncService*>(sync));
+  ObserveServiceForSyncDisables(sync, profile->GetPrefs(),
+                                IsUnifiedConsentEnabled(profile));
   return true;
 }
 
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter.cc b/chrome/browser/metrics/process_memory_metrics_emitter.cc
index 9eadd75d..2783570 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter.cc
@@ -473,6 +473,7 @@
     return;
 
   uint32_t private_footprint_total_kb = 0;
+  uint32_t renderer_private_footprint_total_kb = 0;
   uint32_t shared_footprint_total_kb = 0;
   bool record_uma = pid_scope_ == base::kNullProcessId;
 
@@ -492,6 +493,8 @@
         break;
       }
       case memory_instrumentation::mojom::ProcessType::RENDERER: {
+        renderer_private_footprint_total_kb +=
+            pmd.os_dump().private_footprint_kb;
         resource_coordinator::mojom::PageInfoPtr page_info;
         // If there is more than one frame being hosted in a renderer, don't
         // emit any URLs. This is not ideal, but UKM does not support
@@ -529,12 +532,15 @@
         break;
     }
   }
+
   if (record_uma) {
     UMA_HISTOGRAM_MEMORY_LARGE_MB(
         "Memory.Experimental.Total2.PrivateMemoryFootprint",
         private_footprint_total_kb / 1024);
     UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Total.PrivateMemoryFootprint",
                                   private_footprint_total_kb / 1024);
+    UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Total.RendererPrivateMemoryFootprint",
+                                  renderer_private_footprint_total_kb / 1024);
     UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Total.SharedMemoryFootprint",
                                   shared_footprint_total_kb / 1024);
   }
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
index 294fb47..7136d8c 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/ref_counted.h"
 #include "base/process/process_handle.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/metrics/renderer_uptime_tracker.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -662,3 +663,31 @@
   EXPECT_EQ(1, total_memory_entries);
   EXPECT_EQ(1, entries_with_urls);
 }
+
+TEST_F(ProcessMemoryMetricsEmitterTest,
+       CheckForRendererPrivateMemoryFootprint) {
+  GlobalMemoryDumpPtr global_dump(
+      memory_instrumentation::mojom::GlobalMemoryDump::New());
+  base::flat_map<const char*, int64_t> expected_metrics =
+      GetExpectedRendererMetrics();
+  PopulateRendererMetrics(global_dump, expected_metrics, 201);
+
+  // Take a snapshot of the current state of the histograms.
+  base::HistogramTester sentinel;
+  auto samples = sentinel.GetHistogramSamplesSinceCreation(
+      "Memory.Total.RendererPrivateMemoryFootprint");
+  EXPECT_EQ(0, samples->TotalCount());
+  samples.reset();
+
+  // Simulate some metrics emission.
+  scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter(
+      new ProcessMemoryMetricsEmitterFake(test_ukm_recorder_));
+  emitter->ReceivedMemoryDump(
+      true, GlobalMemoryDump::MoveFrom(std::move(global_dump)));
+  emitter->ReceivedProcessInfos(GetProcessInfo(test_ukm_recorder_));
+
+  // Check that the RendererPrivateMemoryFootprint histogram got written to.
+  samples = sentinel.GetHistogramSamplesSinceCreation(
+      "Memory.Total.RendererPrivateMemoryFootprint");
+  EXPECT_EQ(1, samples->TotalCount());
+}
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index c7856b9..4882373 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
 #include "base/sys_info.h"
 #include "build/build_config.h"
+#include "build/buildflag.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
@@ -13,10 +16,12 @@
 #include "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
 #include "chrome/browser/metrics/testing/metrics_reporting_pref_helper.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/unified_consent_helper.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/unified_consent/unified_consent_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/browser_sync/profile_sync_service.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
@@ -24,6 +29,7 @@
 #include "components/sync/test/fake_server/fake_server_network_resources.h"
 #include "components/ukm/ukm_service.h"
 #include "components/ukm/ukm_source.h"
+#include "components/unified_consent/scoped_unified_consent.h"
 #include "components/variations/service/variations_field_trial_creator.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/browsing_data_remover.h"
@@ -34,6 +40,10 @@
 #include "third_party/metrics_proto/ukm/report.pb.h"
 #include "third_party/zlib/google/compression_utils.h"
 
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+#include "chrome/browser/signin/scoped_account_consistency.h"
+#endif
+
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/signin/signin_manager_factory.h"
 #include "components/signin/core/browser/signin_manager_base.h"
@@ -105,9 +115,20 @@
 };
 
 // Test fixture that provides access to some UKM internals.
-class UkmBrowserTest : public SyncTest {
+class UkmBrowserTestBase : public SyncTest {
  public:
-  UkmBrowserTest() : SyncTest(SINGLE_CLIENT) {}
+  explicit UkmBrowserTestBase(bool is_unified_consent_enabled)
+      : SyncTest(SINGLE_CLIENT),
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+        scoped_dice_(is_unified_consent_enabled
+                         ? std::make_unique<ScopedAccountConsistencyDice>()
+                         : nullptr),
+#endif
+        scoped_unified_consent_(
+            is_unified_consent_enabled
+                ? unified_consent::UnifiedConsentFeatureState::kEnabledNoBump
+                : unified_consent::UnifiedConsentFeatureState::kDisabled) {
+  }
 
   void SetUp() override {
     // Explicitly enable UKM and disable the MetricsReporting (which should
@@ -174,6 +195,7 @@
  protected:
   std::unique_ptr<ProfileSyncServiceHarness> EnableSyncForProfile(
       Profile* profile) {
+    UnifiedConsentServiceFactory::GetForProfile(profile);
     browser_sync::ProfileSyncService* sync_service =
         ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
 
@@ -223,16 +245,42 @@
     return g_browser_process->GetMetricsServicesManager()->GetUkmService();
   }
   base::test::ScopedFeatureList scoped_feature_list_;
+#if BUILDFLAG(ENABLE_DICE_SUPPORT)
+  // ScopedAccountConsistencyDice is required for unified consent to be enabled.
+  // Note that it uses forced field trials to enable DICE which disable metrics
+  // which are required by UkmConsentParamBrowserTest (see
+  // |IsMetricsReportingEnabledForOfficialBuild|).
+  const std::unique_ptr<ScopedAccountConsistencyDice> scoped_dice_;
+#endif
+  const unified_consent::ScopedUnifiedConsent scoped_unified_consent_;
+  DISALLOW_COPY_AND_ASSIGN(UkmBrowserTestBase);
+};
+
+class UkmBrowserTestUnifiedConsentDisabled : public UkmBrowserTestBase {
+ public:
+  UkmBrowserTestUnifiedConsentDisabled() : UkmBrowserTestBase(false) {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UkmBrowserTestUnifiedConsentDisabled);
+};
+
+class UkmBrowserTest : public UkmBrowserTestBase,
+                       public testing::WithParamInterface<bool> {
+ public:
+  UkmBrowserTest() : UkmBrowserTestBase(GetParam()) {}
+
+ private:
   DISALLOW_COPY_AND_ASSIGN(UkmBrowserTest);
 };
 
 // This tests if UKM service is enabled/disabled appropriately based on an
 // input bool param. The bool reflects if metrics reporting state is
 // enabled/disabled via prefs.
-class UkmConsentParamBrowserTest : public UkmBrowserTest,
+class UkmConsentParamBrowserTest : public UkmBrowserTestBase,
                                    public testing::WithParamInterface<bool> {
  public:
-  UkmConsentParamBrowserTest() {}
+  UkmConsentParamBrowserTest()
+      : UkmBrowserTestBase(/*is_unified_consent=*/false) {}
 
   static bool IsMetricsAndCrashReportingEnabled() {
     return ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled();
@@ -262,7 +310,7 @@
 
 class UkmEnabledChecker : public SingleClientStatusChangeChecker {
  public:
-  UkmEnabledChecker(UkmBrowserTest* test,
+  UkmEnabledChecker(UkmBrowserTestBase* test,
                     browser_sync::ProfileSyncService* service,
                     bool want_enabled)
       : SingleClientStatusChangeChecker(service),
@@ -279,7 +327,7 @@
   }
 
  private:
-  UkmBrowserTest* const test_;
+  UkmBrowserTestBase* const test_;
   const bool want_enabled_;
   DISALLOW_COPY_AND_ASSIGN(UkmEnabledChecker);
 };
@@ -288,7 +336,7 @@
 // Keep in sync with UkmTest.testRegularPlusIncognitoCheck in
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, RegularPlusIncognitoCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, RegularPlusIncognitoCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -327,7 +375,7 @@
 // Keep in sync with UkmTest.testIncognitoPlusRegularCheck in
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, IncognitoPlusRegularCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, IncognitoPlusRegularCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -348,7 +396,7 @@
 }
 
 // Make sure that UKM is disabled while a guest profile's window is open.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, RegularPlusGuestCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, RegularPlusGuestCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -376,7 +424,7 @@
 }
 
 // Make sure that UKM is disabled while an non-sync profile's window is open.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, OpenNonSyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, OpenNonSyncCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -407,7 +455,7 @@
 // chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
 
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MetricsConsentCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, MetricsConsentCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -437,7 +485,7 @@
   CloseBrowserSynchronously(sync_browser);
 }
 
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, LogProtoData) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, LogProtoData) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -479,7 +527,7 @@
 // Keep in sync with UkmTest.consentAddedButNoSyncCheck in
 // chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, ConsentAddedButNoSyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, ConsentAddedButNoSyncCheck) {
   MetricsConsentOverride metrics_consent(false);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -502,7 +550,7 @@
 // Keep in sync with UkmTest.singleDisableHistorySyncCheck in
 // chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SingleDisableHistorySyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, SingleDisableHistorySyncCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -515,19 +563,25 @@
   EXPECT_NE(0U, original_client_id);
 
   harness->DisableSyncForDatatype(syncer::TYPED_URLS);
-  EXPECT_FALSE(ukm_enabled());
+  if (IsUnifiedConsentEnabled(profile)) {
+    // Disable history sync does not disable UKM when unified consent is
+    // enabled.
+    EXPECT_TRUE(ukm_enabled());
+  } else {
+    EXPECT_FALSE(ukm_enabled());
 
-  harness->EnableSyncForDatatype(syncer::TYPED_URLS);
-  EXPECT_TRUE(ukm_enabled());
-  // Client ID should be reset.
-  EXPECT_NE(original_client_id, client_id());
+    harness->EnableSyncForDatatype(syncer::TYPED_URLS);
+    EXPECT_TRUE(ukm_enabled());
+    // Client ID should be reset.
+    EXPECT_NE(original_client_id, client_id());
+  }
 
   harness->service()->RequestStop(browser_sync::ProfileSyncService::CLEAR_DATA);
   CloseBrowserSynchronously(sync_browser);
 }
 
 // Make sure that UKM is disabled when any open sync window disables history.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MultiDisableHistorySyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, MultiDisableHistorySyncCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile1 = ProfileManager::GetActiveUserProfile();
@@ -547,10 +601,16 @@
   EXPECT_EQ(original_client_id, client_id());
 
   harness2->DisableSyncForDatatype(syncer::TYPED_URLS);
-  EXPECT_FALSE(ukm_enabled());
-  EXPECT_NE(original_client_id, client_id());
-  original_client_id = client_id();
-  EXPECT_NE(0U, original_client_id);
+  if (IsUnifiedConsentEnabled(profile2)) {
+    // Disable history sync does not disable UKM when unified consent is
+    // enabled.
+    EXPECT_TRUE(ukm_enabled());
+  } else {
+    EXPECT_FALSE(ukm_enabled());
+    EXPECT_NE(original_client_id, client_id());
+    original_client_id = client_id();
+    EXPECT_NE(0U, original_client_id);
+  }
 
   harness2->EnableSyncForDatatype(syncer::TYPED_URLS);
   EXPECT_TRUE(ukm_enabled());
@@ -566,7 +626,7 @@
 
 // Make sure that extension URLs are disabled when an open sync window
 // disables it.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SingleDisableExtensionsSyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, SingleDisableExtensionsSyncCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -579,11 +639,11 @@
   uint64_t original_client_id = client_id();
   EXPECT_NE(0U, original_client_id);
 
-  harness->DisableSyncForDatatype(syncer::EXTENSIONS);
+  ASSERT_TRUE(harness->DisableSyncForDatatype(syncer::EXTENSIONS));
   EXPECT_TRUE(ukm_enabled());
   EXPECT_FALSE(ukm_extensions_enabled());
 
-  harness->EnableSyncForDatatype(syncer::EXTENSIONS);
+  ASSERT_TRUE(harness->EnableSyncForDatatype(syncer::EXTENSIONS));
   EXPECT_TRUE(ukm_enabled());
   EXPECT_TRUE(ukm_extensions_enabled());
   // Client ID should not be reset.
@@ -595,7 +655,7 @@
 
 // Make sure that extension URLs are disabled when any open sync window
 // disables it.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MultiDisableExtensionsSyncCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, MultiDisableExtensionsSyncCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile1 = ProfileManager::GetActiveUserProfile();
@@ -636,7 +696,8 @@
 // Keep in sync with UkmTest.secondaryPassphraseCheck in
 // chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SecondaryPassphraseCheck) {
+IN_PROC_BROWSER_TEST_F(UkmBrowserTestUnifiedConsentDisabled,
+                       SecondaryPassphraseCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -670,7 +731,7 @@
 // Keep in sync with UkmTest.singleSyncSignoutCheck in
 // chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, SingleSyncSignoutCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, SingleSyncSignoutCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -695,7 +756,7 @@
 // there.
 #if !defined(OS_CHROMEOS)
 // Make sure that UKM is disabled when any profile signs out of Sync.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MultiSyncSignoutCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, MultiSyncSignoutCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile1 = ProfileManager::GetActiveUserProfile();
@@ -729,7 +790,7 @@
 
 // Make sure that if history/sync services weren't available when we tried to
 // attach listeners, UKM is not enabled.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, ServiceListenerInitFailedCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, ServiceListenerInitFailedCheck) {
   MetricsConsentOverride metrics_consent(true);
   ChromeMetricsServiceClient::SetNotificationListenerSetupFailedForTesting(
       true);
@@ -745,7 +806,7 @@
 }
 
 // Make sure that UKM is not affected by MetricsReporting Feature (sampling).
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, MetricsReportingCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, MetricsReportingCheck) {
   // Need to set the Metrics Default to OPT_OUT to trigger MetricsReporting.
   DCHECK(g_browser_process);
   PrefService* local_state = g_browser_process->local_state();
@@ -773,7 +834,7 @@
 // Keep in sync with UkmTest.testHistoryDeleteCheck in
 // chrome/android/javatests/src/org/chromium/chrome/browser/metrics/
 // UkmTest.java.
-IN_PROC_BROWSER_TEST_F(UkmBrowserTest, HistoryDeleteCheck) {
+IN_PROC_BROWSER_TEST_P(UkmBrowserTest, HistoryDeleteCheck) {
   MetricsConsentOverride metrics_consent(true);
 
   Profile* profile = ProfileManager::GetActiveUserProfile();
@@ -801,6 +862,9 @@
   CloseBrowserSynchronously(sync_browser);
 }
 
+// Run UKM browser test suite with Unified Consent enabled and disabled.
+INSTANTIATE_TEST_CASE_P(, UkmBrowserTest, testing::Bool());
+
 IN_PROC_BROWSER_TEST_P(UkmConsentParamBrowserTest, GroupPolicyConsentCheck) {
   // Note we are not using the synthetic MetricsConsentOverride since we are
   // testing directly from prefs.
diff --git a/chrome/browser/notifications/platform_notification_service_impl.cc b/chrome/browser/notifications/platform_notification_service_impl.cc
index b0fd4fd..68e9d859 100644
--- a/chrome/browser/notifications/platform_notification_service_impl.cc
+++ b/chrome/browser/notifications/platform_notification_service_impl.cc
@@ -207,8 +207,8 @@
     base::OnceClosure completed_closure) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  // TODO(peter): Should we do permission checks prior to forwarding to the
-  // NotificationEventDispatcher?
+  // TODO(https://crbug.com/870255): If permission is revoked, don't dispatch
+  // an event (still need to remove notification from database though).
 
   // If we programatically closed this notification, don't dispatch any event.
   if (closed_notifications_.erase(notification_id) != 0) {
diff --git a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
index bda0dd5..56b3a44 100644
--- a/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_request_handler_unittest.cc
@@ -1638,6 +1638,7 @@
                       AGGREGATED_REQUEST_RESULT_MAX);
 }
 
+// TODO(https://crbug.com/830282): Flaky on "Marshmallow Phone Tester (rel)".
 TYPED_TEST(OfflinePageRequestHandlerTest,
            DISABLED_LoadMostRecentlyCreatedOfflinePage) {
   this->SimulateHasNetworkConnectivity(false);
@@ -1703,6 +1704,7 @@
                       PAGE_NOT_FOUND_ON_CONNECTED_NETWORK);
 }
 
+// TODO(https://crbug.com/830282): Flaky on "Marshmallow Phone Tester (rel)".
 TYPED_TEST(OfflinePageRequestHandlerTest,
            DISABLED_LoadOfflinePageForUrlWithFragment) {
   this->SimulateHasNetworkConnectivity(false);
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index 39e19f51..8a390ddf 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -22,7 +22,6 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h"
 #include "chrome/browser/page_load_metrics/observers/aborts_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/core_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/document_write_page_load_metrics_observer.h"
@@ -31,6 +30,7 @@
 #include "chrome/browser/page_load_metrics/observers/ukm_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/observers/use_counter_page_load_metrics_observer.h"
 #include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"
+#include "chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h"
 #include "chrome/browser/page_load_metrics/page_load_tracker.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/browser/prerender/prerender_histograms.h"
@@ -82,277 +82,9 @@
 #include "third_party/blink/public/platform/web_feature.mojom.h"
 #include "url/gurl.h"
 
-namespace {
-
-// Waits until specified timing and metadata expectations are satisfied.
-class PageLoadMetricsWaiter
-    : public page_load_metrics::MetricsWebContentsObserver::TestingObserver {
- public:
-  // A bitvector to express which timing fields to match on.
-  enum class TimingField : int {
-    FIRST_LAYOUT = 1 << 0,
-    FIRST_PAINT = 1 << 1,
-    FIRST_CONTENTFUL_PAINT = 1 << 2,
-    FIRST_MEANINGFUL_PAINT = 1 << 3,
-    DOCUMENT_WRITE_BLOCK_RELOAD = 1 << 4,
-    LOAD_EVENT = 1 << 5,
-    // LOAD_TIMING_INFO waits for main frame timing info only.
-    LOAD_TIMING_INFO = 1 << 6,
-  };
-
-  explicit PageLoadMetricsWaiter(content::WebContents* web_contents)
-      : TestingObserver(web_contents), weak_factory_(this) {}
-
-  ~PageLoadMetricsWaiter() override {
-    CHECK(did_add_observer_);
-    CHECK_EQ(nullptr, run_loop_.get());
-  }
-
-  // Add a page-level expectation.
-  void AddPageExpectation(TimingField field) {
-    page_expected_fields_.Set(field);
-    if (field == TimingField::LOAD_TIMING_INFO) {
-      attach_on_tracker_creation_ = true;
-    }
-  }
-
-  // Add a subframe-level expectation.
-  void AddSubFrameExpectation(TimingField field) {
-    CHECK_NE(field, TimingField::LOAD_TIMING_INFO)
-        << "LOAD_TIMING_INFO should only be used as a page-level expectation";
-    subframe_expected_fields_.Set(field);
-    // If the given field is also a page-level field, then add a page-level
-    // expectation as well
-    if (IsPageLevelField(field))
-      page_expected_fields_.Set(field);
-  }
-
-  void AddMinimumPageLoadDataUseExpectation(
-      int expected_minimum_page_load_data_use) {
-    expected_minimum_page_load_data_use_ = expected_minimum_page_load_data_use;
-  }
-
-  // Whether the given TimingField was observed in the page.
-  bool DidObserveInPage(TimingField field) {
-    return observed_page_fields_.IsSet(field);
-  }
-
-  // Waits for PageLoadMetrics events that match the fields set by
-  // |AddPageExpectation| and |AddSubFrameExpectation|. All matching fields
-  // must be set to end this wait.
-  void Wait() {
-    if (expectations_satisfied())
-      return;
-
-    run_loop_ = std::make_unique<base::RunLoop>();
-    run_loop_->Run();
-    run_loop_ = nullptr;
-
-    EXPECT_TRUE(expectations_satisfied());
-  }
-
-  void OnTimingUpdated(content::RenderFrameHost* subframe_rfh,
-                       const page_load_metrics::mojom::PageLoadTiming& timing,
-                       const page_load_metrics::PageLoadExtraInfo& extra_info) {
-    if (expectations_satisfied())
-      return;
-
-    const page_load_metrics::mojom::PageLoadMetadata& metadata =
-        subframe_rfh ? extra_info.subframe_metadata
-                     : extra_info.main_frame_metadata;
-    TimingFieldBitSet matched_bits = GetMatchedBits(timing, metadata);
-    if (subframe_rfh) {
-      subframe_expected_fields_.ClearMatching(matched_bits);
-    } else {
-      page_expected_fields_.ClearMatching(matched_bits);
-      observed_page_fields_.Merge(matched_bits);
-    }
-
-    if (expectations_satisfied() && run_loop_)
-      run_loop_->Quit();
-  }
-
-  void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
-                            extra_request_complete_info) {
-    if (expectations_satisfied())
-      return;
-
-    if (extra_request_complete_info.resource_type !=
-        content::RESOURCE_TYPE_MAIN_FRAME) {
-      // The waiter confirms loading timing for the main frame only.
-      return;
-    }
-
-    if (!extra_request_complete_info.load_timing_info->send_start.is_null() &&
-        !extra_request_complete_info.load_timing_info->send_end.is_null() &&
-        !extra_request_complete_info.load_timing_info->request_start
-             .is_null()) {
-      page_expected_fields_.Clear(TimingField::LOAD_TIMING_INFO);
-      observed_page_fields_.Set(TimingField::LOAD_TIMING_INFO);
-    }
-
-    if (expectations_satisfied() && run_loop_)
-      run_loop_->Quit();
-  }
-
-  void OnDataUseObserved(int64_t received_data_length,
-                         int64_t data_reduction_proxy_bytes_saved) {
-    current_page_load_data_use_ += received_data_length;
-    if (expectations_satisfied() && run_loop_)
-      run_loop_->Quit();
-  }
-
-  int64_t current_page_load_data_use() { return current_page_load_data_use_; }
-
- private:
-  // PageLoadMetricsObserver used by the PageLoadMetricsWaiter to observe
-  // metrics updates.
-  class WaiterMetricsObserver
-      : public page_load_metrics::PageLoadMetricsObserver {
-   public:
-    // We use a WeakPtr to the PageLoadMetricsWaiter because |waiter| can be
-    // destroyed before this WaiterMetricsObserver.
-    explicit WaiterMetricsObserver(base::WeakPtr<PageLoadMetricsWaiter> waiter)
-        : waiter_(waiter) {}
-
-    void OnTimingUpdate(
-        content::RenderFrameHost* subframe_rfh,
-        const page_load_metrics::mojom::PageLoadTiming& timing,
-        const page_load_metrics::PageLoadExtraInfo& extra_info) override {
-      if (waiter_)
-        waiter_->OnTimingUpdated(subframe_rfh, timing, extra_info);
-    }
-
-    void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
-                              extra_request_complete_info) override {
-      if (waiter_)
-        waiter_->OnLoadedResource(extra_request_complete_info);
-    }
-
-    void OnDataUseObserved(int64_t received_data_length,
-                           int64_t data_reduction_proxy_bytes_saved) override {
-      if (waiter_)
-        waiter_->OnDataUseObserved(received_data_length,
-                                   data_reduction_proxy_bytes_saved);
-    }
-
-   private:
-    const base::WeakPtr<PageLoadMetricsWaiter> waiter_;
-  };
-
-  // Manages a bitset of TimingFields.
-  class TimingFieldBitSet {
-   public:
-    TimingFieldBitSet() {}
-
-    // Returns whether this bitset has all bits unset.
-    bool Empty() const { return bitmask_ == 0; }
-
-    // Returns whether this bitset has the given bit set.
-    bool IsSet(TimingField field) const {
-      return (bitmask_ & static_cast<int>(field)) != 0;
-    }
-
-    // Sets the bit for the given |field|.
-    void Set(TimingField field) { bitmask_ |= static_cast<int>(field); }
-
-    // Clears the bit for the given |field|.
-    void Clear(TimingField field) { bitmask_ &= ~static_cast<int>(field); }
-
-    // Merges bits set in |other| into this bitset.
-    void Merge(const TimingFieldBitSet& other) { bitmask_ |= other.bitmask_; }
-
-    // Clears all bits set in the |other| bitset.
-    void ClearMatching(const TimingFieldBitSet& other) {
-      bitmask_ &= ~other.bitmask_;
-    }
-
-   private:
-    int bitmask_ = 0;
-  };
-
-  static bool IsPageLevelField(TimingField field) {
-    switch (field) {
-      case TimingField::FIRST_PAINT:
-      case TimingField::FIRST_CONTENTFUL_PAINT:
-      case TimingField::FIRST_MEANINGFUL_PAINT:
-        return true;
-      default:
-        return false;
-    }
-  }
-
-  static TimingFieldBitSet GetMatchedBits(
-      const page_load_metrics::mojom::PageLoadTiming& timing,
-      const page_load_metrics::mojom::PageLoadMetadata& metadata) {
-    TimingFieldBitSet matched_bits;
-    if (timing.document_timing->first_layout)
-      matched_bits.Set(TimingField::FIRST_LAYOUT);
-    if (timing.document_timing->load_event_start)
-      matched_bits.Set(TimingField::LOAD_EVENT);
-    if (timing.paint_timing->first_paint)
-      matched_bits.Set(TimingField::FIRST_PAINT);
-    if (timing.paint_timing->first_contentful_paint)
-      matched_bits.Set(TimingField::FIRST_CONTENTFUL_PAINT);
-    if (timing.paint_timing->first_meaningful_paint)
-      matched_bits.Set(TimingField::FIRST_MEANINGFUL_PAINT);
-    if (metadata.behavior_flags &
-        blink::WebLoadingBehaviorFlag::
-            kWebLoadingBehaviorDocumentWriteBlockReload)
-      matched_bits.Set(TimingField::DOCUMENT_WRITE_BLOCK_RELOAD);
-
-    return matched_bits;
-  }
-
-  void OnTrackerCreated(page_load_metrics::PageLoadTracker* tracker) override {
-    if (!attach_on_tracker_creation_)
-      return;
-    // A PageLoadMetricsWaiter should only wait for events from a single page
-    // load.
-    ASSERT_FALSE(did_add_observer_);
-    tracker->AddObserver(
-        std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
-    did_add_observer_ = true;
-  }
-
-  void OnCommit(page_load_metrics::PageLoadTracker* tracker) override {
-    if (attach_on_tracker_creation_)
-      return;
-    // A PageLoadMetricsWaiter should only wait for events from a single page
-    // load.
-    ASSERT_FALSE(did_add_observer_);
-    tracker->AddObserver(
-        std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
-    did_add_observer_ = true;
-  }
-
-  bool expectations_satisfied() const {
-    return subframe_expected_fields_.Empty() && page_expected_fields_.Empty() &&
-           (expected_minimum_page_load_data_use_ == 0 ||
-            current_page_load_data_use_ >=
-                expected_minimum_page_load_data_use_);
-  }
-
-  std::unique_ptr<base::RunLoop> run_loop_;
-
-  TimingFieldBitSet page_expected_fields_;
-  TimingFieldBitSet subframe_expected_fields_;
-
-  TimingFieldBitSet observed_page_fields_;
-
-  int64_t expected_minimum_page_load_data_use_ = 0;
-  int64_t current_page_load_data_use_ = 0;
-
-  bool attach_on_tracker_creation_ = false;
-  bool did_add_observer_ = false;
-
-  base::WeakPtrFactory<PageLoadMetricsWaiter> weak_factory_;
-};
-
-using TimingField = PageLoadMetricsWaiter::TimingField;
+using page_load_metrics::PageLoadMetricsTestWaiter;
+using TimingField = page_load_metrics::PageLoadMetricsTestWaiter::TimingField;
 using WebFeature = blink::mojom::WebFeature;
-}  // namespace
-
 using testing::UnorderedElementsAre;
 
 class PageLoadMetricsBrowserTest : public InProcessBrowserTest {
@@ -376,8 +108,8 @@
   }
 
   // Force navigation to a new page, so the currently tracked page load runs its
-  // OnComplete callback. You should prefer to use PageLoadMetricsWaiter, and
-  // only use NavigateToUntrackedUrl for cases where the waiter isn't
+  // OnComplete callback. You should prefer to use PageLoadMetricsTestWaiter,
+  // and only use NavigateToUntrackedUrl for cases where the waiter isn't
   // sufficient.
   void NavigateToUntrackedUrl() {
     ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
@@ -411,10 +143,10 @@
     return total_pageload_histograms - total_internal_histograms == 0;
   }
 
-  std::unique_ptr<PageLoadMetricsWaiter> CreatePageLoadMetricsWaiter() {
+  std::unique_ptr<PageLoadMetricsTestWaiter> CreatePageLoadMetricsTestWaiter() {
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
-    return std::make_unique<PageLoadMetricsWaiter>(web_contents);
+    return std::make_unique<PageLoadMetricsTestWaiter>(web_contents);
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -436,8 +168,8 @@
 
   GURL url = embedded_test_server()->GetURL("/title1.html");
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstPaint);
   ui_test_utils::NavigateToURL(browser(), url);
   waiter->Wait();
 
@@ -491,9 +223,9 @@
   params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
 
   Navigate(&params);
-  auto waiter = std::make_unique<PageLoadMetricsWaiter>(
+  auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
       params.navigated_or_inserted_contents);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   waiter->Wait();
 
   // Due to crbug.com/725347, with browser side navigation enabled, navigations
@@ -507,13 +239,13 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoPaintForEmptyDocument) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/empty.html"));
   waiter->Wait();
-  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::FIRST_PAINT));
+  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
 
   histogram_tester_.ExpectTotalCount(internal::kHistogramFirstLayout, 1);
   histogram_tester_.ExpectTotalCount(internal::kHistogramLoad, 1);
@@ -529,14 +261,14 @@
   GURL a_url(
       embedded_test_server()->GetURL("/page_load_metrics/empty_iframe.html"));
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddSubFrameExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
+  waiter->AddSubFrameExpectation(TimingField::kFirstLayout);
+  waiter->AddSubFrameExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(), a_url);
   waiter->Wait();
-  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::FIRST_PAINT));
+  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
 
   histogram_tester_.ExpectTotalCount(internal::kHistogramFirstLayout, 1);
   histogram_tester_.ExpectTotalCount(internal::kHistogramLoad, 1);
@@ -549,11 +281,11 @@
   ASSERT_TRUE(embedded_test_server()->Start());
 
   GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframe.html"));
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_PAINT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
+  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
+  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(browser(), a_url);
   waiter->Wait();
 
@@ -565,11 +297,11 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInDynamicChildFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_PAINT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
+  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
+  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL("/page_load_metrics/dynamic_iframe.html"));
@@ -585,11 +317,11 @@
 
   GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframes.html"));
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_PAINT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
+  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
+  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(browser(), a_url);
   waiter->Wait();
 
@@ -604,13 +336,13 @@
   GURL a_url(embedded_test_server()->GetURL(
       "/page_load_metrics/main_frame_with_iframe.html"));
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
-  waiter->AddPageExpectation(TimingField::FIRST_PAINT);
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_PAINT);
-  waiter->AddSubFrameExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
+  waiter->AddPageExpectation(TimingField::kFirstPaint);
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
+  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
+  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(browser(), a_url);
   waiter->Wait();
 
@@ -622,9 +354,9 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameDocumentNavigation) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
   waiter->Wait();
@@ -646,9 +378,9 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameUrlNavigation) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
   waiter->Wait();
@@ -657,9 +389,9 @@
   histogram_tester_.ExpectTotalCount(internal::kHistogramLoad, 1);
   histogram_tester_.ExpectTotalCount(internal::kHistogramFirstLayout, 1);
 
-  waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_LAYOUT);
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstLayout);
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
   waiter->Wait();
@@ -743,8 +475,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWrite) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
 
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
@@ -760,8 +492,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteBlock) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
 
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
@@ -776,8 +508,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteReload) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_script_block.html"));
@@ -788,9 +520,9 @@
   histogram_tester_.ExpectTotalCount(internal::kHistogramDocWriteBlockCount, 1);
 
   // Reload should not log the histogram as the script is not blocked.
-  waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::DOCUMENT_WRITE_BLOCK_RELOAD);
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kDocumentWriteBlockReload);
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_script_block.html"));
@@ -799,9 +531,9 @@
   histogram_tester_.ExpectTotalCount(
       internal::kHistogramDocWriteBlockReloadCount, 1);
 
-  waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::DOCUMENT_WRITE_BLOCK_RELOAD);
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kDocumentWriteBlockReload);
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_script_block.html"));
@@ -818,8 +550,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteAsync) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_async_script.html"));
@@ -835,8 +567,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteSameDomain) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_external_script.html"));
@@ -852,8 +584,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWriteScript) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/document_write_no_script.html"));
@@ -912,8 +644,8 @@
   GURL url2(embedded_test_server()->GetURL("/title2.html"));
   NavigateParams params2(browser(), url2, ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   Navigate(&params2);
   waiter->Wait();
 
@@ -934,8 +666,8 @@
 
   NavigateParams params2(browser(), url, ui::PAGE_TRANSITION_RELOAD);
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   Navigate(&params2);
   waiter->Wait();
 
@@ -991,8 +723,8 @@
   GURL url3(embedded_test_server()->GetURL("/title3.html"));
   NavigateParams params3(browser(), url3, ui::PAGE_TRANSITION_TYPED);
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   Navigate(&params3);
   waiter->Wait();
 
@@ -1017,8 +749,8 @@
   EXPECT_TRUE(manager.WaitForRequestStart());
 
   {
-    auto waiter = CreatePageLoadMetricsWaiter();
-    waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+    auto waiter = CreatePageLoadMetricsTestWaiter();
+    waiter->AddPageExpectation(TimingField::kLoadEvent);
     EXPECT_TRUE(content::ExecuteScript(
         browser()->tab_strip_model()->GetActiveWebContents(),
         "window.location.reload();"));
@@ -1036,8 +768,8 @@
                        FirstMeaningfulPaintRecorded) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_MEANINGFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
   waiter->Wait();
@@ -1055,8 +787,8 @@
                        FirstMeaningfulPaintNotRecorded) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
 
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
@@ -1081,8 +813,8 @@
                        NoStatePrefetchObserverCacheable) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
 
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
@@ -1099,8 +831,8 @@
                        NoStatePrefetchObserverNoStore) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
 
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/nostore.html"));
@@ -1116,8 +848,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSize) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
                                               "/page_load_metrics/large.html"));
   waiter->Wait();
@@ -1137,8 +869,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSizeChildFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL("/page_load_metrics/large_iframe.html"));
@@ -1182,8 +914,8 @@
                        UseCounterFeaturesInMainFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features.html"));
@@ -1216,8 +948,8 @@
                        UseCounterCSSPropertiesInMainFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features.html"));
@@ -1239,8 +971,8 @@
                        UseCounterAnimatedCSSPropertiesInMainFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features.html"));
@@ -1270,8 +1002,8 @@
       base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
   ASSERT_TRUE(https_server.Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       https_server.GetURL("/page_load_metrics/use_counter_features.html"));
@@ -1304,8 +1036,8 @@
       base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
   ASSERT_TRUE(https_server.Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       https_server.GetURL("/page_load_metrics/use_counter_features.html"));
@@ -1335,8 +1067,8 @@
       base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
   ASSERT_TRUE(https_server.Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       https_server.GetURL("/page_load_metrics/use_counter_features.html"));
@@ -1358,8 +1090,8 @@
                        UseCounterFeaturesInNonSecureMainFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL(
@@ -1382,11 +1114,6 @@
       static_cast<int32_t>(WebFeature::kDataUriHasOctothorpe), 1);
   histogram_tester_.ExpectBucketCount(
       internal::kFeaturesHistogramName,
-      static_cast<int32_t>(
-          WebFeature::kApplicationCacheManifestSelectInsecureOrigin),
-      1);
-  histogram_tester_.ExpectBucketCount(
-      internal::kFeaturesHistogramName,
       static_cast<int32_t>(WebFeature::kPageVisits), 1);
   histogram_tester_.ExpectBucketCount(
       internal::kFeaturesHistogramName,
@@ -1398,8 +1125,8 @@
                        UseCounterUkmFeaturesLogged) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   GURL url = embedded_test_server()->GetURL(
       "/page_load_metrics/use_counter_features.html");
   ui_test_utils::NavigateToURL(browser(), url);
@@ -1438,8 +1165,8 @@
       base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
   ASSERT_TRUE(https_server.Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   GURL url =
       https_server.GetURL("/page_load_metrics/use_counter_features.html");
   ui_test_utils::NavigateToURL(browser(), url);
@@ -1475,8 +1202,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, UseCounterFeaturesInIframe) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features_in_iframe.html"));
@@ -1503,8 +1230,8 @@
                        UseCounterFeaturesInIframes) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL(
@@ -1532,8 +1259,8 @@
                        UseCounterCSSPropertiesInIframe) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features_in_iframe.html"));
@@ -1557,8 +1284,8 @@
                        UseCounterCSSPropertiesInIframes) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL(
@@ -1583,8 +1310,8 @@
                        UseCounterAnimatedCSSPropertiesInIframe) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "/page_load_metrics/use_counter_features_in_iframe.html"));
@@ -1608,8 +1335,8 @@
                        UseCounterAnimatedCSSPropertiesInIframes) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL(
@@ -1644,8 +1371,8 @@
 
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, LoadingMetrics) {
   ASSERT_TRUE(embedded_test_server()->Start());
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_TIMING_INFO);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadTimingInfo);
   ui_test_utils::NavigateToURL(browser(),
                                embedded_test_server()->GetURL("/title1.html"));
   // Waits until nonzero loading metrics are seen.
@@ -1654,13 +1381,13 @@
 
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, LoadingMetricsFailed) {
   ASSERT_TRUE(embedded_test_server()->Start());
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_TIMING_INFO);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadTimingInfo);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/page_load_metrics/404.html"));
   // Waits until nonzero loading metrics are seen about the failed request. The
   // load timing metrics come before the commit, but because the
-  // PageLoadMetricsWaiter is registered on tracker creation, it is able to
+  // PageLoadMetricsTestWaiter is registered on tracker creation, it is able to
   // catch the events.
   waiter->Wait();
 }
@@ -1711,8 +1438,8 @@
     }
   }
 
-  // The PageLoadMetricsWaiter can observe first meaningful paints on these test
-  // pages while not on other simple pages such as /title1.html.
+  // The PageLoadMetricsTestWaiter can observe first meaningful paints on these
+  // test pages while not on other simple pages such as /title1.html.
   GURL GetTestURL() const {
     return embedded_test_server()->GetURL(
         "/page_load_metrics/main_frame_with_iframe.html");
@@ -1746,10 +1473,10 @@
   // SessionRestoreObserver implementation:
   void OnWillRestoreTab(content::WebContents* contents) override {
     chrome::InitializePageLoadMetricsForWebContents(contents);
-    auto waiter = std::make_unique<PageLoadMetricsWaiter>(contents);
-    waiter->AddPageExpectation(TimingField::FIRST_PAINT);
-    waiter->AddPageExpectation(TimingField::FIRST_CONTENTFUL_PAINT);
-    waiter->AddPageExpectation(TimingField::FIRST_MEANINGFUL_PAINT);
+    auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(contents);
+    waiter->AddPageExpectation(TimingField::kFirstPaint);
+    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
+    waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
     waiters_[contents] = std::move(waiter);
   }
 
@@ -1767,7 +1494,7 @@
 
  private:
   std::unordered_map<content::WebContents*,
-                     std::unique_ptr<PageLoadMetricsWaiter>>
+                     std::unique_ptr<PageLoadMetricsTestWaiter>>
       waiters_;
 
   DISALLOW_COPY_AND_ASSIGN(SessionRestorePaintWaiter);
@@ -1858,9 +1585,9 @@
   NavigateToUntrackedUrl();
   Browser* new_browser = QuitBrowserAndRestore(browser());
 
-  auto waiter = std::make_unique<PageLoadMetricsWaiter>(
+  auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
       new_browser->tab_strip_model()->GetActiveWebContents());
-  waiter->AddPageExpectation(TimingField::FIRST_MEANINGFUL_PAINT);
+  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
   ui_test_utils::NavigateToURL(new_browser, GetTestURL());
   waiter->Wait();
 
@@ -1883,9 +1610,9 @@
   }
 
   // Load a new page after session restore.
-  auto waiter = std::make_unique<PageLoadMetricsWaiter>(
+  auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
       new_browser->tab_strip_model()->GetActiveWebContents());
-  waiter->AddPageExpectation(TimingField::FIRST_MEANINGFUL_PAINT);
+  waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
   ui_test_utils::NavigateToURL(new_browser, GetTestURL());
   waiter->Wait();
 
@@ -2045,8 +1772,8 @@
 IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, ReceivedDataLength) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL(
                                               "/page_load_metrics/large.html"));
   waiter->AddMinimumPageLoadDataUseExpectation(10000);
@@ -2070,7 +1797,7 @@
           true /*relative_url_is_prefix*/);
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
+  auto waiter = CreatePageLoadMetricsTestWaiter();
 
   browser()->OpenURL(content::OpenURLParams(
       embedded_test_server()->GetURL("/mock_page.html"), content::Referrer(),
@@ -2106,16 +1833,16 @@
   content::SetupCrossSiteRedirector(embedded_test_server());
   ASSERT_TRUE(embedded_test_server()->Start());
 
-  auto waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  auto waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "foo.com", "/cross_site_iframe_factory.html?foo"));
   waiter->Wait();
   int64_t one_frame_page_size = waiter->current_page_load_data_use();
 
-  waiter = CreatePageLoadMetricsWaiter();
-  waiter->AddPageExpectation(TimingField::LOAD_EVENT);
+  waiter = CreatePageLoadMetricsTestWaiter();
+  waiter->AddPageExpectation(TimingField::kLoadEvent);
   ui_test_utils::NavigateToURL(
       browser(),
       embedded_test_server()->GetURL(
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc
new file mode 100644
index 0000000..23f1652
--- /dev/null
+++ b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.cc
@@ -0,0 +1,205 @@
+// Copyright 2018 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/page_load_metrics/page_load_metrics_test_waiter.h"
+
+#include "base/logging.h"
+#include "chrome/browser/page_load_metrics/page_load_metrics_observer.h"
+#include "chrome/common/page_load_metrics/page_load_metrics.mojom.h"
+#include "content/public/common/resource_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace page_load_metrics {
+
+PageLoadMetricsTestWaiter::PageLoadMetricsTestWaiter(
+    content::WebContents* web_contents)
+    : TestingObserver(web_contents), weak_factory_(this) {}
+
+PageLoadMetricsTestWaiter::~PageLoadMetricsTestWaiter() {
+  CHECK(did_add_observer_);
+  CHECK_EQ(nullptr, run_loop_.get());
+}
+
+void PageLoadMetricsTestWaiter::AddPageExpectation(TimingField field) {
+  page_expected_fields_.Set(field);
+  if (field == TimingField::kLoadTimingInfo) {
+    attach_on_tracker_creation_ = true;
+  }
+}
+
+void PageLoadMetricsTestWaiter::AddSubFrameExpectation(TimingField field) {
+  CHECK_NE(field, TimingField::kLoadTimingInfo)
+      << "LOAD_TIMING_INFO should only be used as a page-level expectation";
+  subframe_expected_fields_.Set(field);
+  // If the given field is also a page-level field, then add a page-level
+  // expectation as well
+  if (IsPageLevelField(field))
+    page_expected_fields_.Set(field);
+}
+
+void PageLoadMetricsTestWaiter::AddMinimumPageLoadDataUseExpectation(
+    int expected_minimum_page_load_data_use) {
+  expected_minimum_page_load_data_use_ = expected_minimum_page_load_data_use;
+}
+
+bool PageLoadMetricsTestWaiter::DidObserveInPage(TimingField field) const {
+  return observed_page_fields_.IsSet(field);
+}
+
+void PageLoadMetricsTestWaiter::Wait() {
+  if (expectations_satisfied())
+    return;
+
+  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_->Run();
+  run_loop_ = nullptr;
+
+  EXPECT_TRUE(expectations_satisfied());
+}
+
+void PageLoadMetricsTestWaiter::OnTimingUpdated(
+    content::RenderFrameHost* subframe_rfh,
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  if (expectations_satisfied())
+    return;
+  const page_load_metrics::mojom::PageLoadMetadata& metadata =
+      subframe_rfh ? extra_info.subframe_metadata
+                   : extra_info.main_frame_metadata;
+  TimingFieldBitSet matched_bits = GetMatchedBits(timing, metadata);
+  if (subframe_rfh) {
+    subframe_expected_fields_.ClearMatching(matched_bits);
+  } else {
+    page_expected_fields_.ClearMatching(matched_bits);
+    observed_page_fields_.Merge(matched_bits);
+  }
+  if (expectations_satisfied() && run_loop_)
+    run_loop_->Quit();
+}
+
+void PageLoadMetricsTestWaiter::OnLoadedResource(
+    const page_load_metrics::ExtraRequestCompleteInfo&
+        extra_request_complete_info) {
+  if (expectations_satisfied())
+    return;
+
+  if (extra_request_complete_info.resource_type !=
+      content::RESOURCE_TYPE_MAIN_FRAME) {
+    // The waiter confirms loading timing for the main frame only.
+    return;
+  }
+
+  if (!extra_request_complete_info.load_timing_info->send_start.is_null() &&
+      !extra_request_complete_info.load_timing_info->send_end.is_null() &&
+      !extra_request_complete_info.load_timing_info->request_start.is_null()) {
+    page_expected_fields_.Clear(TimingField::kLoadTimingInfo);
+    observed_page_fields_.Set(TimingField::kLoadTimingInfo);
+  }
+
+  if (expectations_satisfied() && run_loop_)
+    run_loop_->Quit();
+}
+
+void PageLoadMetricsTestWaiter::OnDataUseObserved(
+    int64_t received_data_length,
+    int64_t data_reduction_proxy_bytes_saved) {
+  current_page_load_data_use_ += received_data_length;
+  if (expectations_satisfied() && run_loop_)
+    run_loop_->Quit();
+}
+
+bool PageLoadMetricsTestWaiter::IsPageLevelField(TimingField field) {
+  switch (field) {
+    case TimingField::kFirstPaint:
+    case TimingField::kFirstContentfulPaint:
+    case TimingField::kFirstMeaningfulPaint:
+      return true;
+    default:
+      return false;
+  }
+}
+
+PageLoadMetricsTestWaiter::TimingFieldBitSet
+PageLoadMetricsTestWaiter::GetMatchedBits(
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    const page_load_metrics::mojom::PageLoadMetadata& metadata) {
+  PageLoadMetricsTestWaiter::TimingFieldBitSet matched_bits;
+  if (timing.document_timing->first_layout)
+    matched_bits.Set(TimingField::kFirstLayout);
+  if (timing.document_timing->load_event_start)
+    matched_bits.Set(TimingField::kLoadEvent);
+  if (timing.paint_timing->first_paint)
+    matched_bits.Set(TimingField::kFirstPaint);
+  if (timing.paint_timing->first_contentful_paint)
+    matched_bits.Set(TimingField::kFirstContentfulPaint);
+  if (timing.paint_timing->first_meaningful_paint)
+    matched_bits.Set(TimingField::kFirstMeaningfulPaint);
+  if (metadata.behavior_flags & blink::WebLoadingBehaviorFlag::
+                                    kWebLoadingBehaviorDocumentWriteBlockReload)
+    matched_bits.Set(TimingField::kDocumentWriteBlockReload);
+
+  return matched_bits;
+}
+
+void PageLoadMetricsTestWaiter::OnTrackerCreated(
+    page_load_metrics::PageLoadTracker* tracker) {
+  if (!attach_on_tracker_creation_)
+    return;
+  // A PageLoadMetricsWaiter should only wait for events from a single page
+  // load.
+  ASSERT_FALSE(did_add_observer_);
+  tracker->AddObserver(
+      std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
+  did_add_observer_ = true;
+}
+
+void PageLoadMetricsTestWaiter::OnCommit(
+    page_load_metrics::PageLoadTracker* tracker) {
+  if (attach_on_tracker_creation_)
+    return;
+  // A PageLoadMetricsWaiter should only wait for events from a single page
+  // load.
+  ASSERT_FALSE(did_add_observer_);
+  tracker->AddObserver(
+      std::make_unique<WaiterMetricsObserver>(weak_factory_.GetWeakPtr()));
+  did_add_observer_ = true;
+}
+
+bool PageLoadMetricsTestWaiter::expectations_satisfied() const {
+  return subframe_expected_fields_.Empty() && page_expected_fields_.Empty() &&
+         (expected_minimum_page_load_data_use_ == 0 ||
+          current_page_load_data_use_ >= expected_minimum_page_load_data_use_);
+}
+
+PageLoadMetricsTestWaiter::WaiterMetricsObserver::~WaiterMetricsObserver() {}
+
+PageLoadMetricsTestWaiter::WaiterMetricsObserver::WaiterMetricsObserver(
+    base::WeakPtr<PageLoadMetricsTestWaiter> waiter)
+    : waiter_(waiter) {}
+
+void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnTimingUpdate(
+    content::RenderFrameHost* subframe_rfh,
+    const page_load_metrics::mojom::PageLoadTiming& timing,
+    const page_load_metrics::PageLoadExtraInfo& extra_info) {
+  if (waiter_)
+    waiter_->OnTimingUpdated(subframe_rfh, timing, extra_info);
+}
+
+void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnLoadedResource(
+    const page_load_metrics::ExtraRequestCompleteInfo&
+        extra_request_complete_info) {
+  if (waiter_)
+    waiter_->OnLoadedResource(extra_request_complete_info);
+}
+
+void PageLoadMetricsTestWaiter::WaiterMetricsObserver::OnDataUseObserved(
+    int64_t received_data_length,
+    int64_t data_reduction_proxy_bytes_saved) {
+  if (waiter_) {
+    waiter_->OnDataUseObserved(received_data_length,
+                               data_reduction_proxy_bytes_saved);
+  }
+}
+
+}  // namespace page_load_metrics
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h
new file mode 100644
index 0000000..c206f419
--- /dev/null
+++ b/chrome/browser/page_load_metrics/page_load_metrics_test_waiter.h
@@ -0,0 +1,166 @@
+// Copyright 2018 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_PAGE_LOAD_METRICS_PAGE_LOAD_METRICS_TEST_WAITER_H_
+#define CHROME_BROWSER_PAGE_LOAD_METRICS_PAGE_LOAD_METRICS_TEST_WAITER_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
+#include "chrome/browser/page_load_metrics/metrics_web_contents_observer.h"
+#include "chrome/browser/page_load_metrics/page_load_tracker.h"
+#include "content/public/browser/render_frame_host.h"
+
+namespace page_load_metrics {
+
+class PageLoadMetricsTestWaiter
+    : public page_load_metrics::MetricsWebContentsObserver::TestingObserver {
+ public:
+  // A bitvector to express which timing fields to match on.
+  enum class TimingField : int {
+    kFirstLayout = 1 << 0,
+    kFirstPaint = 1 << 1,
+    kFirstContentfulPaint = 1 << 2,
+    kFirstMeaningfulPaint = 1 << 3,
+    kDocumentWriteBlockReload = 1 << 4,
+    kLoadEvent = 1 << 5,
+    // kLoadTimingInfo waits for main frame timing info only.
+    kLoadTimingInfo = 1 << 6,
+  };
+
+  explicit PageLoadMetricsTestWaiter(content::WebContents* web_contents);
+
+  ~PageLoadMetricsTestWaiter() override;
+
+  // Add a page-level expectation.
+  void AddPageExpectation(TimingField field);
+
+  // Add a subframe-level expectation.
+  void AddSubFrameExpectation(TimingField field);
+
+  // Add a data use expectation
+  void AddMinimumPageLoadDataUseExpectation(
+      int expected_minimum_page_load_data_use);
+
+  // Whether the given TimingField was observed in the page.
+  bool DidObserveInPage(TimingField field) const;
+
+  // Waits for PageLoadMetrics events that match the fields set by
+  // |AddPageExpectation| and |AddSubFrameExpectation|. All matching fields
+  // must be set to end this wait.
+  void Wait();
+
+  int64_t current_page_load_data_use() const {
+    return current_page_load_data_use_;
+  }
+
+ private:
+  // PageLoadMetricsObserver used by the PageLoadMetricsTestWaiter to observe
+  // metrics updates.
+  class WaiterMetricsObserver
+      : public page_load_metrics::PageLoadMetricsObserver {
+   public:
+    // We use a WeakPtr to the PageLoadMetricsTestWaiter because |waiter| can be
+    // destroyed before this WaiterMetricsObserver.
+    explicit WaiterMetricsObserver(
+        base::WeakPtr<PageLoadMetricsTestWaiter> waiter);
+    ~WaiterMetricsObserver() override;
+
+    void OnTimingUpdate(
+        content::RenderFrameHost* subframe_rfh,
+        const page_load_metrics::mojom::PageLoadTiming& timing,
+        const page_load_metrics::PageLoadExtraInfo& extra_info) override;
+
+    void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
+                              extra_request_complete_info) override;
+
+    void OnDataUseObserved(int64_t received_data_length,
+                           int64_t data_reduction_proxy_bytes_saved) override;
+
+   private:
+    const base::WeakPtr<PageLoadMetricsTestWaiter> waiter_;
+  };
+
+  // Manages a bitset of TimingFields.
+  class TimingFieldBitSet {
+   public:
+    TimingFieldBitSet() {}
+
+    // Returns whether this bitset has all bits unset.
+    bool Empty() const { return bitmask_ == 0; }
+
+    // Returns whether this bitset has the given bit set.
+    bool IsSet(TimingField field) const {
+      return (bitmask_ & static_cast<int>(field)) != 0;
+    }
+
+    // Sets the bit for the given |field|.
+    void Set(TimingField field) { bitmask_ |= static_cast<int>(field); }
+
+    // Clears the bit for the given |field|.
+    void Clear(TimingField field) { bitmask_ &= ~static_cast<int>(field); }
+
+    // Merges bits set in |other| into this bitset.
+    void Merge(const TimingFieldBitSet& other) { bitmask_ |= other.bitmask_; }
+
+    // Clears all bits set in the |other| bitset.
+    void ClearMatching(const TimingFieldBitSet& other) {
+      bitmask_ &= ~other.bitmask_;
+    }
+
+   private:
+    int bitmask_ = 0;
+  };
+
+  static bool IsPageLevelField(TimingField field);
+
+  static TimingFieldBitSet GetMatchedBits(
+      const page_load_metrics::mojom::PageLoadTiming& timing,
+      const page_load_metrics::mojom::PageLoadMetadata& metadata);
+
+  // Updates observed page fields when a timing update is received by the
+  // MetricsWebContentsObserver. Stops waiting if expectations are satsfied
+  // after update.
+  void OnTimingUpdated(content::RenderFrameHost* subframe_rfh,
+                       const page_load_metrics::mojom::PageLoadTiming& timing,
+                       const page_load_metrics::PageLoadExtraInfo& extra_info);
+
+  // Updates observed page fields when a resource load is observed by
+  // MetricsWebContentsObserver.  Stops waiting if expectations are satsfied
+  // after update.
+  void OnLoadedResource(const page_load_metrics::ExtraRequestCompleteInfo&
+                            extra_request_complete_info);
+
+  // Updates data counters when data use is seen by the
+  // MetricsWebContentsObserver. Stops waiting if expectations are satsfied
+  // after update.
+  void OnDataUseObserved(int64_t received_data_length,
+                         int64_t data_reduction_proxy_bytes_saved);
+
+  void OnTrackerCreated(page_load_metrics::PageLoadTracker* tracker) override;
+
+  void OnCommit(page_load_metrics::PageLoadTracker* tracker) override;
+
+  bool expectations_satisfied() const;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+
+  TimingFieldBitSet page_expected_fields_;
+  TimingFieldBitSet subframe_expected_fields_;
+
+  TimingFieldBitSet observed_page_fields_;
+
+  int64_t expected_minimum_page_load_data_use_ = 0;
+  int64_t current_page_load_data_use_ = 0;
+
+  bool attach_on_tracker_creation_ = false;
+  bool did_add_observer_ = false;
+
+  base::WeakPtrFactory<PageLoadMetricsTestWaiter> weak_factory_;
+};
+
+}  // namespace page_load_metrics
+
+#endif  // CHROME_BROWSER_PAGE_LOAD_METRICS_PAGE_LOAD_METRICS_TEST_WAITER_H_
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 2f54310..1f58e5d 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -522,12 +522,12 @@
   return ukm::GetSourceIdForWebContentsDocument(web_contents());
 }
 
-PasswordManagerMetricsRecorder&
+PasswordManagerMetricsRecorder*
 ChromePasswordManagerClient::GetMetricsRecorder() {
   if (!metrics_recorder_) {
     metrics_recorder_.emplace(GetUkmSourceId(), GetMainFrameURL());
   }
-  return metrics_recorder_.value();
+  return base::OptionalOrNullptr(metrics_recorder_);
 }
 
 void ChromePasswordManagerClient::DidFinishNavigation(
@@ -805,6 +805,12 @@
           BadMessageReason::CPMD_BAD_ORIGIN_PASSWORD_NO_LONGER_GENERATED))
     return;
   password_manager_.OnPasswordNoLongerGenerated(password_form);
+  PasswordGenerationPopupController* controller = popup_controller_.get();
+  if (controller &&
+      controller->state() ==
+          PasswordGenerationPopupController::kEditGeneratedPassword) {
+    HidePasswordGenerationPopup();
+  }
 }
 
 const GURL& ChromePasswordManagerClient::GetMainFrameURL() const {
@@ -970,7 +976,9 @@
 }
 
 void ChromePasswordManagerClient::UserModifiedPasswordField() {
-  GetMetricsRecorder().RecordUserModifiedPasswordField();
+  if (GetMetricsRecorder()) {
+    GetMetricsRecorder()->RecordUserModifiedPasswordField();
+  }
 }
 
 // static
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index 81552f9..2ac5d7a 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -144,7 +144,7 @@
 #endif
 
   ukm::SourceId GetUkmSourceId() override;
-  password_manager::PasswordManagerMetricsRecorder& GetMetricsRecorder()
+  password_manager::PasswordManagerMetricsRecorder* GetMetricsRecorder()
       override;
 
   static void CreateForWebContentsWithAutofillClient(
diff --git a/chrome/browser/password_manager/password_accessory_controller.cc b/chrome/browser/password_manager/password_accessory_controller.cc
index 1dc75f7a..243921b 100644
--- a/chrome/browser/password_manager/password_accessory_controller.cc
+++ b/chrome/browser/password_manager/password_accessory_controller.cc
@@ -7,9 +7,11 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/android/preferences/preferences_launcher.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
+#include "chrome/browser/password_manager/password_accessory_metrics_util.h"
 #include "chrome/browser/password_manager/password_generation_dialog_view_interface.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
@@ -257,6 +259,9 @@
   if (selectedOption ==
       l10n_util::GetStringUTF16(
           IDS_PASSWORD_MANAGER_ACCESSORY_ALL_PASSWORDS_LINK)) {
+    UMA_HISTOGRAM_ENUMERATION("KeyboardAccessory.AccessoryActionSelected",
+                              metrics::AccessoryAction::MANAGE_PASSWORDS,
+                              metrics::AccessoryAction::COUNT);
     chrome::android::PreferencesLauncher::ShowPasswordSettings();
   }
 }
diff --git a/chrome/browser/password_manager/password_accessory_metrics_util.h b/chrome/browser/password_manager/password_accessory_metrics_util.h
new file mode 100644
index 0000000..b41840d
--- /dev/null
+++ b/chrome/browser/password_manager/password_accessory_metrics_util.h
@@ -0,0 +1,67 @@
+// Copyright 2018 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_PASSWORD_MANAGER_PASSWORD_ACCESSORY_METRICS_UTIL_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_ACCESSORY_METRICS_UTIL_H_
+
+namespace metrics {
+
+// Used to record keyboard accessory bar impressions bucketed by content.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. Must be kept in sync with the enum
+// in enums.xml. A java IntDef@ is generated from this.
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.chrome.browser.autofill.keyboard_accessory)
+enum class AccessoryBarContents {
+  NO_CONTENTS = 0,   // Increased if none of the other buckets increases.
+  ANY_CONTENTS = 1,  // Increased if least one of the other buckets increases.
+  WITH_TABS = 2,     // Increased if there are tabs in the opened accessory.
+  WITH_ACTIONS = 3,  // Increased if there are actions in the opened accessory.
+  WITH_AUTOFILL_SUGGESTIONS = 4,  // Increased for autofill suggestions.
+  COUNT,
+};
+
+// Used to record why and how often accessory sheets were opened and closed.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. Must be kept in sync with the enum
+// in enums.xml. A java IntDef@ is generated from this.
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.chrome.browser.autofill.keyboard_accessory)
+enum class AccessorySheetTrigger {
+  ANY_CLOSE = 0,     // Increased for every closure - manual or not.
+  MANUAL_CLOSE = 1,  // Increased for every user-triggered closure.
+  MANUAL_OPEN = 2,   // Increased for every user-triggered opening.
+  COUNT,
+};
+
+// Used to record metrics specific to a tab types (e.g. passwords, payments).
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.chrome.browser.autofill.keyboard_accessory)
+enum class AccessoryTabType {
+  ALL = 0,
+  PASSWORDS = 1,
+  COUNT,
+};
+
+// Used to record impressions and clicks on specific actions and links.
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.chrome.browser.autofill.keyboard_accessory)
+enum class AccessoryAction {
+  GENERATE_PASSWORD_AUTOMATIC = 0,
+  MANAGE_PASSWORDS = 1,
+  COUNT,
+};
+
+// Used to record which type of suggestion was selected.
+// GENERATED_JAVA_ENUM_PACKAGE: (
+//   org.chromium.chrome.browser.autofill.keyboard_accessory)
+enum class AccessorySuggestionType {
+  USERNAME = 0,
+  PASSWORD = 1,
+  COUNT,
+};
+
+}  // namespace metrics
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_ACCESSORY_METRICS_UTIL_H_
diff --git a/chrome/browser/password_manager/password_generation_interactive_uitest.cc b/chrome/browser/password_manager/password_generation_interactive_uitest.cc
index c79365b..120150ec 100644
--- a/chrome/browser/password_manager/password_generation_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_generation_interactive_uitest.cc
@@ -3,11 +3,12 @@
 // found in the LICENSE file.
 
 #include "base/command_line.h"
+#include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
-#include "chrome/browser/password_manager/password_manager_test_base.h"
+#include "chrome/browser/password_manager/password_manager_interactive_test_base.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -19,6 +20,7 @@
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_switches.h"
 #include "components/password_manager/core/browser/password_generation_manager.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/test_password_store.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/render_widget_host.h"
@@ -33,23 +35,48 @@
 
 class TestPopupObserver : public PasswordGenerationPopupObserver {
  public:
+  TestPopupObserver() = default;
+  ~TestPopupObserver() = default;
+
   void OnPopupShown(
       PasswordGenerationPopupController::GenerationState state) override {
     popup_showing_ = true;
     state_ = state;
+    MaybeQuitRunLoop();
   }
 
-  void OnPopupHidden() override { popup_showing_ = false; }
+  void OnPopupHidden() override {
+    popup_showing_ = false;
+    MaybeQuitRunLoop();
+  }
 
   bool popup_showing() const { return popup_showing_; }
   PasswordGenerationPopupController::GenerationState state() const {
     return state_;
   }
 
+  // Waits until the popup is either shown or hidden.
+  void WaitForStatusChange() {
+    base::RunLoop run_loop;
+    run_loop_ = &run_loop;
+    run_loop_->Run();
+  }
+
  private:
+  void MaybeQuitRunLoop() {
+    if (run_loop_) {
+      run_loop_->Quit();
+      run_loop_ = nullptr;
+    }
+  }
+
+  // The loop to be stopped after the popup state change.
+  base::RunLoop* run_loop_ = nullptr;
   bool popup_showing_ = false;
   PasswordGenerationPopupController::GenerationState state_ =
       PasswordGenerationPopupController::kOfferGeneration;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPopupObserver);
 };
 
 enum ReturnCodes {  // Possible results of the JavaScript code.
@@ -60,8 +87,8 @@
 
 }  // namespace
 
-class PasswordGenerationInteractiveTest :
-    public PasswordManagerBrowserTestBase {
+class PasswordGenerationInteractiveTest
+    : public PasswordManagerInteractiveTestBase {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     PasswordManagerBrowserTestBase::SetUpCommandLine(command_line);
@@ -161,6 +188,8 @@
                PasswordGenerationPopupController::kEditGeneratedPassword;
   }
 
+  void WaitForPopupStatusChange() { observer_.WaitForStatusChange(); }
+
  private:
   TestPopupObserver observer_;
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -186,6 +215,53 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
+                       PopupShownAutomaticallyAndPasswordErased) {
+  FocusPasswordField();
+  EXPECT_TRUE(GenerationPopupShowing());
+  SendKeyToPopup(ui::VKEY_DOWN);
+  SendKeyToPopup(ui::VKEY_RETURN);
+
+  // Wait until the password is filled.
+  WaitForNonEmptyFieldValue("password_field");
+
+  // Re-focusing the password field should show the editing popup.
+  FocusPasswordField();
+  EXPECT_TRUE(EditingPopupShowing());
+
+  // Delete the password. The generation prompt should be visible.
+  SimulateUserDeletingFieldContent("password_field");
+  WaitForPopupStatusChange();
+  EXPECT_FALSE(EditingPopupShowing());
+  EXPECT_TRUE(GenerationPopupShowing());
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
+                       PopupShownManuallyAndPasswordErased) {
+  NavigateToFile("/password/password_form.html");
+
+  FocusPasswordField();
+  // The same flow happens when user generates a password from the context menu.
+  password_manager_util::UserTriggeredManualGenerationFromContextMenu(
+      ChromePasswordManagerClient::FromWebContents(WebContents()));
+  EXPECT_TRUE(GenerationPopupShowing());
+  SendKeyToPopup(ui::VKEY_DOWN);
+  SendKeyToPopup(ui::VKEY_RETURN);
+
+  // Wait until the password is filled.
+  WaitForNonEmptyFieldValue("password_field");
+
+  // Re-focusing the password field should show the editing popup.
+  FocusPasswordField();
+  EXPECT_TRUE(EditingPopupShowing());
+
+  // Delete the password. The generation prompt should not be visible.
+  SimulateUserDeletingFieldContent("password_field");
+  WaitForPopupStatusChange();
+  EXPECT_FALSE(EditingPopupShowing());
+  EXPECT_FALSE(GenerationPopupShowing());
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordGenerationInteractiveTest,
                        PopupShownAndDismissed) {
   FocusPasswordField();
   EXPECT_TRUE(GenerationPopupShowing());
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 477df9b..fd162610 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -807,8 +807,16 @@
   EXPECT_TRUE(prompt_observer->IsSavePromptShownAutomatically());
 }
 
+// Flaky on chromeos: http://crbug.com/870372
+#if defined(OS_CHROMEOS)
+#define MAYBE_PromptForFetchSubmitWithoutNavigation \
+  DISABLED_PromptForFetchSubmitWithoutNavigation
+#else
+#define MAYBE_PromptForFetchSubmitWithoutNavigation \
+  PromptForFetchSubmitWithoutNavigation
+#endif
 IN_PROC_BROWSER_TEST_P(PasswordManagerBrowserTestWithViewsFeature,
-                       PromptForFetchSubmitWithoutNavigation) {
+                       MAYBE_PromptForFetchSubmitWithoutNavigation) {
   NavigateToFile("/password/password_fetch_submit.html");
 
   // Need to pay attention for a message that XHR has finished since there
diff --git a/chrome/browser/password_manager/password_manager_interactive_test_base.cc b/chrome/browser/password_manager/password_manager_interactive_test_base.cc
index cc15f49..805ac44 100644
--- a/chrome/browser/password_manager/password_manager_interactive_test_base.cc
+++ b/chrome/browser/password_manager/password_manager_interactive_test_base.cc
@@ -79,3 +79,15 @@
     WaitForElementValue(username_id, kUsername);
   WaitForElementValue(password_id, kPassword);
 }
+
+// Erases all characters that have been typed into |field_id|.
+void PasswordManagerInteractiveTestBase::SimulateUserDeletingFieldContent(
+    const std::string& field_id) {
+  std::string focus("document.getElementById('" + field_id + "').focus();");
+  ASSERT_TRUE(content::ExecuteScript(WebContents(), focus));
+  std::string select("document.getElementById('" + field_id + "').select();");
+  ASSERT_TRUE(content::ExecuteScript(WebContents(), select));
+  content::SimulateKeyPress(WebContents(), ui::DomKey::BACKSPACE,
+                            ui::DomCode::BACKSPACE, ui::VKEY_BACK, false, false,
+                            false, false);
+}
diff --git a/chrome/browser/password_manager/password_manager_interactive_test_base.h b/chrome/browser/password_manager/password_manager_interactive_test_base.h
index 0e507d6..cf8c4c89 100644
--- a/chrome/browser/password_manager/password_manager_interactive_test_base.h
+++ b/chrome/browser/password_manager/password_manager_interactive_test_base.h
@@ -30,6 +30,9 @@
                                       const std::string& password_id,
                                       const std::string& submission_script);
 
+  // Erases all characters that have been typed into |field_id|.
+  void SimulateUserDeletingFieldContent(const std::string& field_id);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(PasswordManagerInteractiveTestBase);
 };
diff --git a/chrome/browser/password_manager/password_manager_interactive_uitest.cc b/chrome/browser/password_manager/password_manager_interactive_uitest.cc
index fd376b5..88ba694 100644
--- a/chrome/browser/password_manager/password_manager_interactive_uitest.cc
+++ b/chrome/browser/password_manager/password_manager_interactive_uitest.cc
@@ -16,22 +16,6 @@
 #include "components/password_manager/core/browser/test_password_store.h"
 #include "content/public/test/browser_test_utils.h"
 
-namespace {
-
-// Erases all characters that have been typed into |field_id|.
-void SimulateUserDeletingFieldContent(content::WebContents* web_contents,
-                                      const std::string& field_id) {
-  std::string focus("document.getElementById('" + field_id + "').focus();");
-  ASSERT_TRUE(content::ExecuteScript(web_contents, focus));
-  std::string select("document.getElementById('" + field_id + "').select();");
-  ASSERT_TRUE(content::ExecuteScript(web_contents, select));
-  content::SimulateKeyPress(web_contents, ui::DomKey::BACKSPACE,
-                            ui::DomCode::BACKSPACE, ui::VKEY_BACK, false, false,
-                            false, false);
-}
-
-}  // namespace
-
 namespace password_manager {
 
 // Test fixture that condionally enable feature kAutofillExpandedPopupViews.
@@ -177,7 +161,7 @@
   prompt_observer.WaitForFallbackForSaving();
 
   // Delete typed content and verify that inactive state is reached.
-  SimulateUserDeletingFieldContent(WebContents(), "password_field");
+  SimulateUserDeletingFieldContent("password_field");
   prompt_observer.WaitForInactiveState();
 }
 
@@ -203,7 +187,7 @@
   prompt_observer.WaitForFallbackForSaving();
 
   // Delete typed content and verify that management state is reached.
-  SimulateUserDeletingFieldContent(WebContents(), "password_field");
+  SimulateUserDeletingFieldContent("password_field");
   prompt_observer.WaitForManagementState();
 }
 
diff --git a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
index 944d21d..e550d77b 100644
--- a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_browser_main.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
@@ -25,6 +26,7 @@
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/policy/machine_level_user_cloud_policy_controller.h"
 #include "chrome/browser/policy/test/local_policy_test_server.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_result_codes.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -42,6 +44,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/views/test/widget_test.h"
 
+#if defined(OS_MACOSX)
+#include "chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h"
+#endif
+
 using testing::DoAll;
 using testing::Invoke;
 using testing::InvokeWithoutArgs;
@@ -65,6 +71,10 @@
  public:
   void OnPolicyRegisterFinished(bool succeeded) override {
     if (!succeeded) {
+      EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
+#if defined(OS_MACOSX)
+      PostAppControllerNSNotifications();
+#endif
       // Close the error dialog.
       ASSERT_EQ(1u, views::test::WidgetTest::GetAllWidgets().size());
       (*views::test::WidgetTest::GetAllWidgets().begin())->Close();
@@ -424,6 +434,7 @@
                                         : std::string(),
             BrowserDMTokenStorage::Get()->RetrieveDMToken());
 
+  // Verify the enrollment result.
   MachineLevelUserCloudPolicyEnrollmentResult expected_result;
   if (is_enrollment_token_valid() && storage_enabled()) {
     expected_result = MachineLevelUserCloudPolicyEnrollmentResult::kSuccess;
@@ -434,6 +445,8 @@
     expected_result =
         MachineLevelUserCloudPolicyEnrollmentResult::kFailedToFetch;
   }
+
+  // Verify the metrics.
   histogram_tester_.ExpectBucketCount(kEnrollmentResultMetrics, expected_result,
                                       1);
   histogram_tester_.ExpectTotalCount(kEnrollmentResultMetrics, 1);
diff --git a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h
new file mode 100644
index 0000000..8caf474
--- /dev/null
+++ b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h
@@ -0,0 +1,18 @@
+// Copyright 2018 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_POLICY_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_BROWSERTEST_MAC_UTIL_H_
+#define CHROME_BROWSER_POLICY_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_BROWSERTEST_MAC_UTIL_H_
+
+namespace policy {
+
+// This is a helper function for MachineLevelUserCloudPolicyEnrollmentTest. It
+// sends few system notifications which app_controller_mac.mm listens to make
+// sure they're handle properly when the EnterpriseStartupDialog is being
+// displayed.
+void PostAppControllerNSNotifications();
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_POLICY_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_BROWSERTEST_MAC_UTIL_H_
diff --git a/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.mm b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.mm
new file mode 100644
index 0000000..091ba6d7
--- /dev/null
+++ b/chrome/browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.mm
@@ -0,0 +1,27 @@
+// Copyright 2018 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/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h"
+
+#import <Cocoa/Cocoa.h>
+
+namespace policy {
+
+void PostAppControllerNSNotifications() {
+  // Simulate the user clicking a window other than the dialog.
+  // The Profile is not ready when the dialog is displayed, so it can't be
+  // accessed.
+  [[NSNotificationCenter defaultCenter]
+      postNotificationName:NSWindowDidResignMainNotification
+                    object:nil];
+
+  // Simulate the user hiding Chrome via Cmd+h when the dialog is displayed.
+  // The ExtensionAppShimHandler hasn't been created when the dialog is
+  // displayed, so it must be skipped.
+  [[NSNotificationCenter defaultCenter]
+      postNotificationName:NSApplicationWillHideNotification
+                    object:nil];
+}
+
+}  // namespace
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
index 6796088..983247e 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_unittest.cc
@@ -281,6 +281,9 @@
     EXPECT_CALL(*this, OnPolicyRefresh(true)).Times(0);
     RegisterPolicyClientWithCallback(signin_service);
 
+    // Sign in to Chrome.
+    signin_manager_->SignIn(kTestGaiaId, kTestUser, "");
+
     // Mimic successful oauth token fetch.
     MakeOAuthTokenFetchSucceed();
 
@@ -433,7 +436,7 @@
       profile_.get()->GetPrefs(), kTestGaiaId, kTestUser);
   GetTokenService()->UpdateCredentials(account_id, "oauth_login_refresh_token");
 
-  // Not ssigned in yet, so client registration should be deferred.
+  // Not signed in yet, so client registration should be deferred.
   ASSERT_FALSE(IsRequestActive());
 
   // Sign in to Chrome.
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index e62bd2aa..0e71e207 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -648,6 +648,9 @@
   { key::kNetworkFileSharesAllowed,
     prefs::kNetworkFileSharesAllowed,
     base::Value::Type::BOOLEAN },
+  { key::kDeviceLocalAccountManagedSessionEnabled,
+    prefs::kManagedSessionEnabled,
+    base::Value::Type::BOOLEAN },
 #endif  // defined(OS_CHROMEOS)
 
 // Metrics reporting is controlled by a platform specific policy for ChromeOS
diff --git a/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher.cc b/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher.cc
index 525a56b..96908ec 100644
--- a/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher.cc
+++ b/chrome/browser/policy/machine_level_user_cloud_policy_register_watcher.cc
@@ -83,7 +83,7 @@
 }
 
 bool MachineLevelUserCloudPolicyRegisterWatcher::IsDialogShowing() {
-  return dialog_ && dialog_->IsShowing();
+  return (dialog_ && dialog_->IsShowing()) || run_loop_.running();
 }
 
 void MachineLevelUserCloudPolicyRegisterWatcher::
diff --git a/chrome/browser/prefs/pref_service_incognito_whitelist.cc b/chrome/browser/prefs/pref_service_incognito_whitelist.cc
index 3a8cc605..ce3199f 100644
--- a/chrome/browser/prefs/pref_service_incognito_whitelist.cc
+++ b/chrome/browser/prefs/pref_service_incognito_whitelist.cc
@@ -27,7 +27,6 @@
 #include "components/rappor/rappor_prefs.h"
 #include "components/reading_list/core/reading_list_pref_names.h"
 #include "components/search_engines/search_engines_pref_names.h"
-#include "components/spellcheck/browser/pref_names.h"
 #include "components/startup_metric_utils/browser/pref_names.h"
 #include "components/suggestions/suggestions_pref_names.h"
 #include "components/ukm/ukm_pref_names.h"
@@ -877,13 +876,6 @@
     prefs::kDefaultSearchProviderEnabled, prefs::kSearchProviderOverrides,
     prefs::kSearchProviderOverridesVersion, prefs::kCountryIDAtInstall,
 
-    // components/spellcheck/browser/pref_names.h
-    spellcheck::prefs::kSpellCheckEnable,
-    spellcheck::prefs::kSpellCheckDictionaries,
-    spellcheck::prefs::kSpellCheckForcedDictionaries,
-    spellcheck::prefs::kSpellCheckDictionary,
-    spellcheck::prefs::kSpellCheckUseSpellingService,
-
     // components/startup_metric_utils/browser/pref_names.h
     startup_metric_utils::prefs::kLastStartupTimestamp,
     startup_metric_utils::prefs::kLastStartupVersion,
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chrome/browser/push_messaging/push_messaging_service_impl.cc
index ffd4065..f5ce717a 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -267,10 +267,10 @@
       "PushMessaging.MessageReceived.Origin", app_identifier.origin());
 
   // The payload of a push message can be valid with content, valid with empty
-  // content, or null. Only set the payload data if it is non-null.
-  content::PushEventPayload payload;
+  // content, or null.
+  base::Optional<std::string> payload;
   if (message.decrypted)
-    payload.setData(message.raw_data);
+    payload = message.raw_data;
 
   // Dispatch the message to the appropriate Service Worker.
   content::BrowserContext::DeliverPushMessage(
@@ -286,7 +286,7 @@
   if (!message_dispatched_callback_for_testing_.is_null()) {
     message_dispatched_callback_for_testing_.Run(
         app_id, app_identifier.origin(),
-        app_identifier.service_worker_registration_id(), payload);
+        app_identifier.service_worker_registration_id(), std::move(payload));
   }
 }
 
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.h b/chrome/browser/push_messaging/push_messaging_service_impl.h
index 6f910f8..bbe679d 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.h
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.h
@@ -15,6 +15,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "chrome/browser/push_messaging/push_messaging_notification_manager.h"
 #include "chrome/common/buildflags.h"
 #include "components/content_settings/core/browser/content_settings_observer.h"
@@ -29,7 +30,6 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/push_messaging_service.h"
-#include "content/public/common/push_event_payload.h"
 
 class Profile;
 class PushMessagingAppIdentifier;
@@ -256,7 +256,7 @@
       base::Callback<void(const std::string& app_id,
                           const GURL& origin,
                           int64_t service_worker_registration_id,
-                          const content::PushEventPayload& payload)>;
+                          base::Optional<std::string> payload)>;
 
   void SetMessageDispatchedCallbackForTesting(
       const MessageDispatchedCallback& callback) {
diff --git a/chrome/browser/push_messaging/push_messaging_service_unittest.cc b/chrome/browser/push_messaging/push_messaging_service_unittest.cc
index 4aae8cb..12cb1b6 100644
--- a/chrome/browser/push_messaging/push_messaging_service_unittest.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_unittest.cc
@@ -26,7 +26,6 @@
 #include "components/gcm_driver/fake_gcm_client_factory.h"
 #include "components/gcm_driver/fake_gcm_profile_service.h"
 #include "components/gcm_driver/gcm_profile_service.h"
-#include "content/public/common/push_event_payload.h"
 #include "content/public/common/push_messaging_status.mojom.h"
 #include "content/public/common/push_subscription_options.h"
 #include "content/public/test/test_browser_thread_bundle.h"
@@ -126,15 +125,15 @@
   void DidDispatchMessage(std::string* app_id_out,
                           GURL* origin_out,
                           int64_t* service_worker_registration_id_out,
-                          content::PushEventPayload* payload_out,
+                          base::Optional<std::string>* payload_out,
                           const std::string& app_id,
                           const GURL& origin,
                           int64_t service_worker_registration_id,
-                          const content::PushEventPayload& payload) {
+                          base::Optional<std::string> payload) {
     *app_id_out = app_id;
     *origin_out = origin;
     *service_worker_registration_id_out = service_worker_registration_id;
-    *payload_out = payload;
+    *payload_out = std::move(payload);
   }
 
  protected:
@@ -210,12 +209,12 @@
   std::string app_id;
   GURL dispatched_origin;
   int64_t service_worker_registration_id;
-  content::PushEventPayload payload;
+  base::Optional<std::string> payload;
 
-  // (5) Observe message dispatchings from the Push Messaging service, and then
-  // dispatch the |message| on the GCM driver as if it had actually been
-  // received by Google Cloud Messaging.
-  push_service->SetMessageDispatchedCallbackForTesting(base::Bind(
+  // (5) Observe message dispatchings from the Push Messaging service, and
+  // then dispatch the |message| on the GCM driver as if it had actually
+  // been received by Google Cloud Messaging.
+  push_service->SetMessageDispatchedCallbackForTesting(base::BindRepeating(
       &PushMessagingServiceTest::DidDispatchMessage, base::Unretained(this),
       &app_id, &dispatched_origin, &service_worker_registration_id, &payload));
 
@@ -234,8 +233,8 @@
   EXPECT_EQ(origin, dispatched_origin);
   EXPECT_EQ(service_worker_registration_id, kTestServiceWorkerId);
 
-  EXPECT_FALSE(payload.is_null);
-  EXPECT_EQ(kTestPayload, payload.data);
+  EXPECT_TRUE(payload);
+  EXPECT_EQ(kTestPayload, *payload);
 }
 
 TEST_F(PushMessagingServiceTest, NormalizeSenderInfo) {
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 6c673fa3..ce93f768 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1032,13 +1032,16 @@
 void RenderViewContextMenu::AppendLinkItems() {
   if (!params_.link_url.is_empty()) {
     if (base::FeatureList::IsEnabled(features::kDesktopPWAWindowing)) {
+      const Browser* browser = GetBrowser();
+      const bool is_app = browser && browser->is_app();
+
       AppendOpenInBookmarkAppLinkItems();
 
       menu_model_.AddItemWithStringId(
           IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
-          GetBrowser()->is_app() ? IDS_CONTENT_CONTEXT_OPENLINKNEWTAB_INAPP
-                                 : IDS_CONTENT_CONTEXT_OPENLINKNEWTAB);
-      if (!GetBrowser()->is_app()) {
+          is_app ? IDS_CONTENT_CONTEXT_OPENLINKNEWTAB_INAPP
+                 : IDS_CONTENT_CONTEXT_OPENLINKNEWTAB);
+      if (!is_app) {
         menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW,
                                         IDS_CONTENT_CONTEXT_OPENLINKNEWWINDOW);
       }
@@ -1049,9 +1052,8 @@
 
       menu_model_.AddItemWithStringId(
           IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD,
-          GetBrowser()->is_app()
-              ? IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD_INAPP
-              : IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD);
+          is_app ? IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD_INAPP
+                 : IDS_CONTENT_CONTEXT_OPENLINKOFFTHERECORD);
 
     } else {
       menu_model_.AddItemWithStringId(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
@@ -1193,8 +1195,10 @@
     return;
 
   int open_in_app_string_id;
-  if (GetBrowser()->app_name() ==
-      web_app::GenerateApplicationNameFromExtensionId(pwa->id())) {
+  const Browser* browser = GetBrowser();
+  if (browser &&
+      browser->app_name() ==
+          web_app::GenerateApplicationNameFromExtensionId(pwa->id())) {
     open_in_app_string_id = IDS_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP_SAMEAPP;
   } else {
     open_in_app_string_id = IDS_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP;
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h
index c262721..f1f113e 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -81,6 +81,8 @@
 
  protected:
   Profile* GetProfile() const;
+
+  // This may return nullptr (e.g. for WebUI dialogs).
   Browser* GetBrowser() const;
 
   // Returns a (possibly truncated) version of the current selection text
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index 2dc7b56..67fe033 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -1244,4 +1244,18 @@
   EXPECT_TRUE(menu.IsItemChecked(IDC_CONTENT_CONTEXT_PICTUREINPICTURE));
 }
 
+// This test checks that we don't crash when creating a context menu for a
+// WebContents with no Browser.
+IN_PROC_BROWSER_TEST_F(ContextMenuBrowserTest, BrowserlessWebContentsCrash) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kDesktopPWAWindowing);
+  std::unique_ptr<content::WebContents> web_contents =
+      content::WebContents::Create(
+          content::WebContents::CreateParams(browser()->profile()));
+  CreateContextMenuInWebContents(
+      web_contents.get(), GURL("http://www.google.com/"),
+      GURL("http://www.google.com/"), base::ASCIIToUTF16("Google"),
+      blink::WebContextMenuData::kMediaTypeNone, ui::MENU_SOURCE_MOUSE);
+}
+
 }  // namespace
diff --git a/chrome/browser/resource_coordinator/render_process_probe.cc b/chrome/browser/resource_coordinator/render_process_probe.cc
index a7970d9..baf5d3d 100644
--- a/chrome/browser/resource_coordinator/render_process_probe.cc
+++ b/chrome/browser/resource_coordinator/render_process_probe.cc
@@ -7,6 +7,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
@@ -92,34 +93,7 @@
 
   ++current_gather_cycle_;
 
-  for (content::RenderProcessHost::iterator rph_iter =
-           content::RenderProcessHost::AllHostsIterator();
-       !rph_iter.IsAtEnd(); rph_iter.Advance()) {
-    content::RenderProcessHost* host = rph_iter.GetCurrentValue();
-    // Process may not be valid yet.
-    if (!host->GetProcess().IsValid()) {
-      continue;
-    }
-
-    auto& render_process_info = render_process_info_map_[host->GetID()];
-    render_process_info.last_gather_cycle_active = current_gather_cycle_;
-    if (render_process_info.metrics.get() == nullptr) {
-      DCHECK(!render_process_info.process.IsValid());
-
-      // Duplicate the process to retain ownership of it through the thread
-      // bouncing.
-      render_process_info.process = host->GetProcess().Duplicate();
-
-#if defined(OS_MACOSX)
-      render_process_info.metrics = base::ProcessMetrics::CreateProcessMetrics(
-          render_process_info.process.Handle(),
-          content::BrowserChildProcessHost::GetPortProvider());
-#else
-      render_process_info.metrics = base::ProcessMetrics::CreateProcessMetrics(
-          render_process_info.process.Handle());
-#endif
-    }
-  }
+  RegisterRenderProcesses();
 
   is_gathering_ = true;
 
@@ -138,13 +112,7 @@
 
   base::TimeTicks collection_start_time = base::TimeTicks::Now();
 
-  // Dispatch the memory collection request.
-  memory_instrumentation::MemoryInstrumentation::GetInstance()
-      ->RequestPrivateMemoryFootprint(
-          base::kNullProcessId,
-          base::BindRepeating(&RenderProcessProbeImpl::
-                                  ProcessGlobalMemoryDumpAndDispatchOnIOThread,
-                              base::Unretained(this), collection_start_time));
+  StartMemoryMeasurement(collection_start_time);
 
   RenderProcessInfoMap::iterator iter = render_process_info_map_.begin();
   while (iter != render_process_info_map_.end()) {
@@ -181,12 +149,23 @@
     mojom::ProcessResourceMeasurementPtr measurement =
         mojom::ProcessResourceMeasurement::New();
 
-    measurement->pid = render_process_info.process.Pid();
+    measurement->pid =
+        GetProcessId(render_process_info_map_entry.first, render_process_info);
     measurement->cpu_usage = render_process_info.cpu_usage;
 
     batch->measurements.push_back(std::move(measurement));
   }
 
+  // Record the overall outcome of the measurement.
+  MeasurementOutcome outcome = MeasurementOutcome::kMeasurementSuccess;
+  if (!global_success)
+    outcome = MeasurementOutcome::kMeasurementPartialSuccess;
+  if (!dump)
+    outcome = MeasurementOutcome::kMeasurementFailure;
+
+  UMA_HISTOGRAM_ENUMERATION("ResourceCoordinator.Measurement.Memory.Outcome",
+                            outcome);
+
   if (dump) {
     // Then amend the ones we have memory metrics for with their private
     // footprint. The global dump may contain non-renderer processes, it may
@@ -195,22 +174,51 @@
     // This may happen due to the inherent race between the request and
     // starting/stopping renderers, or because of other failures
     // This may therefore provide incomplete information.
+    size_t num_non_measured_processes = batch->measurements.size();
+    size_t num_unexpected_processes = 0;
     for (const auto& dump_entry : dump->process_dumps()) {
       base::ProcessId pid = dump_entry.pid();
 
+      bool used_entry = false;
       for (const auto& measurement : batch->measurements) {
-        if (measurement->pid == pid) {
-          measurement->private_footprint_kb =
-              dump_entry.os_dump().private_footprint_kb;
-          break;
-        }
+        if (measurement->pid != pid)
+          continue;
+
+        used_entry = true;
+
+        // The only way this could fail is if there are multiple measurements
+        // for the same PID in the memory dump.
+        DCHECK_LT(0u, num_non_measured_processes);
+        --num_non_measured_processes;
+
+        measurement->private_footprint_kb =
+            dump_entry.os_dump().private_footprint_kb;
+        break;
       }
+
+      if (!used_entry)
+        ++num_unexpected_processes;
     }
+
+    // Record the number of processes we unexpectedly did or didn't get memory
+    // measurements for.
+    UMA_HISTOGRAM_COUNTS_1000(
+        "ResourceCoordinator.Measurement.Memory.UnmeasuredProcesses",
+        num_non_measured_processes);
+    // TODO(siggi): will this count extension/utility/background worker
+    //     processes?
+    UMA_HISTOGRAM_COUNTS_1000(
+        "ResourceCoordinator.Measurement.Memory.ExtraProcesses",
+        num_unexpected_processes);
   } else {
     // We should only get a nullptr in case of failure.
     DCHECK(!global_success);
   }
 
+  // Record the number of processes encountered.
+  UMA_HISTOGRAM_COUNTS_1000("ResourceCoordinator.Measurement.TotalProcesses",
+                            batch->measurements.size());
+
   // TODO(siggi): Because memory dump requests may be combined with earlier,
   //     in-progress requests, this is an upper bound for the start time.
   //     It would be more accurate to get the start time from the memory dump
@@ -218,6 +226,11 @@
   batch->batch_started_time = collection_start_time;
   batch->batch_ended_time = base::TimeTicks::Now();
 
+  // Record the duration of the measurement process.
+  UMA_HISTOGRAM_TIMES("ResourceCoordinator.Measurement.Duration",
+                      batch->batch_ended_time - batch->batch_started_time);
+
+  // TODO(siggi): UMA record measurement time.
   bool should_restart = DispatchMetrics(std::move(batch));
   content::BrowserThread::PostTask(
       content::BrowserThread::UI, FROM_HERE,
@@ -237,6 +250,56 @@
   } else {
     is_gather_cycle_started_ = false;
   }
+
+  AfterFinishCollectionOnUIThread();
+}
+
+void RenderProcessProbeImpl::RegisterRenderProcesses() {
+  for (content::RenderProcessHost::iterator rph_iter =
+           content::RenderProcessHost::AllHostsIterator();
+       !rph_iter.IsAtEnd(); rph_iter.Advance()) {
+    content::RenderProcessHost* host = rph_iter.GetCurrentValue();
+    // Process may not be valid yet.
+    if (!host->GetProcess().IsValid()) {
+      continue;
+    }
+
+    auto& render_process_info = render_process_info_map_[host->GetID()];
+    render_process_info.last_gather_cycle_active = current_gather_cycle_;
+    if (render_process_info.metrics.get() == nullptr) {
+      DCHECK(!render_process_info.process.IsValid());
+
+      // Duplicate the process to retain ownership of it through the thread
+      // bouncing.
+      render_process_info.process = host->GetProcess().Duplicate();
+    }
+
+#if defined(OS_MACOSX)
+    render_process_info.metrics = base::ProcessMetrics::CreateProcessMetrics(
+        render_process_info.process.Handle(),
+        content::BrowserChildProcessHost::GetPortProvider());
+#else
+    render_process_info.metrics = base::ProcessMetrics::CreateProcessMetrics(
+        render_process_info.process.Handle());
+#endif
+  }
+}
+
+void RenderProcessProbeImpl::StartMemoryMeasurement(
+    base::TimeTicks collection_start_time) {
+  // Dispatch the memory collection request.
+  memory_instrumentation::MemoryInstrumentation::GetInstance()
+      ->RequestPrivateMemoryFootprint(
+          base::kNullProcessId,
+          base::BindRepeating(&RenderProcessProbeImpl::
+                                  ProcessGlobalMemoryDumpAndDispatchOnIOThread,
+                              base::Unretained(this), collection_start_time));
+}
+
+base::ProcessId RenderProcessProbeImpl::GetProcessId(
+    int /*host_id*/,
+    const RenderProcessInfo& info) {
+  return info.process.Pid();
 }
 
 void RenderProcessProbeImpl::UpdateWithFieldTrialParams() {
diff --git a/chrome/browser/resource_coordinator/render_process_probe.h b/chrome/browser/resource_coordinator/render_process_probe.h
index f4d11b0..f2f89b8 100644
--- a/chrome/browser/resource_coordinator/render_process_probe.h
+++ b/chrome/browser/resource_coordinator/render_process_probe.h
@@ -52,6 +52,16 @@
   void StartSingleGather() override;
 
  protected:
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class MeasurementOutcome {
+    kMeasurementSuccess = 0,
+    kMeasurementPartialSuccess = 1,
+    kMeasurementFailure = 2,
+
+    kMaxValue = kMeasurementFailure,
+  };
+
   static constexpr base::TimeDelta kUninitializedCPUTime =
       base::TimeDelta::FromMicroseconds(-1);
 
@@ -84,8 +94,14 @@
   // (4) Initiate the next render process metrics collection cycle if the
   // cycle has been started and |restart_cycle| is true, which consists of a
   // delayed call to perform (1) via a timer.
-  // Virtual for testing.
-  virtual void FinishCollectionOnUIThread(bool restart_cycle);
+  void FinishCollectionOnUIThread(bool restart_cycle);
+
+  // Test seams.
+  virtual void AfterFinishCollectionOnUIThread() {}
+  virtual void RegisterRenderProcesses();
+  virtual void StartMemoryMeasurement(base::TimeTicks collection_start_time);
+  virtual base::ProcessId GetProcessId(int host_id,
+                                       const RenderProcessInfo& info);
 
   // Allows FieldTrial parameters to override defaults.
   void UpdateWithFieldTrialParams();
diff --git a/chrome/browser/resource_coordinator/render_process_probe_browsertest.cc b/chrome/browser/resource_coordinator/render_process_probe_browsertest.cc
index 186d9e2..0bb7aaa 100644
--- a/chrome/browser/resource_coordinator/render_process_probe_browsertest.cc
+++ b/chrome/browser/resource_coordinator/render_process_probe_browsertest.cc
@@ -23,6 +23,7 @@
 #include "url/gurl.h"
 
 namespace resource_coordinator {
+namespace {
 
 class TestingRenderProcessProbe : public RenderProcessProbeImpl {
  public:
@@ -40,9 +41,7 @@
     return false;
   }
 
-  void FinishCollectionOnUIThread(bool restart_cycle) override {
-    RenderProcessProbeImpl::FinishCollectionOnUIThread(restart_cycle);
-
+  void AfterFinishCollectionOnUIThread() override {
     current_run_loop_->QuitWhenIdle();
   }
 
@@ -94,6 +93,8 @@
   DISALLOW_COPY_AND_ASSIGN(TestingRenderProcessProbe);
 };
 
+}  // namespace
+
 class RenderProcessProbeBrowserTest : public InProcessBrowserTest {
  public:
   RenderProcessProbeBrowserTest() = default;
diff --git a/chrome/browser/resource_coordinator/render_process_probe_unittest.cc b/chrome/browser/resource_coordinator/render_process_probe_unittest.cc
new file mode 100644
index 0000000..fab578b
--- /dev/null
+++ b/chrome/browser/resource_coordinator/render_process_probe_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright 2018 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/resource_coordinator/render_process_probe.h"
+#include "base/process/process.h"
+#include "base/process/process_metrics.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace resource_coordinator {
+
+namespace {
+
+class TestingRenderProcessProbe : public RenderProcessProbeImpl {
+ public:
+  // Expose for testing.
+  using RenderProcessProbeImpl::MeasurementOutcome;
+
+  void SetMeasurementResults(
+      bool global_success,
+      std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump) {
+    global_success_ = global_success;
+    dump_ = std::move(dump);
+  }
+
+ protected:
+  bool global_success_;
+  std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump_;
+
+  // Overridden for testing.
+  void RegisterRenderProcesses() override;
+  void StartMemoryMeasurement(base::TimeTicks collection_start_time) override;
+  base::ProcessId GetProcessId(int host_id,
+                               const RenderProcessInfo& info) override;
+};
+
+void TestingRenderProcessProbe::RegisterRenderProcesses() {
+  // Register 4 renderer processes, all of which refer to this process.
+  for (int id = 1; id < 5; ++id) {
+    auto& render_process_info = render_process_info_map_[id];
+    render_process_info.last_gather_cycle_active = current_gather_cycle_;
+    render_process_info.process = base::Process::Current();
+    render_process_info.metrics =
+        base::ProcessMetrics::CreateCurrentProcessMetrics();
+  }
+}
+
+void TestingRenderProcessProbe::StartMemoryMeasurement(
+    base::TimeTicks collection_start_time) {
+  // Post the stored results to the completion function.
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&TestingRenderProcessProbe::
+                         ProcessGlobalMemoryDumpAndDispatchOnIOThread,
+                     base::Unretained(this), collection_start_time,
+                     global_success_, std::move(dump_)));
+}
+
+base::ProcessId TestingRenderProcessProbe::GetProcessId(
+    int host_id,
+    const RenderProcessInfo& /* info */) {
+  // Use the host ID for PID for the purposes of this test.
+  return static_cast<base::ProcessId>(host_id);
+}
+
+class RenderProcessProbeTest : public testing::Test {
+ protected:
+  void RunGatherCycle(
+      bool global_success,
+      std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump);
+
+  std::unique_ptr<memory_instrumentation::GlobalMemoryDump> CreateMemoryDump(
+      int first_pid,
+      int num_pids);
+
+  content::TestBrowserThreadBundle browser_thread_bundle_;
+  TestingRenderProcessProbe probe_;
+};
+
+void RenderProcessProbeTest::RunGatherCycle(
+    bool global_success,
+    std::unique_ptr<memory_instrumentation::GlobalMemoryDump> dump) {
+  probe_.SetMeasurementResults(global_success, std::move(dump));
+  probe_.StartSingleGather();
+  browser_thread_bundle_.RunUntilIdle();
+}
+
+std::unique_ptr<memory_instrumentation::GlobalMemoryDump>
+RenderProcessProbeTest::CreateMemoryDump(int first_pid, int num_pids) {
+  memory_instrumentation::mojom::GlobalMemoryDumpPtr global_dump =
+      memory_instrumentation::mojom::GlobalMemoryDump::New();
+
+  for (int pid = first_pid; pid < first_pid + num_pids; ++pid) {
+    memory_instrumentation::mojom::ProcessMemoryDumpPtr process_dump =
+        memory_instrumentation::mojom::ProcessMemoryDump::New();
+
+    process_dump->os_dump = memory_instrumentation::mojom::OSMemDump::New();
+    process_dump->os_dump->private_footprint_kb = pid * 100;
+
+    process_dump->process_type =
+        memory_instrumentation::mojom::ProcessType::RENDERER;
+    process_dump->pid = pid;
+
+    global_dump->process_dumps.push_back(std::move(process_dump));
+  }
+
+  return memory_instrumentation::GlobalMemoryDump::MoveFrom(
+      std::move(global_dump));
+}
+
+}  // namespace
+
+TEST_F(RenderProcessProbeTest, FailureMetrics) {
+  base::HistogramTester tester;
+
+  // Full failure with a null dump.
+  RunGatherCycle(false, nullptr);
+
+  tester.ExpectTotalCount("ResourceCoordinator.Measurement.Duration", 1);
+
+  tester.ExpectUniqueSample("ResourceCoordinator.Measurement.TotalProcesses", 4,
+                            1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.Outcome",
+      TestingRenderProcessProbe::MeasurementOutcome::kMeasurementFailure, 1);
+  tester.ExpectTotalCount(
+      "ResourceCoordinator.Measurement.Memory.UnmeasuredProcesses", 0);
+  tester.ExpectTotalCount(
+      "ResourceCoordinator.Measurement.Memory.ExtraProcesses", 0);
+}
+
+TEST_F(RenderProcessProbeTest, PartialSuccessMetrics) {
+  base::HistogramTester tester;
+
+  // Partial failure with a full dump.
+  RunGatherCycle(false, CreateMemoryDump(1, 4));
+
+  tester.ExpectTotalCount("ResourceCoordinator.Measurement.Duration", 1);
+
+  tester.ExpectUniqueSample("ResourceCoordinator.Measurement.TotalProcesses", 4,
+                            1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.Outcome",
+      TestingRenderProcessProbe::MeasurementOutcome::kMeasurementPartialSuccess,
+      1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.UnmeasuredProcesses", 0, 1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.ExtraProcesses", 0, 1);
+}
+
+TEST_F(RenderProcessProbeTest, SuccessMetrics) {
+  base::HistogramTester tester;
+
+  // Full success with a skewed dump, missing one process while contributing
+  // an extra.
+  RunGatherCycle(true, CreateMemoryDump(2, 4));
+
+  tester.ExpectTotalCount("ResourceCoordinator.Measurement.Duration", 1);
+
+  tester.ExpectUniqueSample("ResourceCoordinator.Measurement.TotalProcesses", 4,
+                            1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.Outcome",
+      TestingRenderProcessProbe::MeasurementOutcome::kMeasurementSuccess, 1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.UnmeasuredProcesses", 1, 1);
+  tester.ExpectUniqueSample(
+      "ResourceCoordinator.Measurement.Memory.ExtraProcesses", 1, 1);
+}
+
+}  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/tab_manager.cc b/chrome/browser/resource_coordinator/tab_manager.cc
index e1566332f..81910624 100644
--- a/chrome/browser/resource_coordinator/tab_manager.cc
+++ b/chrome/browser/resource_coordinator/tab_manager.cc
@@ -648,14 +648,9 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   for (LifecycleUnit* lifecycle_unit : GetSortedLifecycleUnits()) {
-    // TODO(chrisha): Report decision details!
-    auto old_state = lifecycle_unit->GetState();
     DecisionDetails decision_details;
     if (lifecycle_unit->CanDiscard(reason, &decision_details) &&
         lifecycle_unit->Discard(reason)) {
-      // TODO(chrisha): Move this into a LifecycleUnitObserver.
-      TabManagerStatsCollector::RecordDiscardDecision(
-          lifecycle_unit, decision_details, old_state, reason);
       TabLifecycleUnitExternal* tab_lifecycle_unit_external =
           lifecycle_unit->AsTabLifecycleUnitExternal();
       // For now, all LifecycleUnits are TabLifecycleUnitExternals.
@@ -960,7 +955,6 @@
   base::TimeTicks next_state_transition_time = base::TimeTicks::Max();
   const base::TimeTicks now = NowTicks();
   LifecycleUnit* oldest_discardable_lifecycle_unit = nullptr;
-  DecisionDetails oldest_discardable_lifecycle_unit_decision_details;
   LifecycleUnit* oldest_frozen_lifecycle_unit = nullptr;
 
   for (LifecycleUnit* lifecycle_unit : lifecycle_units_) {
@@ -979,8 +973,6 @@
               oldest_discardable_lifecycle_unit
                   ->GetChromeUsageTimeWhenHidden()) {
         oldest_discardable_lifecycle_unit = lifecycle_unit;
-        oldest_discardable_lifecycle_unit_decision_details =
-            std::move(discard_details);
       }
     }
 
@@ -1016,11 +1008,9 @@
   // PerformStateTransitions() is scheduled immediately to check if another
   // discard should happen.
   if (oldest_discardable_lifecycle_unit && ShouldProactivelyDiscardTabs()) {
-    next_state_transition_time =
-        std::min(MaybeDiscardLifecycleUnit(
-                     oldest_discardable_lifecycle_unit,
-                     oldest_discardable_lifecycle_unit_decision_details, now),
-                 next_state_transition_time);
+    next_state_transition_time = std::min(
+        MaybeDiscardLifecycleUnit(oldest_discardable_lifecycle_unit, now),
+        next_state_transition_time);
   }
 
   // Schedule the next call to PerformStateTransitions().
@@ -1044,12 +1034,7 @@
                    proactive_freeze_discard_params_.refreeze_timeout);
 
   if (now >= freeze_time) {
-    auto old_state = lifecycle_unit->GetState();
-    if (lifecycle_unit->Freeze()) {
-      // TODO(chrisha): Move this logging to an observer.
-      TabManagerStatsCollector::RecordFreezeDecision(lifecycle_unit,
-                                                     freeze_details, old_state);
-    }
+    lifecycle_unit->Freeze();
     return base::TimeTicks::Max();
   }
 
@@ -1077,7 +1062,6 @@
 
 base::TimeTicks TabManager::MaybeDiscardLifecycleUnit(
     LifecycleUnit* lifecycle_unit,
-    const DecisionDetails& decision_details,
     base::TimeTicks now) {
   const base::TimeDelta usage_time_not_visible =
       usage_clock_.GetTotalUsageTime() -
@@ -1086,13 +1070,7 @@
       GetTimeInBackgroundBeforeProactiveDiscard() - usage_time_not_visible;
 
   if (time_until_discard <= base::TimeDelta()) {
-    auto old_state = lifecycle_unit->GetState();
-    if (lifecycle_unit->Discard(DiscardReason::kProactive)) {
-      // TODO(chrisha): Move this into a LifecycleUnitObserver.
-      TabManagerStatsCollector::RecordDiscardDecision(
-          lifecycle_unit, decision_details, old_state,
-          DiscardReason::kProactive);
-    }
+    lifecycle_unit->Discard(DiscardReason::kProactive);
     // Request another call to check if another discard should happen.
     return base::TimeTicks();
   }
diff --git a/chrome/browser/resource_coordinator/tab_manager.h b/chrome/browser/resource_coordinator/tab_manager.h
index caba0e46..4258467 100644
--- a/chrome/browser/resource_coordinator/tab_manager.h
+++ b/chrome/browser/resource_coordinator/tab_manager.h
@@ -447,16 +447,13 @@
                                              base::TimeTicks now);
 
   // If enough Chrome usage time has elapsed since |lifecycle_unit| was hidden,
-  // proactively discards it. |lifecycle_unit| must be discardable.
-  // |decision_details| is the result of calling CanDiscard() on it. Returns the
+  // proactively discards it. |lifecycle_unit| must be discardable. Returns the
   // time at which this should be called again, or TimeTicks::Max() if no
   // further call is needed. Always returns a zero TimeTicks when a discard
   // happen, to check immediately if another discard should happen. |now| is the
   // current time.
-  base::TimeTicks MaybeDiscardLifecycleUnit(
-      LifecycleUnit* lifecycle_unit,
-      const DecisionDetails& decision_details,
-      base::TimeTicks now);
+  base::TimeTicks MaybeDiscardLifecycleUnit(LifecycleUnit* lifecycle_unit,
+                                            base::TimeTicks now);
 
   // LifecycleUnitObserver:
   void OnLifecycleUnitVisibilityChanged(
diff --git a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
index 080745b..8fa8c17 100644
--- a/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_delegate_chromeos.cc
@@ -539,13 +539,7 @@
   DecisionDetails decision_details;
   if (!lifecycle_unit->CanDiscard(reason, &decision_details))
     return false;
-  auto old_state = lifecycle_unit->GetState();
   bool did_discard = lifecycle_unit->Discard(reason);
-  if (did_discard) {
-    // TODO(chrisha): Move this to a LifecycleUnitObserver.
-    TabManagerStatsCollector::RecordDiscardDecision(
-        lifecycle_unit, decision_details, old_state, reason);
-  }
   return did_discard;
 }
 
diff --git a/chrome/browser/resource_coordinator/tab_manager_stats_collector.cc b/chrome/browser/resource_coordinator/tab_manager_stats_collector.cc
index 1519d7e..4803e74 100644
--- a/chrome/browser/resource_coordinator/tab_manager_stats_collector.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_stats_collector.cc
@@ -10,11 +10,13 @@
 #include <utility>
 
 #include "base/atomic_sequence_num.h"
+#include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
 #include "base/stl_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -26,6 +28,7 @@
 #include "chrome/browser/resource_coordinator/time.h"
 #include "chrome/browser/sessions/session_restore.h"
 #include "components/metrics/system_memory_stats_recorder.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/swap_metrics_driver.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -124,8 +127,15 @@
   const SessionType session_type_;
 };
 
-TabManagerStatsCollector::TabManagerStatsCollector() {
+TabManagerStatsCollector::TabManagerStatsCollector() : weak_factory_(this) {
   SessionRestore::AddObserver(this);
+
+  // Post an after startup task that starts the periodic sampling of freezing
+  // and discarding stats.
+  content::BrowserThread::PostAfterStartupTask(
+      FROM_HERE, base::SequencedTaskRunnerHandle::Get(),
+      base::BindOnce(&TabManagerStatsCollector::StartPeriodicSampling,
+                     weak_factory_.GetWeakPtr()));
 }
 
 TabManagerStatsCollector::~TabManagerStatsCollector() {
@@ -276,111 +286,6 @@
   }
 }
 
-namespace {
-
-using LifecycleUnitStateChangeReason = ::mojom::LifecycleUnitStateChangeReason;
-
-LifecycleUnitStateChangeReason DiscardReasonToLifecycleUnitStateChangeReason(
-    DiscardReason reason) {
-  // TODO(chrisha): Do away with DiscardReason, and use the mojo enum
-  // everywhere.
-  switch (reason) {
-    case DiscardReason::kExternal:
-      return LifecycleUnitStateChangeReason::EXTENSION_INITIATED;
-
-    case DiscardReason::kProactive:
-      return LifecycleUnitStateChangeReason::BROWSER_INITIATED;
-
-    case DiscardReason::kUrgent:
-      return LifecycleUnitStateChangeReason::SYSTEM_MEMORY_PRESSURE;
-  }
-
-  NOTREACHED();
-  return LifecycleUnitStateChangeReason::BROWSER_INITIATED;
-}
-
-void RecordLifecycleStateChangeUkm(
-    LifecycleUnit* lifecycle_unit,
-    const DecisionDetails& decision_details,
-    LifecycleUnitState old_state,
-    LifecycleUnitState new_state,
-    LifecycleUnitStateChangeReason change_reason) {
-  ukm::SourceId ukm_source_id = lifecycle_unit->GetUkmSourceId();
-  if (ukm_source_id == ukm::kInvalidSourceId)
-    return;
-
-  ukm::builders::TabManager_LifecycleStateChange builder(ukm_source_id);
-
-  builder.SetOldLifecycleState(static_cast<int64_t>(old_state));
-  builder.SetNewLifecycleState(static_cast<int64_t>(new_state));
-  builder.SetLifecycleStateChangeReason(static_cast<int64_t>(change_reason));
-
-  // We only currently report transitions for tabs, so this lookup should never
-  // fail. It will start failing once we add ARC processes as LifecycleUnits.
-  // TODO(chrisha): This should be time since the navigation was committed (the
-  // load started), but that information is currently only persisted inside the
-  // CU-graph. Using time since navigation finished is a cheap approximation for
-  // the time being.
-  auto* tab = lifecycle_unit->AsTabLifecycleUnitExternal();
-  auto* contents = tab->GetWebContents();
-  auto* nav_entry = contents->GetController().GetLastCommittedEntry();
-  if (nav_entry) {
-    auto timestamp = nav_entry->GetTimestamp();
-    if (!timestamp.is_null()) {
-      base::TimeDelta time_since_load = base::Time::Now() - timestamp;
-      builder.SetTimeSinceNavigationMs(time_since_load.InMilliseconds());
-    }
-  }
-
-  // Set all visibility related fields.
-  //
-  // |time_since_visible| is:
-  // - Zero if the LifecycleUnit is currently visible.
-  // - Time since creation if the LifecycleUnit was never visible.
-  // - Time since visible if the LifecycleUnit was visible in the past.
-  auto visibility = lifecycle_unit->GetVisibility();
-  base::TimeDelta time_since_visible;  // Zero.
-  if (visibility != content::Visibility::VISIBLE)
-    time_since_visible = NowTicks() - lifecycle_unit->GetWallTimeWhenHidden();
-  builder.SetTimeSinceVisibilityStateChangeMs(
-      time_since_visible.InMilliseconds());
-  builder.SetVisibilityState(static_cast<int64_t>(visibility));
-
-  // TODO(chrisha): Fix logging to occur when the transition is finalized so
-  // that this is actually known.
-  builder.SetTransitionForced(false);
-
-  // This populates all of the relevant Success/Failure fields, as well as
-  // Outcome.
-  decision_details.Populate(&builder);
-
-  builder.Record(ukm::UkmRecorder::Get());
-}
-
-}  // namespace
-
-// static
-void TabManagerStatsCollector::RecordFreezeDecision(
-    LifecycleUnit* lifecycle_unit,
-    const DecisionDetails& decision_details,
-    LifecycleUnitState old_state) {
-  RecordLifecycleStateChangeUkm(
-      lifecycle_unit, decision_details, old_state, LifecycleUnitState::FROZEN,
-      LifecycleUnitStateChangeReason::BROWSER_INITIATED);
-}
-
-// static
-void TabManagerStatsCollector::RecordDiscardDecision(
-    LifecycleUnit* lifecycle_unit,
-    const DecisionDetails& decision_details,
-    LifecycleUnitState old_state,
-    DiscardReason reason) {
-  RecordLifecycleStateChangeUkm(
-      lifecycle_unit, decision_details, old_state,
-      LifecycleUnitState::DISCARDED,
-      DiscardReasonToLifecycleUnitStateChangeReason(reason));
-}
-
 void TabManagerStatsCollector::OnSessionRestoreStartedLoadingTabs() {
   DCHECK(!is_session_restore_loading_tabs_);
   UpdateSessionAndSequence();
@@ -555,6 +460,120 @@
   sequence_ = 0;
 }
 
+void TabManagerStatsCollector::StartPeriodicSampling() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Post a first task with a random delay less than the sampling interval.
+  base::TimeDelta delay = base::TimeDelta::FromSeconds(
+      base::RandInt(0, kLowFrequencySamplingInterval.InSeconds()));
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&TabManagerStatsCollector::PerformPeriodicSample,
+                     weak_factory_.GetWeakPtr()),
+      delay);
+}
+
+void TabManagerStatsCollector::PerformPeriodicSample() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  sample_start_time_ = NowTicks();
+
+  // Iterate over the tabs and get their data. The TabManager owns us and
+  // outlives us, so will always exist.
+  LifecycleUnitVector lifecycle_units =
+      g_browser_process->GetTabManager()->GetSortedLifecycleUnits();
+  for (auto* lifecycle_unit : lifecycle_units) {
+    DecisionDetails freeze_decision;
+    lifecycle_unit->CanFreeze(&freeze_decision);
+    RecordDecisionDetails(lifecycle_unit, freeze_decision,
+                          ::mojom::LifecycleUnitState::FROZEN);
+
+    DecisionDetails discard_decision;
+    lifecycle_unit->CanDiscard(DiscardReason::kProactive, &discard_decision);
+    RecordDecisionDetails(lifecycle_unit, discard_decision,
+                          ::mojom::LifecycleUnitState::DISCARDED);
+  }
+
+  // Determine when the next sample should run based on when this cycle
+  // started.
+  base::TimeDelta delay =
+      (sample_start_time_ + kLowFrequencySamplingInterval) - NowTicks();
+
+  // In the very unlikely case that the system is so busy that another sample
+  // should already have been taken, then skip a cycle and wait a full sampling
+  // period. This provides rudimentary rate limiting that prevents these samples
+  // from taking up too much time.
+  if (delay <= base::TimeDelta())
+    delay = kLowFrequencySamplingInterval;
+
+  // Schedule the next sample.
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&TabManagerStatsCollector::PerformPeriodicSample,
+                     weak_factory_.GetWeakPtr()),
+      delay);
+}
+
+// static
+void TabManagerStatsCollector::RecordDecisionDetails(
+    LifecycleUnit* lifecycle_unit,
+    const DecisionDetails& decision_details,
+    ::mojom::LifecycleUnitState target_state) {
+  ukm::SourceId ukm_source_id = lifecycle_unit->GetUkmSourceId();
+  if (ukm_source_id == ukm::kInvalidSourceId)
+    return;
+
+  // Don't log anything for invalid decision details (trivial reasons: crashed
+  // tabs, navigations not yet committed, etc).
+  if (decision_details.reasons().empty())
+    return;
+
+  ukm::builders::TabManager_LifecycleStateChange builder(ukm_source_id);
+
+  builder.SetOldLifecycleState(
+      static_cast<int64_t>(lifecycle_unit->GetState()));
+  builder.SetNewLifecycleState(static_cast<int64_t>(target_state));
+  // No LifecycleStateChangeReason is set right now, indicating that this is a
+  // theoretical state change rather than an actual one. This differentiates
+  // sampled lifecycle transitions from actual ones.
+
+  // We only currently report transitions for tabs, so this lookup should never
+  // fail. It will start failing once we add ARC processes as LifecycleUnits.
+  // TODO(chrisha): This should be time since the navigation was committed (the
+  // load started), but that information is currently only persisted inside the
+  // CU-graph. Using time since navigation finished is a cheap approximation for
+  // the time being.
+  auto* tab = lifecycle_unit->AsTabLifecycleUnitExternal();
+  auto* contents = tab->GetWebContents();
+  auto* nav_entry = contents->GetController().GetLastCommittedEntry();
+  if (nav_entry) {
+    auto timestamp = nav_entry->GetTimestamp();
+    if (!timestamp.is_null()) {
+      auto elapsed = base::Time::Now() - timestamp;
+      builder.SetTimeSinceNavigationMs(elapsed.InMilliseconds());
+    }
+  }
+
+  // Set visibility related data.
+  // |time_since_visible| is:
+  // - Zero if the LifecycleUnit is currently visible.
+  // - Time since creation if the LifecycleUnit was never visible.
+  // - Time since visible if the LifecycleUnit was visible in the past.
+  auto visibility = lifecycle_unit->GetVisibility();
+  base::TimeDelta time_since_visible;  // Zero.
+  if (visibility != content::Visibility::VISIBLE)
+    time_since_visible = NowTicks() - lifecycle_unit->GetWallTimeWhenHidden();
+  builder.SetTimeSinceVisibilityStateChangeMs(
+      time_since_visible.InMilliseconds());
+  builder.SetVisibilityState(static_cast<int64_t>(visibility));
+
+  // This populates all of the relevant Success/Failure fields, as well as
+  // Outcome.
+  decision_details.Populate(&builder);
+
+  builder.Record(ukm::UkmRecorder::Get());
+}
+
 // static
 const char TabManagerStatsCollector::
     kHistogramSessionRestoreForegroundTabExpectedTaskQueueingDuration[] =
@@ -620,4 +639,7 @@
     TabManagerStatsCollector::kHistogramSessionOverlapBackgroundTabOpening[] =
         "TabManager.SessionOverlap.BackgroundTabOpening";
 
+// static
+const base::TimeDelta TabManagerStatsCollector::kLowFrequencySamplingInterval;
+
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/tab_manager_stats_collector.h b/chrome/browser/resource_coordinator/tab_manager_stats_collector.h
index a9445b5..5001bb43 100644
--- a/chrome/browser/resource_coordinator/tab_manager_stats_collector.h
+++ b/chrome/browser/resource_coordinator/tab_manager_stats_collector.h
@@ -12,6 +12,7 @@
 #include <unordered_map>
 
 #include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "chrome/browser/resource_coordinator/decision_details.h"
@@ -19,6 +20,7 @@
 #include "chrome/browser/resource_coordinator/lifecycle_unit.h"
 #include "chrome/browser/resource_coordinator/time.h"
 #include "chrome/browser/sessions/session_restore_observer.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 
 namespace content {
 class SwapMetricsDriver;
@@ -104,16 +106,6 @@
   // Record background tab count for BackgroundTabOpening.
   void RecordBackgroundTabCount();
 
-  // Records information about a freezing/discarding event, which may or may not
-  // have been successful.
-  static void RecordFreezeDecision(LifecycleUnit* lifecycle_unit,
-                                   const DecisionDetails& decision_details,
-                                   LifecycleUnitState old_state);
-  static void RecordDiscardDecision(LifecycleUnit* lifecycle_unit,
-                                    const DecisionDetails& decision_details,
-                                    LifecycleUnitState old_state,
-                                    DiscardReason reason);
-
   // SessionRestoreObserver
   void OnSessionRestoreStartedLoadingTabs() override;
   void OnSessionRestoreFinishedLoadingTabs() override;
@@ -184,6 +176,7 @@
                            HistogramsTabCount);
   FRIEND_TEST_ALL_PREFIXES(TabManagerStatsCollectorTest,
                            HistogramsSessionOverlap);
+  FRIEND_TEST_ALL_PREFIXES(TabManagerStatsCollectorTest, PeriodicSamplingWorks);
 
   // Returns true if the browser is currently in more than one session with
   // different types. We do not want to report metrics in this situation to have
@@ -203,6 +196,21 @@
   // Update session and sequence information for UKM recording.
   void UpdateSessionAndSequence();
 
+  // This is called sometime after startup, and initiates periodic CanFreeze/
+  // CanDiscard metric sampling. It posts a delayed task to
+  // PerformPeriodicSample.
+  void StartPeriodicSampling();
+
+  // This is called when a sample should be taken. First call is via
+  // StartPeriodicSampling and then it posts a delayed task to call itself.
+  void PerformPeriodicSample();
+
+  // Helper function for RecordSampledTabData. Records a single UKM entry for
+  // the provided DecisionDetails and destination lifecycle state.
+  static void RecordDecisionDetails(LifecycleUnit* lifecycle_unit,
+                                    const DecisionDetails& decision_details,
+                                    ::mojom::LifecycleUnitState new_state);
+
   static const char
       kHistogramSessionRestoreForegroundTabExpectedTaskQueueingDuration[];
   static const char
@@ -219,6 +227,11 @@
   static const char kHistogramSessionOverlapSessionRestore[];
   static const char kHistogramSessionOverlapBackgroundTabOpening[];
 
+  // The rough sampling interval for low-frequency sampled stats. This should
+  // be O(minutes).
+  static constexpr base::TimeDelta kLowFrequencySamplingInterval =
+      base::TimeDelta::FromMinutes(5);
+
   // TabManagerStatsCollector should be used from a single sequence.
   SEQUENCE_CHECKER(sequence_checker_);
 
@@ -252,6 +265,11 @@
       foreground_contents_switched_to_times_;
 
   BackgroundTabCountStats background_tab_count_stats_;
+
+  // The start time of an ongoing periodic sample.
+  base::TimeTicks sample_start_time_;
+
+  base::WeakPtrFactory<TabManagerStatsCollector> weak_factory_;
 };
 
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resource_coordinator/tab_manager_stats_collector_unittest.cc b/chrome/browser/resource_coordinator/tab_manager_stats_collector_unittest.cc
index d49f4d2..50908c6 100644
--- a/chrome/browser/resource_coordinator/tab_manager_stats_collector_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_manager_stats_collector_unittest.cc
@@ -11,14 +11,22 @@
 #include "base/macros.h"
 #include "base/metrics/metrics_hashes.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/test_mock_time_task_runner.h"
+#include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/resource_coordinator/local_site_characteristics_data_unittest_utils.h"
 #include "chrome/browser/resource_coordinator/tab_helper.h"
 #include "chrome/browser/resource_coordinator/tab_load_tracker.h"
 #include "chrome/browser/resource_coordinator/tab_manager_web_contents_data.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chrome/test/base/testing_profile.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/test/web_contents_tester.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -37,9 +45,20 @@
 constexpr TabLoadTracker::LoadingState LOADING = LoadingState::LOADING;
 constexpr TabLoadTracker::LoadingState LOADED = LoadingState::LOADED;
 
-class TabManagerStatsCollectorTest : public ChromeRenderViewHostTestHarness {
+class TabManagerStatsCollectorTest
+    : public testing::ChromeTestHarnessWithLocalDB {
  protected:
-  TabManagerStatsCollectorTest() = default;
+  TabManagerStatsCollectorTest()
+      : scoped_context_(
+            std::make_unique<base::TestMockTimeTaskRunner::ScopedContext>(
+                task_runner_)),
+        scoped_set_tick_clock_for_testing_(task_runner_->GetMockTickClock()) {
+    base::MessageLoopCurrent::Get()->SetTaskRunner(task_runner_);
+
+    // Start with a non-zero time.
+    task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(42));
+  }
+
   ~TabManagerStatsCollectorTest() override = default;
 
   TabManagerStatsCollector* tab_manager_stats_collector() {
@@ -73,7 +92,16 @@
   }
 
   void SetUp() override {
-    ChromeRenderViewHostTestHarness::SetUp();
+    ChromeTestHarnessWithLocalDB::SetUp();
+
+    // Call the tab manager so that it is created right away.
+    tab_manager();
+  }
+
+  void TearDown() override {
+    task_runner_->RunUntilIdle();
+    scoped_context_.reset();
+    ChromeTestHarnessWithLocalDB::TearDown();
   }
 
   std::unique_ptr<WebContents> CreateWebContentsForUKM(ukm::SourceId id) {
@@ -84,6 +112,28 @@
     return contents;
   }
 
+  std::unique_ptr<WebContents> CreateDiscardableWebContents(ukm::SourceId id) {
+    std::unique_ptr<WebContents> web_contents = CreateWebContentsForUKM(id);
+
+    // Commit an URL and mark the tab as "loaded" to allow discarding.
+    content::WebContentsTester::For(web_contents.get())
+        ->NavigateAndCommit(GURL("https://www.example.com"));
+    TabLoadTracker::Get()->TransitionStateForTesting(web_contents.get(),
+                                                     LoadingState::LOADED);
+
+    base::RepeatingClosure run_loop_cb = base::BindRepeating(
+        &base::TestMockTimeTaskRunner::RunUntilIdle, task_runner_);
+
+    testing::WaitForLocalDBEntryToBeInitialized(web_contents.get(),
+                                                run_loop_cb);
+    testing::ExpireLocalDBObservationWindows(web_contents.get());
+    return web_contents;
+  }
+
+  scoped_refptr<base::TestMockTimeTaskRunner> task_runner_ =
+      base::MakeRefCounted<base::TestMockTimeTaskRunner>();
+  std::unique_ptr<base::TestMockTimeTaskRunner::ScopedContext> scoped_context_;
+  ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing_;
   base::HistogramTester histogram_tester_;
   ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
 
@@ -95,7 +145,7 @@
 
 class TabManagerStatsCollectorParameterizedTest
     : public TabManagerStatsCollectorTest,
-      public testing::WithParamInterface<
+      public ::testing::WithParamInterface<
           std::pair<bool,   // should_test_session_restore
                     bool>>  // should_test_background_tab_opening
 {
@@ -568,4 +618,29 @@
   EXPECT_EQ(0ul, test_ukm_recorder_.entries_count());
 }
 
+TEST_F(TabManagerStatsCollectorTest, PeriodicSamplingWorks) {
+  using UkmEntry = ukm::builders::TabManager_LifecycleStateChange;
+
+  // Create a window, browser and a tab strip. The tabs need to be added to a
+  // tab strip in order to be tracked by the TabManager.
+  auto window = std::make_unique<TestBrowserWindow>();
+  Browser::CreateParams params(profile(), true);
+  params.type = Browser::TYPE_TABBED;
+  params.window = window.get();
+  auto browser = std::make_unique<Browser>(params);
+  TabStripModel* tab_strip = browser->tab_strip_model();
+  tab_strip->AppendWebContents(CreateDiscardableWebContents(1),
+                               true /* foreground */);
+  tab_strip->AppendWebContents(CreateDiscardableWebContents(2), false);
+  tab_strip->AppendWebContents(CreateDiscardableWebContents(3), false);
+
+  tab_manager_stats_collector()->PerformPeriodicSample();
+
+  // Expect two entries per tab (freezing and discard decisions).
+  EXPECT_EQ(6u,
+            test_ukm_recorder_.GetEntriesByName(UkmEntry::kEntryName).size());
+
+  tab_strip->CloseAllTabs();
+}
+
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
index 1838664..2f408ea 100644
--- a/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
+++ b/chrome/browser/resources/chromeos/assistant_optin/assistant_optin.js
@@ -5,6 +5,7 @@
 // <include src="../login/oobe_types.js">
 // <include src="../login/oobe_buttons.js">
 // <include src="../login/oobe_change_picture.js">
+// <include src="../login/oobe_dialog_host_behavior.js">
 // <include src="../login/oobe_dialog.js">
 // <include src="utils.js">
 // <include src="setting_zippy.js">
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
index d413fd0..266cb74 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
@@ -110,13 +110,6 @@
  */
 DesktopAutomationHandler.announceActions = false;
 
-/**
- * The url of the keyboard.
- * @const {string}
- */
-DesktopAutomationHandler.KEYBOARD_URL =
-    'chrome-extension://jkghodnilhceideoidjikpgommlajknk/inputview.html';
-
 DesktopAutomationHandler.prototype = {
   __proto__: BaseAutomationHandler.prototype,
 
@@ -655,9 +648,9 @@
         (!opt_onFocus && target != voxTarget &&
          target.root.role != RoleType.DESKTOP &&
          voxTarget.root.role != RoleType.DESKTOP &&
-         voxTarget.root.url.indexOf(DesktopAutomationHandler.KEYBOARD_URL) !=
-             0) &&
-            !AutomationUtil.isDescendantOf(target, voxTarget))
+         !AutomationUtil.isDescendantOf(target, voxTarget) &&
+         !AutomationUtil.getAncestors(voxTarget.root)
+              .find((n) => n.role == RoleType.KEYBOARD)))
       return false;
 
     if (!this.textEditHandler_ || this.textEditHandler_.node !== target) {
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index fc80caa..3c7dc071 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -1884,8 +1884,16 @@
 
     if (type == EventType.HOVER ||
         EventSourceState.get() == EventSourceType.TOUCH_GESTURE) {
-      if (node.defaultActionVerb != 'none')
+      var isWithinVirtualKeyboard = AutomationUtil.getAncestors(node).find(
+          (n) => n.role == RoleType.KEYBOARD);
+      if (node.defaultActionVerb != 'none' && !isWithinVirtualKeyboard)
         this.format_(node, '@hint_double_tap', buff);
+
+      var enteredVirtualKeyboard =
+          uniqueAncestors.find((n) => n.role == RoleType.KEYBOARD);
+      if (enteredVirtualKeyboard)
+        this.format_(node, '@hint_touch_type', buff);
+
       return;
     }
 
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
index b718c56..98ae0ed1 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings.grd
@@ -3002,6 +3002,9 @@
       <message desc="Menu item text for a command to open the text to speech settings page" name="IDS_CHROMEVOX_SHOW_TTS_SETTINGS">
         Open text-to-speech settings
       </message>
+      <message desc="A hint to the user on how to interact with the virtual on screen keyboard." name="IDS_CHROMEVOX_HINT_TOUCH_TYPE">
+        Find a key, then lift to type
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_cs.xtb b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_cs.xtb
index e1b8cfc..3b65adf 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_cs.xtb
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_cs.xtb
@@ -155,6 +155,7 @@
 <translation id="2462626033734746142">Skupina přepínačů</translation>
 <translation id="2471138580042810658">Nadpis 6</translation>
 <translation id="248982282205370495">{COUNT,plural, =1{hvězdička}few{# hvězdičky}many{# asterisks}other{# hvězdiček}}</translation>
+<translation id="249330843868392562">Otevřít nastavení převodu textu na řeč</translation>
 <translation id="2523609930580546572">Výukový program funkce ChromeVox</translation>
 <translation id="2525706221823668172">Klávesové zkratky pro Chromebooky</translation>
 <translation id="2549392850788122959">Klávesa <ph name="KEY" /> byla resetována.</translation>
@@ -752,6 +753,7 @@
 <translation id="7241683698754534149">Otevřít dlouhý popis na nové kartě</translation>
 <translation id="7248671827512403053">Aplikace</translation>
 <translation id="725969808843520477">Další přepínač</translation>
+<translation id="7261612856573623172">Hlas systémového převodu textu na řeč</translation>
 <translation id="7269119382257320590">Bez interpunkce</translation>
 <translation id="7273174640290488576">Prázdné</translation>
 <translation id="7274770952766771364">Odkaz na poznámku</translation>
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_en-GB.xtb b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_en-GB.xtb
index 34c7581..fa01dfb 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_en-GB.xtb
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_en-GB.xtb
@@ -156,6 +156,7 @@
 <translation id="2462626033734746142">Radio button group</translation>
 <translation id="2471138580042810658">Heading 6</translation>
 <translation id="248982282205370495">{COUNT,plural, =1{asterisk}other{# asterisks}}</translation>
+<translation id="249330843868392562">Open text-to-speech settings</translation>
 <translation id="2523609930580546572">ChromeVox Tutorial</translation>
 <translation id="2525706221823668172">Chromebook keyboard shortcuts</translation>
 <translation id="2549392850788122959"><ph name="KEY" /> has been reset.</translation>
@@ -753,6 +754,7 @@
 <translation id="7241683698754534149">Open long description in a new tab</translation>
 <translation id="7248671827512403053">Application</translation>
 <translation id="725969808843520477">Next radio button</translation>
+<translation id="7261612856573623172">System Text-to-Speech voice</translation>
 <translation id="7269119382257320590">No punctuation</translation>
 <translation id="7273174640290488576">Blank</translation>
 <translation id="7274770952766771364">Note reference</translation>
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_lt.xtb b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_lt.xtb
index d9df2fe3..1e32afcf 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_lt.xtb
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_lt.xtb
@@ -156,6 +156,7 @@
 <translation id="2462626033734746142">Akučių grupė</translation>
 <translation id="2471138580042810658">6 antraštė</translation>
 <translation id="248982282205370495">{COUNT,plural, =1{žvaigždutė}one{# žvaigždutė}few{# žvaigždutės}many{# asterisks}other{# žvaigždučių}}</translation>
+<translation id="249330843868392562">Atidaryti teksto į kalbą nustatymus</translation>
 <translation id="2523609930580546572">„ChromeVox“ mokymo programa</translation>
 <translation id="2525706221823668172">„Chromebook“ spartieji klavišai</translation>
 <translation id="2549392850788122959"><ph name="KEY" /> nustatytas iš naujo.</translation>
@@ -753,6 +754,7 @@
 <translation id="7241683698754534149">Atidaryti ilgą aprašą naujame skirtuke</translation>
 <translation id="7248671827512403053">Programa</translation>
 <translation id="725969808843520477">Kita akutė</translation>
+<translation id="7261612856573623172">Sistemos teksto į kalbą balsas</translation>
 <translation id="7269119382257320590">Be skyrybos ženklų</translation>
 <translation id="7273174640290488576">Tuščia</translation>
 <translation id="7274770952766771364">Pastabos nuoroda</translation>
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_mr.xtb b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_mr.xtb
index 3b32b7e..442d4bc9 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_mr.xtb
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_mr.xtb
@@ -155,6 +155,7 @@
 <translation id="2462626033734746142">रेडिओ बटण गट</translation>
 <translation id="2471138580042810658">मथळा 6</translation>
 <translation id="248982282205370495">{COUNT,plural, =1{चौफुली}one{# चौफुली}other{# चौफुली}}</translation>
+<translation id="249330843868392562">टेक्‍स्‍ट टू स्‍पीच सेटिंग्ज उघडा</translation>
 <translation id="2523609930580546572">ChromeVox ट्यूटोरियल</translation>
 <translation id="2525706221823668172">Chromebook कीबोर्ड शॉर्टकट</translation>
 <translation id="2549392850788122959"><ph name="KEY" /> रीसेट केली गेली.</translation>
@@ -752,6 +753,7 @@
 <translation id="7241683698754534149">एका नवीन टॅबमध्ये मोठे वर्णन उघडा</translation>
 <translation id="7248671827512403053">अॅप्लिकेशन</translation>
 <translation id="725969808843520477">पुढील रेडिओ बटण</translation>
+<translation id="7261612856573623172">सिस्टम टेक्‍स्‍ट टू स्‍पीच व्हॉइस</translation>
 <translation id="7269119382257320590">कोणतेही विरामचिन्‍ह नाही</translation>
 <translation id="7273174640290488576">रिक्त</translation>
 <translation id="7274770952766771364">टीप संदर्भ</translation>
diff --git a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_sk.xtb b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_sk.xtb
index a84597b..31e58596 100644
--- a/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_sk.xtb
+++ b/chrome/browser/resources/chromeos/chromevox/strings/chromevox_strings_sk.xtb
@@ -155,6 +155,7 @@
 <translation id="2462626033734746142">Skupina prepínačov</translation>
 <translation id="2471138580042810658">Nadpis 6</translation>
 <translation id="248982282205370495">{COUNT,plural, =1{hviezdička}few{# hviezdičky}many{# asterisks}other{# hviezdičiek}}</translation>
+<translation id="249330843868392562">Otvoriť nastavenia prevodu textu na reč</translation>
 <translation id="2523609930580546572">Príručka rozšírenia ChromeVox</translation>
 <translation id="2525706221823668172">Klávesové stkratky Chromebooku</translation>
 <translation id="2549392850788122959">Kláves <ph name="KEY" /> bol resetovaný</translation>
@@ -752,6 +753,7 @@
 <translation id="7241683698754534149">Otvoriť dlhý popis na novej karte</translation>
 <translation id="7248671827512403053">Aplikácia</translation>
 <translation id="725969808843520477">Ďalší prepínač</translation>
+<translation id="7261612856573623172">Hlas prevodu textu na reč systému</translation>
 <translation id="7269119382257320590">Žiadna interpunkcia</translation>
 <translation id="7273174640290488576">Prázdne</translation>
 <translation id="7274770952766771364">Odkaz na poznámku</translation>
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_cs.xtb b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_cs.xtb
index 1f74ba0a..2a877778 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_cs.xtb
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_cs.xtb
@@ -1,7 +1,10 @@
 <?xml version="1.0" ?>
 <!DOCTYPE translationbundle>
 <translationbundle lang="cs">
+<translation id="111880247472081232">Nastavení převodu textu na řeč pro poslech vybraného textu byla migrována do globálních nastavení převodu textu na řeč.</translation>
 <translation id="1197088940767939838">Oranžová</translation>
+<translation id="1273314450961659276">Nastavení převodu textu na řeč</translation>
+<translation id="1498542103351704084">Byla aktualizována nastavení převodu textu na řeč pro poslech vybraného textu</translation>
 <translation id="1555130319947370107">Modrá</translation>
 <translation id="1666326070478924810">Nastavení poslechu vybraného textu</translation>
 <translation id="1966649499058910679">Zvýrazňovat jednotlivá vyslovená slova</translation>
@@ -9,11 +12,14 @@
 <translation id="2714180132046334502">Tmavé pozadí</translation>
 <translation id="27349076983469322">Světlé pozadí</translation>
 <translation id="335581015389089642">Řeč</translation>
+<translation id="3784184786832188702">Pro poslech vybraného textu se teď používají globální nastavení převodu textu na řeč.</translation>
 <translation id="5901630391730855834">Žlutá</translation>
 <translation id="6017514345406065928">Zelená</translation>
 <translation id="6475604559827479857">Barva zvýraznění slov:</translation>
 <translation id="6837853484260746864">Vyberte hlas:</translation>
+<translation id="7261612856573623172">Hlas systémového převodu textu na řeč</translation>
 <translation id="7768784765476638775">Poslech vybraného textu</translation>
 <translation id="7914870167134465181">Zvýrazňování</translation>
+<translation id="8324974933005349667">Upravit nastavení převodu textu na řeč</translation>
 <translation id="992256792861109788">Růžová</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_en-GB.xtb b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_en-GB.xtb
index 953d114..6bb1a041 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_en-GB.xtb
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_en-GB.xtb
@@ -1,7 +1,10 @@
 <?xml version="1.0" ?>
 <!DOCTYPE translationbundle>
 <translationbundle lang="en-GB">
+<translation id="111880247472081232">Your Select-to-Speak Text-to-Speech settings were migrated to global Text-to-Speech settings.</translation>
 <translation id="1197088940767939838">Orange</translation>
+<translation id="1273314450961659276">Text-to-Speech settings</translation>
+<translation id="1498542103351704084">Select-to-Speak speech settings updated</translation>
 <translation id="1555130319947370107">Blue</translation>
 <translation id="1666326070478924810">Select to Speak settings</translation>
 <translation id="1966649499058910679">Highlight each word as it is spoken</translation>
@@ -9,11 +12,14 @@
 <translation id="2714180132046334502">Dark background</translation>
 <translation id="27349076983469322">Light background</translation>
 <translation id="335581015389089642">Speech</translation>
+<translation id="3784184786832188702">Select-to-Speak now uses global Text-to-Speech settings.</translation>
 <translation id="5901630391730855834">Yellow</translation>
 <translation id="6017514345406065928">Green</translation>
 <translation id="6475604559827479857">Colour for word highlights:</translation>
 <translation id="6837853484260746864">Select a voice:</translation>
+<translation id="7261612856573623172">System Text-to-Speech voice</translation>
 <translation id="7768784765476638775">Select to Speak</translation>
 <translation id="7914870167134465181">Highlighting</translation>
+<translation id="8324974933005349667">Personalise Text-to-Speech settings</translation>
 <translation id="992256792861109788">Pink</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_lt.xtb b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_lt.xtb
index 29be10ef..109d978 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_lt.xtb
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_lt.xtb
@@ -1,7 +1,10 @@
 <?xml version="1.0" ?>
 <!DOCTYPE translationbundle>
 <translationbundle lang="lt">
+<translation id="111880247472081232">„Teksto ištarimo“ funkcijos teksto į kalbą nustatymai perkelti į visuotinius teksto į kalbą nustatymus.</translation>
 <translation id="1197088940767939838">Oranžinė</translation>
+<translation id="1273314450961659276">Teksto į kalbą nustatymai</translation>
+<translation id="1498542103351704084">Atnaujinti „Teksto ištarimo“ funkcijos kalbos nustatymai</translation>
 <translation id="1555130319947370107">Mėlyna</translation>
 <translation id="1666326070478924810">Funkcijos „Teksto ištarimas“ nustatymai</translation>
 <translation id="1966649499058910679">Paryškinti kiekvieną sakomą žodį</translation>
@@ -9,11 +12,14 @@
 <translation id="2714180132046334502">Tamsus fonas</translation>
 <translation id="27349076983469322">Šviesus fonas</translation>
 <translation id="335581015389089642">Kalba</translation>
+<translation id="3784184786832188702">„Teksto ištarimo“ funkcijai dabar naudojami visuotiniai teksto į kalbą nustatymai.</translation>
 <translation id="5901630391730855834">Geltona</translation>
 <translation id="6017514345406065928">Žalia</translation>
 <translation id="6475604559827479857">Žodžių paryškinimo spalva:</translation>
 <translation id="6837853484260746864">Pasirinkite balsą:</translation>
+<translation id="7261612856573623172">Sistemos teksto į kalbą balsas</translation>
 <translation id="7768784765476638775">Teksto ištarimas</translation>
 <translation id="7914870167134465181">Paryškinimas</translation>
+<translation id="8324974933005349667">Suasmeninkite teksto į kalbą nustatymus</translation>
 <translation id="992256792861109788">Rožinė</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_mr.xtb b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_mr.xtb
index 7dddf72..f963f16 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_mr.xtb
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_mr.xtb
@@ -1,7 +1,10 @@
 <?xml version="1.0" ?>
 <!DOCTYPE translationbundle>
 <translationbundle lang="mr">
+<translation id="111880247472081232">तुमचे बोलण्यासाठी निवडा टेक्‍स्‍ट टू स्‍पीच सेटिंग्ज जागतिक टेक्‍स्‍ट टू स्‍पीच सेटिंग्जवर हलवले गेले.</translation>
 <translation id="1197088940767939838">नारिंगी</translation>
+<translation id="1273314450961659276">टेक्‍स्‍ट टू स्‍पीच सेटिंग्ज</translation>
+<translation id="1498542103351704084">बोलण्यासाठी निवडा स्‍पीच सेटिंग्ज अपडेट झाले</translation>
 <translation id="1555130319947370107">निळा</translation>
 <translation id="1666326070478924810">बोलण्यासाठी निवडा सेटिंग्ज</translation>
 <translation id="1966649499058910679">प्रत्येक शब्द तो बोलला जात असताना हायलाइट करा</translation>
@@ -9,11 +12,14 @@
 <translation id="2714180132046334502">गडद बॅकग्राउंड</translation>
 <translation id="27349076983469322">फिकट बॅकग्राउंड</translation>
 <translation id="335581015389089642">भाषण</translation>
+<translation id="3784184786832188702">बोलण्यासाठी निवडा आता जागतिक टेक्‍स्‍ट टू स्‍पीच सेटिंग्ज वापरते.</translation>
 <translation id="5901630391730855834">पिवळा</translation>
 <translation id="6017514345406065928">हिरवा</translation>
 <translation id="6475604559827479857">शब्द हायलाइट करण्यासाठी रंग:</translation>
 <translation id="6837853484260746864">आवाज निवडा:</translation>
+<translation id="7261612856573623172">सिस्टम टेक्‍स्‍ट टू स्‍पीच व्हॉइस</translation>
 <translation id="7768784765476638775">बोलण्यासाठी निवडा</translation>
 <translation id="7914870167134465181">हायलाइट करणे</translation>
+<translation id="8324974933005349667">टेक्‍स्‍ट टू स्‍पीच सेटिंग्ज पर्सनलाइझ करा</translation>
 <translation id="992256792861109788">गुलाबी</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_sk.xtb b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_sk.xtb
index b54196a..0e61dcd3 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_sk.xtb
+++ b/chrome/browser/resources/chromeos/select_to_speak/strings/select_to_speak_strings_sk.xtb
@@ -1,7 +1,10 @@
 <?xml version="1.0" ?>
 <!DOCTYPE translationbundle>
 <translationbundle lang="sk">
+<translation id="111880247472081232">Vaše nastavenia počúvania vybraného textu a prevodu textu na reč boli migrované na globálne nastavenia prevodu textu na reč.</translation>
 <translation id="1197088940767939838">Oranžová</translation>
+<translation id="1273314450961659276">Nastavenia prevodu textu na reč</translation>
+<translation id="1498542103351704084">Boli aktualizované nastavenia reči v rámci prevodu textu na reč</translation>
 <translation id="1555130319947370107">Modrá</translation>
 <translation id="1666326070478924810">Nastavenia počúvania vybraného textu</translation>
 <translation id="1966649499058910679">Zvýrazňovať jednotivé vyslovené slová</translation>
@@ -9,11 +12,14 @@
 <translation id="2714180132046334502">Tmavé pozadie</translation>
 <translation id="27349076983469322">Svetlé pozadie</translation>
 <translation id="335581015389089642">Reč</translation>
+<translation id="3784184786832188702">Počúvanie vybraného textu teraz používa globálne nastavenia prevodu textu na reč.</translation>
 <translation id="5901630391730855834">Žltá</translation>
 <translation id="6017514345406065928">Zelená</translation>
 <translation id="6475604559827479857">Farba zvýrazňovania slov:</translation>
 <translation id="6837853484260746864">Vyberte hlas:</translation>
+<translation id="7261612856573623172">Hlas prevodu textu na reč systému</translation>
 <translation id="7768784765476638775">Vyslovenie položky po vybratí</translation>
 <translation id="7914870167134465181">Zvýraznenie</translation>
+<translation id="8324974933005349667">Prispôsobiť nastavenia prevodu textu na reč</translation>
 <translation id="992256792861109788">Ružová</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/chrome/browser/resources/interventions_internals/OWNERS b/chrome/browser/resources/interventions_internals/OWNERS
index 0957d79..fd70f95 100644
--- a/chrome/browser/resources/interventions_internals/OWNERS
+++ b/chrome/browser/resources/interventions_internals/OWNERS
@@ -1,3 +1,3 @@
 file://components/previews/OWNERS
 
-# COMPONENT: UI>Browser>Previews
+# COMPONENT: Blink>Previews
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.css b/chrome/browser/resources/local_ntp/custom_backgrounds.css
index e9c2cff..89b55e7a 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.css
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.css
@@ -2,6 +2,21 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file. */
 
+#custom-bg {
+  background-position: center center;
+  background-repeat: no-repeat;
+  background-size: cover;
+  height: 100%;
+  left: 0;
+  margin: 0;
+  opacity: 0;
+  padding: 0;
+  position: fixed;
+  top: 0;
+  transition: opacity 700ms;
+  width: 100%;
+}
+
 #edit-bg {
   border-radius: 8px;
   bottom: 16px;
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js
index 57d45fe..b3ba39d 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.js
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -139,6 +139,44 @@
 }
 
 /**
+ * Sets the visibility of the settings menu and individual options depending on
+ * their respective features and if the user has a theme installed.
+ * @param {boolean} hasTheme True if the user has a theme installed.
+ */
+customBackgrounds.setMenuVisibility = function(hasTheme) {
+  // Hide the settings menu if:
+  // - Custom links and custom backgrounds are not enabled.
+  // - Custom links is not enabled and a theme is installed.
+  if ((!configData.isCustomLinksEnabled &&
+       !configData.isCustomBackgroundsEnabled) ||
+      (!configData.isCustomLinksEnabled && hasTheme)) {
+    $(customBackgrounds.IDS.EDIT_BG).hidden = true;
+    return;
+  }
+
+  // Reset all hidden values.
+  $(customBackgrounds.IDS.EDIT_BG).hidden = false;
+  $(customBackgrounds.IDS.DEFAULT_WALLPAPERS).hidden = false;
+  $(customBackgrounds.IDS.UPLOAD_IMAGE).hidden = false;
+  $(customBackgrounds.IDS.RESTORE_DEFAULT).hidden = false;
+  $(customBackgrounds.IDS.EDIT_BG_DIVIDER).hidden = false;
+  $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).hidden = false;
+
+  // Custom backgrounds is disabled or a theme is installed, hide all custom
+  // background options.
+  if (!configData.isCustomBackgroundsEnabled || hasTheme) {
+    $(customBackgrounds.IDS.DEFAULT_WALLPAPERS).hidden = true;
+    $(customBackgrounds.IDS.UPLOAD_IMAGE).hidden = true;
+    $(customBackgrounds.IDS.RESTORE_DEFAULT).hidden = true;
+    $(customBackgrounds.IDS.EDIT_BG_DIVIDER).hidden = true;
+  }
+
+  // Custom links is disabled, hide all custom link options.
+  if (!configData.isCustomLinksEnabled)
+    $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).hidden = true;
+};
+
+/**
  * Display custom background image attributions on the page.
  * @param {string} attributionLine1 First line of attribution.
  * @param {string} attributionLine2 Second line of attribution.
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index b38e8b9..b913eef 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -22,6 +22,7 @@
 <!-- Remember to update the test HTML files in chrome/test/data/local_ntp/
    whenever making changes to this file.-->
 <body>
+  <div id="custom-bg"></div>
   <!-- Container for the OneGoogleBar HTML. -->
   <div id="one-google" class="hidden"></div>
 
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index d43e004..5c437d1 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -309,7 +309,11 @@
                     info.imageHorizontalAlignment,
                     info.imageVerticalAlignment].join(' ').trim();
 
-  document.body.style.background = background;
+  // If a custom background has been selected the image will be applied to the
+  // custom-background element instead of the body.
+  if (!info.customBackgroundConfigured) {
+    document.body.style.background = background;
+  }
   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, info.alternateLogo);
   var isNonWhiteBackground = !WHITE_BACKGROUND_COLORS.includes(background);
   document.body.classList.toggle(CLASSES.NON_WHITE_BG, isNonWhiteBackground);
@@ -318,7 +322,7 @@
 
   if (info.customBackgroundConfigured) {
     var imageWithOverlay = [
-      customBackgrounds.CUSTOM_BACKGROUND_OVERLAY, info.imageUrl
+      customBackgrounds.CUSTOM_BACKGROUND_OVERLAY, 'url(' + info.imageUrl + ')'
     ].join(',').trim();
 
     if (imageWithOverlay != document.body.style.backgroundImage) {
@@ -326,10 +330,23 @@
       customBackgrounds.clearAttribution();
     }
 
-    document.body.style.setProperty('background-image', imageWithOverlay);
+    // |image| and |imageWithOverlay| use the same url as their source. Waiting
+    // to display the custom background until |image| is fully loaded ensures
+    // that |imageWithOverlay| is also loaded.
+    $('custom-bg').style.backgroundImage = imageWithOverlay;
+    var image = new Image();
+    image.onload = function() {
+      $('custom-bg').style.opacity = '1';
+    };
+    image.src = info.imageUrl;
 
     customBackgrounds.setAttribution(
         info.attribution1, info.attribution2, info.attributionActionUrl);
+  } else {
+    $('custom-bg').style.opacity = '0';
+    window.setTimeout(function() {
+      $('custom-bg').style.backgroundImage = '';
+    }, 1000);
   }
 
   $(customBackgrounds.IDS.RESTORE_DEFAULT)
@@ -338,11 +355,9 @@
           !info.customBackgroundConfigured);
 
   if (configData.isGooglePage) {
-    // Hide the settings menu if the user has a theme. Do not hide if custom
-    // links is enabled.
-    $('edit-bg').hidden =
-        (!configData.isCustomBackgroundsEnabled || !info.usingDefaultTheme) &&
-        !configData.isCustomLinksEnabled;
+    // Hide the settings menu or individual options if the related features are
+    // disabled and/or a theme is installed.
+    customBackgrounds.setMenuVisibility(!info.usingDefaultTheme);
   }
 }
 
@@ -359,8 +374,7 @@
 
   var message = {cmd: 'updateTheme'};
   message.isThemeDark = isThemeDark;
-  message.isUsingTheme =
-      !info.customBackgroundConfigured && !info.usingDefaultTheme;
+  message.isUsingTheme = !info.usingDefaultTheme;
 
   var titleColor = NTP_DESIGN.titleColor;
   if (!info.usingDefaultTheme && info.textColorRgba) {
@@ -925,21 +939,6 @@
     if (configData.isCustomBackgroundsEnabled ||
         configData.isCustomLinksEnabled) {
       customBackgrounds.init();
-
-      // Hide items in the settings menu if a feature is disabled.
-      if (configData.isCustomBackgroundsEnabled &&
-          !configData.isCustomLinksEnabled) {
-        // Only custom backgrounds enabled, hide all custom link options.
-        $(customBackgrounds.IDS.CUSTOM_LINKS_RESTORE_DEFAULT).hidden = true;
-      } else if (
-          !configData.isCustomBackgroundsEnabled &&
-          configData.isCustomLinksEnabled) {
-        // Only custom links enabled, hide all custom background options.
-        $(customBackgrounds.IDS.DEFAULT_WALLPAPERS).hidden = true;
-        $(customBackgrounds.IDS.UPLOAD_IMAGE).hidden = true;
-        $(customBackgrounds.IDS.RESTORE_DEFAULT).hidden = true;
-        $(customBackgrounds.IDS.EDIT_BG_DIVIDER).hidden = true;
-      }
     }
 
 
diff --git a/chrome/browser/resources/settings/basic_page/basic_page.html b/chrome/browser/resources/settings/basic_page/basic_page.html
index 155a112a..29374f6 100644
--- a/chrome/browser/resources/settings/basic_page/basic_page.html
+++ b/chrome/browser/resources/settings/basic_page/basic_page.html
@@ -180,13 +180,6 @@
           </settings-section>
         </template>
 <if expr="chromeos">
-        <template is="dom-if" if="[[showCrostini]]" restamp>
-          <settings-section page-title="$i18n{crostiniPageTitle}"
-              section="crostini">
-            <settings-crostini-page prefs="{{prefs}}">
-            </settings-crostini-page>
-          </settings-section>
-        </template>
         <template is="dom-if"
             if="[[shouldShowAndroidApps_(showAndroidApps, androidAppsInfo, pageVisibility)]]"
             restamp>
@@ -198,6 +191,13 @@
             </settings-android-apps-page>
           </settings-section>
         </template>
+        <template is="dom-if" if="[[showCrostini]]" restamp>
+          <settings-section page-title="$i18n{crostiniPageTitle}"
+              section="crostini">
+            <settings-crostini-page prefs="{{prefs}}">
+            </settings-crostini-page>
+          </settings-section>
+        </template>
 </if>
 <if expr="not chromeos">
         <template is="dom-if" if="[[showPage_(pageVisibility.defaultBrowser)]]"
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_page.html b/chrome/browser/resources/settings/crostini_page/crostini_page.html
index fc8ba307..ad43733 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_page.html
+++ b/chrome/browser/resources/settings/crostini_page/crostini_page.html
@@ -24,8 +24,8 @@
             on-click="onSubpageTap_">
           <div class="start">
             $i18n{crostiniPageLabel}
-            <div class="secondary" id="secondaryText">
-                $i18n{crostiniSubtext}
+            <div class="secondary" id="secondaryText"
+                inner-h-t-m-l="[[i18nAdvanced('crostiniSubtext')]]">
             </div>
           </div>
           <cr-policy-pref-indicator pref="[[prefs.crostini.enabled]]"
diff --git a/chrome/browser/resources/settings/crostini_page/crostini_page.js b/chrome/browser/resources/settings/crostini_page/crostini_page.js
index 043219c7..31488e1 100644
--- a/chrome/browser/resources/settings/crostini_page/crostini_page.js
+++ b/chrome/browser/resources/settings/crostini_page/crostini_page.js
@@ -12,7 +12,7 @@
 Polymer({
   is: 'settings-crostini-page',
 
-  behaviors: [PrefsBehavior],
+  behaviors: [I18nBehavior, PrefsBehavior],
 
   properties: {
     /** Preferences state. */
diff --git a/chrome/browser/resources/settings/device_page/display_size_slider.js b/chrome/browser/resources/settings/device_page/display_size_slider.js
index 924c9bfd..7dd8f904a 100644
--- a/chrome/browser/resources/settings/device_page/display_size_slider.js
+++ b/chrome/browser/resources/settings/device_page/display_size_slider.js
@@ -128,6 +128,16 @@
     'pageup end': 'resetToMaxIndex_',
   },
 
+  /** @override */
+  ready: function() {
+    chrome.settingsPrivate.onPrefsChanged.addListener((prefs) => {
+      prefs.forEach((pref) => {
+        if (pref.key == this.pref.key && this.pref.value != pref.value)
+          this.pref.value = pref.value;
+      });
+    });
+  },
+
   /** @private {boolean} */
   usedMouse_: false,
 
@@ -469,18 +479,20 @@
   updateIndex_: function() {
     if (!this.ticks || this.ticks.length == 0)
       return;
-    if (!this.pref)
+    if (!this.pref || typeof(this.pref.value) != 'number')
       return;
+    let resolvedTick = this.ticks.length - 1;
     for (let i = 0; i < this.ticks.length; i++) {
-      if (this.ticks[i].value == this.pref.value) {
-        this._setIndex(i);
-        this.setAttribute(
-            'aria-valuenow',
-            this.getAriaValueForIndex_(this.ticks, this.index));
-        this.setAttribute(
-            'aria-valuetext', this.getLabelForIndex_(this.ticks, this.index));
+      if (this.ticks[i].value >= this.pref.value) {
+        resolvedTick = i;
+        break;
       }
     }
+    this._setIndex(resolvedTick);
+    this.setAttribute(
+        'aria-valuenow', this.getAriaValueForIndex_(this.ticks, this.index));
+    this.setAttribute(
+        'aria-valuetext', this.getLabelForIndex_(this.ticks, this.index));
   },
 
   /**
diff --git a/chrome/browser/resources/settings/icons.html b/chrome/browser/resources/settings/icons.html
index c6df3cae..d7b66a6 100644
--- a/chrome/browser/resources/settings/icons.html
+++ b/chrome/browser/resources/settings/icons.html
@@ -38,15 +38,15 @@
       </g>
 
 <if expr="chromeos">
+      <!-- Icons from http://icons/ -->
+      <g id="play-prism"><path fill="#5A5A5A" d="M20.18 10.88l-3.06-1.74L14.26 12l2.86 2.86 3.06-1.74c.55-.31.82-.71.82-1.12 0-.41-.27-.81-.82-1.12zM4.71 2.45l8.42 8.42 2.55-2.55-10.7-6.06c-.07-.04-.14-.07-.21-.1-.17-.07-.3.05-.15.21.03.02.06.05.09.08zm0 19.1l-.08.08c-.15.15-.02.28.15.21.07-.03.14-.06.21-.1l10.69-6.06-2.55-2.55s-7.2 7.21-8.42 8.42zM12 12L3.38 3.38c-.19-.19-.38-.07-.38.19v16.86c0 .26.19.38.38.19L12 12z"></path></g>
+
       <!-- Crostini Mascot icon for Settings drawer -->
       <g id="crostini-mascot" fill-rule="evenodd">
         <rect width="24" height="24" fill="none"></rect>
         <path d="M6.70994751,22.1802367 C3.77039152,20.9576222 1.88689678,18.4876617 1.82270453,14.1866283 C1.52620735,14.3410271 1.18877909,14.3669908 0.800057996,14.221322 C-0.312414045,13.804436 -0.0624529958,12.4120387 0.345560769,11.1705211 C0.68346303,10.142341 1.83735671,9.39181671 2.40240955,9.23479478 C3.71763772,4.05541221 6.80346772,0 12,0 C17.2584272,0 20.3654882,3.88805047 21.6436562,9.24850027 C22.2231374,9.43172978 23.3254781,10.1695473 23.6544392,11.1705211 C24.062453,12.4120387 24.312414,13.804436 23.199942,14.221322 C22.8112853,14.3669667 22.4739044,14.3410357 22.1774428,14.186705 C22.1189307,18.5169382 20.2342349,20.9750038 17.2936622,22.1879533 C17.3379726,22.2841737 17.3614392,22.3916932 17.3614392,22.5115654 C17.3614392,23.3336051 17.750632,24 14.8965517,24 C12.813813,24 12.4581871,23.5470722 12.4180882,23.0543665 C12.2796354,22.9647547 12.14026,22.8305157 12,22.8305157 C11.8594243,22.8305157 11.7197425,22.9755283 11.5809922,23.0650454 C11.536581,23.553597 11.171131,24 9.10344828,24 C6.24936798,24 6.63856075,23.3336051 6.63856075,22.5115654 C6.63856075,22.3884786 6.66330281,22.278416 6.70994751,22.1802367 Z M12,20.3378361 C17.4851654,20.3378361 19.4630542,17.934489 19.4630542,12.6147346 C19.4630542,8.20063746 17.6641739,4.14700996 14.6790451,3.47027327 C13.7222433,3.25336374 13.052482,5.61336212 12,5.61336212 C10.947518,5.61336212 10.1820765,3.25336374 9.32095491,3.47027327 C6.32316904,4.22539115 4.53694581,8.43392639 4.53694581,12.6147346 C4.53694581,17.6346061 6.51483457,20.3378361 12,20.3378361 Z M8.27586207,9.99377535 C7.59026721,9.99377535 7.03448276,9.42257974 7.03448276,8.71797424 C7.03448276,8.01336875 7.59026721,7.44217314 8.27586207,7.44217314 C8.96145693,7.44217314 9.51724138,8.01336875 9.51724138,8.71797424 C9.51724138,9.42257974 8.96145693,9.99377535 8.27586207,9.99377535 Z M15.7241379,9.99377535 C15.0385431,9.99377535 14.4827586,9.42257974 14.4827586,8.71797424 C14.4827586,8.01336875 15.0385431,7.44217314 15.7241379,7.44217314 C16.4097328,7.44217314 16.9655172,8.01336875 16.9655172,8.71797424 C16.9655172,9.42257974 16.4097328,9.99377535 15.7241379,9.99377535 Z M12.224615,10.6827627 L13.9283449,11.4510541 C14.2234366,11.5841248 14.3564393,11.9349546 14.2254148,12.2346546 C14.1996198,12.293657 14.1645229,12.3479919 14.1215061,12.39552 L12.4275305,14.2671455 C12.2090286,14.5085621 11.8392003,14.5243719 11.6014958,14.3024578 C11.5890079,14.2907994 11.5770255,14.2785946 11.5655826,14.265878 L9.87720766,12.3895636 C9.65939502,12.1475054 9.67603268,11.771949 9.91436891,11.5507349 C9.9604013,11.5080095 10.012868,11.4730393 10.0697797,11.4471502 L11.7484004,10.6835476 C11.8998356,10.6146599 12.0729604,10.6143745 12.224615,10.6827627 Z"></path>
       </g>
 
-      <!-- Icons from https://icons.googleplex.com. -->
-      <g id="play-prism"><path fill="#5A5A5A" d="M20.18 10.88l-3.06-1.74L14.26 12l2.86 2.86 3.06-1.74c.55-.31.82-.71.82-1.12 0-.41-.27-.81-.82-1.12zM4.71 2.45l8.42 8.42 2.55-2.55-10.7-6.06c-.07-.04-.14-.07-.21-.1-.17-.07-.3.05-.15.21.03.02.06.05.09.08zm0 19.1l-.08.08c-.15.15-.02.28.15.21.07-.03.14-.06.21-.1l10.69-6.06-2.55-2.55s-7.2 7.21-8.42 8.42zM12 12L3.38 3.38c-.19-.19-.38-.07-.38.19v16.86c0 .26.19.38.38.19L12 12z"></path></g>
-
       <!-- Icons for MultiDevice Settings UI -->
       <g id="smart-lock" fill="#9AA0A6" fill-rule="nonzero"><path d="M18,9 L17,9 L17,7 C17,4.24 14.76,2 12,2 C9.24,2 7,4.24 7,7 L7,9 L6,9 C4.9,9 4,9.9 4,11 L4,21 C4,22.1 4.9,23 6,23 L18,23 C19.1,23 20,22.1 20,21 L20,11 C20,9.9 19.1,9 18,9 Z M9,7 C9,5.34 10.34,4 12,4 C13.66,4 15,5.34 15,7 L15,9 L9,9 L9,7 Z M18,21 L6,21 L6,11 L18,11 L18,21 Z M12,18 C13.1,18 14,17.1 14,16 C14,14.9 13.1,14 12,14 C10.9,14 10,14.9 10,16 C10,17.1 10.9,18 12,18 Z"></path></g>
 
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js b/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js
index 2cd2b557..58a2c4fd 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_browser_proxy.js
@@ -9,6 +9,8 @@
 
     /** @return {!Promise<!MultiDevicePageContentData>} */
     getPageContentData() {}
+
+    retryPendingHostSetup() {}
   }
 
   /**
@@ -24,6 +26,11 @@
     getPageContentData() {
       return cr.sendWithPromise('getPageContentData');
     }
+
+    /** @override */
+    retryPendingHostSetup() {
+      chrome.send('retryPendingHostSetup');
+    }
   }
 
   cr.addSingletonGetter(MultiDeviceBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/multidevice_page/multidevice_page.js b/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
index 4c7af9d..97054e51 100644
--- a/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
+++ b/chrome/browser/resources/settings/multidevice_page/multidevice_page.js
@@ -135,13 +135,11 @@
         this.browserProxy_.showMultiDeviceSetupDialog();
         return;
       case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_SERVER:
-        // TODO(jordynass): Implement this when API is ready.
-        console.log('Trying to connect to server again.');
-        return;
+        // Intentional fall-through.
       case settings.MultiDeviceSettingsMode.HOST_SET_WAITING_FOR_VERIFICATION:
-        // TODO(jordynass): Implement this when API is ready.
-        console.log('Trying to verify multidevice connection.');
-        return;
+        // If this device is waiting for action on the server or the host
+        // device, clicking the button should trigger this action.
+        this.browserProxy_.retryPendingHostSetup();
     }
   },
 });
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 2dd48404..866453bdf 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -11,10 +11,10 @@
  *   ACCESSIBILITY: (undefined|!settings.Route),
  *   ACCOUNTS: (undefined|!settings.Route),
  *   ADVANCED: (undefined|!settings.Route),
- *   CROSTINI: (undefined|!settings.Route),
- *   CROSTINI_DETAILS: (undefined|!settings.Route),
  *   ANDROID_APPS: (undefined|!settings.Route),
  *   ANDROID_APPS_DETAILS: (undefined|!settings.Route),
+ *   CROSTINI: (undefined|!settings.Route),
+ *   CROSTINI_DETAILS: (undefined|!settings.Route),
  *   APPEARANCE: (undefined|!settings.Route),
  *   AUTOFILL: (undefined|!settings.Route),
  *   BASIC: (undefined|!settings.Route),
@@ -255,14 +255,14 @@
     // <if expr="chromeos">
     r.GOOGLE_ASSISTANT = r.SEARCH.createChild('/googleAssistant');
 
+    r.ANDROID_APPS = r.BASIC.createSection('/androidApps', 'androidApps');
+    r.ANDROID_APPS_DETAILS = r.ANDROID_APPS.createChild('/androidApps/details');
+
     if (loadTimeData.valueExists('showCrostini') &&
         loadTimeData.getBoolean('showCrostini')) {
       r.CROSTINI = r.BASIC.createSection('/crostini', 'crostini');
       r.CROSTINI_DETAILS = r.CROSTINI.createChild('/crostini/details');
     }
-
-    r.ANDROID_APPS = r.BASIC.createSection('/androidApps', 'androidApps');
-    r.ANDROID_APPS_DETAILS = r.ANDROID_APPS.createChild('/androidApps/details');
     // </if>
 
     if (pageVisibility.onStartup !== false) {
diff --git a/chrome/browser/resources/settings/settings_main/settings_main.html b/chrome/browser/resources/settings/settings_main/settings_main.html
index 9343b83..87bd32f 100644
--- a/chrome/browser/resources/settings/settings_main/settings_main.html
+++ b/chrome/browser/resources/settings/settings_main/settings_main.html
@@ -43,8 +43,8 @@
     <template is="dom-if" if="[[showPages_.settings]]">
       <settings-basic-page prefs="{{prefs}}"
           page-visibility="[[pageVisibility]]"
-          show-crostini="[[showCrostini]]"
           show-android-apps="[[showAndroidApps]]"
+          show-crostini="[[showCrostini]]"
           show-multidevice="[[showMultidevice]]"
           have-play-store-app="[[havePlayStoreApp]]"
           autofill-home-enabled="[[autofillHomeEnabled]]"
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
index 15b3c7b..f8f22e9b 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -120,17 +120,17 @@
         $i18n{searchPageTitle}
       </a>
 <if expr="chromeos">
-      <a href="/crostini" hidden="[[!showCrostini]]">
-        <iron-icon icon="settings:crostini-mascot"></iron-icon>
-        $i18n{crostiniPageTitle}
-      </a>
-</if>
-<if expr="chromeos">
       <a href="/androidApps" hidden="[[!showAndroidApps]]">
         <iron-icon icon="settings:play-prism"></iron-icon>
         $i18n{androidAppsPageTitle}
       </a>
 </if>
+<if expr="chromeos">
+      <a href="/crostini" hidden="[[!showCrostini]]">
+        <iron-icon icon="settings:crostini-mascot"></iron-icon>
+        $i18n{crostiniPageTitle}
+      </a>
+</if>
 <if expr="not chromeos">
       <a id="defaultBrowser" href="/defaultBrowser"
         hidden="[[!pageVisibility.defaultBrowser]]">
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 174b6209..f0e12e2 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -1180,28 +1180,6 @@
                  file="site_settings/zoom_levels.js"
                  type="chrome_html" />
       <if expr="chromeos">
-        <structure name="IDR_SETTINGS_CROSTINI_PAGE_HTML"
-                   file="crostini_page/crostini_page.html"
-                   type="chrome_html" />
-        <structure name="IDR_SETTINGS_CROSTINI_PAGE_JS"
-                   file="crostini_page/crostini_page.js"
-                   type="chrome_html" />
-        <structure name="IDR_SETTINGS_CROSTINI_SUBPAGE_HTML"
-                   file="crostini_page/crostini_subpage.html"
-                   type="chrome_html" />
-        <structure name="IDR_SETTINGS_CROSTINI_SUBPAGE_JS"
-                   file="crostini_page/crostini_subpage.js"
-                   type="chrome_html" />
-        <structure name="IDR_SETTINGS_CROSTINI_BROWSER_PROXY_JS"
-                   file="crostini_page/crostini_browser_proxy.js"
-                   type="chrome_html"
-                   preprocess="true"
-                   allowexternalscript="true" />
-        <structure name="IDR_SETTINGS_CROSTINI_BROWSER_PROXY_HTML"
-                   file="crostini_page/crostini_browser_proxy.html"
-                   type="chrome_html"
-                   preprocess="true"
-                   allowexternalscript="true" />
         <structure name="IDR_SETTINGS_ANDROID_APPS_PAGE_HTML"
                    file="android_apps_page/android_apps_page.html"
                    type="chrome_html" />
@@ -1230,6 +1208,28 @@
         <structure name="IDR_SETTINGS_ANDROID_SETTINGS_ELEMENT_JS"
                    file="android_apps_page/android_settings_element.js"
                    type="chrome_html" />
+        <structure name="IDR_SETTINGS_CROSTINI_PAGE_HTML"
+                   file="crostini_page/crostini_page.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CROSTINI_PAGE_JS"
+                   file="crostini_page/crostini_page.js"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CROSTINI_SUBPAGE_HTML"
+                   file="crostini_page/crostini_subpage.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CROSTINI_SUBPAGE_JS"
+                   file="crostini_page/crostini_subpage.js"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CROSTINI_BROWSER_PROXY_JS"
+                   file="crostini_page/crostini_browser_proxy.js"
+                   type="chrome_html"
+                   preprocess="true"
+                   allowexternalscript="true" />
+        <structure name="IDR_SETTINGS_CROSTINI_BROWSER_PROXY_HTML"
+                   file="crostini_page/crostini_browser_proxy.html"
+                   type="chrome_html"
+                   preprocess="true"
+                   allowexternalscript="true" />
         <structure name="IDR_SETTINGS_BLUETOOTH_DEVICE_LIST_ITEM_HTML"
                    file="bluetooth_page/bluetooth_device_list_item.html"
                    type="chrome_html" />
diff --git a/chrome/browser/resources/settings/settings_ui/settings_ui.html b/chrome/browser/resources/settings/settings_ui/settings_ui.html
index 72ac2cc..88d91ba7 100644
--- a/chrome/browser/resources/settings/settings_ui/settings_ui.html
+++ b/chrome/browser/resources/settings/settings_ui/settings_ui.html
@@ -66,8 +66,8 @@
       <div class="drawer-content">
         <template is="dom-if" id="drawerTemplate">
           <settings-menu page-visibility="[[pageVisibility_]]"
-              show-crostini="[[showCrostini_]]"
               show-android-apps="[[showAndroidApps_]]"
+              show-crostini="[[showCrostini_]]"
               show-multidevice="[[showMultidevice_]]"
               have-play-store-app="[[havePlayStoreApp_]]"
               autofill-home-enabled="[[autofillHomeEnabled_]]"
@@ -81,8 +81,8 @@
       <settings-main id="main" prefs="{{prefs}}"
           toolbar-spinner-active="{{toolbarSpinnerActive_}}"
           page-visibility="[[pageVisibility_]]"
-          show-crostini="[[showCrostini_]]"
           show-android-apps="[[showAndroidApps_]]"
+          show-crostini="[[showCrostini_]]"
           show-multidevice="[[showMultidevice_]]"
           have-play-store-app="[[havePlayStoreApp_]]"
           autofill-home-enabled="[[autofillHomeEnabled_]]"
diff --git a/chrome/browser/resources/settings/settings_ui/settings_ui.js b/chrome/browser/resources/settings/settings_ui/settings_ui.js
index fde4fd05..b7e7f6e 100644
--- a/chrome/browser/resources/settings/settings_ui/settings_ui.js
+++ b/chrome/browser/resources/settings/settings_ui/settings_ui.js
@@ -51,10 +51,10 @@
     pageVisibility_: {type: Object, value: settings.pageVisibility},
 
     /** @private */
-    showCrostini_: Boolean,
+    showAndroidApps_: Boolean,
 
     /** @private */
-    showAndroidApps_: Boolean,
+    showCrostini_: Boolean,
 
     /** @private */
     showMultidevice_: Boolean,
@@ -144,10 +144,10 @@
     };
     // </if>
 
-    this.showCrostini_ = loadTimeData.valueExists('showCrostini') &&
-        loadTimeData.getBoolean('showCrostini');
     this.showAndroidApps_ = loadTimeData.valueExists('androidAppsVisible') &&
         loadTimeData.getBoolean('androidAppsVisible');
+    this.showCrostini_ = loadTimeData.valueExists('showCrostini') &&
+        loadTimeData.getBoolean('showCrostini');
     this.showMultidevice_ = this.showAndroidApps_ &&
         loadTimeData.valueExists('enableMultideviceSettings') &&
         loadTimeData.getBoolean('enableMultideviceSettings');
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 99af512..ae4a2178 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -504,6 +504,12 @@
 // TODO(crbug.com/863942): Should switching default search provider retain the
 // copy of user uploaded photos?
 void InstantService::ApplyOrResetCustomBackgroundThemeInfo() {
+  // Reset the pref if the feature is disabled.
+  if (!features::IsCustomBackgroundsEnabled()) {
+    ResetCustomBackgroundThemeInfo();
+    return;
+  }
+
   // Custom backgrounds for non-Google search providers are not supported.
   if (!search::DefaultSearchProviderIsGoogle(profile_)) {
     ResetCustomBackgroundThemeInfo();
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index 87cb417..c0d3224 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -8,7 +8,9 @@
 
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/search/instant_unittest_base.h"
+#include "chrome/browser/search/ntp_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/search/instant_types.h"
 #include "chrome/common/url_constants.h"
@@ -19,6 +21,19 @@
 
 using InstantServiceTest = InstantUnitTestBase;
 
+class InstantServiceTestCustomBackgroundsEnabled : public InstantServiceTest {
+ public:
+  InstantServiceTestCustomBackgroundsEnabled() {
+    scoped_feature_list_.InitAndEnableFeature(features::kNtpBackgrounds);
+  }
+  ~InstantServiceTestCustomBackgroundsEnabled() override {}
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(InstantServiceTestCustomBackgroundsEnabled);
+};
+
 TEST_F(InstantServiceTest, GetNTPTileSuggestion) {
   ntp_tiles::NTPTile some_tile;
   some_tile.source = ntp_tiles::TileSource::TOP_SITES;
@@ -36,7 +51,7 @@
   EXPECT_EQ(ntp_tiles::TileTitleSource::TITLE_TAG, items[0].title_source);
 }
 
-TEST_F(InstantServiceTest, SetCustomBackgroundURL) {
+TEST_F(InstantServiceTestCustomBackgroundsEnabled, SetCustomBackgroundURL) {
   const GURL kUrl("https://www.foo.com");
   instant_service_->SetCustomBackgroundURL(kUrl);
 
@@ -44,7 +59,16 @@
   EXPECT_EQ(kUrl, theme_info->custom_background_url);
 }
 
-TEST_F(InstantServiceTest, SetCustomBackgroundURLInvalidURL) {
+TEST_F(InstantServiceTest, SetCustomBackgroundURL) {
+  const GURL kUrl("https://www.foo.com");
+  instant_service_->SetCustomBackgroundURL(kUrl);
+
+  ThemeBackgroundInfo* theme_info = instant_service_->GetThemeInfoForTesting();
+  EXPECT_EQ(GURL(), theme_info->custom_background_url);
+}
+
+TEST_F(InstantServiceTestCustomBackgroundsEnabled,
+       SetCustomBackgroundURLInvalidURL) {
   const GURL kUrl("foo");
   instant_service_->SetCustomBackgroundURL(kUrl);
 
@@ -52,7 +76,8 @@
   EXPECT_EQ(std::string(), theme_info->custom_background_url.spec());
 }
 
-TEST_F(InstantServiceTest, SetCustomBackgroundURLWithAttributions) {
+TEST_F(InstantServiceTestCustomBackgroundsEnabled,
+       SetCustomBackgroundURLWithAttributions) {
   const GURL kUrl("https://www.foo.com");
   const std::string kAttributionLine1 = "foo";
   const std::string kAttributionLine2 = "bar";
@@ -69,7 +94,8 @@
   EXPECT_EQ(kActionUrl, theme_info->custom_background_attribution_action_url);
 }
 
-TEST_F(InstantServiceTest, ChangingSearchProviderClearsThemeInfoAndPref) {
+TEST_F(InstantServiceTestCustomBackgroundsEnabled,
+       ChangingSearchProviderClearsThemeInfoAndPref) {
   const GURL kUrl("https://www.foo.com");
   const std::string kAttributionLine1 = "foo";
   const std::string kAttributionLine2 = "bar";
@@ -106,7 +132,7 @@
   EXPECT_EQ(GURL(), theme_info->custom_background_attribution_action_url);
 }
 
-TEST_F(InstantServiceTest,
+TEST_F(InstantServiceTestCustomBackgroundsEnabled,
        ChangingSearchProviderRemovesLocalBackgroundImageCopy) {
   base::FilePath user_data_dir;
   base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
@@ -125,7 +151,8 @@
   EXPECT_EQ(false, file_exists);
 }
 
-TEST_F(InstantServiceTest, CustomBackgroundAttributionActionUrlReset) {
+TEST_F(InstantServiceTestCustomBackgroundsEnabled,
+       CustomBackgroundAttributionActionUrlReset) {
   const GURL kUrl("https://www.foo.com");
   const std::string kAttributionLine1 = "foo";
   const std::string kAttributionLine2 = "bar";
diff --git a/chrome/browser/shell_integration_linux.cc b/chrome/browser/shell_integration_linux.cc
index c418971e..c5a8c3e 100644
--- a/chrome/browser/shell_integration_linux.cc
+++ b/chrome/browser/shell_integration_linux.cc
@@ -52,36 +52,7 @@
 #include "ui/gfx/image/image_family.h"
 #include "url/gurl.h"
 
-namespace shell_integration {
-
-// Allows LaunchXdgUtility to join a process.
-// thread_restrictions.h assumes it to be in shell_integration namespace.
-class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
-    : public base::ScopedAllowBaseSyncPrimitives {};
-
-bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
-  // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
-  // files on top of originals after making changes to them. In the event that
-  // the original files are owned by another user (e.g. root, which can happen
-  // if they are updated within sudo), mv will prompt the user to confirm if
-  // standard input is a terminal (otherwise it just does it). So make sure it's
-  // not, to avoid locking everything up waiting for mv.
-  *exit_code = EXIT_FAILURE;
-  int devnull = open("/dev/null", O_RDONLY);
-  if (devnull < 0)
-    return false;
-
-  base::LaunchOptions options;
-  options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
-  base::Process process = base::LaunchProcess(argv, options);
-  close(devnull);
-  if (!process.IsValid())
-    return false;
-  LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
-  return process.WaitForExit(exit_code);
-}
-
-namespace {
+namespace shell_integration_linux {
 
 const char kXdgSettings[] = "xdg-settings";
 const char kXdgSettingsDefaultBrowser[] = "default-web-browser";
@@ -146,7 +117,7 @@
     argv.push_back(kXdgSettingsDefaultSchemeHandler);
     argv.push_back(protocol);
   }
-  argv.push_back(shell_integration_linux::GetDesktopName(env.get()));
+  argv.push_back(GetDesktopName(env.get()));
 
   int exit_code;
   bool ran_ok = LaunchXdgUtility(argv, &exit_code);
@@ -163,9 +134,10 @@
 // If |protocol| is empty this function checks if Chrome is the default browser,
 // otherwise it checks if Chrome is the default handler application for
 // |protocol|.
-DefaultWebClientState GetIsDefaultWebClient(const std::string& protocol) {
+shell_integration::DefaultWebClientState GetIsDefaultWebClient(
+    const std::string& protocol) {
 #if defined(OS_CHROMEOS)
-  return UNKNOWN_DEFAULT;
+  return shell_integration::UNKNOWN_DEFAULT;
 #else
   base::AssertBlockingAllowed();
 
@@ -180,7 +152,7 @@
     argv.push_back(kXdgSettingsDefaultSchemeHandler);
     argv.push_back(protocol);
   }
-  argv.push_back(shell_integration_linux::GetDesktopName(env.get()));
+  argv.push_back(GetDesktopName(env.get()));
 
   std::string reply;
   int success_code;
@@ -195,13 +167,13 @@
 
   if (!ran_ok || success_code != EXIT_SUCCESS) {
     // xdg-settings failed: we can't determine or set the default browser.
-    return UNKNOWN_DEFAULT;
+    return shell_integration::UNKNOWN_DEFAULT;
   }
 
   // Allow any reply that starts with "yes".
   return base::StartsWith(reply, "yes", base::CompareCase::SENSITIVE)
-             ? IS_DEFAULT
-             : NOT_DEFAULT;
+             ? shell_integration::IS_DEFAULT
+             : shell_integration::NOT_DEFAULT;
 #endif
 }
 
@@ -220,54 +192,6 @@
   return desktop_file_name;
 }
 
-}  // namespace
-
-bool SetAsDefaultBrowser() {
-  return SetDefaultWebClient(std::string());
-}
-
-bool SetAsDefaultProtocolClient(const std::string& protocol) {
-  return SetDefaultWebClient(protocol);
-}
-
-DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
-  return SET_DEFAULT_UNATTENDED;
-}
-
-base::string16 GetApplicationNameForProtocol(const GURL& url) {
-  return base::ASCIIToUTF16("xdg-open");
-}
-
-DefaultWebClientState GetDefaultBrowser() {
-  return GetIsDefaultWebClient(std::string());
-}
-
-bool IsFirefoxDefaultBrowser() {
-  std::vector<std::string> argv;
-  argv.push_back(kXdgSettings);
-  argv.push_back("get");
-  argv.push_back(kXdgSettingsDefaultBrowser);
-
-  std::string browser;
-  // We don't care about the return value here.
-  base::GetAppOutput(base::CommandLine(argv), &browser);
-  return browser.find("irefox") != std::string::npos;
-}
-
-DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
-  return GetIsDefaultWebClient(protocol);
-}
-
-std::string GetWMClassFromAppName(std::string app_name) {
-  base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
-  base::TrimString(app_name, "_", &app_name);
-  return app_name;
-}
-
-}  // namespace shell_integration
-
-namespace shell_integration_linux {
-
 namespace {
 
 #if defined(USE_GLIB)
@@ -335,6 +259,39 @@
 
 }  // namespace
 
+// Allows LaunchXdgUtility to join a process.
+// thread_restrictions.h assumes it to be in shell_integration_linux namespace.
+class LaunchXdgUtilityScopedAllowBaseSyncPrimitives
+    : public base::ScopedAllowBaseSyncPrimitives {};
+
+bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code) {
+  // xdg-settings internally runs xdg-mime, which uses mv to move newly-created
+  // files on top of originals after making changes to them. In the event that
+  // the original files are owned by another user (e.g. root, which can happen
+  // if they are updated within sudo), mv will prompt the user to confirm if
+  // standard input is a terminal (otherwise it just does it). So make sure it's
+  // not, to avoid locking everything up waiting for mv.
+  *exit_code = EXIT_FAILURE;
+  int devnull = open("/dev/null", O_RDONLY);
+  if (devnull < 0)
+    return false;
+
+  base::LaunchOptions options;
+  options.fds_to_remap.push_back(std::make_pair(devnull, STDIN_FILENO));
+  base::Process process = base::LaunchProcess(argv, options);
+  close(devnull);
+  if (!process.IsValid())
+    return false;
+  LaunchXdgUtilityScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
+  return process.WaitForExit(exit_code);
+}
+
+std::string GetWMClassFromAppName(std::string app_name) {
+  base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
+  base::TrimString(app_name, "_", &app_name);
+  return app_name;
+}
+
 base::FilePath GetDataWriteLocation(base::Environment* env) {
   return base::nix::GetXDGDirectory(env, "XDG_DATA_HOME", ".local/share");
 }
@@ -419,8 +376,7 @@
 
 std::string GetProgramClassName(const base::CommandLine& command_line,
                                 const std::string& desktop_file_name) {
-  std::string class_name =
-      shell_integration::GetDesktopBaseName(desktop_file_name);
+  std::string class_name = GetDesktopBaseName(desktop_file_name);
   std::string user_data_dir =
       command_line.GetSwitchValueNative(switches::kUserDataDir);
   // If the user launches with e.g. --user-data-dir=/tmp/my-user-data, set the
@@ -436,8 +392,7 @@
                                  const std::string& desktop_file_name) {
   if (command_line.HasSwitch(switches::kWmClass))
     return command_line.GetSwitchValueASCII(switches::kWmClass);
-  std::string class_class =
-      shell_integration::GetDesktopBaseName(desktop_file_name);
+  std::string class_class = GetDesktopBaseName(desktop_file_name);
   if (!class_class.empty()) {
     // Capitalize the first character like gtk does.
     class_class[0] = base::ToUpperASCII(class_class[0]);
@@ -632,7 +587,7 @@
   if (no_display)
     g_key_file_set_string(key_file, kDesktopEntry, "NoDisplay", "true");
 
-  std::string wmclass = shell_integration::GetWMClassFromAppName(app_name);
+  std::string wmclass = GetWMClassFromAppName(app_name);
   g_key_file_set_string(key_file, kDesktopEntry, "StartupWMClass",
                         wmclass.c_str());
 
@@ -741,3 +696,43 @@
 #endif
 
 }  // namespace shell_integration_linux
+
+namespace shell_integration {
+
+bool SetAsDefaultBrowser() {
+  return shell_integration_linux::SetDefaultWebClient(std::string());
+}
+
+bool SetAsDefaultProtocolClient(const std::string& protocol) {
+  return shell_integration_linux::SetDefaultWebClient(protocol);
+}
+
+DefaultWebClientSetPermission GetDefaultWebClientSetPermission() {
+  return SET_DEFAULT_UNATTENDED;
+}
+
+base::string16 GetApplicationNameForProtocol(const GURL& url) {
+  return base::ASCIIToUTF16("xdg-open");
+}
+
+DefaultWebClientState GetDefaultBrowser() {
+  return shell_integration_linux::GetIsDefaultWebClient(std::string());
+}
+
+bool IsFirefoxDefaultBrowser() {
+  std::vector<std::string> argv;
+  argv.push_back(shell_integration_linux::kXdgSettings);
+  argv.push_back("get");
+  argv.push_back(shell_integration_linux::kXdgSettingsDefaultBrowser);
+
+  std::string browser;
+  // We don't care about the return value here.
+  base::GetAppOutput(base::CommandLine(argv), &browser);
+  return browser.find("irefox") != std::string::npos;
+}
+
+DefaultWebClientState IsDefaultProtocolClient(const std::string& protocol) {
+  return shell_integration_linux::GetIsDefaultWebClient(protocol);
+}
+
+}  // namespace shell_integration
diff --git a/chrome/browser/shell_integration_linux.h b/chrome/browser/shell_integration_linux.h
index 63e5529..0714226a 100644
--- a/chrome/browser/shell_integration_linux.h
+++ b/chrome/browser/shell_integration_linux.h
@@ -16,22 +16,6 @@
 class Environment;
 }
 
-// TODO(loyso): Move these helpers to shell_integration_linux namespace.
-namespace shell_integration {
-
-// Helper to launch xdg scripts. We don't want them to ask any questions on the
-// terminal etc. The function returns true if the utility launches and exits
-// cleanly, in which case |exit_code| returns the utility's exit code.
-// thread_restrictions.h assumes it to be in shell_integration namespace.
-bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code);
-
-// Windows that correspond to web apps need to have a deterministic (and
-// different) WMClass than normal chrome windows so the window manager groups
-// them as a separate application.
-std::string GetWMClassFromAppName(std::string app_name);
-
-}  // namespace shell_integration
-
 namespace shell_integration_linux {
 
 // Get the path to write user-specific application data files to, as specified
@@ -111,6 +95,17 @@
                                   const std::string& title);
 #endif
 
+// Windows that correspond to web apps need to have a deterministic (and
+// different) WMClass than normal chrome windows so the window manager groups
+// them as a separate application.
+std::string GetWMClassFromAppName(std::string app_name);
+
+// Helper to launch xdg scripts. We don't want them to ask any questions on the
+// terminal etc. The function returns true if the utility launches and exits
+// cleanly, in which case |exit_code| returns the utility's exit code.
+// thread_restrictions.h assumes it to be in shell_integration namespace.
+bool LaunchXdgUtility(const std::vector<std::string>& argv, int* exit_code);
+
 namespace internal {
 
 // Exposed for testing.  Clients should use the corresponding functions in
diff --git a/chrome/browser/site_details_browsertest.cc b/chrome/browser/site_details_browsertest.cc
index 3cfcf40..67ef39e 100644
--- a/chrome/browser/site_details_browsertest.cc
+++ b/chrome/browser/site_details_browsertest.cc
@@ -579,8 +579,8 @@
                   ElementsAre(Bucket(12, 1), Bucket(29, 1), Bucket(68, 1))));
 }
 
-// Flaky on Windows and Mac. crbug.com/671891
-#if defined(OS_WIN) || defined(OS_MACOSX)
+// Flaky on Windows, Mac and ChromeOS. crbug.com/671891
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
 #define MAYBE_IsolateExtensions DISABLED_IsolateExtensions
 #else
 #define MAYBE_IsolateExtensions IsolateExtensions
diff --git a/chrome/browser/sync/test/integration/autofill_helper.cc b/chrome/browser/sync/test/integration/autofill_helper.cc
index 7b2145a..92188d7 100644
--- a/chrome/browser/sync/test/integration/autofill_helper.cc
+++ b/chrome/browser/sync/test/integration/autofill_helper.cc
@@ -341,11 +341,11 @@
   return pdm->GetProfiles();
 }
 
-int GetProfileCount(int profile) {
+size_t GetProfileCount(int profile) {
   return GetAllAutoFillProfiles(profile).size();
 }
 
-int GetKeyCount(int profile) {
+size_t GetKeyCount(int profile) {
   return GetAllKeys(profile).size();
 }
 
diff --git a/chrome/browser/sync/test/integration/autofill_helper.h b/chrome/browser/sync/test/integration/autofill_helper.h
index 93ac327..241d61d0 100644
--- a/chrome/browser/sync/test/integration/autofill_helper.h
+++ b/chrome/browser/sync/test/integration/autofill_helper.h
@@ -96,10 +96,10 @@
 
 // Returns the number of autofill profiles contained by sync profile
 // |profile|.
-int GetProfileCount(int profile);
+size_t GetProfileCount(int profile);
 
 // Returns the number of autofill keys contained by sync profile |profile|.
-int GetKeyCount(int profile);
+size_t GetKeyCount(int profile);
 
 // Compares the Autofill profiles in the PersonalDataManagers of sync profiles
 // |profile_a| and |profile_b|. Returns true if they match.
diff --git a/chrome/browser/sync/test/integration/performance/autofill_sync_perf_test.cc b/chrome/browser/sync/test/integration/performance/autofill_sync_perf_test.cc
index bb7dbec..4f7027d 100644
--- a/chrome/browser/sync/test/integration/performance/autofill_sync_perf_test.cc
+++ b/chrome/browser/sync/test/integration/performance/autofill_sync_perf_test.cc
@@ -36,8 +36,8 @@
 //
 // TODO(akalin): If this works, decomp the magic number calculation
 // into a macro and have all the perf tests use it.
-static const int kNumKeys = 163;
-static const int kNumProfiles = 163;
+constexpr size_t kNumKeys = 163;
+constexpr size_t kNumProfiles = 163;
 
 std::string IntToName(int n) {
   return base::StringPrintf("Name%d", n);
@@ -166,7 +166,7 @@
 
   RemoveProfiles(0);
   dt = TimeMutualSyncCycle(GetClient(0), GetClient(1));
-  ASSERT_EQ(0, GetProfileCount(1));
+  ASSERT_EQ(0U, GetProfileCount(1));
   PrintResult("autofill", "delete_autofill_profiles", dt);
 }
 
@@ -224,7 +224,7 @@
   // TODO(lipalani): fix this. The following line is added to force sync.
   ForceSync(0);
   dt = TimeMutualSyncCycle(GetClient(0), GetClient(1));
-  ASSERT_EQ(0, GetKeyCount(1));
+  ASSERT_EQ(0U, GetKeyCount(1));
   PrintResult("autofill", "delete_autofill_keys", dt);
 }
 
diff --git a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
index 72c9547..ee1ecfe 100644
--- a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
+++ b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/signin/unified_consent_helper.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/sync/test/integration/quiesce_status_change_checker.h"
 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
@@ -25,6 +26,7 @@
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
+#include "chrome/browser/unified_consent/unified_consent_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/invalidation/impl/p2p_invalidation_service.h"
@@ -33,6 +35,7 @@
 #include "components/sync/base/progress_marker_map.h"
 #include "components/sync/driver/about_sync_util.h"
 #include "components/sync/engine/sync_string_conversions.h"
+#include "components/unified_consent/unified_consent_service.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "services/identity/public/cpp/identity_manager.h"
 
@@ -205,7 +208,17 @@
   // Choose the datatypes to be synced. If all datatypes are to be synced,
   // set sync_everything to true; otherwise, set it to false.
   bool sync_everything = (synced_datatypes == syncer::UserSelectableTypes());
-  service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
+  if (IsUnifiedConsentEnabled(profile_)) {
+    // When unified consent given is set to |true|, the unified consent service
+    // enables syncing all datatypes.
+    UnifiedConsentServiceFactory::GetForProfile(profile_)
+      ->SetUnifiedConsentGiven(sync_everything);
+    if (!sync_everything) {
+      service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
+    }
+  } else {
+    service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
+  }
 
   // Notify ProfileSyncService that we are done with configuration.
   if (skip_passphrase_verification) {
@@ -494,6 +507,12 @@
     return true;
   }
 
+  // Disable unified consent first as otherwise disabling sync is not possible.
+  if (IsUnifiedConsentEnabled(profile_)) {
+    UnifiedConsentServiceFactory::GetForProfile(profile_)
+        ->SetUnifiedConsentGiven(false);
+  }
+
   synced_datatypes.RetainAll(syncer::UserSelectableTypes());
   synced_datatypes.Remove(datatype);
   service()->OnUserChoseDatatypes(false, synced_datatypes);
diff --git a/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
index e1b364c..9b26fd4 100644
--- a/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_bookmarks_sync_test.cc
@@ -113,17 +113,6 @@
 IN_PROC_BROWSER_TEST_P(SingleClientBookmarksSyncTestIncludingUssTests, Sanity) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
-  // TODO(crbug.com/516866): Move the following block after adding the bookmark
-  // nodes to the model upon implementing the merge logic in USS. This is here
-  // to make sure that when sync starts, the model is completely empty and
-  // doesn't require any merge logic which isn't the general case obviously.
-
-  // Setup sync, wait for its completion, and make sure changes were synced.
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(
-      UpdatedProgressMarkerChecker(GetSyncService(kSingleProfileIndex)).Wait());
-  ASSERT_TRUE(ModelMatchesVerifier(kSingleProfileIndex));
-
   // Starting state:
   // other_node
   //    -> top
@@ -152,8 +141,11 @@
       kSingleProfileIndex, tier1_b, 0, "tier1_b_url0",
       GURL("http://www.nhl.com"));
 
-  // TODO(crbug.com/516866): Call SetupSync() here upon implementing the merge
-  // logic in USS. Refer the TODO above for details.
+  // Setup sync, wait for its completion, and make sure changes were synced.
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  ASSERT_TRUE(
+      UpdatedProgressMarkerChecker(GetSyncService(kSingleProfileIndex)).Wait());
+  ASSERT_TRUE(ModelMatchesVerifier(kSingleProfileIndex));
 
   //  Ultimately we want to end up with the following model; but this test is
   //  more about the journey than the destination.
diff --git a/chrome/browser/sync/test/integration/single_client_directory_sync_test.cc b/chrome/browser/sync/test/integration/single_client_directory_sync_test.cc
index dd4ab794..ca2f3d1 100644
--- a/chrome/browser/sync/test/integration/single_client_directory_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_directory_sync_test.cc
@@ -9,6 +9,7 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/sync/test/integration/bookmarks_helper.h"
@@ -16,6 +17,7 @@
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
 #include "components/browser_sync/profile_sync_service.h"
+#include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/model/model_type_store_service.h"
 #include "components/sync/syncable/directory.h"
 #include "sql/test/test_helpers.h"
@@ -75,6 +77,13 @@
 
 IN_PROC_BROWSER_TEST_F(SingleClientDirectorySyncTest,
                        StopThenDisableDeletesDirectory) {
+  // If SyncStandaloneTransport is enabled, then the sync service will
+  // immediately restart (and thus recreate directory files) after RequestStop.
+  // TODO(crbug.com/856179): Rewrite this test to pass with
+  // kSyncStandaloneTransport enabled.
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(switches::kSyncStandaloneTransport);
+
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   browser_sync::ProfileSyncService* sync_service = GetSyncService(0);
   FilePath directory_path = sync_service->GetSyncClient()
diff --git a/chrome/browser/sync/test/integration/sync_errors_test.cc b/chrome/browser/sync/test/integration/sync_errors_test.cc
index fabd3d4..c6288bf3 100644
--- a/chrome/browser/sync/test/integration/sync_errors_test.cc
+++ b/chrome/browser/sync/test/integration/sync_errors_test.cc
@@ -4,6 +4,7 @@
 
 #include "base/macros.h"
 #include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/sync/test/integration/bookmarks_helper.h"
 #include "chrome/browser/sync/test/integration/passwords_helper.h"
@@ -30,12 +31,25 @@
   explicit SyncDisabledChecker(ProfileSyncService* service)
       : SingleClientStatusChangeChecker(service) {}
 
+  SyncDisabledChecker(ProfileSyncService* service,
+                      base::OnceClosure condition_satisfied_callback)
+      : SingleClientStatusChangeChecker(service),
+        condition_satisfied_callback_(std::move(condition_satisfied_callback)) {
+  }
+
   bool IsExitConditionSatisfied() override {
-    return !service()->IsSetupInProgress() &&
-           !service()->IsFirstSetupComplete();
+    bool satisfied =
+        !service()->IsSetupInProgress() && !service()->IsFirstSetupComplete();
+    if (satisfied && condition_satisfied_callback_) {
+      std::move(condition_satisfied_callback_).Run();
+    }
+    return satisfied;
   }
 
   std::string GetDebugMessage() const override { return "Sync Disabled"; }
+
+ private:
+  base::OnceClosure condition_satisfied_callback_;
 };
 
 class SyncEngineStoppedChecker : public SingleClientStatusChangeChecker {
@@ -188,22 +202,31 @@
 
   std::string description = "Not My Fault";
   std::string url = "www.google.com";
-  EXPECT_TRUE(GetFakeServer()->TriggerActionableError(
-      sync_pb::SyncEnums::NOT_MY_BIRTHDAY,
-      description,
-      url,
+  ASSERT_TRUE(GetFakeServer()->TriggerActionableError(
+      sync_pb::SyncEnums::NOT_MY_BIRTHDAY, description, url,
       sync_pb::SyncEnums::DISABLE_SYNC_ON_CLIENT));
 
   // Now make one more change so we will do another sync.
   const BookmarkNode* node2 = AddFolder(0, 0, "title2");
   SetTitle(0, node2, "new_title2");
-  ASSERT_TRUE(SyncDisabledChecker(GetSyncService(0)).Wait());
-  syncer::SyncStatus status;
-  GetSyncService(0)->QueryDetailedSyncStatus(&status);
-  ASSERT_EQ(status.sync_protocol_error.error_type, syncer::NOT_MY_BIRTHDAY);
-  ASSERT_EQ(status.sync_protocol_error.action, syncer::DISABLE_SYNC_ON_CLIENT);
-  ASSERT_EQ(status.sync_protocol_error.url, url);
-  ASSERT_EQ(status.sync_protocol_error.error_description, description);
+
+  auto condition = base::BindLambdaForTesting([&]() {
+    syncer::SyncStatus status;
+    GetSyncService(0)->QueryDetailedSyncStatus(&status);
+
+    // Note: If SyncStandaloneTransport is enabled, then on
+    // receiving the error, the SyncService will immediately
+    // start up again in transport mode, which resets the
+    // status. So query the status that the checker recorded at
+    // the time Sync was off. syncer::SyncStatus status =
+    // checker.GetSyncStatusAtExit();
+    EXPECT_EQ(status.sync_protocol_error.error_type, syncer::NOT_MY_BIRTHDAY);
+    EXPECT_EQ(status.sync_protocol_error.action,
+              syncer::DISABLE_SYNC_ON_CLIENT);
+    EXPECT_EQ(status.sync_protocol_error.url, url);
+    EXPECT_EQ(status.sync_protocol_error.error_description, description);
+  });
+  EXPECT_TRUE(SyncDisabledChecker(GetSyncService(0), condition).Wait());
 }
 
 // Tests that on receiving CLIENT_DATA_OBSOLETE sync engine gets restarted and
diff --git a/chrome/browser/sync/test/integration/two_client_autocomplete_sync_test.cc b/chrome/browser/sync/test/integration/two_client_autocomplete_sync_test.cc
new file mode 100644
index 0000000..6e39cfc
--- /dev/null
+++ b/chrome/browser/sync/test/integration/two_client_autocomplete_sync_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2018 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 "base/macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/sync/test/integration/autofill_helper.h"
+#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "components/autofill/core/browser/webdata/autofill_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using autofill::AutofillKey;
+using autofill_helper::AddKeys;
+using autofill_helper::GetAllKeys;
+using autofill_helper::KeysMatch;
+using autofill_helper::RemoveKey;
+
+class TwoClientAutocompleteSyncTest : public SyncTest {
+ public:
+  TwoClientAutocompleteSyncTest() : SyncTest(TWO_CLIENT) {}
+  ~TwoClientAutocompleteSyncTest() override {}
+
+  bool TestUsesSelfNotifications() override { return false; }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TwoClientAutocompleteSyncTest);
+};
+
+IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest, WebDataServiceSanity) {
+  ASSERT_TRUE(SetupSync());
+
+  // Client0 adds a key.
+  AddKeys(0, {AutofillKey("name0", "value0")});
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllKeys(0).size());
+
+  // Client1 adds a key.
+  AddKeys(1, {AutofillKey("name1", "value1-0")});
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(2U, GetAllKeys(0).size());
+
+  // Client0 adds a key with the same name.
+  AddKeys(0, {AutofillKey("name1", "value1-1")});
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(3U, GetAllKeys(0).size());
+
+  // Client1 removes a key.
+  RemoveKey(1, AutofillKey("name1", "value1-0"));
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(2U, GetAllKeys(0).size());
+
+  // Client0 removes the rest.
+  RemoveKey(0, AutofillKey("name0", "value0"));
+  RemoveKey(0, AutofillKey("name1", "value1-1"));
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(0U, GetAllKeys(0).size());
+}
+
+IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest, AddUnicodeProfile) {
+  ASSERT_TRUE(SetupClients());
+
+  std::set<AutofillKey> keys;
+  keys.insert(AutofillKey(base::WideToUTF16(L"Sigur R\u00F3s"),
+                          base::WideToUTF16(L"\u00C1g\u00E6tis byrjun")));
+  AddKeys(0, keys);
+  ASSERT_TRUE(SetupSync());
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest,
+                       AddDuplicateNamesToSameProfile) {
+  ASSERT_TRUE(SetupClients());
+
+  std::set<AutofillKey> keys;
+  keys.insert(AutofillKey("name0", "value0-0"));
+  keys.insert(AutofillKey("name0", "value0-1"));
+  keys.insert(AutofillKey("name1", "value1"));
+  AddKeys(0, keys);
+  ASSERT_TRUE(SetupSync());
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(2U, GetAllKeys(0).size());
+}
+
+IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest,
+                       AddDuplicateNamesToDifferentProfiles) {
+  ASSERT_TRUE(SetupClients());
+
+  std::set<AutofillKey> keys0;
+  keys0.insert(AutofillKey("name0", "value0-0"));
+  keys0.insert(AutofillKey("name1", "value1"));
+  AddKeys(0, keys0);
+
+  std::set<AutofillKey> keys1;
+  keys1.insert(AutofillKey("name0", "value0-1"));
+  keys1.insert(AutofillKey("name2", "value2"));
+  keys1.insert(AutofillKey("name3", "value3"));
+  AddKeys(1, keys1);
+
+  ASSERT_TRUE(SetupSync());
+  EXPECT_TRUE(AutofillKeysChecker(0, 1).Wait());
+  EXPECT_EQ(5U, GetAllKeys(0).size());
+}
+
+}  // namespace
diff --git a/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc b/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
index 7e07a34..056df0ca 100644
--- a/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_autofill_sync_test.cc
@@ -1,4 +1,3 @@
-
 // Copyright (c) 2012 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.
@@ -6,140 +5,38 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
 #include "chrome/browser/sync/test/integration/autofill_helper.h"
-#include "chrome/browser/sync/test/integration/bookmarks_helper.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "components/autofill/core/browser/autofill_profile.h"
 #include "components/autofill/core/browser/credit_card.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
-#include "components/autofill/core/browser/webdata/autofill_entry.h"
 #include "components/autofill/core/browser/webdata/autofill_table.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
-using autofill::AutofillKey;
 using autofill::AutofillTable;
 using autofill::AutofillProfile;
 using autofill::AutofillType;
 using autofill::CreditCard;
 using autofill::PersonalDataManager;
-using autofill_helper::AddKeys;
 using autofill_helper::AddProfile;
 using autofill_helper::CreateAutofillProfile;
 using autofill_helper::CreateUniqueAutofillProfile;
 using autofill_helper::GetAllAutoFillProfiles;
-using autofill_helper::GetAllKeys;
 using autofill_helper::GetPersonalDataManager;
 using autofill_helper::GetProfileCount;
-using autofill_helper::KeysMatch;
 using autofill_helper::ProfilesMatch;
 using autofill_helper::PROFILE_FRASIER;
 using autofill_helper::PROFILE_HOMER;
 using autofill_helper::PROFILE_MARION;
 using autofill_helper::PROFILE_NULL;
-using autofill_helper::RemoveKey;
 using autofill_helper::RemoveProfile;
 using autofill_helper::SetCreditCards;
 using autofill_helper::UpdateProfile;
-using bookmarks_helper::AddFolder;
-using bookmarks_helper::AddURL;
-using bookmarks_helper::IndexedURL;
-using bookmarks_helper::IndexedURLTitle;
-
-class TwoClientAutocompleteSyncTest : public SyncTest {
- public:
-  TwoClientAutocompleteSyncTest() : SyncTest(TWO_CLIENT) {}
-  ~TwoClientAutocompleteSyncTest() override {}
-
-  bool TestUsesSelfNotifications() override { return false; }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TwoClientAutocompleteSyncTest);
-};
-
-IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest, WebDataServiceSanity) {
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-
-  // Client0 adds a key.
-  std::set<AutofillKey> keys;
-  keys.insert(AutofillKey("name0", "value0"));
-  AddKeys(0, keys);
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllKeys(0).size());
-
-  // Client1 adds a key.
-  keys.clear();
-  keys.insert(AutofillKey("name1", "value1-0"));
-  AddKeys(1, keys);
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(2U, GetAllKeys(0).size());
-
-  // Client0 adds a key with the same name.
-  keys.clear();
-  keys.insert(AutofillKey("name1", "value1-1"));
-  AddKeys(0, keys);
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(3U, GetAllKeys(0).size());
-
-  // Client1 removes a key.
-  RemoveKey(1, AutofillKey("name1", "value1-0"));
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(2U, GetAllKeys(0).size());
-
-  // Client0 removes the rest.
-  RemoveKey(0, AutofillKey("name0", "value0"));
-  RemoveKey(0, AutofillKey("name1", "value1-1"));
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(0U, GetAllKeys(0).size());
-}
-
-IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest, AddUnicodeProfile) {
-  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
-
-  std::set<AutofillKey> keys;
-  keys.insert(AutofillKey(base::WideToUTF16(L"Sigur R\u00F3s"),
-                          base::WideToUTF16(L"\u00C1g\u00E6tis byrjun")));
-  AddKeys(0, keys);
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-}
-
-IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest,
-                       AddDuplicateNamesToSameProfile) {
-  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
-
-  std::set<AutofillKey> keys;
-  keys.insert(AutofillKey("name0", "value0-0"));
-  keys.insert(AutofillKey("name0", "value0-1"));
-  keys.insert(AutofillKey("name1", "value1"));
-  AddKeys(0, keys);
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(2U, GetAllKeys(0).size());
-}
-
-IN_PROC_BROWSER_TEST_F(TwoClientAutocompleteSyncTest,
-                       AddDuplicateNamesToDifferentProfiles) {
-  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
-
-  std::set<AutofillKey> keys0;
-  keys0.insert(AutofillKey("name0", "value0-0"));
-  keys0.insert(AutofillKey("name1", "value1"));
-  AddKeys(0, keys0);
-
-  std::set<AutofillKey> keys1;
-  keys1.insert(AutofillKey("name0", "value0-1"));
-  keys1.insert(AutofillKey("name2", "value2"));
-  keys1.insert(AutofillKey("name3", "value3"));
-  AddKeys(1, keys1);
-
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillKeysChecker(0, 1).Wait());
-  ASSERT_EQ(5U, GetAllKeys(0).size());
-}
 
 // Class that enables or disables USS based on test parameter. Must be the first
 // base class of the test fixture.
@@ -179,36 +76,36 @@
 
   // Client0 adds a profile.
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
   // Client1 adds a profile.
   AddProfile(1, CreateAutofillProfile(PROFILE_MARION));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(2U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(2U, GetAllAutoFillProfiles(0).size());
 
   // Client0 adds the same profile.
   AddProfile(0, CreateAutofillProfile(PROFILE_MARION));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(2U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(2U, GetAllAutoFillProfiles(0).size());
 
   // Client1 removes a profile.
   RemoveProfile(1, GetAllAutoFillProfiles(1)[0]->guid());
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
   // Client0 updates a profile.
   UpdateProfile(0,
                 GetAllAutoFillProfiles(0)[0]->guid(),
                 AutofillType(autofill::NAME_FIRST),
                 base::ASCIIToUTF16("Bart"));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
   // Client1 removes remaining profile.
   RemoveProfile(1, GetAllAutoFillProfiles(1)[0]->guid());
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(0U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(0U, GetAllAutoFillProfiles(0).size());
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, AddDuplicateProfiles) {
@@ -217,8 +114,8 @@
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
@@ -233,97 +130,226 @@
   AddProfile(0, profile0);
   AddProfile(1, profile1);
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 }
 
+// Tests that a null profile does not get synced across clients.
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, AddEmptyProfile) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   AddProfile(0, CreateAutofillProfile(PROFILE_NULL));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(0U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(0U, GetAllAutoFillProfiles(0).size());
 }
 
+// Tests that adding a profile on one client results in it being added on the
+// other client when sync is running.
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, AddProfile) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+
+  // Wait for the sync to happen.
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+
+  // Make sure that both clients have one profile.
+  EXPECT_EQ(1U, GetProfileCount(0));
+  EXPECT_EQ(1U, GetProfileCount(1));
 }
 
-IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, AddMultipleProfiles) {
+// Tests that adding a profile on one client results in it being added on the
+// other client when sync gets started.
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       AddProfile_BeforeSyncStart) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed";
+
+  // Add the new autofill profile before starting sync.
+  AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
+  // Wait for the sync to happen.
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+
+  // Make sure that both clients have one profile.
+  EXPECT_EQ(1U, GetProfileCount(0));
+  EXPECT_EQ(1U, GetProfileCount(1));
+}
+
+// Tests that adding the same profile on the two clients before sync is started
+// results in each client only having one profile after sync is started
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       ClientsAddSameProfile) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed";
+
+  // Add the same profile in the two clients.
+  AddProfile(0, CreateUniqueAutofillProfile());
+  AddProfile(1, CreateUniqueAutofillProfile());
+
+  // Make sure the GUIDs are different.
+  ASSERT_NE(GetAllAutoFillProfiles(0)[0]->guid(),
+            GetAllAutoFillProfiles(1)[0]->guid());
+
+  // Wait for the sync to happen.
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+
+  // Make sure that both clients have one profile.
+  EXPECT_EQ(1U, GetProfileCount(0));
+  EXPECT_EQ(1U, GetProfileCount(1));
+
+  // Make sure that they have the same GUID.
+  EXPECT_EQ(GetAllAutoFillProfiles(0)[0]->guid(),
+            GetAllAutoFillProfiles(1)[0]->guid());
+}
+
+// Tests that adding multiple profiles to one client results in all of them
+// being added to the other client.
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       AddMultipleProfilesOnOneClient) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
   AddProfile(0, CreateAutofillProfile(PROFILE_MARION));
   AddProfile(0, CreateAutofillProfile(PROFILE_FRASIER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(3U, GetAllAutoFillProfiles(0).size());
-}
-
-IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, DeleteProfile) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-
-  AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
-
-  RemoveProfile(1, GetAllAutoFillProfiles(1)[0]->guid());
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(0U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(3U, GetAllAutoFillProfiles(0).size());
 }
 
-IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, MergeProfiles) {
+// Tests that adding multiple profiles to two client results both clients having
+// all profiles.
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       AddMultipleProfilesOnTwoClients) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
   AddProfile(1, CreateAutofillProfile(PROFILE_MARION));
   AddProfile(1, CreateAutofillProfile(PROFILE_FRASIER));
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(3U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(3U, GetAllAutoFillProfiles(0).size());
 }
 
+// Tests that deleting a profile on one client results in it being deleted on
+// the other client.
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, DeleteProfile) {
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+
+  // Setup the test by making the 2 clients have the same profile.
+  AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
+
+  // Remove the profile from one client.
+  RemoveProfile(1, GetAllAutoFillProfiles(1)[0]->guid());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+
+  // Make sure both clients don't have any profiles.
+  EXPECT_EQ(0U, GetAllAutoFillProfiles(0).size());
+}
+
+// Tests that modifying a profile while syncing results in the other client
+// getting the updated profile.
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, UpdateFields) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
-  UpdateProfile(0,
-                GetAllAutoFillProfiles(0)[0]->guid(),
+  // Modify the profile on one client.
+  std::string new_name = "Lisa";
+  std::string new_email = "grrrl@TV.com";
+  UpdateProfile(0, GetAllAutoFillProfiles(0)[0]->guid(),
                 AutofillType(autofill::NAME_FIRST),
-                base::ASCIIToUTF16("Lisa"));
-  UpdateProfile(0,
-                GetAllAutoFillProfiles(0)[0]->guid(),
+                base::ASCIIToUTF16(new_name));
+  UpdateProfile(0, GetAllAutoFillProfiles(0)[0]->guid(),
                 AutofillType(autofill::EMAIL_ADDRESS),
-                base::ASCIIToUTF16("grrrl@TV.com"));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+                base::ASCIIToUTF16(new_email));
+
+  // Make sure the change is propagated to the other client.
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_EQ(new_name,
+            base::UTF16ToUTF8(GetAllAutoFillProfiles(1)[0]->GetRawInfo(
+                autofill::NAME_FIRST)));
+  EXPECT_EQ(new_email,
+            base::UTF16ToUTF8(GetAllAutoFillProfiles(1)[0]->GetRawInfo(
+                autofill::EMAIL_ADDRESS)));
 }
 
-IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, ConflictingFields) {
+// Tests that modifying a profile at the same time one two clients while
+// syncing results in the both client having the same profile (doesn't matter
+// which one).
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       UpdateConflictingFields) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
+  // Make the two clients have the same profile.
   AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
+  // Update the same field differently on the two clients at the same time.
   UpdateProfile(0,
                 GetAllAutoFillProfiles(0)[0]->guid(),
                 AutofillType(autofill::NAME_FIRST),
                 base::ASCIIToUTF16("Lisa"));
+  UpdateProfile(1, GetAllAutoFillProfiles(1)[0]->guid(),
+                AutofillType(autofill::NAME_FIRST), base::ASCIIToUTF16("Bart"));
+
+  // Don't care which write wins the conflict, only that the two clients agree.
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
+}
+
+// Tests that modifying a profile at the same time one two clients while
+// syncing results in the both client having the same profile (doesn't matter
+// which one).
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
+                       UpdateConflictingFieldsDuringInitialMerge) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  // Make the two clients have the same profile.
+  AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
+  AddProfile(1, CreateAutofillProfile(PROFILE_HOMER));
+
+  // Update the same field differently on the two clients at the same time.
+  UpdateProfile(0, GetAllAutoFillProfiles(0)[0]->guid(),
+                AutofillType(autofill::NAME_FIRST), base::ASCIIToUTF16("Lisa"));
+  UpdateProfile(1, GetAllAutoFillProfiles(1)[0]->guid(),
+                AutofillType(autofill::NAME_FIRST), base::ASCIIToUTF16("Bart"));
+
+  // Start sync.
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+
+  // Don't care which write wins the conflict, only that the two clients agree.
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
+}
+
+// Tests that modifying a profile at the same time on two clients while
+// syncing results in both client having the same profile (doesn't matter which
+// one).
+IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, DeleteAndUpdate) {
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+
+  // Make the two clients have the same profile.
+  AddProfile(0, CreateAutofillProfile(PROFILE_HOMER));
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
+
+  RemoveProfile(0, GetAllAutoFillProfiles(0)[0]->guid());
   UpdateProfile(1,
                 GetAllAutoFillProfiles(1)[0]->guid(),
                 AutofillType(autofill::NAME_FIRST),
                 base::ASCIIToUTF16("Bart"));
 
-  // Don't care which write wins the conflict, only that the two clients agree.
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  // TODO(crbug.com/870333): The result should be deterministic for USS (either
+  // update or delete, but always do the same).
+  EXPECT_EQ(GetAllAutoFillProfiles(0).size(), GetAllAutoFillProfiles(1).size());
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, MaxLength) {
@@ -347,7 +373,7 @@
                 AutofillType(autofill::ADDRESS_HOME_LINE1),
                 max_length_string);
 
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest, ExceedsMaxLength) {
@@ -376,7 +402,7 @@
                 AutofillType(autofill::ADDRESS_HOME_LINE1),
                 exceeds_max_length_string);
 
-  ASSERT_TRUE(BookmarksMatchChecker().Wait());
+  ASSERT_TRUE(AwaitQuiescence());
   EXPECT_FALSE(ProfilesMatch(0, 1));
 }
 
@@ -396,11 +422,11 @@
   // profile to sync between both clients, it should give the credit card enough
   // time to sync. We cannot directly wait/block for the credit card to sync
   // because we're expecting it to not sync.
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
-  ASSERT_EQ(1U, GetAllAutoFillProfiles(0).size());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_EQ(1U, GetAllAutoFillProfiles(0).size());
 
   PersonalDataManager* pdm = GetPersonalDataManager(1);
-  ASSERT_EQ(0U, pdm->GetCreditCards().size());
+  EXPECT_EQ(0U, pdm->GetCreditCards().size());
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientAutofillProfileSyncTest,
@@ -414,17 +440,17 @@
   // For clean profiles, the autofill profiles count should be zero. We are not
   // enforcing this, we only check that the final count is equal to initial
   // count plus new autofill profiles count.
-  int init_autofill_profiles_count = GetProfileCount(0);
+  size_t init_autofill_profiles_count = GetProfileCount(0);
 
   // Add a new autofill profile to the first client.
   AddProfile(0, CreateUniqueAutofillProfile());
 
-  ASSERT_TRUE(AutofillProfileChecker(0, 1).Wait());
+  EXPECT_TRUE(AutofillProfileChecker(0, 1).Wait());
 
   // Check that the total number of autofill profiles is as expected
   for (int i = 0; i < num_clients(); ++i) {
-    ASSERT_EQ(GetProfileCount(i), init_autofill_profiles_count + 1) <<
-        "Total autofill profile count is wrong.";
+    EXPECT_EQ(GetProfileCount(i), init_autofill_profiles_count + 1U)
+        << "Total autofill profile count is wrong.";
   }
 }
 
diff --git a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
index 154216a..ffb8d9c 100644
--- a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
@@ -451,7 +451,7 @@
 }
 
 // Add bookmarks with different name and same URL.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        SC_DuplicateBookmarksWithSameURL) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(AllModelsMatchVerifier());
@@ -1234,7 +1234,7 @@
   ASSERT_TRUE(BookmarksMatchVerifierChecker().Wait());
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_BiDirectionalPushAddingBM) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(AllModelsMatchVerifier());
@@ -1252,7 +1252,7 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_BiDirectionalPush_AddingSameBMs) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(AllModelsMatchVerifier());
@@ -1269,14 +1269,14 @@
   ASSERT_TRUE(BookmarksMatchChecker().Wait());
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_BootStrapEmptyStateEverywhere) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(AwaitQuiescence());
   ASSERT_TRUE(AllModelsMatchVerifier());
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_Merge_CaseInsensitivity_InNames) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1298,7 +1298,7 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_SimpleMergeOfDifferentBMModels) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1324,7 +1324,7 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeSimpleBMHierarchyUnderBMBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1347,7 +1347,7 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1365,7 +1365,7 @@
 }
 
 // Merge bookmark folders with different bookmarks.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeBMFoldersWithDifferentBMs) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1388,7 +1388,7 @@
 }
 
 // Merge moderately complex bookmark models.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeDifferentBMModelsModeratelyComplex) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1429,7 +1429,7 @@
 }
 
 // Merge simple bookmark subset under bookmark folder.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeSimpleBMHierarchySubsetUnderBMFolder) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1454,7 +1454,7 @@
 }
 
 // Merge subsets of bookmark under bookmark bar.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_MergeSimpleBMHierarchySubsetUnderBookmarkBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1478,7 +1478,7 @@
 }
 
 // Merge simple bookmark hierarchy under bookmark folder.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_Merge_SimpleBMHierarchy_Under_BMFolder) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1509,7 +1509,7 @@
 
 // Merge disjoint sets of bookmark hierarchy under bookmark
 // folder.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_Merge_SimpleBMHierarchy_DisjointSets_Under_BMFolder) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1538,7 +1538,8 @@
 }
 
 // Merge disjoint sets of bookmark hierarchy under bookmark bar.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(
+    TwoClientBookmarksSyncTestIncludingUssTests,
     MC_Merge_SimpleBMHierarchy_DisjointSets_Under_BookmarkBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1561,7 +1562,7 @@
 }
 
 // Merge sets of duplicate bookmarks under bookmark bar.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest,
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
                        MC_Merge_SimpleBMHierarchy_DuplicateBMs_Under_BMBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
@@ -1621,7 +1622,8 @@
 }
 
 // Test adding duplicate folder - Both with different BMs underneath.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest, MC_DuplicateFolders) {
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+                       MC_DuplicateFolders) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
 
@@ -1643,7 +1645,8 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest, MC_DeleteBookmark) {
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+                       MC_DeleteBookmark) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(GetClient(1)->DisableSyncForDatatype(syncer::BOOKMARKS));
 
@@ -1969,7 +1972,8 @@
 // Trigger the server side creation of Synced Bookmarks. Ensure both clients
 // remain syncing afterwards. Add bookmarks to the synced bookmarks folder
 // and ensure both clients receive the bookmark.
-IN_PROC_BROWSER_TEST_F(TwoClientBookmarksSyncTest, CreateSyncedBookmarks) {
+IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
+                       CreateSyncedBookmarks) {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
   ASSERT_TRUE(AllModelsMatchVerifier());
 
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index a4cc36bb..ee180ed 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -51,7 +51,7 @@
 // theme packs that aren't int-equal to this. Increment this number if you
 // change default theme assets or if you need themes to recreate their generated
 // images (which are cached).
-const int kThemePackVersion = 53;
+const int kThemePackVersion = 54;
 
 // IDs that are in the DataPack won't clash with the positive integer
 // uint16_t. kHeaderID should always have the maximum value because we want the
@@ -496,16 +496,22 @@
   void Draw(gfx::Canvas* canvas) override {
     canvas->DrawColor(background_color_);
 
+    // Begin with the frame background image, if any.  Since the frame and tabs
+    // have grown taller and changed alignment over time, not all themes have a
+    // sufficiently tall image; tiling by vertically mirroring in this case is
+    // the least-glitchy-looking option.  Note that the behavior here needs to
+    // stay in sync with how the browser frame will actually be drawn.
     if (!image_to_tint_.isNull()) {
       gfx::ImageSkia bg_tint = gfx::ImageSkiaOperations::CreateHSLShiftedImage(
           image_to_tint_, hsl_shift_);
       canvas->TileImageInt(bg_tint, 0, vertical_offset_, 0, 0, size().width(),
-                           size().height());
+                           size().height(), 1.0f, SkShader::kRepeat_TileMode,
+                           SkShader::kMirror_TileMode);
     }
 
-    // If they've provided a custom image, overlay it.  Since tabs have grown
-    // taller over time, not all themes have a sufficiently tall image; tiling
-    // by vertically mirroring in this case is the least-glitchy-looking option.
+    // If the theme has a custom tab background image, overlay it.  Vertical
+    // mirroring is used for the same reason as above.  This behavior needs to
+    // stay in sync with how tabs are drawn.
     if (!overlay_.isNull()) {
       canvas->TileImageInt(overlay_, 0, 0, 0, 0, size().width(),
                            size().height(), 1.0f, SkShader::kRepeat_TileMode,
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.cc b/chrome/browser/ui/apps/chrome_app_delegate.cc
index 49418767c4..deeb577 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.cc
+++ b/chrome/browser/ui/apps/chrome_app_delegate.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/favicon/favicon_utils.h"
 #include "chrome/browser/file_select_helper.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/shell_integration.h"
@@ -366,6 +367,18 @@
 #endif
 }
 
+gfx::Size ChromeAppDelegate::EnterPictureInPicture(
+    content::WebContents* web_contents,
+    const viz::SurfaceId& surface_id,
+    const gfx::Size& natural_size) {
+  return PictureInPictureWindowManager::GetInstance()->EnterPictureInPicture(
+      web_contents, surface_id, natural_size);
+}
+
+void ChromeAppDelegate::ExitPictureInPicture() {
+  PictureInPictureWindowManager::GetInstance()->ExitPictureInPicture();
+}
+
 void ChromeAppDelegate::Observe(int type,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.h b/chrome/browser/ui/apps/chrome_app_delegate.h
index f65e2c51..73eb6cf4 100644
--- a/chrome/browser/ui/apps/chrome_app_delegate.h
+++ b/chrome/browser/ui/apps/chrome_app_delegate.h
@@ -74,6 +74,10 @@
   void OnHide() override;
   void OnShow() override;
   bool TakeFocus(content::WebContents* web_contents, bool reverse) override;
+  gfx::Size EnterPictureInPicture(content::WebContents* web_contents,
+                                  const viz::SurfaceId& surface_id,
+                                  const gfx::Size& natural_size) override;
+  void ExitPictureInPicture() override;
 
   // content::NotificationObserver:
   void Observe(int type,
diff --git a/chrome/browser/ui/ash/chrome_keyboard_ui.cc b/chrome/browser/ui/ash/chrome_keyboard_ui.cc
index 37ee9b4..1b77e32f 100644
--- a/chrome/browser/ui/ash/chrome_keyboard_ui.cc
+++ b/chrome/browser/ui/ash/chrome_keyboard_ui.cc
@@ -37,6 +37,8 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_messages.h"
 #include "ipc/ipc_message_macros.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/platform/aura_window_properties.h"
 #include "ui/aura/layout_manager.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_event_dispatcher.h"
@@ -330,6 +332,9 @@
     // but this causes the shadows to be clipped too, so clipping needs to
     // be disabled.
     keyboard_contents_->GetNativeView()->layer()->SetMasksToBounds(false);
+
+    keyboard_contents_->GetNativeView()->SetProperty(
+        ui::kAXRoleOverride, ax::mojom::Role::kKeyboard);
   }
 
   return keyboard_contents_->GetNativeView();
diff --git a/chrome/browser/ui/ash/keyboard_controller_browsertest.cc b/chrome/browser/ui/ash/keyboard_controller_browsertest.cc
index 297f7a3..49814059c 100644
--- a/chrome/browser/ui/ash/keyboard_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/keyboard_controller_browsertest.cc
@@ -226,7 +226,7 @@
   int screen_height = ash::Shell::GetPrimaryRootWindow()->bounds().height();
   gfx::Rect test_bounds(0, 0, 0, screen_height - ime_window_visible_height + 1);
   auto* controller = keyboard::KeyboardController::Get();
-  controller->ShowKeyboard(true);
+  controller->ShowKeyboard(false /* locked */);
   controller->NotifyKeyboardWindowLoaded();
   controller->GetKeyboardWindow()->SetBounds(test_bounds);
 
diff --git a/chrome/browser/ui/ash/login_screen_client.cc b/chrome/browser/ui/ash/login_screen_client.cc
index 036848b..f1a3f9c 100644
--- a/chrome/browser/ui/ash/login_screen_client.cc
+++ b/chrome/browser/ui/ash/login_screen_client.cc
@@ -166,6 +166,10 @@
   chromeos::LoginDisplayHost::default_host()->ShowResetScreen();
 }
 
+void LoginScreenClient::ShowAccountAccessHelpApp() {
+  chromeos::LoginDisplayHost::default_host()->ShowAccountAccessHelpApp();
+}
+
 void LoginScreenClient::LoadWallpaper(const AccountId& account_id) {
   WallpaperControllerClient::Get()->ShowUserWallpaper(account_id);
 }
diff --git a/chrome/browser/ui/ash/login_screen_client.h b/chrome/browser/ui/ash/login_screen_client.h
index 160c5ff..0525a29 100644
--- a/chrome/browser/ui/ash/login_screen_client.h
+++ b/chrome/browser/ui/ash/login_screen_client.h
@@ -86,6 +86,7 @@
   void LaunchKioskApp(const std::string& app_id) override;
   void LaunchArcKioskApp(const AccountId& account_id) override;
   void ShowResetScreen() override;
+  void ShowAccountAccessHelpApp() override;
 
  private:
   void SetPublicSessionKeyboardLayout(
diff --git a/chrome/browser/ui/cocoa/app_menu/app_menu_controller_unittest.mm b/chrome/browser/ui/cocoa/app_menu/app_menu_controller_unittest.mm
index 37a6efb..6c90f72 100644
--- a/chrome/browser/ui/cocoa/app_menu/app_menu_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/app_menu/app_menu_controller_unittest.mm
@@ -161,6 +161,10 @@
   void EnableSync() {
     EXPECT_CALL(*mock_sync_service_, GetState())
         .WillRepeatedly(Return(syncer::SyncService::State::ACTIVE));
+    EXPECT_CALL(*mock_sync_service_, GetDisableReasons())
+        .WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
+    EXPECT_CALL(*mock_sync_service_, IsFirstSetupComplete())
+        .WillRepeatedly(Return(true));
     EXPECT_CALL(*mock_sync_service_,
                 IsDataTypeControllerRunning(syncer::SESSIONS))
         .WillRepeatedly(Return(true));
diff --git a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
index 00b2ca6a..5f95dee3 100644
--- a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
+++ b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.cc
@@ -34,7 +34,7 @@
       base::WrapUnique(new AlternateNavInfoBarDelegate(
           Profile::FromBrowserContext(web_contents->GetBrowserContext()), text,
           std::make_unique<AutocompleteMatch>(match), match.destination_url,
-          search_url))));
+          search_url, base::OnceClosure()))));
 }
 
 // static
@@ -42,13 +42,15 @@
     content::WebContents* web_contents,
     const base::string16& text,
     const GURL& destination_url,
-    const GURL& original_url) {
+    const GURL& original_url,
+    base::OnceClosure link_clicked_callback) {
   InfoBarService* infobar_service =
       InfoBarService::FromWebContents(web_contents);
   infobar_service->AddInfoBar(AlternateNavInfoBarDelegate::CreateInfoBar(
       base::WrapUnique(new AlternateNavInfoBarDelegate(
           Profile::FromBrowserContext(web_contents->GetBrowserContext()), text,
-          nullptr, destination_url, original_url))));
+          nullptr, destination_url, original_url,
+          std::move(link_clicked_callback)))));
 }
 
 AlternateNavInfoBarDelegate::AlternateNavInfoBarDelegate(
@@ -56,13 +58,15 @@
     const base::string16& text,
     std::unique_ptr<AutocompleteMatch> match,
     const GURL& destination_url,
-    const GURL& original_url)
+    const GURL& original_url,
+    base::OnceClosure link_clicked_callback)
     : infobars::InfoBarDelegate(),
       profile_(profile),
       text_(text),
       match_(std::move(match)),
       destination_url_(destination_url),
-      original_url_(original_url) {
+      original_url_(original_url),
+      link_clicked_callback_(std::move(link_clicked_callback)) {
   if (match_)
     DCHECK_EQ(destination_url_, match_->destination_url);
 
@@ -114,6 +118,8 @@
       history_service->DeleteURL(original_url_);
   }
 
+  if (!link_clicked_callback_.is_null())
+    std::move(link_clicked_callback_).Run();
   // Pretend the user typed this URL, so that navigating to it will be the
   // default action when it's typed again in the future.
   InfoBarService::WebContentsFromInfoBar(infobar())->OpenURL(
diff --git a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h
index fc405d9..b070598 100644
--- a/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h
+++ b/chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h
@@ -9,6 +9,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/infobars/core/infobar_delegate.h"
@@ -39,7 +40,8 @@
   static void CreateForIDNNavigation(content::WebContents* web_contents,
                                      const base::string16& text,
                                      const GURL& suggested_url,
-                                     const GURL& original_url);
+                                     const GURL& original_url,
+                                     base::OnceClosure link_clicked_callback);
   base::string16 GetMessageTextWithOffset(size_t* link_offset) const;
   base::string16 GetLinkText() const;
   GURL GetLinkURL() const;
@@ -50,7 +52,8 @@
                               const base::string16& text,
                               std::unique_ptr<AutocompleteMatch> match,
                               const GURL& destination_url,
-                              const GURL& original_url);
+                              const GURL& original_url,
+                              base::OnceClosure link_clicked_callback);
 
   // Returns an alternate nav infobar that owns |delegate|.
   static std::unique_ptr<infobars::InfoBar> CreateInfoBar(
@@ -83,6 +86,8 @@
   // the URL that visually matches a top domain.
   const GURL original_url_;
 
+  base::OnceClosure link_clicked_callback_;
+
   DISALLOW_COPY_AND_ASSIGN(AlternateNavInfoBarDelegate);
 };
 
diff --git a/chrome/browser/ui/omnibox/idn_navigation_observer.cc b/chrome/browser/ui/omnibox/idn_navigation_observer.cc
index 7463315..217eecc 100644
--- a/chrome/browser/ui/omnibox/idn_navigation_observer.cc
+++ b/chrome/browser/ui/omnibox/idn_navigation_observer.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/omnibox/idn_navigation_observer.h"
 
+#include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h"
 #include "components/omnibox/browser/autocomplete_match.h"
@@ -12,6 +14,18 @@
 #include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_entry.h"
 
+namespace {
+
+void RecordEvent(IdnNavigationObserver::NavigationSuggestionEvent event) {
+  UMA_HISTOGRAM_ENUMERATION(IdnNavigationObserver::kHistogramName, event);
+}
+
+}  // namespace
+
+// static
+const char IdnNavigationObserver::kHistogramName[] =
+    "NavigationSuggestion.Event";
+
 IdnNavigationObserver::IdnNavigationObserver(content::WebContents* web_contents)
     : WebContentsObserver(web_contents) {}
 
@@ -32,9 +46,12 @@
   replace_host.SetHostStr(result.matching_top_domain);
   const GURL suggested_url = url.ReplaceComponents(replace_host);
 
+  RecordEvent(NavigationSuggestionEvent::kInfobarShown);
+
   AlternateNavInfoBarDelegate::CreateForIDNNavigation(
       web_contents(), base::UTF8ToUTF16(result.matching_top_domain),
-      suggested_url, load_details.entry->GetVirtualURL());
+      suggested_url, load_details.entry->GetVirtualURL(),
+      base::BindOnce(RecordEvent, NavigationSuggestionEvent::kLinkClicked));
 }
 
 // static
diff --git a/chrome/browser/ui/omnibox/idn_navigation_observer.h b/chrome/browser/ui/omnibox/idn_navigation_observer.h
index 6e14ce0..a40ecd1 100644
--- a/chrome/browser/ui/omnibox/idn_navigation_observer.h
+++ b/chrome/browser/ui/omnibox/idn_navigation_observer.h
@@ -14,6 +14,19 @@
     : public content::WebContentsObserver,
       public content::WebContentsUserData<IdnNavigationObserver> {
  public:
+  // Used for metrics.
+  enum class NavigationSuggestionEvent {
+    kNone = 0,
+    kInfobarShown = 1,
+    kLinkClicked = 2,
+
+    // Append new items to the end of the list above; do not modify or
+    // replace existing values. Comment out obsolete items.
+    kMaxValue = kLinkClicked,
+  };
+
+  static const char kHistogramName[];
+
   static void CreateForWebContents(content::WebContents* web_contents);
 
   explicit IdnNavigationObserver(content::WebContents* web_contents);
diff --git a/chrome/browser/ui/omnibox/idn_navigation_observer_browsertest.cc b/chrome/browser/ui/omnibox/idn_navigation_observer_browsertest.cc
index 9193f61..4d355678 100644
--- a/chrome/browser/ui/omnibox/idn_navigation_observer_browsertest.cc
+++ b/chrome/browser/ui/omnibox/idn_navigation_observer_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/history/history_test_utils.h"
 #include "chrome/browser/infobars/infobar_observer.h"
@@ -9,6 +10,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/omnibox/alternate_nav_infobar_delegate.h"
+#include "chrome/browser/ui/omnibox/idn_navigation_observer.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -77,6 +79,8 @@
   if (!GetParam())
     return;
 
+  base::HistogramTester histograms;
+
   history::HistoryService* const history_service =
       HistoryServiceFactory::GetForProfile(browser()->profile(),
                                            ServiceAccessType::EXPLICIT_ACCESS);
@@ -116,6 +120,14 @@
   // history.
   ui_test_utils::HistoryEnumerator enumerator(browser()->profile());
   EXPECT_FALSE(base::ContainsValue(enumerator.urls(), kIdnUrl));
+
+  histograms.ExpectTotalCount(IdnNavigationObserver::kHistogramName, 2);
+  histograms.ExpectBucketCount(
+      IdnNavigationObserver::kHistogramName,
+      IdnNavigationObserver::NavigationSuggestionEvent::kInfobarShown, 1);
+  histograms.ExpectBucketCount(
+      IdnNavigationObserver::kHistogramName,
+      IdnNavigationObserver::NavigationSuggestionEvent::kLinkClicked, 1);
 }
 
 // The infobar shouldn't be shown when the feature is disabled.
diff --git a/chrome/browser/ui/sync/one_click_signin_sync_observer_unittest.cc b/chrome/browser/ui/sync/one_click_signin_sync_observer_unittest.cc
index 91711e9..088103e 100644
--- a/chrome/browser/ui/sync/one_click_signin_sync_observer_unittest.cc
+++ b/chrome/browser/ui/sync/one_click_signin_sync_observer_unittest.cc
@@ -67,6 +67,8 @@
 
   bool IsSetupInProgress() const override { return setup_in_progress_; }
 
+  int GetDisableReasons() const override { return DISABLE_REASON_NONE; }
+
   State GetState() const override { return state_; }
 
   void set_first_setup_complete(bool complete) {
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
index a781a13f..2b22dfef 100644
--- a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model_unittest.cc
@@ -215,6 +215,8 @@
         .WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
     EXPECT_CALL(*mock_sync_service_, GetState())
         .WillRepeatedly(Return(syncer::SyncService::State::ACTIVE));
+    EXPECT_CALL(*mock_sync_service_, IsFirstSetupComplete())
+        .WillRepeatedly(Return(true));
     EXPECT_CALL(*mock_sync_service_,
                 IsDataTypeControllerRunning(syncer::SESSIONS))
         .WillRepeatedly(Return(true));
diff --git a/chrome/browser/ui/view_ids.h b/chrome/browser/ui/view_ids.h
index ae98cca7..823099e 100644
--- a/chrome/browser/ui/view_ids.h
+++ b/chrome/browser/ui/view_ids.h
@@ -24,6 +24,7 @@
   VIEW_ID_WINDOW_TITLE,
   VIEW_ID_PROFILE_INDICATOR_ICON,
   VIEW_ID_AVATAR_BUTTON,
+  VIEW_ID_HOSTED_APP_BUTTON_CONTAINER,
 
   // Tabs within a window/tab strip, counting from the left.
   VIEW_ID_TAB_0,
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
index 703eaf3..12a20b0f 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.cc
@@ -62,7 +62,7 @@
   // Set up a custom WM_CLASS for app windows. This allows task switchers in
   // X11 environments to distinguish them from main browser windows.
   init_params->wm_class_name =
-      shell_integration::GetWMClassFromAppName(app_name);
+      shell_integration_linux::GetWMClassFromAppName(app_name);
   init_params->wm_class_class = shell_integration_linux::GetProgramClassClass();
   const char kX11WindowRoleApp[] = "app";
   init_params->wm_role_name = std::string(kX11WindowRoleApp);
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
index d387496..238b41c 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.cc
@@ -11,23 +11,25 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/bookmarks/browser/bookmark_node.h"
 
-std::unique_ptr<ShowPromoDelegate> ShowPromoDelegate::CreatePromoDelegate() {
-  return std::make_unique<BookmarkBarPromoBubbleView>();
+std::unique_ptr<ShowPromoDelegate> ShowPromoDelegate::CreatePromoDelegate(
+    int string_specifier) {
+  return std::make_unique<BookmarkBarPromoBubbleView>(string_specifier);
 }
 
 struct BookmarkBarPromoBubbleView::BubbleImpl : public FeaturePromoBubbleView {
   // Anchors the BookmarkBarPromoBubbleView to |anchor_view|.
   // The bubble widget and promo are owned by their native widget.
-  explicit BubbleImpl(views::View* anchor_view)
+  explicit BubbleImpl(views::View* anchor_view, int string_specifier)
       : FeaturePromoBubbleView(anchor_view,
                                views::BubbleBorder::TOP_LEFT,
-                               IDS_BOOKMARK_BAR_PROMO_DEFAULT,
+                               string_specifier,
                                ActivationAction::DO_NOT_ACTIVATE) {}
 
   ~BubbleImpl() override = default;
 };
 
-BookmarkBarPromoBubbleView::BookmarkBarPromoBubbleView() = default;
+BookmarkBarPromoBubbleView::BookmarkBarPromoBubbleView(int string_specifier)
+    : string_specifier(string_specifier) {}
 
 void BookmarkBarPromoBubbleView::ShowForNode(
     const bookmarks::BookmarkNode* node) {
@@ -36,5 +38,5 @@
           BrowserList::GetInstance()->GetLastActive())
           ->bookmark_bar()
           ->GetBookmarkButtonForNode(node);
-  new BookmarkBarPromoBubbleView::BubbleImpl(anchor_view);
+  new BookmarkBarPromoBubbleView::BubbleImpl(anchor_view, string_specifier);
 }
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
index 38a0e7fa..d8d32e0 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_bubble_view.h
@@ -21,7 +21,7 @@
 // relevant bookmark node.
 class BookmarkBarPromoBubbleView : public ShowPromoDelegate {
  public:
-  BookmarkBarPromoBubbleView();
+  explicit BookmarkBarPromoBubbleView(int string_specifier);
   ~BookmarkBarPromoBubbleView() override = default;
 
   // ShowPromoDelegate:
@@ -32,6 +32,9 @@
  private:
   struct BubbleImpl;
 
+  // The string that will be shown on this bubble.
+  int string_specifier;
+
   DISALLOW_COPY_AND_ASSIGN(BookmarkBarPromoBubbleView);
 };
 
diff --git a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
index d306042..4bdf3d6 100644
--- a/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/feature_promos/bookmark_bar_promo_dialog_browsertest.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "components/strings/grit/components_strings.h"
 
 class BookmarkBarPromoDialogTest : public DialogBrowserTest {
  public:
@@ -25,7 +26,8 @@
                                              "a ");
 
     // Now actually show the promo.
-    BookmarkBarPromoBubbleView bubble_view;
+    BookmarkBarPromoBubbleView bubble_view(
+        IDS_NUX_GOOGLE_APPS_DESCRIPTION_PROMO_BUBBLE);
     bubble_view.ShowForNode(model->bookmark_bar_node()->GetChild(0));
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
index aa30c75..4bbcc0a 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -151,7 +151,7 @@
                            V1BackButton);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
                            ToggleTabletModeOnMinimizedWindow);
-  FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
+  FRIEND_TEST_ALL_PREFIXES(HostedAppNonClientFrameViewAshTest,
                            ActiveStateOfButtonMatchesWidget);
   FRIEND_TEST_ALL_PREFIXES(BrowserNonClientFrameViewAshTest,
                            RestoreMinimizedBrowserUpdatesCaption);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
index 6a2595d..f6e05ef5 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc
@@ -77,6 +77,7 @@
 #include "ui/aura/test/env_test_helper.h"
 #include "ui/base/class_property.h"
 #include "ui/base/hit_test.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/events/base_event_utils.h"
@@ -478,19 +479,6 @@
                test.size_button()->icon_definition_for_test()->name);
 }
 
-// Regression test for https://crbug.com/839955
-IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
-                       ActiveStateOfButtonMatchesWidget) {
-  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
-
-  ash::FrameCaptionButtonContainerView::TestApi test(
-      GetFrameViewAsh(browser_view)->caption_button_container_);
-  EXPECT_TRUE(test.size_button()->paint_as_active());
-
-  browser_view->GetWidget()->Deactivate();
-  EXPECT_FALSE(test.size_button()->paint_as_active());
-}
-
 // This is a regression test that session restore minimized browser should
 // update caption buttons (https://crbug.com/827444).
 IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewAshTest,
@@ -807,14 +795,19 @@
     app_menu_button_ = hosted_app_button_container_->app_menu_button_;
   }
 
-  AppMenu* GetAppMenu(HostedAppButtonContainer* button_container) {
-    return button_container->app_menu_button_->app_menu_for_testing();
+  AppMenu* GetAppMenu() {
+    return hosted_app_button_container_->app_menu_button_
+        ->app_menu_for_testing();
   }
 
-  SkColor GetActiveColor(HostedAppButtonContainer* button_container) {
+  SkColor GetActiveColor() {
     return hosted_app_button_container_->active_color_;
   }
 
+  bool GetPaintingAsActive() {
+    return hosted_app_button_container_->paint_as_active_;
+  }
+
   PageActionIconView* GetPageActionIcon(PageActionIconType type) {
     return browser_view_->toolbar_button_provider()
         ->GetPageActionIconContainerView()
@@ -875,7 +868,7 @@
   aura::Window* window = browser_view_->GetWidget()->GetNativeWindow();
   EXPECT_EQ(GetThemeColor(),window->GetProperty(ash::kFrameActiveColorKey));
   EXPECT_EQ(GetThemeColor(), window->GetProperty(ash::kFrameInactiveColorKey));
-  EXPECT_EQ(SK_ColorWHITE, GetActiveColor(hosted_app_button_container_));
+  EXPECT_EQ(SK_ColorWHITE, GetActiveColor());
 }
 
 // Make sure that for hosted apps, the height of the frame doesn't exceed the
@@ -954,9 +947,9 @@
 // Tests that the show app menu command opens the app menu for web app windows.
 IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
                        BrowserCommandShowAppMenu) {
-  EXPECT_EQ(nullptr, GetAppMenu(hosted_app_button_container_));
+  EXPECT_EQ(nullptr, GetAppMenu());
   chrome::ExecuteCommand(app_browser_, IDC_SHOW_APP_MENU);
-  EXPECT_NE(nullptr, GetAppMenu(hosted_app_button_container_));
+  EXPECT_NE(nullptr, GetAppMenu());
 }
 
 // Tests that the focus next pane command focuses the app menu for web app
@@ -1018,7 +1011,7 @@
   SimulateClickOnView(app_menu_button_);
 
   // All extension actions should always be showing in the menu.
-  EXPECT_EQ(3u, GetAppMenu(hosted_app_button_container_)
+  EXPECT_EQ(3u, GetAppMenu()
                     ->extension_toolbar_for_testing()
                     ->container_for_testing()
                     ->VisibleBrowserActions());
@@ -1029,6 +1022,27 @@
   EXPECT_EQ(1u, browser_actions_container_->VisibleBrowserActions());
 }
 
+// Regression test for https://crbug.com/839955
+IN_PROC_BROWSER_TEST_P(HostedAppNonClientFrameViewAshTest,
+                       ActiveStateOfButtonMatchesWidget) {
+  // The caption button part of this test is covered for OopAsh by
+  // CustomFrameViewAshTest::ActiveStateOfButtonMatchesWidget.
+  if (features::IsAshInBrowserProcess()) {
+    ash::FrameCaptionButtonContainerView::TestApi test(
+        GetFrameViewAsh(browser_view_)->caption_button_container_);
+    EXPECT_TRUE(test.size_button()->paint_as_active());
+  }
+  EXPECT_TRUE(GetPaintingAsActive());
+
+  browser_view_->GetWidget()->Deactivate();
+  if (features::IsAshInBrowserProcess()) {
+    ash::FrameCaptionButtonContainerView::TestApi test(
+        GetFrameViewAsh(browser_view_)->caption_button_container_);
+    EXPECT_FALSE(test.size_button()->paint_as_active());
+  }
+  EXPECT_FALSE(GetPaintingAsActive());
+}
+
 namespace {
 
 class BrowserNonClientFrameViewAshBackButtonTest
diff --git a/chrome/browser/ui/views/frame/desktop_browser_frame_aurax11.cc b/chrome/browser/ui/views/frame/desktop_browser_frame_aurax11.cc
index e0332c8a..061fef6 100644
--- a/chrome/browser/ui/views/frame/desktop_browser_frame_aurax11.cc
+++ b/chrome/browser/ui/views/frame/desktop_browser_frame_aurax11.cc
@@ -36,7 +36,7 @@
   const Browser& browser = *browser_view()->browser();
   params.wm_class_name =
       browser.is_app() && !browser.is_devtools()
-          ? shell_integration::GetWMClassFromAppName(browser.app_name())
+          ? shell_integration_linux::GetWMClassFromAppName(browser.app_name())
           // This window is a hosted app or v1 packaged app.
           // NOTE: v2 packaged app windows are created by
           // ChromeNativeAppWindowViews.
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
index 16ddfec..8976a72 100644
--- a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
@@ -754,9 +754,10 @@
   canvas->DrawRect(titlebar_rect, flags);
   const gfx::ImageSkia frame_image = GetFrameImage();
   if (!frame_image.isNull()) {
-    canvas->TileImageInt(frame_image, 0, 0, titlebar_rect.x(),
-                         titlebar_rect.y(), titlebar_rect.width(),
-                         titlebar_rect.height(), scale);
+    canvas->TileImageInt(
+        frame_image, 0, 0, titlebar_rect.x(), titlebar_rect.y(),
+        titlebar_rect.width(), titlebar_rect.height(), scale,
+        SkShader::kRepeat_TileMode, SkShader::kMirror_TileMode);
   }
   const gfx::ImageSkia frame_overlay_image = GetFrameOverlayImage();
   if (!frame_overlay_image.isNull()) {
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.cc b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
index c1d621bf6..0974199 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
@@ -70,6 +70,9 @@
 
 }  // namespace
 
+const char HostedAppButtonContainer::kViewClassName[] =
+    "HostedAppButtonContainer";
+
 const base::TimeDelta HostedAppButtonContainer::kTitlebarAnimationDelay =
     base::TimeDelta::FromMilliseconds(750);
 
@@ -331,7 +334,7 @@
 }
 
 const char* HostedAppButtonContainer::GetClassName() const {
-  return "HostedAppButtonContainer";
+  return kViewClassName;
 }
 
 views::MenuButton* HostedAppButtonContainer::GetOverflowReferenceView() {
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.h b/chrome/browser/ui/views/frame/hosted_app_button_container.h
index d7268438..c970d2a 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.h
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.h
@@ -44,6 +44,8 @@
                                  public ImmersiveModeController::Observer,
                                  public views::WidgetObserver {
  public:
+  static const char kViewClassName[];
+
   // |active_color| and |inactive_color| indicate the colors to use
   // for button icons when the window is focused and blurred respectively.
   HostedAppButtonContainer(views::Widget* widget,
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 52a8575..03451378 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -9,9 +9,11 @@
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
+#include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h"
 #include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
@@ -29,6 +31,7 @@
 #include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/theme_provider.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/color_utils.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/image/image.h"
@@ -51,6 +54,12 @@
 
 using content::WebContents;
 
+namespace {
+
+constexpr SkColor kTitleBarFeatureColor = SK_ColorWHITE;
+
+}  // namespace
+
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameView, public:
 
@@ -70,6 +79,11 @@
   layout_->set_delegate(this);
   SetLayoutManager(std::unique_ptr<views::LayoutManager>(layout_));
 
+  // This must be initialised before the call to GetFrameColor().
+  platform_observer_.reset(OpaqueBrowserFrameViewPlatformSpecific::Create(
+      this, layout_,
+      ThemeServiceFactory::GetForProfile(browser_view->browser()->profile())));
+
   minimize_button_ = InitWindowCaptionButton(IDR_MINIMIZE,
                                              IDR_MINIMIZE_H,
                                              IDR_MINIMIZE_P,
@@ -106,15 +120,23 @@
 
   window_title_ = new views::Label(browser_view->GetWindowTitle());
   window_title_->SetVisible(browser_view->ShouldShowWindowTitle());
-  window_title_->SetEnabledColor(SK_ColorWHITE);
+  window_title_->SetEnabledColor(kTitleBarFeatureColor);
   window_title_->SetSubpixelRenderingEnabled(false);
   window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   window_title_->set_id(VIEW_ID_WINDOW_TITLE);
   AddChildView(window_title_);
 
-  platform_observer_.reset(OpaqueBrowserFrameViewPlatformSpecific::Create(
-      this, layout_,
-      ThemeServiceFactory::GetForProfile(browser_view->browser()->profile())));
+  if (extensions::HostedAppBrowserController::IsForExperimentalHostedAppBrowser(
+          browser_view->browser())) {
+    hosted_app_button_container_ = new HostedAppButtonContainer(
+        frame, browser_view,
+        color_utils::GetReadableColor(kTitleBarFeatureColor,
+                                      GetFrameColor(true)),
+        color_utils::GetReadableColor(kTitleBarFeatureColor,
+                                      GetFrameColor(false)));
+    hosted_app_button_container_->set_id(VIEW_ID_HOSTED_APP_BUTTON_CONTAINER);
+    AddChildView(hosted_app_button_container_);
+  }
 }
 
 OpaqueBrowserFrameView::~OpaqueBrowserFrameView() {}
@@ -276,6 +298,8 @@
   minimize_button_->SetState(views::Button::STATE_NORMAL);
   maximize_button_->SetState(views::Button::STATE_NORMAL);
   // The close button isn't affected by this constraint.
+  if (hosted_app_button_container_)
+    hosted_app_button_container_->UpdateContentSettingViewsVisibility();
 }
 
 void OpaqueBrowserFrameView::UpdateWindowIcon() {
@@ -294,12 +318,20 @@
 
 void OpaqueBrowserFrameView::ActivationChanged(bool active) {
   BrowserNonClientFrameView::ActivationChanged(active);
+  if (hosted_app_button_container_)
+    hosted_app_button_container_->SetPaintAsActive(active);
   MaybeRedrawFrameButtons();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameView, views::View overrides:
 
+void OpaqueBrowserFrameView::ChildPreferredSizeChanged(views::View* child) {
+  BrowserNonClientFrameView::ChildPreferredSizeChanged(child);
+  if (browser_view()->initialized() && child == hosted_app_button_container_)
+    Layout();
+}
+
 void OpaqueBrowserFrameView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   node_data->role = ax::mojom::Role::kTitleBar;
 }
@@ -470,7 +502,9 @@
   if (frame()->IsFullscreen())
     return;  // Nothing is visible, so don't bother to paint.
 
-  frame_background_->set_frame_color(GetFrameColor());
+  SkColor frame_color = GetFrameColor();
+  window_title_->SetBackgroundColor(frame_color);
+  frame_background_->set_frame_color(frame_color);
   frame_background_->set_use_custom_frame(frame()->UseCustomFrame());
   frame_background_->set_is_active(ShouldPaintAsActive());
   frame_background_->set_incognito(browser_view()->IsIncognito());
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
index 4c7c92dd..959a53cf 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
@@ -21,6 +21,7 @@
 
 class BrowserView;
 class OpaqueBrowserFrameViewLayout;
+class HostedAppButtonContainer;
 class OpaqueBrowserFrameViewPlatformSpecific;
 class TabIconView;
 
@@ -71,6 +72,7 @@
   bool HasClientEdge() const override;
 
   // views::View:
+  void ChildPreferredSizeChanged(views::View* child) override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   void OnNativeThemeChanged(const ui::NativeTheme* native_theme) override;
 
@@ -187,6 +189,8 @@
   TabIconView* window_icon_;
   views::Label* window_title_;
 
+  HostedAppButtonContainer* hosted_app_button_container_ = nullptr;
+
   // Background painter for the window frame.
   std::unique_ptr<views::FrameBackground> frame_background_;
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
index 3a3fe5fc..4acb7a4 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc
@@ -8,6 +8,7 @@
 #include "base/containers/adapters.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/ui/views/frame/hosted_app_button_container.h"
 #include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/common/chrome_switches.h"
 #include "components/signin/core/browser/profile_management_switches.h"
@@ -171,13 +172,22 @@
   if (!delegate_->ShouldShowWindowTitle())
     return FrameTopBorderThickness(restored);
 
-  // The + 2 here puts at least 1 px of space on top and bottom of the icon.
-  const int icon_height =
-      TitlebarTopThickness(restored) + delegate_->GetIconSize() + 2;
+  // Adding 2px of vertical padding puts at least 1 px of space on the top and
+  // bottom of the element.
+  constexpr int kVerticalPadding = 2;
+  const int icon_height = TitlebarTopThickness(restored) +
+                          delegate_->GetIconSize() + kVerticalPadding;
   const int caption_button_height = DefaultCaptionButtonY(restored) +
                                     kCaptionButtonHeight +
                                     kCaptionButtonBottomPadding;
-  return std::max(icon_height, caption_button_height) +
+  int hosted_app_button_height = 0;
+  if (hosted_app_button_container_) {
+    hosted_app_button_height =
+        hosted_app_button_container_->GetPreferredSize().height() +
+        kVerticalPadding;
+  }
+  return std::max(std::max(icon_height, caption_button_height),
+                  hosted_app_button_height) +
          kContentEdgeShadowThickness;
 }
 
@@ -375,7 +385,7 @@
   bool should_show_icon = delegate_->ShouldShowWindowIcon() && window_icon_;
   bool should_show_title = delegate_->ShouldShowWindowTitle() && window_title_;
 
-  if (should_show_icon || should_show_title) {
+  if (should_show_icon || should_show_title || hosted_app_button_container_) {
     use_hidden_icon_location = false;
 
     // Our frame border has a different "3D look" than Windows'.  Theirs has
@@ -391,15 +401,22 @@
     // caption button, we vertically center it.  We want to bias rounding to
     // put extra space below the icon, since we'll use the same Y coordinate for
     // the title, and the majority of the font weight is below the centerline.
+    const int available_height = NonClientTopHeight(false);
     const int icon_height =
         unavailable_px_at_top + size + kContentEdgeShadowThickness;
-    const int y =
-        unavailable_px_at_top + (NonClientTopHeight(false) - icon_height) / 2;
+    const int y = unavailable_px_at_top + (available_height - icon_height) / 2;
 
     window_icon_bounds_ =
         gfx::Rect(available_space_leading_x_ + kIconLeftSpacing, y, size, size);
     available_space_leading_x_ += size + kIconLeftSpacing;
     minimum_size_for_buttons_ += size + kIconLeftSpacing;
+
+    if (hosted_app_button_container_) {
+      available_space_trailing_x_ =
+          hosted_app_button_container_->LayoutInContainer(
+              available_space_leading_x_, available_space_trailing_x_,
+              available_height);
+    }
   }
 
   if (should_show_icon)
@@ -650,8 +667,13 @@
     case VIEW_ID_AVATAR_BUTTON:
       new_avatar_button_ = view;
       break;
+    case VIEW_ID_HOSTED_APP_BUTTON_CONTAINER:
+      DCHECK_EQ(view->GetClassName(), HostedAppButtonContainer::kViewClassName);
+      hosted_app_button_container_ =
+          static_cast<HostedAppButtonContainer*>(view);
+      break;
     default:
-      NOTIMPLEMENTED() << "Unknown view id " << id;
+      NOTREACHED() << "Unknown view id " << id;
       break;
   }
 }
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
index e17abfb..9e89c65 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h
@@ -11,6 +11,7 @@
 #include "ui/views/layout/layout_manager.h"
 #include "ui/views/window/frame_buttons.h"
 
+class HostedAppButtonContainer;
 class OpaqueBrowserFrameViewLayoutDelegate;
 
 namespace views {
@@ -238,6 +239,8 @@
   views::View* window_icon_;
   views::Label* window_title_;
 
+  HostedAppButtonContainer* hosted_app_button_container_ = nullptr;
+
   views::View* incognito_icon_;
 
   std::vector<views::FrameButton> leading_buttons_;
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 3240412..218e234a 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -434,13 +434,23 @@
   if (image_->GetPreferredSize().IsEmpty())
     return 0;
 
-  // In touch, the icon-to-label spacing is a custom value.
-  constexpr int kIconLabelSpacingTouch = 4;
-  const int default_spacing =
-      ui::MaterialDesignController::IsTouchOptimizedUiEnabled()
-          ? kIconLabelSpacingTouch
-          : GetLayoutConstant(LOCATION_BAR_ELEMENT_PADDING) +
-                GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left();
+  // Touch Optimized, Refresh, and Touch Refresh all have custom spacing values.
+  int default_spacing = 0;
+  switch (ui::MaterialDesignController::GetMode()) {
+    case ui::MaterialDesignController::MATERIAL_TOUCH_OPTIMIZED:
+      default_spacing = 4;
+      break;
+    case ui::MaterialDesignController::MATERIAL_REFRESH:
+      default_spacing = 8;
+      break;
+    case ui::MaterialDesignController::MATERIAL_TOUCH_REFRESH:
+      default_spacing = 10;
+      break;
+    default:
+      default_spacing =
+          GetLayoutConstant(LOCATION_BAR_ELEMENT_PADDING) +
+          GetLayoutInsets(LOCATION_BAR_ICON_INTERIOR_PADDING).left();
+  }
 
   return default_spacing +
          (ShouldShowExtraInternalSpace() ? GetPrefixedSeparatorWidth() : 0);
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
index e45806a..7e65a74 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.h
@@ -166,8 +166,9 @@
   // Spacing between the image and the label.
   int GetInternalSpacing() const;
 
-  // Retrieves the width taken the separator including padding before the
-  // separator stroke, taking into account whether it is shown or not.
+  // Returns the width taken by the separator stroke and the before-padding.
+  // If the separator is not shown, and ShouldShowExtraEndSpace() is false,
+  // this returns 0.
   int GetPrefixedSeparatorWidth() const;
 
   // Padding after the separator.
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
index c30bd9f..bf0358a 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
@@ -476,6 +476,11 @@
   SetMouseHandler(nullptr);
 }
 
+void OmniboxPopupContentsView::ProvideButtonFocusHint(size_t line) {
+  OmniboxResultView* result = result_view_at(line);
+  result->ProvideButtonFocusHint();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // OmniboxPopupContentsView, views::View overrides:
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
index 46d484b..619eded 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
@@ -58,6 +58,9 @@
   // Called by the active result view to inform model (due to mouse event).
   void UnselectButton();
 
+  // Called to inform result view of button focus.
+  void ProvideButtonFocusHint(size_t line);
+
   // OmniboxPopupView:
   bool IsOpen() const override;
   void InvalidateLine(size_t line) override;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index d34aa209..1102576 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -366,6 +366,10 @@
   SchedulePaint();
 }
 
+void OmniboxResultView::ProvideButtonFocusHint() {
+  suggestion_tab_switch_button_->ProvideFocusHint();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // OmniboxResultView, private:
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.h b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
index 7ba0fc9..e7529a6 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
@@ -70,6 +70,9 @@
   // Called when tab switch button pressed, due to being a listener.
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
+  // Called to indicate tab switch button has been focused.
+  void ProvideButtonFocusHint();
+
   // views::View:
   void Layout() override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.cc b/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.cc
index d5ee964..9d937c5 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.cc
@@ -143,6 +143,10 @@
   }
 }
 
+void OmniboxTabSwitchButton::ProvideFocusHint() {
+  NotifyAccessibilityEvent(ax::mojom::Event::kHover, true);
+}
+
 bool OmniboxTabSwitchButton::IsSelected() const {
   return model_->IsButtonSelected();
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.h b/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.h
index 35214de6..4228ef7 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_tab_switch_button.h
@@ -40,6 +40,9 @@
   // so the button can adjust its size or even presence.
   void ProvideWidthHint(size_t width);
 
+  // Called to indicate button has been focused.
+  void ProvideFocusHint();
+
  private:
   // Consults the parent views to see if the button is selected.
   bool IsSelected() const;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 89b0c572..882c3f77 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -482,6 +482,8 @@
           OmniboxPopupModel::NORMAL &&
       !event.IsShiftDown()) {
     model()->popup_model()->SetSelectedLineState(OmniboxPopupModel::TAB_SWITCH);
+    popup_view_->ProvideButtonFocusHint(
+        model()->popup_model()->selected_line());
     return true;
   }
 
@@ -506,6 +508,8 @@
   if (event.IsShiftDown() &&
       model()->popup_model()->SelectedLineHasTabMatch()) {
     model()->popup_model()->SetSelectedLineState(OmniboxPopupModel::TAB_SWITCH);
+    popup_view_->ProvideButtonFocusHint(
+        model()->popup_model()->selected_line());
   }
 
   return true;
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.cc b/chrome/browser/ui/views/overlay/overlay_window_views.cc
index 080b08e0..4527318b 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.cc
@@ -40,6 +40,7 @@
 const float kPlayPauseControlRatioToWindow = 0.3;
 
 const int kCloseButtonMargin = 8;
+const int kCloseButtonSize = 24;
 
 const int kMinPlayPauseButtonSize = 48;
 
@@ -129,7 +130,6 @@
 OverlayWindowViews::OverlayWindowViews(
     content::PictureInPictureWindowController* controller)
     : controller_(controller),
-      close_button_size_(gfx::Size()),
       play_pause_button_size_(gfx::Size()),
       window_background_view_(new views::View()),
       video_view_(new views::View()),
@@ -224,7 +224,16 @@
                                           views::ImageButton::ALIGN_MIDDLE);
   close_controls_view_->SetBackgroundImageAlignment(
       views::ImageButton::ALIGN_LEFT, views::ImageButton::ALIGN_TOP);
-  UpdateCloseControlsSize();
+  close_controls_view_->SetSize(gfx::Size(kCloseButtonSize, kCloseButtonSize));
+  close_controls_view_->SetImage(
+      views::Button::STATE_NORMAL,
+      gfx::CreateVectorIcon(views::kIcCloseIcon,
+                            std::round(kCloseButtonSize * 2.0 / 3.0),
+                            kControlIconColor));
+  const gfx::ImageSkia close_background = gfx::CreateVectorIcon(
+      kPictureInPictureControlBackgroundIcon, kCloseButtonSize, kBgColor);
+  close_controls_view_->SetBackgroundImage(kBgColor, &close_background,
+                                           &close_background);
 
   // Accessibility.
   close_controls_view_->SetFocusForPlatform();  // Make button focusable.
@@ -311,11 +320,9 @@
   controls_background_view_->SetBoundsRect(
       gfx::Rect(gfx::Point(0, 0), GetBounds().size()));
 
-  close_controls_view_->SetBoundsRect(
-      gfx::Rect(gfx::Point(GetBounds().size().width() -
-                               close_button_size_.width() - kCloseButtonMargin,
-                           kCloseButtonMargin),
-                close_button_size_));
+  close_controls_view_->SetPosition(gfx::Point(
+      GetBounds().size().width() - kCloseButtonSize - kCloseButtonMargin,
+      kCloseButtonMargin));
 
   play_pause_controls_view_->SetBoundsRect(gfx::Rect(
       gfx::Point(
@@ -324,33 +331,6 @@
       play_pause_button_size_));
 }
 
-void OverlayWindowViews::UpdateCloseControlsSize() {
-  const gfx::Size window_size = GetBounds().size();
-
-  // |close_button_size_| can only be three sizes, dependent on the width of
-  // |this|.
-  int new_close_button_dimension = 24;
-  if (window_size.width() > 640 && window_size.width() <= 1440) {
-    new_close_button_dimension = 48;
-  } else if (window_size.width() > 1440) {
-    new_close_button_dimension = 72;
-  }
-
-  close_button_size_.SetSize(new_close_button_dimension,
-                             new_close_button_dimension);
-  close_controls_view_->SetSize(close_button_size_);
-  close_controls_view_->SetImage(
-      views::Button::STATE_NORMAL,
-      gfx::CreateVectorIcon(views::kIcCloseIcon,
-                            std::round(close_button_size_.width() * 2.0 / 3.0),
-                            kControlIconColor));
-  const gfx::ImageSkia close_background =
-      gfx::CreateVectorIcon(kPictureInPictureControlBackgroundIcon,
-                            close_button_size_.width(), kBgColor);
-  close_controls_view_->SetBackgroundImage(kBgColor, &close_background,
-                                           &close_background);
-}
-
 void OverlayWindowViews::UpdatePlayPauseControlsSize() {
   const gfx::Size window_size = GetBounds().size();
 
@@ -505,8 +485,10 @@
     // Only show the media controls when the mouse is hovering over the window.
     // This is checking for both ENTERED and MOVED because ENTERED is not fired
     // after a resize on Windows.
-    case ui::ET_MOUSE_ENTERED:
+#if defined (OS_WIN)
     case ui::ET_MOUSE_MOVED:
+#endif  // OS_WIN
+    case ui::ET_MOUSE_ENTERED:
       UpdateControlsVisibility(true);
       break;
 
@@ -587,7 +569,6 @@
 
 void OverlayWindowViews::OnNativeWidgetSizeChanged(const gfx::Size& new_size) {
   // Update the view layers to scale to |new_size|.
-  UpdateCloseControlsSize();
   UpdatePlayPauseControlsSize();
   UpdateVideoLayerSizeWithAspectRatio(new_size);
 
diff --git a/chrome/browser/ui/views/overlay/overlay_window_views.h b/chrome/browser/ui/views/overlay/overlay_window_views.h
index 4a7918f..a7896bd5 100644
--- a/chrome/browser/ui/views/overlay/overlay_window_views.h
+++ b/chrome/browser/ui/views/overlay/overlay_window_views.h
@@ -92,11 +92,6 @@
   // Updates the bounds of the controls.
   void UpdateControlsBounds();
 
-  // Update the size of |close_controls_view_| as the size of the window
-  // changes. This will scale to one of three sizes, based off the current width
-  // of the window.
-  void UpdateCloseControlsSize();
-
   // Update the size of |play_pause_controls_view_| as the size of the window
   // changes.
   void UpdatePlayPauseControlsSize();
@@ -125,8 +120,7 @@
   gfx::Size min_size_;
   gfx::Size max_size_;
 
-  // Current sizes of |close_controls_view_| and |play_pause_controls_view_|.
-  gfx::Size close_button_size_;
+  // Current size of |play_pause_controls_view_|.
   gfx::Size play_pause_button_size_;
 
   // Current bounds of the Picture-in-Picture window.
diff --git a/chrome/browser/ui/webui/browsing_history_handler_unittest.cc b/chrome/browser/ui/webui/browsing_history_handler_unittest.cc
index d789c14..94a73fcdb 100644
--- a/chrome/browser/ui/webui/browsing_history_handler_unittest.cc
+++ b/chrome/browser/ui/webui/browsing_history_handler_unittest.cc
@@ -65,6 +65,10 @@
 
   State GetState() const override { return state_; }
 
+  int GetDisableReasons() const override { return DISABLE_REASON_NONE; }
+
+  bool IsFirstSetupComplete() const override { return true; }
+
   syncer::ModelTypeSet GetActiveDataTypes() const override {
     return syncer::ModelTypeSet::All();
   }
diff --git a/chrome/browser/ui/webui/interventions_internals/OWNERS b/chrome/browser/ui/webui/interventions_internals/OWNERS
index 6340a5d..ecb8556 100644
--- a/chrome/browser/ui/webui/interventions_internals/OWNERS
+++ b/chrome/browser/ui/webui/interventions_internals/OWNERS
@@ -4,4 +4,4 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 
-# COMPONENT: UI>Browser>Previews
+# COMPONENT: Blink>Previews
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
index c9bf388..a9a50cf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.cc
@@ -8,6 +8,7 @@
 #include "base/bind_helpers.h"
 #include "base/logging.h"
 #include "chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_dialog.h"
+#include "chromeos/components/proximity_auth/logging/logging.h"
 #include "content/public/browser/web_ui.h"
 
 namespace chromeos {
@@ -45,6 +46,14 @@
   return page_content_dictionary;
 }
 
+void OnRetrySetHostNowResult(bool success) {
+  if (success)
+    return;
+
+  PA_LOG(WARNING) << "OnRetrySetHostNowResult(): Attempt to retry setting the "
+                  << "host device failed.";
+}
+
 }  // namespace
 
 MultideviceHandler::MultideviceHandler(
@@ -64,6 +73,10 @@
       "getPageContentData",
       base::BindRepeating(&MultideviceHandler::HandleGetPageContent,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "retryPendingHostSetup",
+      base::BindRepeating(&MultideviceHandler::HandleRetryPendingHostSetup,
+                          base::Unretained(this)));
 }
 
 void MultideviceHandler::OnJavascriptAllowed() {
@@ -105,6 +118,13 @@
                      callback_weak_ptr_factory_.GetWeakPtr(), callback_id));
 }
 
+void MultideviceHandler::HandleRetryPendingHostSetup(
+    const base::ListValue* args) {
+  DCHECK(args->empty());
+  multidevice_setup_client_->RetrySetHostNow(
+      base::BindOnce(&OnRetrySetHostNowResult));
+}
+
 void MultideviceHandler::OnHostStatusFetched(
     const std::string& js_callback_id,
     multidevice_setup::mojom::HostStatus host_status,
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
index d87e2076..571820a5 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler.h
@@ -42,6 +42,7 @@
 
   void HandleShowMultiDeviceSetupDialog(const base::ListValue* args);
   void HandleGetPageContent(const base::ListValue* args);
+  void HandleRetryPendingHostSetup(const base::ListValue* args);
 
   void OnHostStatusFetched(
       const std::string& js_callback_id,
diff --git a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
index ee44e2d..9dd5dbf 100644
--- a/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/multidevice_handler_unittest.cc
@@ -122,6 +122,13 @@
     VerifyPageContentDict(call_data.arg2(), host_status, host_device);
   }
 
+  void CallRetryPendingHostSetup(bool success) {
+    base::ListValue empty_args;
+    test_web_ui()->HandleReceivedMessage("retryPendingHostSetup", &empty_args);
+    fake_multidevice_setup_client()->InvokePendingRetrySetHostNowCallback(
+        success);
+  }
+
   const content::TestWebUI::CallData& CallDataAtIndex(size_t index) {
     return *test_web_ui_->call_data()[index];
   }
@@ -177,6 +184,11 @@
                            test_device_);
 }
 
+TEST_F(MultideviceHandlerTest, RetryPendingHostSetup) {
+  CallRetryPendingHostSetup(true /* success */);
+  CallRetryPendingHostSetup(false /* success */);
+}
+
 }  // namespace settings
 
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index e298dd7..c1c0f0c 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -394,13 +394,17 @@
 void AddCrostiniStrings(content::WebUIDataSource* html_source) {
   LocalizedString localized_strings[] = {
       {"crostiniPageTitle", IDS_SETTINGS_CROSTINI_TITLE},
-      {"crostiniSubtext", IDS_SETTINGS_CROSTINI_SUBTEXT},
       {"crostiniPageLabel", IDS_SETTINGS_CROSTINI_LABEL},
       {"crostiniEnable", IDS_SETTINGS_TURN_ON},
       {"crostiniRemove", IDS_SETTINGS_CROSTINI_REMOVE},
   };
   AddLocalizedStringsBulk(html_source, localized_strings,
                           arraysize(localized_strings));
+  html_source->AddString(
+      "crostiniSubtext",
+      l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_CROSTINI_SUBTEXT,
+          GetHelpUrlWithBoard(chrome::kLinuxAppsLearnMoreURL)));
 }
 
 void AddAndroidAppStrings(content::WebUIDataSource* html_source) {
diff --git a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
index 871a956..86d2bdb 100644
--- a/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
+++ b/chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/browsing_data/counters/browsing_data_counter_factory.h"
 #include "chrome/browser/browsing_data/counters/browsing_data_counter_utils.h"
 #include "chrome/browser/history/web_history_service_factory.h"
-#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_features.h"
@@ -28,11 +28,11 @@
 #include "components/feature_engagement/buildflags.h"
 #include "components/prefs/pref_member.h"
 #include "components/prefs/pref_service.h"
-#include "components/signin/core/browser/signin_manager.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
+#include "services/identity/public/cpp/identity_manager.h"
 #include "ui/base/text/bytes_formatting.h"
 
 #if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
@@ -327,10 +327,11 @@
 }
 
 void ClearBrowsingDataHandler::UpdateSyncState() {
-  auto* signin_manager = SigninManagerFactory::GetForProfile(profile_);
+  identity::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(profile_);
   CallJavascriptFunction(
       "cr.webUIListenerCallback", base::Value("update-sync-state"),
-      base::Value(signin_manager && signin_manager->IsAuthenticated()),
+      base::Value(identity_manager && identity_manager->HasPrimaryAccount()),
       base::Value(sync_service_ && sync_service_->IsSyncActive() &&
                   sync_service_->GetActiveDataTypes().Has(
                       syncer::HISTORY_DELETE_DIRECTIVES)),
diff --git a/chrome/browser/ui/webui/sync_internals_browsertest.js b/chrome/browser/ui/webui/sync_internals_browsertest.js
index 273c005..48eff41 100644
--- a/chrome/browser/ui/webui/sync_internals_browsertest.js
+++ b/chrome/browser/ui/webui/sync_internals_browsertest.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+GEN('#include "components/sync/driver/sync_driver_switches.h"');
+
 /**
  * Test fixture for sync internals WebUI testing.
  * @constructor
@@ -56,6 +58,22 @@
   }
 };
 
+function SyncInternalsWebUITestWithStandaloneTransport() {}
+
+SyncInternalsWebUITestWithStandaloneTransport.prototype = {
+  __proto__: SyncInternalsWebUITest.prototype,
+
+  featureList: ['switches::kSyncStandaloneTransport', ''],
+};
+
+function SyncInternalsWebUITestWithoutStandaloneTransport() {}
+
+SyncInternalsWebUITestWithoutStandaloneTransport.prototype = {
+  __proto__: SyncInternalsWebUITest.prototype,
+
+  featureList: ['', 'switches::kSyncStandaloneTransport'],
+};
+
 /**
  * Constant hard-coded value to return from mock getAllNodes.
  * @const
@@ -241,11 +259,18 @@
 // On chromeos, browser tests are signed in by default.  On other platforms,
 // browser tests are signed out.
 GEN('#if defined(OS_CHROMEOS)');
-TEST_F('SyncInternalsWebUITest', 'SignedIn', function() {
+TEST_F('SyncInternalsWebUITestWithStandaloneTransport', 'SignedIn', function() {
   assertNotEquals(null, chrome.sync.aboutInfo);
-  expectTrue(this.hasInDetails(true, 'Summary', 'Waiting for start request'));
+  expectTrue(this.hasInDetails(true, 'Summary', 'Initializing'));
   expectTrue(this.hasInDetails(true, 'Username', 'stub-user@example.com'));
 });
+TEST_F(
+    'SyncInternalsWebUITestWithoutStandaloneTransport', 'SignedIn', function() {
+      assertNotEquals(null, chrome.sync.aboutInfo);
+      expectTrue(
+          this.hasInDetails(true, 'Summary', 'Waiting for start request'));
+      expectTrue(this.hasInDetails(true, 'Username', 'stub-user@example.com'));
+    });
 GEN('#else');
 TEST_F('SyncInternalsWebUITest', 'SignedOut', function() {
   assertNotEquals(null, chrome.sync.aboutInfo);
diff --git a/chrome/browser/unified_consent/chrome_unified_consent_service_client.cc b/chrome/browser/unified_consent/chrome_unified_consent_service_client.cc
index 0a6df09a..de23739 100644
--- a/chrome/browser/unified_consent/chrome_unified_consent_service_client.cc
+++ b/chrome/browser/unified_consent/chrome_unified_consent_service_client.cc
@@ -4,49 +4,101 @@
 
 #include "chrome/browser/unified_consent/chrome_unified_consent_service_client.h"
 
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/metrics/metrics_reporting_state.h"
 #include "chrome/browser/net/prediction_options.h"
 #include "chrome/common/pref_names.h"
+#include "components/metrics/metrics_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/spellcheck/browser/pref_names.h"
 
 ChromeUnifiedConsentServiceClient::ChromeUnifiedConsentServiceClient(
     PrefService* pref_service)
-    : pref_service_(pref_service) {}
-
-void ChromeUnifiedConsentServiceClient::SetAlternateErrorPagesEnabled(
-    bool enabled) {
-  pref_service_->SetBoolean(prefs::kAlternateErrorPagesEnabled, enabled);
+    : pref_service_(pref_service) {
+  DCHECK(pref_service_);
+  ObserveServicePrefChange(Service::kAlternateErrorPages,
+                           prefs::kAlternateErrorPagesEnabled, pref_service_);
+  ObserveServicePrefChange(Service::kMetricsReporting,
+                           metrics::prefs::kMetricsReportingEnabled,
+                           g_browser_process->local_state());
+  ObserveServicePrefChange(Service::kNetworkPrediction,
+                           prefs::kNetworkPredictionOptions, pref_service_);
+  ObserveServicePrefChange(Service::kSafeBrowsing, prefs::kSafeBrowsingEnabled,
+                           pref_service_);
+  ObserveServicePrefChange(
+      Service::kSafeBrowsingExtendedReporting,
+      safe_browsing::GetExtendedReportingPrefName(*pref_service_),
+      pref_service_);
+  ObserveServicePrefChange(Service::kSearchSuggest,
+                           prefs::kSearchSuggestEnabled, pref_service_);
+  ObserveServicePrefChange(Service::kSpellCheck,
+                           spellcheck::prefs::kSpellCheckUseSpellingService,
+                           pref_service_);
 }
 
-void ChromeUnifiedConsentServiceClient::SetMetricsReportingEnabled(
-    bool enabled) {
-  ChangeMetricsReportingState(enabled);
+ChromeUnifiedConsentServiceClient::~ChromeUnifiedConsentServiceClient() {}
+
+ChromeUnifiedConsentServiceClient::ServiceState
+ChromeUnifiedConsentServiceClient::GetServiceState(Service service) {
+  bool enabled;
+  switch (service) {
+    case Service::kAlternateErrorPages:
+      enabled = pref_service_->GetBoolean(prefs::kAlternateErrorPagesEnabled);
+      break;
+    case Service::kMetricsReporting:
+      enabled =
+          ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled();
+      break;
+    case Service::kNetworkPrediction:
+      enabled = pref_service_->GetInteger(prefs::kNetworkPredictionOptions) ==
+                chrome_browser_net::NETWORK_PREDICTION_DEFAULT;
+      break;
+    case Service::kSafeBrowsing:
+      enabled = pref_service_->GetBoolean(prefs::kSafeBrowsingEnabled);
+      break;
+    case Service::kSafeBrowsingExtendedReporting:
+      enabled = safe_browsing::IsExtendedReportingEnabled(*pref_service_);
+      break;
+    case Service::kSearchSuggest:
+      enabled = pref_service_->GetBoolean(prefs::kSearchSuggestEnabled);
+      break;
+    case Service::kSpellCheck:
+      enabled = pref_service_->GetBoolean(
+          spellcheck::prefs::kSpellCheckUseSpellingService);
+      break;
+  }
+  return enabled ? ServiceState::kEnabled : ServiceState::kDisabled;
 }
 
-void ChromeUnifiedConsentServiceClient::SetSearchSuggestEnabled(bool enabled) {
-  pref_service_->SetBoolean(prefs::kSearchSuggestEnabled, enabled);
-}
-
-void ChromeUnifiedConsentServiceClient::SetSafeBrowsingEnabled(bool enabled) {
-  pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, enabled);
-}
-
-void ChromeUnifiedConsentServiceClient::SetSafeBrowsingExtendedReportingEnabled(
-    bool enabled) {
-  safe_browsing::SetExtendedReportingPref(pref_service_, enabled);
-}
-
-void ChromeUnifiedConsentServiceClient::SetNetworkPredictionEnabled(
-    bool enabled) {
-  pref_service_->SetInteger(prefs::kNetworkPredictionOptions,
-                            enabled
-                                ? chrome_browser_net::NETWORK_PREDICTION_DEFAULT
-                                : chrome_browser_net::NETWORK_PREDICTION_NEVER);
-}
-
-void ChromeUnifiedConsentServiceClient::SetSpellCheckEnabled(bool enabled) {
-  pref_service_->SetBoolean(spellcheck::prefs::kSpellCheckUseSpellingService,
-                            enabled);
+void ChromeUnifiedConsentServiceClient::SetServiceEnabled(Service service,
+                                                          bool enabled) {
+  switch (service) {
+    case Service::kAlternateErrorPages:
+      pref_service_->SetBoolean(prefs::kAlternateErrorPagesEnabled, enabled);
+      break;
+    case Service::kMetricsReporting:
+      ChangeMetricsReportingState(enabled);
+      break;
+    case Service::kNetworkPrediction:
+      pref_service_->SetInteger(
+          prefs::kNetworkPredictionOptions,
+          enabled ? chrome_browser_net::NETWORK_PREDICTION_DEFAULT
+                  : chrome_browser_net::NETWORK_PREDICTION_NEVER);
+      break;
+    case Service::kSafeBrowsing:
+      pref_service_->SetBoolean(prefs::kSafeBrowsingEnabled, enabled);
+      break;
+    case Service::kSafeBrowsingExtendedReporting:
+      safe_browsing::SetExtendedReportingPref(pref_service_, enabled);
+      break;
+    case Service::kSearchSuggest:
+      pref_service_->SetBoolean(prefs::kSearchSuggestEnabled, enabled);
+      break;
+    case Service::kSpellCheck:
+      pref_service_->SetBoolean(
+          spellcheck::prefs::kSpellCheckUseSpellingService, enabled);
+      break;
+  }
 }
diff --git a/chrome/browser/unified_consent/chrome_unified_consent_service_client.h b/chrome/browser/unified_consent/chrome_unified_consent_service_client.h
index b2a48778..9550cf3 100644
--- a/chrome/browser/unified_consent/chrome_unified_consent_service_client.h
+++ b/chrome/browser/unified_consent/chrome_unified_consent_service_client.h
@@ -14,15 +14,11 @@
     : public unified_consent::UnifiedConsentServiceClient {
  public:
   explicit ChromeUnifiedConsentServiceClient(PrefService* pref_service);
-  ~ChromeUnifiedConsentServiceClient() override = default;
+  ~ChromeUnifiedConsentServiceClient() override;
 
-  void SetAlternateErrorPagesEnabled(bool enabled) override;
-  void SetMetricsReportingEnabled(bool enabled) override;
-  void SetSearchSuggestEnabled(bool enabled) override;
-  void SetSafeBrowsingEnabled(bool enabled) override;
-  void SetSafeBrowsingExtendedReportingEnabled(bool enabled) override;
-  void SetNetworkPredictionEnabled(bool enabled) override;
-  void SetSpellCheckEnabled(bool enabled) override;
+  // unified_consent::UnifiedConsentServiceClient:
+  ServiceState GetServiceState(Service service) override;
+  void SetServiceEnabled(Service service, bool enabled) override;
 
  private:
   PrefService* pref_service_;
diff --git a/chrome/browser/unified_consent/unified_consent_test_util.cc b/chrome/browser/unified_consent/unified_consent_test_util.cc
index cff4721..b7a9aca 100644
--- a/chrome/browser/unified_consent/unified_consent_test_util.cc
+++ b/chrome/browser/unified_consent/unified_consent_test_util.cc
@@ -14,6 +14,9 @@
 
 namespace {
 
+using Service = unified_consent::UnifiedConsentServiceClient::Service;
+using ServiceState = unified_consent::UnifiedConsentServiceClient::ServiceState;
+
 class FakeUnifiedConsentServiceClient
     : public unified_consent::UnifiedConsentServiceClient {
  public:
@@ -21,13 +24,10 @@
   ~FakeUnifiedConsentServiceClient() override = default;
 
   // UnifiedConsentServiceClient:
-  void SetAlternateErrorPagesEnabled(bool enabled) override {}
-  void SetMetricsReportingEnabled(bool enabled) override {}
-  void SetSearchSuggestEnabled(bool enabled) override {}
-  void SetSafeBrowsingEnabled(bool enabled) override {}
-  void SetSafeBrowsingExtendedReportingEnabled(bool enabled) override {}
-  void SetNetworkPredictionEnabled(bool enabled) override {}
-  void SetSpellCheckEnabled(bool enabled) override {}
+  ServiceState GetServiceState(Service service) override {
+    return ServiceState::kNotSupported;
+  }
+  void SetServiceEnabled(Service service, bool enabled) override {}
 };
 
 }  // namespace
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index 0528656..abd0d1f 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -224,6 +224,7 @@
 component("vr_common") {
   sources = [
     "browser_ui_interface.h",
+    "controller_delegate.h",
     "fps_meter.cc",
     "fps_meter.h",
     "gesture_detector.cc",
@@ -237,6 +238,8 @@
     "model/speech_recognition_model.h",
     "model/ui_mode.h",
     "model/web_vr_model.h",
+    "render_loop.cc",
+    "render_loop.h",
     "sample_queue.cc",
     "sample_queue.h",
     "service/browser_xr_device.cc",
@@ -473,6 +476,7 @@
   sources = [
     "test/animation_utils.cc",
     "test/animation_utils.h",
+    "test/constants.cc",
     "test/constants.h",
     "test/mock_browser_ui_interface.cc",
     "test/mock_browser_ui_interface.h",
diff --git a/chrome/browser/vr/DEPS b/chrome/browser/vr/DEPS
index fbf92ce..521c6ee 100644
--- a/chrome/browser/vr/DEPS
+++ b/chrome/browser/vr/DEPS
@@ -3,5 +3,5 @@
   "+cc/base",
   "+cc/test",
   "+cc/trees",
-  "+device/vr/openvr/test/fake_openvr_log.h"
+  "+device/vr/public/mojom"
 ]
diff --git a/chrome/browser/vr/animation_unittest.cc b/chrome/browser/vr/animation_unittest.cc
index cb97c2f..9794ee2 100644
--- a/chrome/browser/vr/animation_unittest.cc
+++ b/chrome/browser/vr/animation_unittest.cc
@@ -63,6 +63,13 @@
                                  cc::KeyframeModel* keyframe_model) override {
     background_color_ = color;
   }
+  void NotifyClientFilterAnimated(const cc::FilterOperations& filter,
+                                  int target_property_id,
+                                  cc::KeyframeModel* keyframe_model) override {}
+  void NotifyClientScrollOffsetAnimated(
+      const gfx::ScrollOffset& scroll_offset,
+      int target_property_id,
+      cc::KeyframeModel* keyframe_model) override {}
 
  private:
   cc::TransformOperations layout_offset_;
diff --git a/chrome/browser/vr/controller_delegate.h b/chrome/browser/vr/controller_delegate.h
new file mode 100644
index 0000000..1df466e3
--- /dev/null
+++ b/chrome/browser/vr/controller_delegate.h
@@ -0,0 +1,40 @@
+// Copyright 2018 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_VR_CONTROLLER_DELEGATE_H_
+#define CHROME_BROWSER_VR_CONTROLLER_DELEGATE_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/time/time.h"
+#include "device/vr/public/mojom/vr_service.mojom.h"
+
+namespace vr {
+
+class InputEvent;
+struct ControllerModel;
+struct RenderInfo;
+
+using InputEventList = std::vector<std::unique_ptr<InputEvent>>;
+
+// Communicates with the PlatformController to update it and obtain input and
+// movement information.
+class ControllerDelegate {
+ public:
+  virtual ~ControllerDelegate() {}
+
+  virtual void UpdateController(const RenderInfo& render_info,
+                                base::TimeTicks current_time,
+                                bool is_webxr_frame) = 0;
+  virtual ControllerModel GetModel(const RenderInfo& render_info) = 0;
+  virtual InputEventList GetGestures(base::TimeTicks current_time) = 0;
+  virtual device::mojom::XRInputSourceStatePtr GetInputSourceState() = 0;
+  virtual void OnResume() = 0;
+  virtual void OnPause() = 0;
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_VR_CONTROLLER_DELEGATE_H_
diff --git a/chrome/browser/vr/elements/ui_element.h b/chrome/browser/vr/elements/ui_element.h
index 96dcc8f..c11813e 100644
--- a/chrome/browser/vr/elements/ui_element.h
+++ b/chrome/browser/vr/elements/ui_element.h
@@ -401,6 +401,16 @@
   void NotifyClientSizeAnimated(const gfx::SizeF& size,
                                 int target_property_id,
                                 cc::KeyframeModel* keyframe_model) override;
+  void NotifyClientFilterAnimated(const cc::FilterOperations& filter,
+                                  int target_property_id,
+                                  cc::KeyframeModel* keyframe_model) override {}
+  void NotifyClientColorAnimated(SkColor color,
+                                 int target_property_id,
+                                 cc::KeyframeModel* keyframe_model) override {}
+  void NotifyClientScrollOffsetAnimated(
+      const gfx::ScrollOffset& scroll_offset,
+      int target_property_id,
+      cc::KeyframeModel* keyframe_model) override {}
 
   void SetTransitionedProperties(const std::set<TargetProperty>& properties);
   void SetTransitionDuration(base::TimeDelta delta);
diff --git a/chrome/browser/vr/model/controller_model.h b/chrome/browser/vr/model/controller_model.h
index beb2700..abb49de0 100644
--- a/chrome/browser/vr/model/controller_model.h
+++ b/chrome/browser/vr/model/controller_model.h
@@ -36,7 +36,6 @@
   bool recentered = false;
   PlatformController::Handedness handedness = PlatformController::kRightHanded;
   base::TimeTicks last_orientation_timestamp;
-  base::TimeTicks last_touch_timestamp;
   base::TimeTicks last_button_timestamp;
   int battery_level = 0;
 };
diff --git a/chrome/browser/vr/render_loop.cc b/chrome/browser/vr/render_loop.cc
new file mode 100644
index 0000000..38d3ddde
--- /dev/null
+++ b/chrome/browser/vr/render_loop.cc
@@ -0,0 +1,41 @@
+// Copyright 2018 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/vr/render_loop.h"
+
+#include <utility>
+
+#include "base/time/time.h"
+#include "chrome/browser/vr/controller_delegate.h"
+#include "chrome/browser/vr/input_event.h"
+#include "chrome/browser/vr/model/controller_model.h"
+#include "chrome/browser/vr/ui_interface.h"
+
+namespace vr {
+
+RenderLoop::RenderLoop(std::unique_ptr<UiInterface> ui) : ui_(std::move(ui)) {}
+RenderLoop::~RenderLoop() = default;
+
+void RenderLoop::ProcessControllerInput(const RenderInfo& render_info,
+                                        base::TimeTicks current_time,
+                                        bool is_webxr_frame) {
+  DCHECK(controller_delegate_);
+  DCHECK(ui_);
+
+  controller_delegate_->UpdateController(render_info, current_time,
+                                         is_webxr_frame);
+  auto input_event_list = controller_delegate_->GetGestures(current_time);
+  if (is_webxr_frame) {
+    ui_->HandleMenuButtonEvents(&input_event_list);
+  } else {
+    ReticleModel reticle_model;
+    ControllerModel controller_model =
+        controller_delegate_->GetModel(render_info);
+    ui_->HandleInput(current_time, render_info, controller_model,
+                     &reticle_model, &input_event_list);
+    ui_->OnControllerUpdated(controller_model, reticle_model);
+  }
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/render_loop.h b/chrome/browser/vr/render_loop.h
new file mode 100644
index 0000000..6b04d2e
--- /dev/null
+++ b/chrome/browser/vr/render_loop.h
@@ -0,0 +1,43 @@
+// Copyright 2018 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_VR_RENDER_LOOP_H_
+#define CHROME_BROWSER_VR_RENDER_LOOP_H_
+
+#include <memory>
+
+#include "chrome/browser/vr/vr_export.h"
+
+namespace base {
+class TimeTicks;
+}
+
+namespace vr {
+
+class ControllerDelegate;
+class UiInterface;
+struct RenderInfo;
+
+// This abstract class handles all input/output activities during a frame.
+// This includes head movement, controller movement and input, audio output and
+// rendering of the frame.
+// TODO(acondor): Move more functionality cross platform functionality from
+// VrShellGl and make this class concrete (http://crbug.com/767282).
+class VR_EXPORT RenderLoop {
+ public:
+  explicit RenderLoop(std::unique_ptr<UiInterface> ui);
+  virtual ~RenderLoop();
+
+ protected:
+  void ProcessControllerInput(const RenderInfo& render_info,
+                              base::TimeTicks current_time,
+                              bool is_webxr_frame);
+
+  std::unique_ptr<UiInterface> ui_;
+  std::unique_ptr<ControllerDelegate> controller_delegate_;
+};
+
+}  // namespace vr
+
+#endif  // CHROME_BROWSER_VR_RENDER_LOOP_H_
diff --git a/chrome/browser/vr/test/constants.cc b/chrome/browser/vr/test/constants.cc
new file mode 100644
index 0000000..ca8f958
--- /dev/null
+++ b/chrome/browser/vr/test/constants.cc
@@ -0,0 +1,16 @@
+// Copyright 2018 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/vr/test/constants.h"
+
+namespace vr {
+
+gfx::Transform GetPixelDaydreamProjMatrix() {
+  static const gfx::Transform pixel_daydream_proj_matrix(
+      1.03317f, 0.0f, 0.271253f, 0.0f, 0.0f, 0.862458f, -0.0314586f, 0.0f, 0.0f,
+      0.0f, -1.002f, -0.2002f, 0.0f, 0.0f, -1.0f, 0.0f);
+  return pixel_daydream_proj_matrix;
+}
+
+}  // namespace vr
diff --git a/chrome/browser/vr/test/constants.h b/chrome/browser/vr/test/constants.h
index 4a2b1da..5c2fdff 100644
--- a/chrome/browser/vr/test/constants.h
+++ b/chrome/browser/vr/test/constants.h
@@ -12,36 +12,21 @@
 namespace vr {
 
 // Proj matrix as used on a Pixel phone with the Daydream headset.
-static const gfx::Transform kPixelDaydreamProjMatrix(1.03317f,
-                                                     0.0f,
-                                                     0.271253f,
-                                                     0.0f,
-                                                     0.0f,
-                                                     0.862458f,
-                                                     -0.0314586f,
-                                                     0.0f,
-                                                     0.0f,
-                                                     0.0f,
-                                                     -1.002f,
-                                                     -0.2002f,
-                                                     0.0f,
-                                                     0.0f,
-                                                     -1.0f,
-                                                     0.0f);
-static const gfx::Transform kStartHeadPose;
-static const gfx::Vector3dF kStartControllerPosition(0.3, -0.3, -0.3);
-static const gfx::Vector3dF kForwardVector(0.0f, 0.0f, -1.0f);
-static const gfx::Vector3dF kBackwardVector(0.0f, 0.0f, 1.0f);
+gfx::Transform GetPixelDaydreamProjMatrix();
+static constexpr gfx::Transform kStartHeadPose;
+static constexpr gfx::Vector3dF kStartControllerPosition(0.3, -0.3, -0.3);
+static constexpr gfx::Vector3dF kForwardVector(0.0f, 0.0f, -1.0f);
+static constexpr gfx::Vector3dF kBackwardVector(0.0f, 0.0f, 1.0f);
 
-constexpr float kEpsilon = 1e-5f;
+static constexpr float kEpsilon = 1e-5f;
 
 // Resolution of Pixel Phone for one eye.
-static const gfx::Size kPixelHalfScreen(960, 1080);
+static constexpr gfx::Size kPixelHalfScreen(960, 1080);
 
-static const char* kLoremIpsum100Chars =
+static constexpr const char* kLoremIpsum100Chars =
     "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis erat nisl, "
     "tempus nec neque at nullam.";
-static const char* kLoremIpsum700Chars =
+static constexpr const char* kLoremIpsum700Chars =
     "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo "
     "ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis "
     "dis parturient montes, nascetur ridiculus mus. Donec quam felis, "
diff --git a/chrome/browser/vr/ui.cc b/chrome/browser/vr/ui.cc
index 17fbbb32..65d20f0 100644
--- a/chrome/browser/vr/ui.cc
+++ b/chrome/browser/vr/ui.cc
@@ -559,43 +559,23 @@
   button->OnHoverLeave(base::TimeTicks::Now());
 }
 
-void Ui::PerformControllerActionForTesting(
-    ControllerTestInput controller_input,
-    std::queue<ControllerModel>& controller_model_queue) {
+gfx::Point3F Ui::GetTargetPointForTesting(UserFriendlyElementName element_name,
+                                          const gfx::PointF& position) {
   auto* target_element = scene()->GetUiElementByName(
-      UserFriendlyElementNameToUiElementName(controller_input.element_name));
+      UserFriendlyElementNameToUiElementName(element_name));
   DCHECK(target_element) << "Unsupported test element";
   // The position to click is provided for a unit square, so scale it to match
   // the actual element.
-  controller_input.position.Scale(target_element->size().width(),
-                                  target_element->size().height());
-  auto target = gfx::Point3F(controller_input.position.x(),
-                             controller_input.position.y(), 0.0f);
+  auto scaled_position = ScalePoint(position, target_element->size().width(),
+                                    target_element->size().height());
+  gfx::Point3F target(scaled_position.x(), scaled_position.y(), 0.0f);
   target_element->ComputeTargetWorldSpaceTransform().TransformPoint(&target);
-  gfx::Point3F origin;
-  gfx::Vector3dF direction(target - origin);
+  // We do hit testing with respect to the eye position (world origin), so we
+  // need to project the target point into the background.
+  gfx::Vector3dF direction = target - kOrigin;
   direction.GetNormalized(&direction);
-  ControllerModel controller_model;
-  controller_model.laser_direction = direction;
-  controller_model.laser_origin = origin;
-
-  switch (controller_input.action) {
-    case VrControllerTestAction::kClick:
-      // Add in the button down action
-      controller_model.touchpad_button_state =
-          UiInputManager::ButtonState::DOWN;
-      controller_model_queue.push(controller_model);
-      // Add in the button up action
-      controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
-      controller_model_queue.push(controller_model);
-      break;
-    case VrControllerTestAction::kHover:
-      controller_model.touchpad_button_state = UiInputManager::ButtonState::UP;
-      controller_model_queue.push(controller_model);
-      break;
-    default:
-      NOTREACHED() << "Given unsupported controller action";
-  }
+  return kOrigin +
+         gfx::ScaleVector3d(direction, scene()->background_distance());
 }
 
 ContentElement* Ui::GetContentElement() {
diff --git a/chrome/browser/vr/ui.h b/chrome/browser/vr/ui.h
index e4e5643..bd9755a0 100644
--- a/chrome/browser/vr/ui.h
+++ b/chrome/browser/vr/ui.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <queue>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -156,10 +157,8 @@
   void OnContentBoundsChanged(int width, int height) override;
 
   void AcceptDoffPromptForTesting() override;
-  void PerformControllerActionForTesting(
-      ControllerTestInput controller_input,
-      std::queue<ControllerModel>& controller_model_queue) override;
-
+  gfx::Point3F GetTargetPointForTesting(UserFriendlyElementName element_name,
+                                        const gfx::PointF& position) override;
   bool IsContentVisibleAndOpaque() override;
   bool IsContentOverlayTextureEmpty() override;
   void SetContentUsesQuadLayer(bool uses_quad_buffers) override;
diff --git a/chrome/browser/vr/ui_interface.h b/chrome/browser/vr/ui_interface.h
index 8f095e1..2f17e12 100644
--- a/chrome/browser/vr/ui_interface.h
+++ b/chrome/browser/vr/ui_interface.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <queue>
+#include <vector>
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/vr/browser_ui_interface.h"
@@ -15,7 +16,10 @@
 #include "chrome/browser/vr/ui_element_renderer.h"
 #include "chrome/browser/vr/ui_input_manager.h"
 #include "chrome/browser/vr/ui_scene.h"
-#include "chrome/browser/vr/ui_test_input.h"
+
+namespace gfx {
+class Point3F;
+}
 
 namespace vr {
 
@@ -24,6 +28,7 @@
 struct ControllerModel;
 struct RenderInfo;
 struct ReticleModel;
+enum class UserFriendlyElementName;
 
 // This interface represents the methods that should be called by its owner, and
 // also serves to make all such methods virtual for the sake of separating a UI
@@ -75,9 +80,9 @@
   virtual void OnSwapContents(int new_content_id) = 0;
   virtual void OnContentBoundsChanged(int width, int height) = 0;
   virtual void AcceptDoffPromptForTesting() = 0;
-  virtual void PerformControllerActionForTesting(
-      ControllerTestInput controller_input,
-      std::queue<ControllerModel>& controller_model_queue) = 0;
+  virtual gfx::Point3F GetTargetPointForTesting(
+      UserFriendlyElementName element_name,
+      const gfx::PointF& position) = 0;
   virtual bool IsContentVisibleAndOpaque() = 0;
   virtual bool IsContentOverlayTextureEmpty() = 0;
   virtual void SetContentUsesQuadLayer(bool uses_quad_buffers) = 0;
diff --git a/chrome/browser/vr/ui_pixeltest.cc b/chrome/browser/vr/ui_pixeltest.cc
index ad9ca201..256dbef 100644
--- a/chrome/browser/vr/ui_pixeltest.cc
+++ b/chrome/browser/vr/ui_pixeltest.cc
@@ -38,7 +38,7 @@
   // Draw UI.
   DrawUi(gfx::Vector3dF(0.0f, 0.0f, -1.0f), gfx::Point3F(0.5f, -0.5f, 0.0f),
          UiInputManager::ButtonState::UP, 1.0f, kIdentity, kIdentity,
-         kPixelDaydreamProjMatrix);
+         GetPixelDaydreamProjMatrix());
 
   // Read pixels into SkBitmap.
   auto bitmap = SaveCurrentFrameBufferToSkBitmap();
diff --git a/chrome/browser/vr/ui_test_input.cc b/chrome/browser/vr/ui_test_input.cc
index ff04d44..9a5808e6 100644
--- a/chrome/browser/vr/ui_test_input.cc
+++ b/chrome/browser/vr/ui_test_input.cc
@@ -3,10 +3,10 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/vr/ui_test_input.h"
-#include "chrome/browser/vr/elements/ui_element_name.h"
 
 #include "base/logging.h"
 #include "base/macros.h"
+#include "chrome/browser/vr/elements/ui_element_name.h"
 
 namespace vr {
 
diff --git a/chrome/browser/vr/ui_test_input.h b/chrome/browser/vr/ui_test_input.h
index ff795d1..f570071 100644
--- a/chrome/browser/vr/ui_test_input.h
+++ b/chrome/browser/vr/ui_test_input.h
@@ -45,6 +45,9 @@
   kClick,
   kHover,
   kRevertToRealController,
+  kClickDown,
+  kClickUp,
+  kMove,
 };
 
 // Holds all information necessary to perform a simulated controller action on
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index 0023bce..25780fa 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -697,14 +697,14 @@
               OnContentScreenBoundsChanged(
                   SizeFsAreApproximatelyEqual(expected_bounds, kTolerance)));
 
-  ui_->OnProjMatrixChanged(kPixelDaydreamProjMatrix);
+  ui_->OnProjMatrixChanged(GetPixelDaydreamProjMatrix());
   OnBeginFrame();
 }
 
 TEST_F(UiTest, PropagateContentBoundsOnFullscreen) {
   CreateScene(kNotInWebVr);
 
-  ui_->OnProjMatrixChanged(kPixelDaydreamProjMatrix);
+  ui_->OnProjMatrixChanged(GetPixelDaydreamProjMatrix());
   ui_->SetFullscreen(true);
 
   gfx::SizeF expected_bounds(0.587874f, 0.330614f);
@@ -712,7 +712,7 @@
               OnContentScreenBoundsChanged(
                   SizeFsAreApproximatelyEqual(expected_bounds, kTolerance)));
 
-  ui_->OnProjMatrixChanged(kPixelDaydreamProjMatrix);
+  ui_->OnProjMatrixChanged(GetPixelDaydreamProjMatrix());
   OnBeginFrame();
 }
 
@@ -720,7 +720,7 @@
   CreateScene(kNotInWebVr);
 
   EXPECT_FALSE(RunForMs(0));
-  ui_->OnProjMatrixChanged(kPixelDaydreamProjMatrix);
+  ui_->OnProjMatrixChanged(GetPixelDaydreamProjMatrix());
 
   UiElement* content_quad = scene_->GetUiElementByName(kContentQuad);
   gfx::SizeF content_quad_size = content_quad->size();
@@ -730,7 +730,7 @@
 
   EXPECT_CALL(*browser_, OnContentScreenBoundsChanged(testing::_)).Times(0);
 
-  ui_->OnProjMatrixChanged(kPixelDaydreamProjMatrix);
+  ui_->OnProjMatrixChanged(GetPixelDaydreamProjMatrix());
 }
 
 TEST_F(UiTest, LoadingIndicatorBindings) {
diff --git a/chrome/browser/vr/vr_geometry_util_unittest.cc b/chrome/browser/vr/vr_geometry_util_unittest.cc
index b48495f..3c035315 100644
--- a/chrome/browser/vr/vr_geometry_util_unittest.cc
+++ b/chrome/browser/vr/vr_geometry_util_unittest.cc
@@ -15,7 +15,7 @@
   gfx::SizeF size(2.4f, 1.6f);
 
   gfx::SizeF screen_size =
-      CalculateScreenSize(kPixelDaydreamProjMatrix, 2.5f, size);
+      CalculateScreenSize(GetPixelDaydreamProjMatrix(), 2.5f, size);
 
   EXPECT_FLOAT_EQ(screen_size.width(), 0.49592164f);
   EXPECT_FLOAT_EQ(screen_size.height(), 0.27598655f);
diff --git a/chrome/browser/web_applications/components/web_app_shortcut_linux.cc b/chrome/browser/web_applications/components/web_app_shortcut_linux.cc
index f638c0f..51b34ed 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut_linux.cc
+++ b/chrome/browser/web_applications/components/web_app_shortcut_linux.cc
@@ -79,7 +79,8 @@
     argv.push_back(temp_file_path.value());
     argv.push_back(icon_name);
     int exit_code;
-    if (!shell_integration::LaunchXdgUtility(argv, &exit_code) || exit_code) {
+    if (!shell_integration_linux::LaunchXdgUtility(argv, &exit_code) ||
+        exit_code) {
       LOG(WARNING) << "Could not install icon " << icon_name << ".png at size "
                    << width << ".";
     }
@@ -172,7 +173,7 @@
     argv.push_back(temp_directory_path.value());
   argv.push_back(temp_file_path.value());
   int exit_code;
-  shell_integration::LaunchXdgUtility(argv, &exit_code);
+  shell_integration_linux::LaunchXdgUtility(argv, &exit_code);
   return exit_code == 0;
 }
 
@@ -220,7 +221,7 @@
     argv.push_back(directory_filename.value());
   argv.push_back(shortcut_filename.value());
   int exit_code;
-  shell_integration::LaunchXdgUtility(argv, &exit_code);
+  shell_integration_linux::LaunchXdgUtility(argv, &exit_code);
 }
 
 bool CreateDesktopShortcut(
diff --git a/chrome/browser/web_applications/extensions/BUILD.gn b/chrome/browser/web_applications/extensions/BUILD.gn
index 215aa6e..a6800e8 100644
--- a/chrome/browser/web_applications/extensions/BUILD.gn
+++ b/chrome/browser/web_applications/extensions/BUILD.gn
@@ -12,6 +12,8 @@
     "bookmark_app_data_retriever.h",
     "bookmark_app_installation_task.cc",
     "bookmark_app_installation_task.h",
+    "bookmark_app_installer.cc",
+    "bookmark_app_installer.h",
     "bookmark_app_shortcut_installation_task.cc",
     "bookmark_app_shortcut_installation_task.h",
     "pending_bookmark_app_manager.cc",
@@ -40,6 +42,7 @@
   sources = [
     "bookmark_app_data_retriever_unittest.cc",
     "bookmark_app_installation_task_unittest.cc",
+    "bookmark_app_installer_unittest.cc",
   ]
 
   deps = [
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installation_task.cc b/chrome/browser/web_applications/extensions/bookmark_app_installation_task.cc
index 66eac95..69317aa 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_installation_task.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installation_task.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_data_retriever.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_installer.h"
 
 namespace extensions {
 
@@ -20,7 +21,13 @@
   data_retriever_ = std::move(data_retriever);
 }
 
-BookmarkAppInstallationTask::BookmarkAppInstallationTask()
-    : data_retriever_(std::make_unique<BookmarkAppDataRetriever>()) {}
+void BookmarkAppInstallationTask::SetInstallerForTesting(
+    std::unique_ptr<BookmarkAppInstaller> installer) {
+  installer_ = std::move(installer);
+}
+
+BookmarkAppInstallationTask::BookmarkAppInstallationTask(Profile* profile)
+    : data_retriever_(std::make_unique<BookmarkAppDataRetriever>()),
+      installer_(std::make_unique<BookmarkAppInstaller>(profile)) {}
 
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installation_task.h b/chrome/browser/web_applications/extensions/bookmark_app_installation_task.h
index ad7f832..ed515370 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_installation_task.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installation_task.h
@@ -10,9 +10,12 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 
+class Profile;
+
 namespace extensions {
 
 class BookmarkAppDataRetriever;
+class BookmarkAppInstaller;
 
 // Class to install a BookmarkApp-based Shortcut or WebApp from a WebContents
 // or WebApplicationInfo. Can only be called from the UI thread.
@@ -21,6 +24,7 @@
   enum class Result {
     kSuccess,
     kGetWebApplicationInfoFailed,
+    kInstallationFailed,
   };
 
   using ResultCallback = base::OnceCallback<void(Result)>;
@@ -29,14 +33,17 @@
 
   void SetDataRetrieverForTesting(
       std::unique_ptr<BookmarkAppDataRetriever> data_retriever);
+  void SetInstallerForTesting(std::unique_ptr<BookmarkAppInstaller> installer);
 
  protected:
-  BookmarkAppInstallationTask();
+  explicit BookmarkAppInstallationTask(Profile* profile);
 
   BookmarkAppDataRetriever& data_retriever() { return *data_retriever_; }
+  BookmarkAppInstaller& installer() { return *installer_; }
 
  private:
   std::unique_ptr<BookmarkAppDataRetriever> data_retriever_;
+  std::unique_ptr<BookmarkAppInstaller> installer_;
 
   DISALLOW_COPY_AND_ASSIGN(BookmarkAppInstallationTask);
 };
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
index e0ef7280..a871b404 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installation_task_unittest.cc
@@ -10,16 +10,18 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
-#include "base/message_loop/message_loop.h"
+#include "base/macros.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_data_retriever.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_installer.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.h"
 #include "chrome/common/web_application_info.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
-#include "content/public/test/test_browser_thread_bundle.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
@@ -44,6 +46,17 @@
     std::move(quit_closure).Run();
   }
 
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    // CrxInstaller in BookmarkAppInstaller needs an ExtensionService, so
+    // create one for the profile.
+    TestExtensionSystem* test_system =
+        static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()));
+    test_system->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
+                                        profile()->GetPath(),
+                                        false /* autoupdate_enabled */);
+  }
+
  protected:
   bool app_installed() {
     return app_installation_result_.value() ==
@@ -91,29 +104,52 @@
   DISALLOW_COPY_AND_ASSIGN(TestDataRetriever);
 };
 
+class TestInstaller : public BookmarkAppInstaller {
+ public:
+  explicit TestInstaller(Profile* profile, bool succeeds)
+      : BookmarkAppInstaller(profile), succeeds_(succeeds) {}
+
+  ~TestInstaller() override = default;
+
+  void Install(const WebApplicationInfo& web_app_info,
+               ResultCallback callback) override {
+    web_app_info_ = web_app_info;
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), succeeds_));
+  }
+
+  const WebApplicationInfo& web_app_info() { return web_app_info_.value(); }
+
+ private:
+  bool succeeds_;
+  base::Optional<WebApplicationInfo> web_app_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestInstaller);
+};
+
 TEST_F(BookmarkAppInstallationTaskTest, ShortcutFromContents_Delete) {
-  auto installer = std::make_unique<BookmarkAppShortcutInstallationTask>();
-  installer->SetDataRetrieverForTesting(
+  auto task = std::make_unique<BookmarkAppShortcutInstallationTask>(profile());
+  task->SetDataRetrieverForTesting(
       std::make_unique<TestDataRetriever>(nullptr));
 
   base::RunLoop run_loop;
-  installer->InstallFromWebContents(
+  task->InstallFromWebContents(
       web_contents(),
       base::BindOnce(&BookmarkAppInstallationTaskTest::OnInstallationTaskResult,
                      base::Unretained(this), run_loop.QuitClosure()));
-  installer.reset();
+  task.reset();
   run_loop.RunUntilIdle();
 
   // Shouldn't crash.
 }
 
 TEST_F(BookmarkAppInstallationTaskTest, ShortcutFromContents_NoWebAppInfo) {
-  auto installer = std::make_unique<BookmarkAppShortcutInstallationTask>();
-  installer->SetDataRetrieverForTesting(
+  auto task = std::make_unique<BookmarkAppShortcutInstallationTask>(profile());
+  task->SetDataRetrieverForTesting(
       std::make_unique<TestDataRetriever>(nullptr));
 
   base::RunLoop run_loop;
-  installer->InstallFromWebContents(
+  task->InstallFromWebContents(
       web_contents(),
       base::BindOnce(&BookmarkAppInstallationTaskTest::OnInstallationTaskResult,
                      base::Unretained(this), run_loop.QuitClosure()));
@@ -125,23 +161,55 @@
 }
 
 TEST_F(BookmarkAppInstallationTaskTest, ShortcutFromContents_NoManifest) {
-  auto installer = std::make_unique<BookmarkAppShortcutInstallationTask>();
+  auto task = std::make_unique<BookmarkAppShortcutInstallationTask>(profile());
 
   WebApplicationInfo info;
   info.app_url = GURL(kWebAppUrl);
   info.title = base::UTF8ToUTF16(kWebAppTitle);
-  installer->SetDataRetrieverForTesting(std::make_unique<TestDataRetriever>(
+  task->SetDataRetrieverForTesting(std::make_unique<TestDataRetriever>(
       std::make_unique<WebApplicationInfo>(std::move(info))));
 
+  auto installer =
+      std::make_unique<TestInstaller>(profile(), true /* succeeds */);
+  auto* installer_ptr = installer.get();
+  task->SetInstallerForTesting(std::move(installer));
+
   base::RunLoop run_loop;
-  installer->InstallFromWebContents(
+  task->InstallFromWebContents(
       web_contents(),
       base::BindOnce(&BookmarkAppInstallationTaskTest::OnInstallationTaskResult,
                      base::Unretained(this), run_loop.QuitClosure()));
   run_loop.Run();
 
   EXPECT_TRUE(app_installed());
-  // TODO(crbug.com/864904): Test that the right app got installed.
+
+  const auto& installed_info = installer_ptr->web_app_info();
+  EXPECT_EQ(info.app_url, installed_info.app_url);
+  EXPECT_EQ(info.title, installed_info.title);
+}
+
+TEST_F(BookmarkAppInstallationTaskTest,
+       ShortcutFromContents_InstallationFails) {
+  auto task = std::make_unique<BookmarkAppShortcutInstallationTask>(profile());
+
+  WebApplicationInfo info;
+  info.app_url = GURL(kWebAppUrl);
+  info.title = base::UTF8ToUTF16(kWebAppTitle);
+  task->SetDataRetrieverForTesting(std::make_unique<TestDataRetriever>(
+      std::make_unique<WebApplicationInfo>(std::move(info))));
+  task->SetInstallerForTesting(
+      std::make_unique<TestInstaller>(profile(), false /* succeeds */));
+
+  base::RunLoop run_loop;
+  task->InstallFromWebContents(
+      web_contents(),
+      base::BindOnce(&BookmarkAppInstallationTaskTest::OnInstallationTaskResult,
+                     base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+
+  EXPECT_FALSE(app_installed());
+  EXPECT_EQ(BookmarkAppInstallationTask::Result::kInstallationFailed,
+            app_installation_result());
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installer.cc b/chrome/browser/web_applications/extensions/bookmark_app_installer.cc
new file mode 100644
index 0000000..7a504a8
--- /dev/null
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installer.cc
@@ -0,0 +1,45 @@
+// Copyright 2018 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/web_applications/extensions/bookmark_app_installer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/web_application_info.h"
+
+namespace extensions {
+
+BookmarkAppInstaller::BookmarkAppInstaller(Profile* profile)
+    : crx_installer_(CrxInstaller::CreateSilent(
+          ExtensionSystem::Get(profile)->extension_service())) {}
+
+BookmarkAppInstaller::~BookmarkAppInstaller() = default;
+
+void BookmarkAppInstaller::Install(const WebApplicationInfo& web_app_info,
+                                   ResultCallback callback) {
+  crx_installer_->set_installer_callback(
+      base::BindOnce(&BookmarkAppInstaller::OnInstall,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  crx_installer_->InstallWebApp(web_app_info);
+}
+
+void BookmarkAppInstaller::SetCrxInstallerForTesting(
+    scoped_refptr<CrxInstaller> crx_installer) {
+  crx_installer_ = crx_installer;
+}
+
+void BookmarkAppInstaller::OnInstall(
+    ResultCallback callback,
+    const base::Optional<CrxInstallError>& error) {
+  // TODO(crbug.com/864904): Finish the installation i.e. set launch container.
+
+  const bool installation_succeeded = !error;
+  std::move(callback).Run(installation_succeeded);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installer.h b/chrome/browser/web_applications/extensions/bookmark_app_installer.h
new file mode 100644
index 0000000..32aa0b7
--- /dev/null
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installer.h
@@ -0,0 +1,56 @@
+// Copyright 2018 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_WEB_APPLICATIONS_EXTENSIONS_BOOKMARK_APP_INSTALLER_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_BOOKMARK_APP_INSTALLER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+
+class Profile;
+
+struct WebApplicationInfo;
+
+namespace extensions {
+
+class CrxInstaller;
+class CrxInstallError;
+
+// Class used by BookmarkAppInstallationTask to actually install the Bookmark
+// App in the system.
+class BookmarkAppInstaller {
+ public:
+  using ResultCallback = base::OnceCallback<void(bool)>;
+
+  // Constructs a BookmarkAppInstaller that will install the Bookmark App in
+  // |profile|.
+  explicit BookmarkAppInstaller(Profile* profile);
+  virtual ~BookmarkAppInstaller();
+
+  // TODO(crbug.com/864904): This should take more options e.g. what container
+  // to launch the app in, should the app sync, etc.
+  virtual void Install(const WebApplicationInfo& web_app_info,
+                       ResultCallback callback);
+
+  void SetCrxInstallerForTesting(scoped_refptr<CrxInstaller> crx_installer);
+
+ private:
+  void OnInstall(ResultCallback callback,
+                 const base::Optional<CrxInstallError>& error);
+
+  scoped_refptr<CrxInstaller> crx_installer_;
+
+  // We need a WeakPtr because CrxInstaller is refcounted and it can run its
+  // callback after this class has been destroyed.
+  base::WeakPtrFactory<BookmarkAppInstaller> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(BookmarkAppInstaller);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_EXTENSIONS_BOOKMARK_APP_INSTALLER_H_
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_installer_unittest.cc b/chrome/browser/web_applications/extensions/bookmark_app_installer_unittest.cc
new file mode 100644
index 0000000..73b42b6
--- /dev/null
+++ b/chrome/browser/web_applications/extensions/bookmark_app_installer_unittest.cc
@@ -0,0 +1,137 @@
+// Copyright 2018 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/web_applications/extensions/bookmark_app_installer.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/crx_installer.h"
+#include "chrome/browser/extensions/test_extension_system.h"
+#include "chrome/common/web_application_info.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "extensions/browser/install/crx_install_error.h"
+
+namespace extensions {
+
+namespace {
+
+const char kWebAppUrl[] = "https://foo.example";
+const char kWebAppTitle[] = "Foo Title";
+
+}  // namespace
+
+class BookmarkAppInstallerTest : public ChromeRenderViewHostTestHarness {
+ public:
+  // Subclass that runs a closure when an extension is unpacked successfully.
+  // Useful for tests that want to trigger their own succeess/failure events.
+  class FakeCrxInstaller : public CrxInstaller {
+   public:
+    explicit FakeCrxInstaller(Profile* profile)
+        : CrxInstaller(
+              ExtensionSystem::Get(profile)->extension_service()->AsWeakPtr(),
+              nullptr,
+              nullptr) {
+    }
+
+    void OnUnpackSuccess(
+        const base::FilePath& temp_dir,
+        const base::FilePath& extension_dir,
+        std::unique_ptr<base::DictionaryValue> original_manifest,
+        const Extension* extension,
+        const SkBitmap& install_icon,
+        const base::Optional<int>& dnr_ruleset_checksum) override {
+      run_loop_.QuitClosure().Run();
+    }
+
+    void WaitForInstallToTrigger() { run_loop_.Run(); }
+
+    void SimulateInstallFailed() {
+      CrxInstallError error(CrxInstallErrorType::DECLINED,
+                            CrxInstallErrorDetail::INSTALL_NOT_ENABLED,
+                            base::ASCIIToUTF16(""));
+      NotifyCrxInstallComplete(error);
+    }
+
+   private:
+    ~FakeCrxInstaller() override = default;
+
+    base::RunLoop run_loop_;
+
+    DISALLOW_COPY_AND_ASSIGN(FakeCrxInstaller);
+  };
+
+  BookmarkAppInstallerTest() = default;
+  ~BookmarkAppInstallerTest() override = default;
+
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    // CrxInstaller in BookmarkAppInstaller needs an ExtensionService, so
+    // create one for the profile.
+    TestExtensionSystem* test_system =
+        static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile()));
+    test_system->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
+                                        profile()->GetPath(),
+                                        false /* autoupdate_enabled */);
+  }
+
+  void InstallCallback(base::OnceClosure quit_closure, bool app_installed) {
+    app_installed_ = app_installed;
+    std::move(quit_closure).Run();
+  }
+
+  bool app_installed() { return app_installed_.value(); }
+  bool install_callback_called() { return app_installed_.has_value(); }
+
+ private:
+  base::Optional<bool> app_installed_;
+
+  DISALLOW_COPY_AND_ASSIGN(BookmarkAppInstallerTest);
+};
+
+TEST_F(BookmarkAppInstallerTest, BasicInstallSucceeds) {
+  BookmarkAppInstaller installer(profile());
+
+  WebApplicationInfo info;
+  info.app_url = GURL(kWebAppUrl);
+  info.title = base::ASCIIToUTF16(kWebAppTitle);
+
+  base::RunLoop run_loop;
+  installer.Install(
+      info, base::BindOnce(&BookmarkAppInstallerTest::InstallCallback,
+                           base::Unretained(this), run_loop.QuitClosure()));
+  run_loop.Run();
+  EXPECT_TRUE(app_installed());
+}
+
+TEST_F(BookmarkAppInstallerTest, BasicInstallFails) {
+  BookmarkAppInstaller installer(profile());
+  auto fake_crx_installer =
+      base::MakeRefCounted<BookmarkAppInstallerTest::FakeCrxInstaller>(
+          profile());
+  installer.SetCrxInstallerForTesting(fake_crx_installer);
+
+  base::RunLoop run_loop;
+
+  WebApplicationInfo info;
+  info.app_url = GURL(kWebAppUrl);
+  info.title = base::ASCIIToUTF16(kWebAppTitle);
+  installer.Install(
+      info, base::BindOnce(&BookmarkAppInstallerTest::InstallCallback,
+                           base::Unretained(this), run_loop.QuitClosure()));
+
+  fake_crx_installer->WaitForInstallToTrigger();
+  fake_crx_installer->SimulateInstallFailed();
+  run_loop.Run();
+
+  EXPECT_FALSE(app_installed());
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.cc b/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.cc
index dccc059b..e71f589 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.cc
@@ -11,13 +11,15 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_data_retriever.h"
+#include "chrome/browser/web_applications/extensions/bookmark_app_installer.h"
 #include "chrome/common/web_application_info.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace extensions {
 
-BookmarkAppShortcutInstallationTask::BookmarkAppShortcutInstallationTask() =
-    default;
+BookmarkAppShortcutInstallationTask::BookmarkAppShortcutInstallationTask(
+    Profile* profile)
+    : BookmarkAppInstallationTask(profile) {}
 
 BookmarkAppShortcutInstallationTask::~BookmarkAppShortcutInstallationTask() =
     default;
@@ -53,15 +55,30 @@
   data_retriever().GetIcons(
       web_app_info->app_url, icon_urls,
       base::BindOnce(&BookmarkAppShortcutInstallationTask::OnGetIcons,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(result_callback)));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(result_callback),
+                     std::move(web_app_info)));
 }
 
 void BookmarkAppShortcutInstallationTask::OnGetIcons(
     ResultCallback result_callback,
+    std::unique_ptr<WebApplicationInfo> web_app_info,
     std::vector<WebApplicationInfo::IconInfo> icons) {
-  // TODO(crbug.com/864904): Continue the installation process.
-  std::move(result_callback).Run(Result::kSuccess);
+  web_app_info->icons = std::move(icons);
+
+  // TODO(crbug.com/864904): Make this a WebContents observer and cancel the
+  // task if the WebContents has been destroyed.
+  installer().Install(
+      *web_app_info,
+      base::BindOnce(&BookmarkAppShortcutInstallationTask::OnInstalled,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(result_callback)));
+}
+
+void BookmarkAppShortcutInstallationTask::OnInstalled(
+    ResultCallback result_callback,
+    bool success) {
+  std::move(result_callback)
+      .Run(success ? Result::kSuccess : Result::kInstallationFailed);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.h b/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.h
index 71d117c..7576e4b2 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_shortcut_installation_task.h
@@ -13,6 +13,8 @@
 #include "chrome/browser/web_applications/extensions/bookmark_app_installation_task.h"
 #include "chrome/common/web_application_info.h"
 
+class Profile;
+
 namespace content {
 class WebContents;
 }
@@ -20,10 +22,12 @@
 namespace extensions {
 
 // Subclass of BookmarkAppInstallationTask that exclusively installs
-// BookmarkApp-based Shortcuts.
+// BookmarkApp-based shortcuts.
 class BookmarkAppShortcutInstallationTask : public BookmarkAppInstallationTask {
  public:
-  BookmarkAppShortcutInstallationTask();
+  // Constructs a task that will install a BookmarkApp-based shortcut for
+  // |profile|.
+  explicit BookmarkAppShortcutInstallationTask(Profile* profile);
   ~BookmarkAppShortcutInstallationTask() override;
 
   void InstallFromWebContents(content::WebContents* web_contents,
@@ -34,7 +38,9 @@
       ResultCallback result_callback,
       std::unique_ptr<WebApplicationInfo> web_app_info);
   void OnGetIcons(ResultCallback result_callback,
+                  std::unique_ptr<WebApplicationInfo> web_app_info,
                   std::vector<WebApplicationInfo::IconInfo> icons);
+  void OnInstalled(ResultCallback result_callback, bool success);
 
   base::WeakPtrFactory<BookmarkAppShortcutInstallationTask> weak_ptr_factory_{
       this};
diff --git a/chrome/chrome_cleaner/BUILD.gn b/chrome/chrome_cleaner/BUILD.gn
index 040bc97..c85bcc5 100644
--- a/chrome/chrome_cleaner/BUILD.gn
+++ b/chrome/chrome_cleaner/BUILD.gn
@@ -11,6 +11,7 @@
   ]
 
   deps = [
+    ":other_executable_definitions",
     "//chrome/chrome_cleaner/engines:resources",
     "//chrome/chrome_cleaner/os:common_os",
     "//chrome/chrome_cleaner/pup_data:pup_data_base",
@@ -18,6 +19,7 @@
 
     # Tests from sub-directories.
     "//chrome/chrome_cleaner/http:unittest_sources",
+    "//chrome/chrome_cleaner/logging:unittest_sources",
     "//chrome/chrome_cleaner/os:unittest_sources",
     "//chrome/chrome_cleaner/pup_data:unittest_sources",
     "//chrome/chrome_cleaner/settings:unittest_sources",
@@ -34,3 +36,18 @@
     "//testing/gtest",
   ]
 }
+
+# This library should only be included in executable targets.
+static_library("other_executable_definitions") {
+  sources = [
+    "//chrome/chrome_cleaner/logging/other_logging_definitions.cc",
+    "//chrome/chrome_cleaner/settings/other_settings_definitions.cc",
+  ]
+
+  deps = [
+    "//chrome/chrome_cleaner/logging:logging_definitions",
+    "//chrome/chrome_cleaner/logging:noop_logging",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/settings:settings_definitions",
+  ]
+}
diff --git a/chrome/chrome_cleaner/DEPS b/chrome/chrome_cleaner/DEPS
index 16f67f85..8f02ad1f 100644
--- a/chrome/chrome_cleaner/DEPS
+++ b/chrome/chrome_cleaner/DEPS
@@ -1,4 +1,6 @@
 include_rules = [
   "+sandbox/win/src",
   "+testing/gtest",
+  "+components/chrome_cleaner",
+  "+third_party/protobuf/src/google/protobuf",
 ]
diff --git a/chrome/chrome_cleaner/chrome_utils/BUILD.gn b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
new file mode 100644
index 0000000..ea3ca11
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/BUILD.gn
@@ -0,0 +1,47 @@
+# Copyright 2018 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.
+
+source_set("chrome_util_lib") {
+  sources = [
+    "chrome_util.cc",
+    "chrome_util.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//components/chrome_cleaner/public/constants:constants",
+  ]
+}
+
+source_set("extensions_util_lib") {
+  sources = [
+    "extensions_util.cc",
+    "extensions_util.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/os:common_os",
+  ]
+}
+
+source_set("unittest_sources") {
+  testonly = true
+
+  sources = [
+    "extensions_util_unittest.cc",
+  ]
+
+  deps = [
+    ":chrome_util_lib",
+    ":extensions_util_lib",
+    "//base:base",
+    "//base/test:test_support",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/test:test_util",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/chrome_cleaner/chrome_utils/DEPS b/chrome/chrome_cleaner/chrome_utils/DEPS
new file mode 100644
index 0000000..9648b97a
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+components/chrome_cleaner",
+]
diff --git a/chrome/chrome_cleaner/chrome_utils/chrome_util.cc b/chrome/chrome_cleaner/chrome_utils/chrome_util.cc
new file mode 100644
index 0000000..df5f3d8
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/chrome_util.cc
@@ -0,0 +1,82 @@
+// Copyright 2018 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/chrome_cleaner/chrome_utils/chrome_util.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/registry.h"
+#include "chrome/chrome_cleaner/os/system_util.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+
+namespace chrome_cleaner {
+
+// Chrome shortcut filename.
+const wchar_t kChromeShortcutFilename[] = L"Google Chrome.lnk";
+
+// The KO language version doesn't have the term Google in the filename.
+const wchar_t kKOChromeShortcutFilename[] = L"Chrome.lnk";
+
+bool RetrieveChromeVersionAndInstalledDomain(base::string16* chrome_version,
+                                             bool* system_install) {
+  DCHECK(chrome_version);
+
+  const base::CommandLine* const command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(kChromeVersionSwitch)) {
+    LOG(WARNING) << "Can't get Chrome version information from flag: "
+                 << "The " << kChromeVersionSwitch << " switch was not set.";
+    return false;
+  }
+
+  *chrome_version = command_line->GetSwitchValueNative(kChromeVersionSwitch);
+  // The system install flag should be set only by Chrome, in which case the
+  // Chrome version flag will also be set. Therefore, the presence or absence
+  // of the system install flag at this point fully determines whether or not
+  // we have a system-level install of Chrome.
+  if (system_install)
+    *system_install = command_line->HasSwitch(kChromeSystemInstallSwitch);
+  return true;
+}
+
+bool RetrieveChromeExePathFromCommandLine(base::FilePath* chrome_exe_path) {
+  DCHECK(chrome_exe_path);
+
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(kChromeExePathSwitch)) {
+    LOG(WARNING) << "Failed to locate Chrome executable from flag: "
+                 << "The " << kChromeExePathSwitch << " switch was not set.";
+    return false;
+  }
+
+  base::FilePath chrome_exe_from_flag =
+      command_line->GetSwitchValuePath(kChromeExePathSwitch);
+  if (!base::PathExists(chrome_exe_from_flag)) {
+    LOG(WARNING) << "Failed to locate Chrome executable from flag: "
+                 << kChromeExePathSwitch << " = '"
+                 << SanitizePath(chrome_exe_from_flag) << "'";
+    return false;
+  }
+
+  *chrome_exe_path = chrome_exe_from_flag;
+  return true;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/chrome_util.h b/chrome/chrome_cleaner/chrome_utils/chrome_util.h
new file mode 100644
index 0000000..03a4134
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/chrome_util.h
@@ -0,0 +1,37 @@
+// Copyright 2018 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_CHROME_CLEANER_CHROME_UTILS_CHROME_UTIL_H_
+#define CHROME_CHROME_CLEANER_CHROME_UTILS_CHROME_UTIL_H_
+
+#include <set>
+
+#include "base/strings/string16.h"
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace chrome_cleaner {
+
+// Chrome shortcut filename.
+extern const wchar_t kChromeShortcutFilename[];
+// The KO language version doesn't have the term Google in the filename.
+extern const wchar_t kKOChromeShortcutFilename[];
+
+// Retrieve installed chrome version to |chrome_version|. The flag
+// |system_install| receives whether the chrome is installed system wide or per
+// user. |system_install| is optional and can be null.
+// Return true on success.
+bool RetrieveChromeVersionAndInstalledDomain(base::string16* chrome_version,
+                                             bool* system_install);
+
+// Retrieve path to Chrome's executable from the path given on the command
+// line. Return true if Chrome's exe path was given on the command line and the
+// path exists.
+bool RetrieveChromeExePathFromCommandLine(base::FilePath* chrome_exe_path);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_CHROME_UTIL_H_
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util.cc b/chrome/chrome_cleaner/chrome_utils/extensions_util.cc
new file mode 100644
index 0000000..1182e8b
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util.cc
@@ -0,0 +1,75 @@
+// Copyright 2018 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/chrome_cleaner/chrome_utils/extensions_util.h"
+
+#include "base/stl_util.h"
+#include "base/win/registry.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/os/system_util.h"
+
+namespace chrome_cleaner {
+namespace {
+
+const int kExtensionIdLength = 32;
+
+struct RegistryKey {
+  HKEY hkey;
+  const wchar_t* path;
+};
+
+const RegistryKey extension_forcelist_keys[] = {
+    {HKEY_LOCAL_MACHINE,
+     L"software\\policies\\google\\chrome\\ExtensionInstallForcelist"},
+    {HKEY_CURRENT_USER,
+     L"software\\policies\\google\\chrome\\ExtensionInstallForcelist"}};
+
+void GetForcelistPoliciesForAccessMask(
+    REGSAM access_mask,
+    std::vector<ExtensionRegistryPolicy>* policies) {
+  for (size_t i = 0; i < base::size(extension_forcelist_keys); ++i) {
+    base::win::RegistryValueIterator forcelist_it(
+        extension_forcelist_keys[i].hkey, extension_forcelist_keys[i].path,
+        access_mask);
+    for (; forcelist_it.Valid(); ++forcelist_it) {
+      base::string16 entry;
+      GetRegistryValueAsString(forcelist_it.Value(), forcelist_it.ValueSize(),
+                               forcelist_it.Type(), &entry);
+
+      // Extract the extension ID from the beginning of the registry entry,
+      // since it also contains an update URL.
+      if (entry.length() >= kExtensionIdLength) {
+        base::string16 extension_id = entry.substr(0, kExtensionIdLength);
+
+        policies->emplace_back(extension_id, extension_forcelist_keys[i].hkey,
+                               extension_forcelist_keys[i].path,
+                               forcelist_it.Name());
+      }
+    }
+  }
+}
+
+}  // namespace
+
+ExtensionRegistryPolicy::ExtensionRegistryPolicy(
+    const base::string16& extension_id,
+    HKEY hkey,
+    const base::string16& path,
+    const base::string16& name)
+    : extension_id(extension_id), hkey(hkey), path(path), name(name) {}
+
+ExtensionRegistryPolicy::ExtensionRegistryPolicy(ExtensionRegistryPolicy&&) =
+    default;
+
+ExtensionRegistryPolicy& ExtensionRegistryPolicy::operator=(
+    ExtensionRegistryPolicy&&) = default;
+
+void GetExtensionForcelistRegistryPolicies(
+    std::vector<ExtensionRegistryPolicy>* policies) {
+  GetForcelistPoliciesForAccessMask(KEY_WOW64_32KEY, policies);
+  if (IsX64Architecture())
+    GetForcelistPoliciesForAccessMask(KEY_WOW64_64KEY, policies);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util.h b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
new file mode 100644
index 0000000..09ee0ed7
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util.h
@@ -0,0 +1,38 @@
+// Copyright 2018 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_CHROME_CLEANER_CHROME_UTILS_EXTENSIONS_UTIL_H_
+#define CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSIONS_UTIL_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+
+namespace chrome_cleaner {
+
+// A registry key that holds some form of policy for |extension_id|.
+struct ExtensionRegistryPolicy {
+  base::string16 extension_id;
+  HKEY hkey;
+  base::string16 path;
+  base::string16 name;
+
+  ExtensionRegistryPolicy(const base::string16& extension_id,
+                          HKEY hkey,
+                          const base::string16& path,
+                          const base::string16& name);
+  ExtensionRegistryPolicy(ExtensionRegistryPolicy&&);
+  ExtensionRegistryPolicy& operator=(ExtensionRegistryPolicy&&);
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionRegistryPolicy);
+};
+
+// Find all extension forcelist registry policies and append to |policies|.
+void GetExtensionForcelistRegistryPolicies(
+    std::vector<ExtensionRegistryPolicy>* policies);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_CHROME_UTILS_EXTENSIONS_UTIL_H_
diff --git a/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
new file mode 100644
index 0000000..69d9a0a
--- /dev/null
+++ b/chrome/chrome_cleaner/chrome_utils/extensions_util_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright 2018 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/chrome_cleaner/chrome_utils/extensions_util.h"
+
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_reg_util_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+namespace {
+
+const int kExtensionIdLength = 32;
+
+struct TestRegistryEntry {
+  HKEY hkey;
+  const base::string16 path;
+  const base::string16 name;
+  const base::string16 value;
+};
+
+const TestRegistryEntry extension_forcelist_entries[] = {
+    {HKEY_LOCAL_MACHINE,
+     L"software\\policies\\google\\chrome\\ExtensionInstallForcelist", L"test1",
+     L"ababababcdcdcdcdefefefefghghghgh;https://clients2.google.com/service/"
+     L"update2/crx"},
+    {HKEY_CURRENT_USER,
+     L"software\\policies\\google\\chrome\\ExtensionInstallForcelist", L"test2",
+     L"aaaabbbbccccddddeeeeffffgggghhhh;https://clients2.google.com/service/"
+     L"update2/crx"}};
+
+bool ExtensionPolicyFound(
+    TestRegistryEntry test_entry,
+    const std::vector<ExtensionRegistryPolicy>& found_policies) {
+  for (const ExtensionRegistryPolicy& policy : found_policies) {
+    base::string16 test_entry_value(test_entry.value);
+    if (policy.extension_id == test_entry_value.substr(0, kExtensionIdLength) &&
+        policy.hkey == test_entry.hkey && policy.path == test_entry.path &&
+        policy.name == test_entry.name) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
+TEST(ExtensionsUtilTest, GetExtensionForcelistRegistryPolicies) {
+  registry_util::RegistryOverrideManager registry_override;
+  registry_override.OverrideRegistry(HKEY_CURRENT_USER);
+  registry_override.OverrideRegistry(HKEY_LOCAL_MACHINE);
+  for (const TestRegistryEntry& policy : extension_forcelist_entries) {
+    base::win::RegKey policy_key;
+    ASSERT_EQ(ERROR_SUCCESS, policy_key.Create(policy.hkey, policy.path.c_str(),
+                                               KEY_ALL_ACCESS));
+    DCHECK(policy_key.Valid());
+    ASSERT_EQ(ERROR_SUCCESS,
+              policy_key.WriteValue(policy.name.c_str(), policy.value.c_str()));
+  }
+
+  std::vector<ExtensionRegistryPolicy> policies;
+  GetExtensionForcelistRegistryPolicies(&policies);
+
+  for (const TestRegistryEntry& expected_result : extension_forcelist_entries) {
+    EXPECT_TRUE(ExtensionPolicyFound(expected_result, policies));
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/http/http_agent_factory.cc b/chrome/chrome_cleaner/http/http_agent_factory.cc
index 6c6ca31..6cc5931d 100644
--- a/chrome/chrome_cleaner/http/http_agent_factory.cc
+++ b/chrome/chrome_cleaner/http/http_agent_factory.cc
@@ -15,7 +15,8 @@
 
 HttpAgentFactory::~HttpAgentFactory() = default;
 
-std::unique_ptr<chrome_cleaner::HttpAgent> HttpAgentFactory::CreateHttpAgent() {
+std::unique_ptr<chrome_cleaner::HttpAgent> HttpAgentFactory::CreateHttpAgent()
+    const {
   std::unique_ptr<FileVersionInfo> file_version_info(
       FileVersionInfo::CreateFileVersionInfoForModule(CURRENT_MODULE()));
 
diff --git a/chrome/chrome_cleaner/http/http_agent_factory.h b/chrome/chrome_cleaner/http/http_agent_factory.h
index 69f6781..c7d9f3d 100644
--- a/chrome/chrome_cleaner/http/http_agent_factory.h
+++ b/chrome/chrome_cleaner/http/http_agent_factory.h
@@ -21,7 +21,7 @@
   virtual ~HttpAgentFactory();
 
   // Returns an HttpAgent instance.
-  virtual std::unique_ptr<chrome_cleaner::HttpAgent> CreateHttpAgent();
+  virtual std::unique_ptr<chrome_cleaner::HttpAgent> CreateHttpAgent() const;
 };
 
 }  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/http/mock_http_agent_factory.cc b/chrome/chrome_cleaner/http/mock_http_agent_factory.cc
index 38b18f5..a0bc8da 100644
--- a/chrome/chrome_cleaner/http/mock_http_agent_factory.cc
+++ b/chrome/chrome_cleaner/http/mock_http_agent_factory.cc
@@ -208,7 +208,7 @@
 }
 
 std::unique_ptr<chrome_cleaner::HttpAgent>
-MockHttpAgentFactory::CreateHttpAgent() {
+MockHttpAgentFactory::CreateHttpAgent() const {
   // Set the configuration index to the next one (one per HttpAgent).
   if (config_->current_index_ == MockHttpAgentConfig::kInvalidIndex)
     config_->current_index_ = 0;
diff --git a/chrome/chrome_cleaner/http/mock_http_agent_factory.h b/chrome/chrome_cleaner/http/mock_http_agent_factory.h
index 5494204..d9f6902a 100644
--- a/chrome/chrome_cleaner/http/mock_http_agent_factory.h
+++ b/chrome/chrome_cleaner/http/mock_http_agent_factory.h
@@ -126,7 +126,7 @@
   explicit MockHttpAgentFactory(MockHttpAgentConfig* config);
 
   // HttpAgentFactory:
-  std::unique_ptr<chrome_cleaner::HttpAgent> CreateHttpAgent() override;
+  std::unique_ptr<chrome_cleaner::HttpAgent> CreateHttpAgent() const override;
 
  private:
   MockHttpAgentConfig* config_{nullptr};
diff --git a/chrome/chrome_cleaner/logging/BUILD.gn b/chrome/chrome_cleaner/logging/BUILD.gn
new file mode 100644
index 0000000..764d74a2
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/BUILD.gn
@@ -0,0 +1,248 @@
+# Copyright 2018 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.
+
+source_set("scoped_timed_task_logger") {
+  sources = [
+    "scoped_timed_task_logger.cc",
+    "scoped_timed_task_logger.h",
+  ]
+
+  deps = [
+    "//base:base",
+  ]
+}
+
+source_set("common") {
+  sources = [
+    "detailed_info_sampler.cc",
+    "detailed_info_sampler.h",
+    "info_sampler.h",
+    "logging_service_api.cc",
+    "logging_service_api.h",
+    "network_checker.h",
+    "registry_logger.cc",
+    "registry_logger.h",
+    "safe_browsing_reporter.cc",
+    "safe_browsing_reporter.h",
+    "scoped_logging.cc",
+    "scoped_logging.h",
+    "utils.cc",
+    "utils.h",
+  ]
+
+  deps = [
+    ":logging_definitions",
+    ":scoped_timed_task_logger",
+    "//base:base",
+    "//chrome/chrome_cleaner//http:http",  # For safe_browsing_reporter
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/constants:version_header",
+    "//chrome/chrome_cleaner/http:http_status_codes",
+    "//chrome/chrome_cleaner/logging/proto:removal_status_proto",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//chrome/chrome_cleaner/settings:settings",
+    "//chrome/chrome_cleaner/settings:settings_types",
+    "//components/chrome_cleaner/public/constants:constants",
+    "//url:url",
+  ]
+
+  public_deps = [
+    "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
+    "//net/traffic_annotation:traffic_annotation",
+  ]
+
+  libs = [
+    "iphlpapi.lib",  # For NotifyAddrChange.
+    "wininet.lib",  # For InternetCheckConnection.
+  ]
+}
+
+source_set("logging_definitions") {
+  sources = [
+    "logging_definitions.h",
+  ]
+}
+
+source_set("dummy_api_keys") {
+  sources = [
+    "api_keys.h",
+    "dummy_api_keys.cc",
+  ]
+}
+
+static_library("cleaner_logging") {
+  sources = [
+    "cleaner_logging_service.cc",
+    "cleaner_logging_service.h",
+    "message_builder.cc",
+    "message_builder.h",
+    "pending_logs_service.cc",
+    "pending_logs_service.h",
+  ]
+
+  deps = [
+    ":dummy_api_keys",
+    "//base",
+    "//chrome/chrome_cleaner/chrome_utils:chrome_util_lib",
+    "//chrome/chrome_cleaner/constants:chrome_cleanup_tool_branding_header",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/constants:version_header",
+    "//chrome/chrome_cleaner/logging/proto:chrome_cleaner_report_proto",
+    "//chrome/chrome_cleaner/logging/proto:removal_status_proto",
+    "//chrome/chrome_cleaner/os:cleaner_os",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//chrome/chrome_cleaner/settings:settings",
+    "//chrome/chrome_cleaner/strings",
+    "//components/chrome_cleaner/public/constants:constants",
+  ]
+
+  public_deps = [
+    ":common",
+  ]
+}
+
+static_library("reporter_logging") {
+  sources = [
+    "reporter_logging_service.cc",
+    "reporter_logging_service.h",
+  ]
+
+  deps = [
+    ":dummy_api_keys",
+    "//base",
+    "//chrome/chrome_cleaner/chrome_utils:chrome_util_lib",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/constants:version_header",
+    "//chrome/chrome_cleaner/logging:noop_logging",
+    "//chrome/chrome_cleaner/logging/proto:reporter_logs_proto",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//chrome/chrome_cleaner/settings:settings",
+    "//components/chrome_cleaner/public/constants:constants",
+  ]
+
+  public_deps = [
+    ":common",
+  ]
+}
+
+static_library("interface_log_service") {
+  sources = [
+    "interface_log_service.cc",
+    "interface_log_service.h",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/constants:version_header",
+    "//chrome/chrome_cleaner/os:common_os",
+  ]
+
+  public_deps = [
+    "//chrome/chrome_cleaner/logging/proto:interface_logger_proto",
+  ]
+
+  all_dependent_configs = [ "//third_party/protobuf:using_proto" ]
+}
+
+static_library("noop_logging") {
+  sources = [
+    "noop_logging_service.cc",
+    "noop_logging_service.h",
+  ]
+
+  deps = [
+    ":common",
+    "//base",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//components/chrome_cleaner/public/constants:constants",
+  ]
+}
+
+source_set("mock_logging_service") {
+  testonly = true
+
+  sources = [
+    "mock_logging_service.cc",
+    "mock_logging_service.h",
+  ]
+  deps = [
+    "//base",
+    "//chrome/chrome_cleaner/logging:common",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//components/chrome_cleaner/public/constants:constants",
+    "//testing/gmock",
+  ]
+}
+
+source_set("test_utils") {
+  testonly = true
+
+  sources = [
+    "test_utils.cc",
+    "test_utils.h",
+  ]
+
+  deps = [
+    ":common",
+    "//base",
+    "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
+    "//url",
+  ]
+}
+
+source_set("unittest_sources") {
+  testonly = true
+
+  sources = [
+    "cleaner_logging_service_unittest.cc",
+    "detailed_info_sampler_unittest.cc",
+    "interface_log_service_unittest.cc",
+    "message_builder_unittest.cc",
+    "pending_logs_service_unittest.cc",
+    "registry_logger_unittest.cc",
+    "reporter_logging_service_unittest.cc",
+    "safe_browsing_reporter_unittest.cc",
+    "scoped_timed_task_logger_unittest.cc",
+    "utils_unittest.cc",
+  ]
+
+  deps = [
+    ":cleaner_logging",
+    ":mock_logging_service",
+    ":reporter_logging",
+    ":test_utils",
+    "//base:base",
+    "//base/test:test_support",
+    "//chrome/chrome_cleaner/constants:common_strings",
+    "//chrome/chrome_cleaner/constants:version_header",
+    "//chrome/chrome_cleaner/http:http",
+    "//chrome/chrome_cleaner/http:mock_http_agent_factory",
+    "//chrome/chrome_cleaner/logging:interface_log_service",
+    "//chrome/chrome_cleaner/logging:scoped_timed_task_logger",
+    "//chrome/chrome_cleaner/logging/proto:chrome_cleaner_report_proto",
+    "//chrome/chrome_cleaner/logging/proto:interface_logger_proto",
+    "//chrome/chrome_cleaner/logging/proto:removal_status_proto",
+    "//chrome/chrome_cleaner/logging/proto:reporter_logs_proto",
+    "//chrome/chrome_cleaner/os:cleaner_os",
+    "//chrome/chrome_cleaner/os:common_os",
+    "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
+    "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//chrome/chrome_cleaner/test:test_branding_header",
+    "//chrome/chrome_cleaner/test:test_pup_data",
+    "//chrome/chrome_cleaner/test:test_util",
+    "//chrome/chrome_cleaner/test/resources:test_resources",
+    "//components/chrome_cleaner/public/constants:constants",
+    "//net/traffic_annotation:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/chrome_cleaner/logging/api_keys.h b/chrome/chrome_cleaner/logging/api_keys.h
new file mode 100644
index 0000000..297d2c87
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/api_keys.h
@@ -0,0 +1,15 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_API_KEYS_H_
+#define CHROME_CHROME_CLEANER_LOGGING_API_KEYS_H_
+
+namespace chrome_cleaner {
+
+extern const char* kSafeBrowsingCleanerUrl;
+extern const char* kSafeBrowsingReporterUrl;
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_API_KEYS_H_
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_definitions.cc b/chrome/chrome_cleaner/logging/cleaner_logging_definitions.cc
new file mode 100644
index 0000000..cfdf9a6
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_definitions.cc
@@ -0,0 +1,16 @@
+// Copyright 2018 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.
+
+// Provides the logging service instance to be used by the cleaner executable.
+
+#include "chrome/chrome_cleaner/logging/cleaner_logging_service.h"
+#include "chrome/chrome_cleaner/logging/logging_definitions.h"
+
+namespace chrome_cleaner {
+
+LoggingServiceAPI* GetLoggingServiceForCurrentBuild() {
+  return CleanerLoggingService::GetInstance();
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service.cc b/chrome/chrome_cleaner/logging/cleaner_logging_service.cc
new file mode 100644
index 0000000..b02032e
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service.cc
@@ -0,0 +1,886 @@
+// Copyright 2018 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/chrome_cleaner/logging/cleaner_logging_service.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/cpu.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/win/i18n.h"
+#include "base/win/windows_version.h"
+#include "chrome/chrome_cleaner/chrome_utils/chrome_util.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleanup_tool_branding.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "chrome/chrome_cleaner/logging/api_keys.h"
+#include "chrome/chrome_cleaner/logging/pending_logs_service.h"
+#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
+#include "chrome/chrome_cleaner/logging/registry_logger.h"
+#include "chrome/chrome_cleaner/logging/utils.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
+#include "chrome/chrome_cleaner/os/rebooter.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/os/resource_util.h"
+#include "chrome/chrome_cleaner/os/system_util.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+#include "chrome/chrome_cleaner/strings/string_util.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+
+namespace chrome_cleaner {
+
+namespace {
+// TODO(joenotcharles): Refer to the report definition in the "data" section.
+constexpr net::NetworkTrafficAnnotationTag kCleanerReportTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("chrome_cleanup_report", R"(
+          semantics {
+            sender: "Chrome Cleanup"
+            description:
+              "Chrome on Windows is able to detect and remove software that "
+              "violates Google's Unwanted Software Policy "
+              "(https://www.google.com/about/unwanted-software-policy.html). "
+              "When potentially unwanted software is detected in the "
+              "background, Chrome offers to remove it. If the user accepts the "
+              "cleanup and chooses to \"Report details to Google\", Chrome "
+              "will upload details of the unwanted software and its removal, "
+              "as well as some details about the system, to help Google track "
+              "the spread of unwanted software. "
+              "The user can also use the settings page to ask Chrome to search "
+              "for unwanted software and remove it. In this case if the user "
+              "chooses \"Report details to Google\", the system details will "
+              "be uploaded to Google whether or not unwanted software is found."
+            trigger:
+              "The user either accepted a prompt to remove unwanted software, "
+              "or went to \"Clean up computer\" in the settings page and chose "
+              "to \"Find and remove harmful software\", and enabled \"Report "
+              "details to Google\"."
+            data:
+              "The user's Chrome version, Windows version, and locale, file "
+              "metadata related to the unwanted software that was detected, "
+              "automatically installed Chrome extensions, and system settings "
+              "commonly used by malicious software as described at "
+              "https://www.google.com/chrome/privacy/whitepaper.html#unwantedsoftware. "
+              "Contents of files are never reported. No user identifiers are "
+              "reported, and common user identifiers found in metadata are "
+              "replaced with generic strings, but it is possible some metadata "
+              "may contain personally identifiable information."
+            destination: GOOGLE_OWNED_SERVICE
+          }
+          policy {
+            cookies_allowed: NO
+            setting:
+              "Chrome Cleanup can be explicitly requested by the user in "
+              "\"Clean up computer\" in the \"Reset and cleanup\" section of "
+              "settings, under Advanced. To disable this report, turn off "
+              "\"Report details to Google\" before choosing \"Find and remove "
+              "harmful software\"."
+            chrome_policy {
+              ChromeCleanupReportingEnabled {
+                ChromeCleanupReportingEnabled: false
+              }
+              ChromeCleanupEnabled {
+                ChromeCleanupEnabled: false
+              }
+            }
+          }
+          comments:
+            "If ChromeCleanupEnabled is set to \"false\", Chrome Cleanup will "
+            "never run, so no reports will be uploaded to Google. Otherwise, "
+            "ChromeCleanupReportingEnabled can be used to override the "
+            "\"Report details to Google\" control: if it is set to \"true\", "
+            "reports will always be sent, and if it is set to \"false\", "
+            "reports will never be sent."
+          )");
+
+// Convert a FileInformation proto object to its corresponding
+// internal::FileInformation struct. This function is thread lock safe.
+void ProtoObjectToFileInformation(const FileInformation& proto_file_information,
+                                  internal::FileInformation* file_information) {
+  DCHECK(file_information);
+  file_information->path = base::UTF8ToUTF16(proto_file_information.path());
+  file_information->creation_date = proto_file_information.creation_date();
+  file_information->last_modified_date =
+      proto_file_information.last_modified_date();
+  file_information->sha256 = proto_file_information.sha256();
+  file_information->size = proto_file_information.size();
+  file_information->company_name =
+      base::UTF8ToUTF16(proto_file_information.company_name());
+  file_information->company_short_name =
+      base::UTF8ToUTF16(proto_file_information.company_short_name());
+  file_information->product_name =
+      base::UTF8ToUTF16(proto_file_information.product_name());
+  file_information->product_short_name =
+      base::UTF8ToUTF16(proto_file_information.product_short_name());
+  file_information->internal_name =
+      base::UTF8ToUTF16(proto_file_information.internal_name());
+  file_information->original_filename =
+      base::UTF8ToUTF16(proto_file_information.original_filename());
+  file_information->file_description =
+      base::UTF8ToUTF16(proto_file_information.file_description());
+  file_information->file_version =
+      base::UTF8ToUTF16(proto_file_information.file_version());
+  file_information->active_file = proto_file_information.active_file();
+}
+
+}  // namespace
+
+void AppendFileInformation(const FileInformation& file,
+                           MessageBuilder* builder) {
+  internal::FileInformation file_information;
+  ProtoObjectToFileInformation(file, &file_information);
+  builder->Add(FileInformationToString(file_information));
+}
+
+void AppendFolderInformation(const FolderInformation& folder,
+                             MessageBuilder* builder) {
+  if (folder.path().empty())
+    return;
+
+  builder->Add(L"path = '", folder.path(), L"'");
+  if (!folder.creation_date().empty())
+    builder->Add(L", folder_creation_date = '", folder.creation_date(), L"'");
+  if (!folder.last_modified_date().empty()) {
+    builder->Add(L", folder_last_modified_date = '",
+                 folder.last_modified_date(), L"'");
+  }
+}
+
+void AppendMatchedFile(const MatchedFile& file, MessageBuilder* builder) {
+  AppendFileInformation(file.file_information(), builder);
+  builder->Add(L", removal_status = ", file.removal_status());
+}
+
+void AppendMatchedRegistryEntry(const MatchedRegistryEntry& registry,
+                                MessageBuilder* builder) {
+  builder->Add(registry.key_path(), L"\\", registry.value_name(), L" ",
+               registry.value_substring());
+}
+
+void AppendScheduledTask(const ScheduledTask& scheduled_task,
+                         MessageBuilder* builder) {
+  MessageBuilder::ScopedIndent scoped_indent(builder);
+  builder->AddLine(scheduled_task.description(), L" (", scheduled_task.name(),
+                   "):");
+
+  MessageBuilder::ScopedIndent scoped_indent_2(builder);
+  builder->AddHeaderLine(L"Actions");
+  for (auto action : scheduled_task.actions()) {
+    MessageBuilder::ScopedIndent scoped_indent(builder);
+    builder->Add(L"File information: ");
+    AppendFileInformation(action.file_information(), builder);
+    builder->NewLine();
+    builder->AddFieldValueLine(L"Working directory: ", action.working_dir());
+    builder->AddFieldValueLine(L"Arguments: ", action.arguments());
+  }
+}
+
+ChromeCleanerReport::CleanerStartup GetCleanerStartupFromCommandLine(
+    const base::CommandLine* command_line) {
+  if (!command_line->HasSwitch(kChromePromptSwitch))
+    return ChromeCleanerReport::CLEANER_STARTUP_NOT_PROMPTED;
+
+  std::string chrome_prompt_string =
+      command_line->GetSwitchValueASCII(kChromePromptSwitch);
+  int chrome_prompt_value = 0;
+  if (base::StringToInt(chrome_prompt_string, &chrome_prompt_value) &&
+      ChromeCleanerReport_CleanerStartup_IsValid(chrome_prompt_value) &&
+      (chrome_prompt_value == ChromeCleanerReport::CLEANER_STARTUP_PROMPTED ||
+       chrome_prompt_value ==
+           ChromeCleanerReport::CLEANER_STARTUP_SHOWN_FROM_MENU ||
+       chrome_prompt_value ==
+           ChromeCleanerReport::CLEANER_STARTUP_USER_INITIATED)) {
+    return static_cast<ChromeCleanerReport::CleanerStartup>(
+        chrome_prompt_value);
+  }
+
+  LOG(ERROR) << "Invalid value passed to --" << kChromePromptSwitch << ": '"
+             << chrome_prompt_string << "'.";
+  return ChromeCleanerReport::CLEANER_STARTUP_UNKNOWN;
+}
+
+CleanerLoggingService* CleanerLoggingService::GetInstance() {
+  return base::Singleton<CleanerLoggingService>::get();
+}
+
+// Please avoid setting any state here, all setup should be done in
+// SetupInitialState to allow Terminate to reset this class to its default
+// state.
+CleanerLoggingService::CleanerLoggingService()
+    : uploads_enabled_(false),
+      initialized_(false),
+      sampler_(DetailedInfoSampler::kDefaultMaxFiles) {}
+
+CleanerLoggingService::~CleanerLoggingService() {
+  // If initialize is called, the function |Terminate| must be called by the
+  // user of this class before deleting this object.
+  DCHECK(!initialized_)
+      << "'Terminate' must be called before deleting CleanerLoggingService.";
+}
+
+void CleanerLoggingService::Initialize(RegistryLogger* registry_logger) {
+  DCHECK(!initialized_) << "CleanerLoggingService already initialized.";
+  EnableUploads(false, registry_logger);
+
+  FileRemovalStatusUpdater::GetInstance()->Clear();
+
+  logging::SetLogMessageHandler(
+      CleanerLoggingService::LogMessageHandlerFunction);
+
+  Settings* settings = Settings::GetInstance();
+  const bool metrics_enabled = settings->metrics_enabled();
+  const bool sber_enabled = settings->sber_enabled();
+  const std::string cleanup_id = settings->cleanup_id();
+  const Engine::Name engine = settings->engine();
+  const std::string engine_version = settings->engine_version();
+
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  const ChromeCleanerReport::CleanerStartup cleaner_startup =
+      GetCleanerStartupFromCommandLine(command_line);
+  const bool chrome_prompt =
+      cleaner_startup == ChromeCleanerReport::CLEANER_STARTUP_PROMPTED;
+
+  int channel = 0;
+  bool has_chrome_channel = GetChromeChannelFromCommandLine(&channel);
+
+  const bool post_reboot = Rebooter::IsPostReboot();
+
+  std::vector<base::string16> languages;
+  base::win::i18n::GetUserPreferredUILanguageList(&languages);
+
+  base::string16 chrome_version_string;
+  bool chrome_version_string_succeeded =
+      RetrieveChromeVersionAndInstalledDomain(&chrome_version_string, nullptr);
+
+  base::CPU cpu_info;
+  std::string cpu_architecture = base::SysInfo::OperatingSystemArchitecture();
+
+  {
+    base::AutoLock lock(raw_log_lines_buffer_lock_);
+    raw_log_lines_buffer_.clear();
+  }
+
+  {
+    base::AutoLock lock(lock_);
+
+    // Ensure that logging report starts in a cleared state.
+    chrome_cleaner_report_.Clear();
+    matched_files_.clear();
+    matched_folders_.clear();
+
+    // Initialize with invalid exit code to identify whether it was set or not.
+    chrome_cleaner_report_.set_exit_code(RESULT_CODE_PENDING);
+    chrome_cleaner_report_.set_intermediate_log(true);
+    chrome_cleaner_report_.set_uma_user(metrics_enabled);
+    chrome_cleaner_report_.set_sber_enabled(sber_enabled);
+    chrome_cleaner_report_.set_post_reboot(post_reboot);
+    // TODO(veranika): this field is deprecated. Stop reporting it.
+    chrome_cleaner_report_.set_chrome_prompt(chrome_prompt);
+    if (cleaner_startup != ChromeCleanerReport::CLEANER_STARTUP_UNSPECIFIED)
+      chrome_cleaner_report_.set_cleaner_startup(cleaner_startup);
+    chrome_cleaner_report_.set_cleanup_id(cleanup_id);
+
+    // Set invariant environment / machine data.
+    ChromeCleanerReport_EnvironmentData* env_data =
+        chrome_cleaner_report_.mutable_environment();
+    env_data->set_windows_version(base::win::GetVersion());
+    env_data->set_cleaner_version(CHROME_VERSION_UTF8_STRING);
+    if (languages.size() > 0)
+      env_data->set_default_locale(base::WideToUTF8(languages[0]));
+    env_data->set_detailed_system_report(false);
+    env_data->set_bitness(IsX64Process() ? 64 : 32);
+
+    if (chrome_version_string_succeeded)
+      env_data->set_chrome_version(base::WideToUTF8(chrome_version_string));
+
+    if (has_chrome_channel)
+      env_data->set_chrome_channel(channel);
+
+    ChromeCleanerReport_EnvironmentData_Machine* machine =
+        env_data->mutable_machine();
+    machine->set_cpu_architecture(cpu_architecture);
+    machine->set_cpu_vendor(cpu_info.vendor_name());
+    machine->set_cpuid(cpu_info.signature());
+
+    env_data->mutable_engine()->set_name(engine);
+    if (!engine_version.empty())
+      env_data->mutable_engine()->set_version(engine_version);
+
+    initialized_ = true;
+  }
+}
+
+void CleanerLoggingService::Terminate() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(initialized_) << "Logging service is not initialized.";
+
+  size_t num_log_lines = 0;
+  {
+    // Get all values from the logging report we are interested in before
+    // clearing it.
+    base::AutoLock lock(raw_log_lines_buffer_lock_);
+    num_log_lines = raw_log_lines_buffer_.size();
+  }
+
+  if (num_log_lines > 0) {
+    LOG(WARNING) << "At least the last " << num_log_lines
+                 << " log lines have not been uploaded to Safe Browsing.";
+  }
+
+  {
+    base::AutoLock lock(raw_log_lines_buffer_lock_);
+    raw_log_lines_buffer_.clear();
+    initialized_ = false;
+  }
+}
+
+void CleanerLoggingService::SendLogsToSafeBrowsing(
+    const UploadResultCallback& done_callback,
+    RegistryLogger* registry_logger) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(initialized_);
+
+  // If reporting is not enabled or required, call |done_callback|
+  if (!uploads_enabled() || !IsReportingNeeded())
+    return done_callback.Run(false);  // false since no logs were uploaded.
+
+  ChromeCleanerReport chrome_cleaner_report;
+  GetCurrentChromeCleanerReport(&chrome_cleaner_report);
+
+  {
+    base::AutoLock lock(lock_);
+    // No need to repeat the log lines in subsequent uploads.
+    chrome_cleaner_report_.clear_raw_log_line();
+    chrome_cleaner_report_.mutable_environment()->set_detailed_system_report(
+        false);
+  }
+
+  // TODO(csharp): Move this to the main controller.
+  // Register a task to try again if we ever fail half way through uploading
+  // |chrome_cleaner_report|. Will be cleared upon success in
+  // |OnReportUploadResult|.
+  ClearTempLogFile(registry_logger);
+  PendingLogsService::ScheduleLogsUploadTask(PRODUCT_SHORTNAME_STRING,
+                                             chrome_cleaner_report,
+                                             &temp_log_file_, registry_logger);
+
+  std::string serialized_report;
+  if (!chrome_cleaner_report.SerializeToString(&serialized_report)) {
+    LOG(WARNING) << "Failed to serialize report";
+    return done_callback.Run(false);  // false since no logs were uploaded.
+  }
+
+  SafeBrowsingReporter::UploadReport(
+      base::BindRepeating(&CleanerLoggingService::OnReportUploadResult,
+                          base::Unretained(this), done_callback,
+                          registry_logger),
+      kSafeBrowsingCleanerUrl, serialized_report,
+      kCleanerReportTrafficAnnotation);
+}
+
+void CleanerLoggingService::CancelWaitForShutdown() {
+  SafeBrowsingReporter::CancelWaitForShutdown();
+}
+
+void CleanerLoggingService::EnableUploads(bool enable,
+                                          RegistryLogger* registry_logger) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (enable == uploads_enabled_)
+    return;
+  uploads_enabled_ = enable;
+
+  // Make sure not to keep any scheduled logs upload if the user opts-out of
+  // logs upload. TODO(csharp): maybe we should also clear all other pending
+  // logs, not just ours.
+  if (!enable)
+    ClearTempLogFile(registry_logger);
+}
+
+bool CleanerLoggingService::uploads_enabled() const {
+  return uploads_enabled_;
+}
+
+void CleanerLoggingService::SetDetailedSystemReport(
+    bool detailed_system_report) {
+  base::AutoLock lock(lock_);
+  chrome_cleaner_report_.mutable_environment()->set_detailed_system_report(
+      detailed_system_report);
+}
+
+bool CleanerLoggingService::detailed_system_report_enabled() const {
+  return chrome_cleaner_report_.environment().detailed_system_report();
+}
+
+void CleanerLoggingService::AddFoundUwS(const std::string& found_uws_name) {
+  base::AutoLock lock(lock_);
+  chrome_cleaner_report_.add_found_uws(found_uws_name);
+}
+
+void CleanerLoggingService::AddDetectedUwS(const PUPData::PUP* found_uws,
+                                           UwSDetectedFlags flags) {
+  DCHECK(found_uws);
+  UwS detected_uws =
+      PUPToUwS(found_uws, flags, /*cleaning_files=*/true, &sampler_);
+  AddDetectedUwS(detected_uws);
+}
+
+void CleanerLoggingService::AddDetectedUwS(const UwS& uws) {
+  base::AutoLock lock(lock_);
+  UwS* added_uws = chrome_cleaner_report_.add_detected_uws();
+  *added_uws = uws;
+  UpdateMatchedFilesAndFoldersMaps(added_uws);
+}
+
+void CleanerLoggingService::SetExitCode(ResultCode exit_code) {
+  ResultCode previous_exit_code = RESULT_CODE_INVALID;
+  {
+    base::AutoLock lock(lock_);
+    previous_exit_code =
+        static_cast<ResultCode>(chrome_cleaner_report_.exit_code());
+    chrome_cleaner_report_.set_exit_code(exit_code);
+    // Once an exit code has been provided, this is not an intermediate log
+    // anymore.
+    chrome_cleaner_report_.set_intermediate_log(false);
+  }
+  // The DCHECK can't be under |lock_|. The only valid reason to overwrite a non
+  // pending exit code, is when we failed to read pending upload log files.
+  DCHECK(previous_exit_code == RESULT_CODE_PENDING ||
+         exit_code == RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE);
+}
+
+void CleanerLoggingService::AddLoadedModule(
+    const base::string16& name,
+    ModuleHost module_host,
+    const internal::FileInformation& file_information) {
+  FileInformation reported_file_information;
+  FileInformationToProtoObject(file_information, &reported_file_information);
+
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport::SystemReport::LoadedModule* loaded_module =
+      chrome_cleaner_report_.mutable_system_report()->add_loaded_modules();
+  loaded_module->set_name(base::UTF16ToUTF8(name));
+  loaded_module->set_host(module_host);
+  *loaded_module->mutable_file_information() = reported_file_information;
+}
+
+void CleanerLoggingService::AddService(
+    const base::string16& display_name,
+    const base::string16& service_name,
+    const internal::FileInformation& file_information) {
+  FileInformation reported_file_information;
+  FileInformationToProtoObject(file_information, &reported_file_information);
+
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport::SystemReport::Service* service =
+      chrome_cleaner_report_.mutable_system_report()->add_services();
+  service->set_display_name(base::UTF16ToUTF8(display_name));
+  service->set_service_name(base::UTF16ToUTF8(service_name));
+  *service->mutable_file_information() = reported_file_information;
+}
+
+void CleanerLoggingService::AddInstalledProgram(
+    const base::FilePath& folder_path) {
+  FolderInformation folder_information;
+  if (!RetrieveFolderInformation(folder_path, &folder_information))
+    return;
+
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport::SystemReport::InstalledProgram* installed_program =
+      chrome_cleaner_report_.mutable_system_report()->add_installed_programs();
+  *installed_program->mutable_folder_information() = folder_information;
+}
+
+void CleanerLoggingService::AddProcess(
+    const base::string16& name,
+    const internal::FileInformation& file_information) {
+  FileInformation reported_file_information;
+  FileInformationToProtoObject(file_information, &reported_file_information);
+
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport::SystemReport::Process* process =
+      chrome_cleaner_report_.mutable_system_report()->add_processes();
+  process->set_name(base::UTF16ToUTF8(name));
+  *process->mutable_file_information() = reported_file_information;
+}
+
+void CleanerLoggingService::AddRegistryValue(
+    const internal::RegistryValue& registry_value,
+    const std::vector<internal::FileInformation>& file_informations) {
+  RegistryValue new_registry_value;
+  new_registry_value.set_key_path(base::UTF16ToUTF8(registry_value.key_path));
+  new_registry_value.set_value_name(
+      base::UTF16ToUTF8(registry_value.value_name));
+  new_registry_value.set_data(base::UTF16ToUTF8(registry_value.data));
+
+  for (const auto& file_information : file_informations) {
+    FileInformation* reported_file_information =
+        new_registry_value.add_file_informations();
+    FileInformationToProtoObject(file_information, reported_file_information);
+  }
+
+  base::AutoLock lock(lock_);
+  *chrome_cleaner_report_.mutable_system_report()->add_registry_values() =
+      new_registry_value;
+}
+
+void CleanerLoggingService::AddLayeredServiceProvider(
+    const std::vector<base::string16>& guids,
+    const internal::FileInformation& file_information) {
+  ChromeCleanerReport_SystemReport_LayeredServiceProvider
+      layered_service_provider;
+  FileInformation* reported_file_information =
+      layered_service_provider.mutable_file_information();
+  FileInformationToProtoObject(file_information, reported_file_information);
+
+  for (const auto& guid : guids)
+    layered_service_provider.add_guids(base::UTF16ToUTF8(guid));
+
+  base::AutoLock lock(lock_);
+  *chrome_cleaner_report_.mutable_system_report()
+       ->add_layered_service_providers() = layered_service_provider;
+}
+
+void CleanerLoggingService::SetWinInetProxySettings(
+    const base::string16& config,
+    const base::string16& bypass,
+    const base::string16& auto_config_url,
+    bool autodetect) {
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport_SystemReport_SystemProxySettings*
+      win_inet_proxy_settings = chrome_cleaner_report_.mutable_system_report()
+                                    ->mutable_win_inet_proxy_settings();
+  win_inet_proxy_settings->set_config(base::UTF16ToUTF8(config));
+  win_inet_proxy_settings->set_bypass(base::UTF16ToUTF8(bypass));
+  win_inet_proxy_settings->set_auto_config_url(
+      base::UTF16ToUTF8(auto_config_url));
+  win_inet_proxy_settings->set_autodetect(autodetect);
+}
+
+void CleanerLoggingService::SetWinHttpProxySettings(
+    const base::string16& config,
+    const base::string16& bypass) {
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport_SystemReport_SystemProxySettings*
+      win_http_proxy_settings = chrome_cleaner_report_.mutable_system_report()
+                                    ->mutable_win_http_proxy_settings();
+  win_http_proxy_settings->set_config(base::UTF16ToUTF8(config));
+  win_http_proxy_settings->set_bypass(base::UTF16ToUTF8(bypass));
+}
+
+void CleanerLoggingService::AddInstalledExtension(
+    const base::string16& extension_id,
+    ExtensionInstallMethod install_method) {
+  base::AutoLock lock(lock_);
+  ChromeCleanerReport_SystemReport_InstalledExtension* installed_extension =
+      chrome_cleaner_report_.mutable_system_report()
+          ->add_installed_extensions();
+  installed_extension->set_extension_id(base::UTF16ToUTF8(extension_id));
+  installed_extension->set_install_method(install_method);
+}
+
+void CleanerLoggingService::AddScheduledTask(
+    const base::string16& name,
+    const base::string16& description,
+    const std::vector<internal::FileInformation>& actions) {
+  ScheduledTask scheduled_task;
+  scheduled_task.set_name(base::UTF16ToUTF8(name));
+  scheduled_task.set_description(base::UTF16ToUTF8(description));
+
+  for (const auto& action : actions) {
+    FileInformation* reported_action =
+        scheduled_task.add_actions()->mutable_file_information();
+    FileInformationToProtoObject(action, reported_action);
+  }
+
+  base::AutoLock lock(lock_);
+  *chrome_cleaner_report_.mutable_system_report()->add_scheduled_tasks() =
+      scheduled_task;
+}
+
+void CleanerLoggingService::LogProcessInformation(
+    SandboxType process_type,
+    const SystemResourceUsage& usage) {
+  ProcessInformation info =
+      GetProcessInformationProtoObject(process_type, usage);
+  base::AutoLock lock(lock_);
+  chrome_cleaner_report_.add_process_information()->Swap(&info);
+}
+
+bool CleanerLoggingService::AllExpectedRemovalsConfirmed() const {
+  FileRemovalStatusUpdater* status_updater =
+      FileRemovalStatusUpdater::GetInstance();
+
+  base::AutoLock lock(lock_);
+  for (const UwS& uws : chrome_cleaner_report_.detected_uws()) {
+    if (uws.state() != UwS::REMOVABLE)
+      continue;
+    for (const MatchedFile& file : uws.files()) {
+      base::string16 sanitized_path =
+          base::UTF8ToUTF16(file.file_information().path());
+      RemovalStatus removal_status =
+          status_updater->GetRemovalStatusOfSanitizedPath(sanitized_path);
+
+      // If the removal status was never set in FileRemovalStatusUpdater, fall
+      // back to the status that was originally set when the MatchedFile object
+      // was created.
+      if (removal_status == REMOVAL_STATUS_UNSPECIFIED)
+        removal_status = file.removal_status();
+
+      // Non-active files are collected in the report for later auditing, but
+      // not expected to be removed.
+      if (removal_status == REMOVAL_STATUS_MATCHED_ONLY &&
+          !file.file_information().active_file()) {
+        continue;
+      }
+
+      if (removal_status != REMOVAL_STATUS_REMOVED &&
+          removal_status != REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL &&
+          removal_status != REMOVAL_STATUS_NOT_FOUND &&
+          removal_status != REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL_FALLBACK &&
+          removal_status != REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+std::string CleanerLoggingService::RawReportContent() {
+  ChromeCleanerReport chrome_cleaner_report;
+  GetCurrentChromeCleanerReport(&chrome_cleaner_report);
+
+  std::string chrome_cleaner_report_string;
+  chrome_cleaner_report.SerializeToString(&chrome_cleaner_report_string);
+  return chrome_cleaner_report_string;
+}
+
+bool CleanerLoggingService::ReadContentFromFile(
+    const base::FilePath& log_file) {
+  std::string proto_string;
+  if (!base::ReadFileToString(log_file, &proto_string)) {
+    LOG(ERROR) << "Can't read content of '" << SanitizePath(log_file) << "'.";
+    return false;
+  } else if (proto_string.empty()) {
+    LOG(ERROR) << "Empty log file: " << SanitizePath(log_file);
+    return false;
+  }
+  bool succeeded = false;
+  {
+    base::AutoLock lock(lock_);
+    succeeded = chrome_cleaner_report_.ParseFromString(proto_string);
+    if (succeeded) {
+      matched_files_.clear();
+      matched_folders_.clear();
+      for (UwS& detected_uws : *chrome_cleaner_report_.mutable_detected_uws())
+        UpdateMatchedFilesAndFoldersMaps(&detected_uws);
+    }
+  }
+  if (!succeeded) {
+    LOG(ERROR) << "Read invalid protobuf from '" << SanitizePath(log_file)
+               << "'.";
+    return false;
+  }
+  return true;
+}
+
+void CleanerLoggingService::ScheduleFallbackLogsUpload(
+    RegistryLogger* registry_logger,
+    ResultCode result_code) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(initialized_);
+  // Even if we don't upload logs, we can still let Chrome UMA know we got to
+  // this stage.
+  DCHECK(registry_logger);
+  registry_logger->WriteExitCode(result_code);
+
+  if (!uploads_enabled())
+    return;
+
+  ChromeCleanerReport chrome_cleaner_report;
+  GetCurrentChromeCleanerReport(&chrome_cleaner_report);
+  chrome_cleaner_report.set_exit_code(result_code);
+
+  ClearTempLogFile(registry_logger);
+  PendingLogsService::ScheduleLogsUploadTask(PRODUCT_SHORTNAME_STRING,
+                                             chrome_cleaner_report,
+                                             &temp_log_file_, registry_logger);
+}
+
+void CleanerLoggingService::OnReportUploadResult(
+    const UploadResultCallback& done_callback,
+    RegistryLogger* registry_logger,
+    SafeBrowsingReporter::Result result,
+    const std::string& serialized_report,
+    std::unique_ptr<ChromeFoilResponse> response) {
+  DCHECK(registry_logger);
+  if (result == SafeBrowsingReporter::Result::UPLOAD_SUCCESS)
+    ClearTempLogFile(registry_logger);
+  done_callback.Run(result == SafeBrowsingReporter::Result::UPLOAD_SUCCESS);
+}
+
+bool CleanerLoggingService::IsReportingNeeded() const {
+  Settings* settings = Settings::GetInstance();
+  if (settings->execution_mode() != ExecutionMode::kScanning &&
+      settings->execution_mode() != ExecutionMode::kCleanup) {
+    NOTREACHED();
+    return false;
+  }
+
+  // Raw log lines are collected in vector raw_log_lines_buffer_ and moved to
+  // proto field raw_log_line whenever the proto is generated. Logs should be
+  // uploaded if either is non-empty.
+
+  {
+    base::AutoLock lock(raw_log_lines_buffer_lock_);
+    if (!raw_log_lines_buffer_.empty())
+      return true;
+  }
+
+  {
+    base::AutoLock lock(lock_);
+    return chrome_cleaner_report_.exit_code() != RESULT_CODE_PENDING ||
+           chrome_cleaner_report_.raw_log_line_size() > 0 ||
+           chrome_cleaner_report_.found_uws_size() > 0;
+  }
+}
+
+void CleanerLoggingService::ClearTempLogFile(RegistryLogger* registry_logger) {
+  if (!temp_log_file_.empty()) {
+    PendingLogsService::ClearPendingLogFile(PRODUCT_SHORTNAME_STRING,
+                                            temp_log_file_, registry_logger);
+    temp_log_file_.clear();
+  }
+}
+
+// static.
+bool CleanerLoggingService::LogMessageHandlerFunction(int severity,
+                                                      const char* file,
+                                                      int line,
+                                                      size_t message_start,
+                                                      const std::string& str) {
+  CleanerLoggingService* logging_service = CleanerLoggingService::GetInstance();
+  std::string utf8_str;
+  if (base::IsStringUTF8(str))
+    utf8_str = str;
+  else
+    utf8_str = RemoveInvalidUTF8Chars(str);
+
+  base::AutoLock lock(logging_service->raw_log_lines_buffer_lock_);
+  logging_service->raw_log_lines_buffer_.push_back(utf8_str);
+
+  // Returning false, pretending the event wasn't handled here, let the other
+  // handlers receive it.
+  return false;
+}
+
+void CleanerLoggingService::GetCurrentChromeCleanerReport(
+    ChromeCleanerReport* chrome_cleaner_report) {
+  DCHECK(chrome_cleaner_report);
+
+  UpdateFileRemovalStatuses();
+
+  std::vector<std::string> raw_log_lines_to_send;
+  {
+    base::AutoLock lock(raw_log_lines_buffer_lock_);
+    raw_log_lines_to_send.reserve(raw_log_lines_buffer_.size());
+    raw_log_lines_to_send.insert(raw_log_lines_to_send.begin(),
+                                 raw_log_lines_buffer_.begin(),
+                                 raw_log_lines_buffer_.end());
+    raw_log_lines_buffer_.clear();
+  }
+
+  {
+    base::AutoLock lock(lock_);
+    for (const std::string& line : raw_log_lines_to_send)
+      chrome_cleaner_report_.add_raw_log_line(line);
+    chrome_cleaner_report->CopyFrom(chrome_cleaner_report_);
+  }
+}
+
+void CleanerLoggingService::UpdateMatchedFilesAndFoldersMaps(UwS* added_uws) {
+  for (MatchedFile& file : *added_uws->mutable_files())
+    matched_files_[file.file_information().path()].push_back(&file);
+  for (MatchedFolder& folder : *added_uws->mutable_folders())
+    matched_folders_[folder.folder_information().path()].push_back(&folder);
+}
+
+void CleanerLoggingService::UpdateFileRemovalStatuses() {
+  for (const auto& path_and_status :
+       FileRemovalStatusUpdater::GetInstance()->GetAllRemovalStatuses()) {
+    std::string sanitized_path = base::UTF16ToUTF8(path_and_status.first);
+    FileRemovalStatusUpdater::FileRemovalStatus status = path_and_status.second;
+    DCHECK(status.removal_status != REMOVAL_STATUS_UNSPECIFIED);
+
+    bool known_matched_file = true;
+    {
+      base::AutoLock lock(lock_);
+
+      auto file_it = matched_files_.find(sanitized_path);
+      auto folder_it = matched_folders_.find(sanitized_path);
+
+      if (file_it != matched_files_.end()) {
+        for (MatchedFile* matched_file : file_it->second)
+          matched_file->set_removal_status(status.removal_status);
+      } else if (folder_it != matched_folders_.end()) {
+        for (MatchedFolder* matched_folder : folder_it->second)
+          matched_folder->set_removal_status(status.removal_status);
+      } else {
+        known_matched_file = false;
+      }
+    }
+
+    // Files that were deleted should be matched with an UwS by calling
+    // AddDetectedUwS before logging. But there are some circumstances where
+    // AddDetectedUwS is not called, such as when there are no existing files
+    // for that UwS at the moment it is logged, so it should not be marked
+    // removable. These should match up to circumstances where no files are
+    // deleted, but just in case, log any file deletions that aren't matched to
+    // an UwS for investigation.
+    if (!known_matched_file) {
+      FileInformation file_information;
+      bool got_file_information = GetFileInformationProtoObject(
+          status.path, /*detailed_information=*/true, &file_information);
+
+      base::AutoLock lock(lock_);
+      // Since the item might have been deleted at this point, just assume it
+      // was a file.
+      MatchedFile* file = nullptr;
+      auto* unknown_uws = chrome_cleaner_report_.mutable_unknown_uws();
+      for (int i = 0; i < unknown_uws->files_size(); i++) {
+        if (unknown_uws->files(i).file_information().path() == sanitized_path) {
+          file = unknown_uws->mutable_files(i);
+          break;
+        }
+      }
+
+      if (file == nullptr)
+        file = chrome_cleaner_report_.mutable_unknown_uws()->add_files();
+
+      if (got_file_information) {
+        *file->mutable_file_information() = file_information;
+      } else {
+        // If we can't get the detailed file information, still record at least
+        // the path.
+        file->mutable_file_information()->set_path(sanitized_path);
+      }
+      file->set_removal_status(status.removal_status);
+    }
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service.h b/chrome/chrome_cleaner/logging/cleaner_logging_service.h
new file mode 100644
index 0000000..80fce80
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service.h
@@ -0,0 +1,194 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_CLEANER_LOGGING_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_CLEANER_LOGGING_SERVICE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/memory/singleton.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+#include "chrome/chrome_cleaner/logging/detailed_info_sampler.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/logging/message_builder.h"
+#include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace chrome_cleaner {
+
+// Auxiliary functions to convert Cleaner proto messages to their string
+// equivalent and append the result to a MessageBuilder.
+void AppendMatchedFile(const MatchedFile& file, MessageBuilder* builder);
+void AppendFolderInformation(const FolderInformation& folder,
+                             MessageBuilder* builder);
+void AppendMatchedRegistryEntry(const MatchedRegistryEntry& registry,
+                                MessageBuilder* builder);
+
+// Return the enumerator corresponding to the value of the chrome prompt flag
+// as set on the command line. Used to set the cleaner_startup field in the
+// cleaner logs. Exposed for testing.
+ChromeCleanerReport::CleanerStartup GetCleanerStartupFromCommandLine(
+    const base::CommandLine* command_line);
+
+// Manage where the logs are sent, and expose an API for more specific logging.
+class CleanerLoggingService : public LoggingServiceAPI {
+ public:
+  // Return the singleton instance which will get destroyed by the AtExitMgr.
+  static CleanerLoggingService* GetInstance();
+
+  // LoggingServiceAPI:
+  void Initialize(RegistryLogger* registry_logger) override;
+  void Terminate() override;
+
+  void SendLogsToSafeBrowsing(const UploadResultCallback& done_callback,
+                              RegistryLogger* registry_logger) override;
+  void CancelWaitForShutdown() override;
+  void EnableUploads(bool enabled, RegistryLogger* registry_logger) override;
+  bool uploads_enabled() const override;
+  void SetDetailedSystemReport(bool detailed_system_report) override;
+  bool detailed_system_report_enabled() const override;
+  void AddFoundUwS(const std::string& found_uws_name) override;
+  void AddDetectedUwS(const PUPData::PUP* found_uws,
+                      UwSDetectedFlags flags) override;
+  void AddDetectedUwS(const UwS& uws) override;
+  void SetExitCode(ResultCode exit_code) override;
+  void AddLoadedModule(
+      const base::string16& name,
+      ModuleHost host,
+      const internal::FileInformation& file_information) override;
+  void AddInstalledProgram(const base::FilePath& folder_path) override;
+  void AddService(const base::string16& display_name,
+                  const base::string16& service_name,
+                  const internal::FileInformation& file_information) override;
+  void AddProcess(const base::string16& name,
+                  const internal::FileInformation& file_information) override;
+  void AddRegistryValue(
+      const internal::RegistryValue& registry_value,
+      const std::vector<internal::FileInformation>& file_informations) override;
+  void AddLayeredServiceProvider(
+      const std::vector<base::string16>& guids,
+      const internal::FileInformation& file_information) override;
+  void SetWinInetProxySettings(const base::string16& config,
+                               const base::string16& bypass,
+                               const base::string16& auto_config_url,
+                               bool autodetect) override;
+  void SetWinHttpProxySettings(const base::string16& config,
+                               const base::string16& bypass) override;
+  void AddInstalledExtension(const base::string16& extension_id,
+                             ExtensionInstallMethod install_method) override;
+  void AddScheduledTask(
+      const base::string16& name,
+      const base::string16& description,
+      const std::vector<internal::FileInformation>& actions) override;
+  void LogProcessInformation(SandboxType process_type,
+                             const SystemResourceUsage& usage) override;
+
+  bool AllExpectedRemovalsConfirmed() const override;
+
+  std::string RawReportContent() override;
+  bool ReadContentFromFile(const base::FilePath& log_file) override;
+  void ScheduleFallbackLogsUpload(RegistryLogger* registry_logger,
+                                  ResultCode result_code) override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<CleanerLoggingService>;
+
+  CleanerLoggingService();
+  ~CleanerLoggingService() override;
+
+  // Callback for |safe_browsing_reporter_|.
+  void OnReportUploadResult(const UploadResultCallback& done_callback,
+                            RegistryLogger* registry_logger,
+                            SafeBrowsingReporter::Result result,
+                            const std::string& serialized_report,
+                            std::unique_ptr<ChromeFoilResponse> response);
+
+  // Return true if |chrome_cleaner_report_|'s values have changed since it has
+  // been cleared.
+  bool IsReportingNeeded() const;
+
+  // Clears the temporary log file and it's associated scheduled task.
+  void ClearTempLogFile(RegistryLogger* registry_logger);
+
+  // Callback for logging::SetLogMessageHandler.
+  static bool LogMessageHandlerFunction(int severity,
+                                        const char* file,
+                                        int line,
+                                        size_t message_start,
+                                        const std::string& str);
+
+  // Returns a copy of |chrome_cleaner_report_| in |chrome_cleaner_report|, with
+  // an updated Client ID.
+  void GetCurrentChromeCleanerReport(
+      ChromeCleanerReport* chrome_cleaner_report);
+
+  // Adds all files and folder paths to the corresponding FileInformation and
+  // FolderInformation objects. Expects the lock to be held by the caller.
+  void UpdateMatchedFilesAndFoldersMaps(UwS* added_uws);
+
+  // Reads the removal status of all files and folders from
+  // FileRemovalStatusUpdater and updates them in the report.
+  void UpdateFileRemovalStatuses();
+
+  // Cache of the strings extracted from the proper locale resource.
+  mutable std::map<uint32_t, base::string16> resource_strings_cache_;
+
+  // Any access to |chrome_cleaner_report_|, |matched_files_|, and
+  // |matched_folders_| must be protected by |lock_|. While under this lock, no
+  // outside function calls and no logging (this includes DCHECK) should be
+  // made. Trying to log while under this lock will result in a deadlock, since
+  // adding log lines to our raw_log_lines field requires acquiring the lock,
+  // and we do not allow reentrancy.
+  mutable base::Lock lock_;
+  ChromeCleanerReport chrome_cleaner_report_;
+  // Map files and folder names to the corresponding MatchedFile and
+  // MatchedFolder objects, to allow updates after paths are collected by
+  // the scanner (e.g. to update removal status according to information given
+  // by the cleaner). Each file path is associated with a vector in case it's
+  // matched for more than one UwS.
+  std::unordered_map<std::string, std::vector<MatchedFile*>> matched_files_;
+  std::unordered_map<std::string, std::vector<MatchedFolder*>> matched_folders_;
+
+  // Saves raw log lines that will be uploaded in the cleaner report.
+  std::vector<std::string> raw_log_lines_buffer_;
+  mutable base::Lock raw_log_lines_buffer_lock_;
+
+  // |uploads_enabled| must only be accessed from the thread that created the
+  // CleanerLoggingService.
+  THREAD_CHECKER(thread_checker_);
+
+  // The path to the temporary log file to retry uploading in case we fail.
+  // Set as we register a task to retry the logs upload and cleared if another
+  // one is scheduled at a later stage and when the logs upload succeeds.
+  base::FilePath temp_log_file_;
+
+  // Default to false, so EnableUploads must be called to set it to true.
+  bool uploads_enabled_;
+
+  // Whether the logging service has been initialized.
+  bool initialized_;
+
+  // Sampler to choose which files to log detailed info for.
+  DetailedInfoSampler sampler_;
+
+  DISALLOW_COPY_AND_ASSIGN(CleanerLoggingService);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_CLEANER_LOGGING_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc b/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc
new file mode 100644
index 0000000..7232959
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/cleaner_logging_service_unittest.cc
@@ -0,0 +1,1437 @@
+// Copyright 2018 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/chrome_cleaner/logging/cleaner_logging_service.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_path_override.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_reg_util_win.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/http/http_agent_factory.h"
+#include "chrome/chrome_cleaner/http/mock_http_agent_factory.h"
+#include "chrome/chrome_cleaner/logging/pending_logs_service.h"
+#include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
+#include "chrome/chrome_cleaner/logging/registry_logger.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/logging/test_utils.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/os/task_scheduler.h"
+#include "chrome/chrome_cleaner/proto/shared_pup_enums.pb.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "chrome/chrome_cleaner/test/test_file_util.h"
+#include "chrome/chrome_cleaner/test/test_name_helper.h"
+#include "chrome/chrome_cleaner/test/test_settings_util.h"
+#include "chrome/chrome_cleaner/test/test_task_scheduler.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+using testing::_;
+using testing::Return;
+
+const internal::FileInformation kFileInformation1(L"some/path/something.tmp",
+                                                  "3/1/2016",
+                                                  "3/3/2016",
+                                                  "somedigest1234",
+                                                  9876,
+                                                  L"Company Name",
+                                                  L"CNShort",
+                                                  L"Product Name",
+                                                  L"PNShort",
+                                                  L"Internal Name",
+                                                  L"Something_Original.tmp",
+                                                  L"Very descriptive",
+                                                  L"42.1.2");
+
+const internal::FileInformation kFileInformation2(
+    L"some/path/somethingelse.tmp",
+    "2/1/2016",
+    "2/3/2016",
+    "someotherdigest1234",
+    6543,
+    L"Company Name 2",
+    L"CN2Short",
+    L"Product Name2",
+    L"PN2Short",
+    L"Internal Name2",
+    L"Something_Original2.tmp",
+    L"Very descriptive2",
+    L"43.2.3");
+
+const wchar_t kMatchedFileToStringExpectedString[] =
+    L"path = 'some/path/something.tmp', file_creation_date = "
+    L"'3/1/2016', file_last_modified_date = '3/3/2016', digest = "
+    L"'somedigest1234', size = '9876', company_name = 'Company Name', "
+    L"company_short_name = 'CNShort', product_name = 'Product Name', "
+    L"product_short_name = 'PNShort', internal_name = 'Internal Name', "
+    L"original_filename = 'Something_Original.tmp', file_description = 'Very "
+    L"descriptive', file_version = '42.1.2', active_file = '0', removal_status "
+    L"= 0";
+
+const wchar_t kMatchedRegistryEntryKey[] = L"123";
+const wchar_t kMatchedRegistryEntryValueName[] = L"Value Name";
+const wchar_t kMatchedRegistryEntryValueSubstring[] = L"Value Substring";
+const wchar_t kMatchedRegistryEntryToStringExpectedString[] =
+    L"123\\Value Name Value Substring";
+const internal::RegistryValue kRegistryValue1 = {
+    L"HKCU\\something\\that\\exists", L"ValueName", L"Some content"};
+const internal::RegistryValue kRegistryValue2 = {
+    L"HKCU\\something\\else", L"ValueName2", L"Some other content"};
+const wchar_t kSystemProxySettingsConfig[] = L"http://someconfigurl.com/hello";
+const wchar_t kSystemProxySettingsBypass[] = L"http://somebypassurl.com/hello";
+
+const char kFileContent1[] = "This is the file content.";
+
+constexpr PUPData::UwSSignature kMatchedUwSSignature{
+    /*id=*/1, PUPData::FLAGS_NONE, "Observed/matched_uws"};
+
+constexpr PUPData::UwSSignature kRemovedUwSSignature{
+    /*id=*/2, PUPData::FLAGS_ACTION_REMOVE, "Removed/removed_uws"};
+
+constexpr PUPData::UwSSignature kMatchedUwSSlowSignature{
+    /*id=*/3, PUPData::FLAGS_NONE, "Observed/matched_uws_slow_"};
+
+void CompareRegistryEntries(
+    const std::vector<PUPData::RegistryFootprint>& expanded_registry_footprints,
+    const ::google::protobuf::RepeatedPtrField<MatchedRegistryEntry>&
+        registry_entries) {
+  ASSERT_EQ(expanded_registry_footprints.size(),
+            static_cast<size_t>(registry_entries.size()));
+  for (unsigned int i = 0; i < expanded_registry_footprints.size(); ++i) {
+    EXPECT_EQ(
+        base::WideToUTF8(expanded_registry_footprints[i].key_path.FullPath()),
+        registry_entries.Get(i).key_path());
+    EXPECT_EQ(base::WideToUTF8(expanded_registry_footprints[i].value_name),
+              registry_entries.Get(i).value_name());
+    EXPECT_EQ(base::WideToUTF8(expanded_registry_footprints[i].value_substring),
+              registry_entries.Get(i).value_substring());
+  }
+}
+
+void CompareUwSData(const PUPData::PUP& uws, const UwS& log_uws) {
+  EXPECT_EQ(uws.signature().name, log_uws.name());
+  EXPECT_EQ(uws.signature().id, log_uws.id());
+
+  ASSERT_EQ(uws.expanded_disk_footprints.size(),
+            static_cast<size_t>(log_uws.files_size() + log_uws.folders_size()));
+  int files_index = 0;
+  int folders_index = 0;
+  for (const auto& file_path : uws.expanded_disk_footprints.file_paths()) {
+    // The path will be sanitized in the logs.
+    std::string sanitized_path = base::UTF16ToUTF8(SanitizePath(file_path));
+    if (base::DirectoryExists(file_path)) {
+      ASSERT_TRUE(folders_index < log_uws.folders_size());
+      EXPECT_EQ(sanitized_path,
+                log_uws.folders(folders_index).folder_information().path());
+      ++folders_index;
+    } else {
+      ASSERT_TRUE(files_index < log_uws.files_size());
+      EXPECT_EQ(sanitized_path,
+                log_uws.files(files_index).file_information().path());
+      ++files_index;
+    }
+  }
+
+  EXPECT_EQ(log_uws.folders_size(), folders_index);
+  EXPECT_EQ(log_uws.files_size(), files_index);
+
+  CompareRegistryEntries(uws.expanded_registry_footprints,
+                         log_uws.registry_entries());
+
+  ASSERT_EQ(uws.expanded_scheduled_tasks.size(),
+            static_cast<size_t>(log_uws.scheduled_tasks_size()));
+  for (unsigned int i = 0; i < uws.expanded_scheduled_tasks.size(); ++i) {
+    EXPECT_EQ(base::WideToUTF8(uws.expanded_scheduled_tasks[i]),
+              log_uws.scheduled_tasks(i).scheduled_task().name());
+  }
+}
+
+void ExpectFileInformationEqualToProtoObj(
+    const internal::FileInformation& file_information,
+    const FileInformation& proto_file_information) {
+  EXPECT_EQ(proto_file_information.path(),
+            base::UTF16ToUTF8(file_information.path));
+  EXPECT_EQ(proto_file_information.creation_date(),
+            file_information.creation_date);
+  EXPECT_EQ(proto_file_information.last_modified_date(),
+            file_information.last_modified_date);
+  EXPECT_EQ(proto_file_information.sha256(), file_information.sha256);
+  EXPECT_EQ(proto_file_information.size(), file_information.size);
+  EXPECT_EQ(proto_file_information.company_name(),
+            base::UTF16ToUTF8(file_information.company_name));
+  EXPECT_EQ(proto_file_information.company_short_name(),
+            base::UTF16ToUTF8(file_information.company_short_name));
+  EXPECT_EQ(proto_file_information.product_name(),
+            base::UTF16ToUTF8(file_information.product_name));
+  EXPECT_EQ(proto_file_information.product_short_name(),
+            base::UTF16ToUTF8(file_information.product_short_name));
+  EXPECT_EQ(proto_file_information.internal_name(),
+            base::UTF16ToUTF8(file_information.internal_name));
+  EXPECT_EQ(proto_file_information.original_filename(),
+            base::UTF16ToUTF8(file_information.original_filename));
+  EXPECT_EQ(proto_file_information.file_description(),
+            base::UTF16ToUTF8(file_information.file_description));
+  EXPECT_EQ(proto_file_information.file_version(),
+            base::UTF16ToUTF8(file_information.file_version));
+  EXPECT_EQ(proto_file_information.active_file(), file_information.active_file);
+}
+
+void ExpectRegistryValueEqualToProtoObj(
+    const internal::RegistryValue& registry_value,
+    const RegistryValue& proto_registry_value) {
+  EXPECT_EQ(proto_registry_value.key_path(),
+            base::UTF16ToUTF8(registry_value.key_path));
+  EXPECT_EQ(proto_registry_value.value_name(),
+            base::UTF16ToUTF8(registry_value.value_name));
+  EXPECT_EQ(proto_registry_value.data(),
+            base::UTF16ToUTF8(registry_value.data));
+}
+
+void NoSleep(base::TimeDelta) {}
+
+}  // namespace
+
+// Test parametrized with the execution mode.
+class CleanerLoggingServiceTest : public testing::TestWithParam<ExecutionMode> {
+ public:
+  // LoggingServiceAPI::UploadResultCallback implementation.
+  void LoggingServiceDone(base::OnceClosure run_loop_quit, bool success) {
+    done_callback_called_ = true;
+    upload_success_ = success;
+
+    // A RunLoop will be waiting for this callback to run before proceeding
+    // with the test. Now that the callback has run, we can quit the RunLoop.
+    std::move(run_loop_quit).Run();
+  }
+
+  void SetUp() override {
+    overriden_settings_ =
+        std::make_unique<SettingsWithExecutionModeOverride>(GetParam());
+    Settings::SetInstanceForTesting(overriden_settings_.get());
+
+    registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
+    // The registry logger must be created after calling OverrideRegistry.
+    registry_logger_.reset(new RegistryLogger(RegistryLogger::Mode::REMOVER));
+
+    // By default, tests use the NoOpLoggingService, so individual tests that
+    // need logging need to enable it.
+    logging_service_ = CleanerLoggingService::GetInstance();
+    logging_service_->Initialize(registry_logger_.get());
+
+    ASSERT_TRUE(uws_dir_.CreateUniqueTempDir());
+
+    // Add a folder for each UwS.
+    base::FilePath matched_uws_folder;
+    base::CreateTemporaryDirInDir(uws_dir_.GetPath(), L"matched_uws",
+                                  &matched_uws_folder);
+    base::FilePath removed_uws_folder;
+    base::CreateTemporaryDirInDir(uws_dir_.GetPath(), L"removed_uws",
+                                  &removed_uws_folder);
+    base::FilePath matched_uws_slow_folder;
+    base::CreateTemporaryDirInDir(uws_dir_.GetPath(), L"matched_uws_slow",
+                                  &matched_uws_slow_folder);
+    matched_uws_.AddDiskFootprint(matched_uws_folder);
+    removed_uws_.AddDiskFootprint(removed_uws_folder);
+    matched_uws_slow_.AddDiskFootprint(matched_uws_slow_folder);
+
+    // Add a file for each UwS.
+    matched_uws_file_ = matched_uws_folder.Append(L"matched_uws.tmp");
+    matched_uws_.AddDiskFootprint(matched_uws_file_);
+    removed_uws_file_ = removed_uws_folder.Append(L"removed_uws.tmp");
+    removed_uws_.AddDiskFootprint(removed_uws_file_);
+    matched_uws_slow_file_ =
+        matched_uws_slow_folder.Append(L"matched_uws_slow_.tmp");
+    matched_uws_slow_.AddDiskFootprint(matched_uws_slow_file_);
+
+    // Add a registry entry for each UwS.
+    RegKeyPath reg_path(HKEY_LOCAL_MACHINE, L"software\\test\\uws");
+    PUPData::RegistryFootprint reg(reg_path, L"matched_uws", L"1",
+                                   REGISTRY_VALUE_MATCH_KEY);
+    matched_uws_.expanded_registry_footprints.push_back(reg);
+
+    reg = PUPData::RegistryFootprint(reg_path, L"removed_uws", L"2",
+                                     REGISTRY_VALUE_MATCH_KEY);
+    removed_uws_.expanded_registry_footprints.push_back(reg);
+
+    reg = PUPData::RegistryFootprint(reg_path, L"matched_uws_slow", L"3",
+                                     REGISTRY_VALUE_MATCH_KEY);
+    matched_uws_slow_.expanded_registry_footprints.push_back(reg);
+
+    // Add dummy expanded scheduled tasks to one UwS.
+    matched_uws_.expanded_scheduled_tasks.push_back(L"ScheduledTask1");
+    matched_uws_.expanded_scheduled_tasks.push_back(L"ScheduledTask2");
+
+    // Use a mock HttpAgent instead of making real network requests.
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(
+        http_agent_factory_.get());
+    SafeBrowsingReporter::SetSleepCallbackForTesting(
+        base::BindRepeating(&NoSleep));
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(&network_checker_);
+  }
+
+  void TearDown() override {
+    // Disable logs uploading to delete any pending logs data.
+    logging_service_->EnableUploads(false, registry_logger_.get());
+
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(nullptr);
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(nullptr);
+
+    logging_service_->Terminate();
+    registry_logger_.reset();
+
+    Settings::SetInstanceForTesting(nullptr);
+  }
+
+ protected:
+  // Sends logs to Safe Browsing, and waits for LoggingServiceDone to be called
+  // before returning.
+  void DoSendLogsToSafeBrowsing() {
+    base::RunLoop run_loop;
+    logging_service_->SendLogsToSafeBrowsing(
+        base::BindRepeating(&CleanerLoggingServiceTest::LoggingServiceDone,
+                            base::Unretained(this), run_loop.QuitClosure()),
+        registry_logger_.get());
+    run_loop.Run();
+  }
+
+  CleanerLoggingServiceTest()
+      : logging_service_(nullptr),
+        scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI),
+        done_callback_called_(false),
+        upload_success_(false),
+        matched_uws_(&kMatchedUwSSignature),
+        removed_uws_(&kRemovedUwSSignature),
+        matched_uws_slow_(&kMatchedUwSSlowSignature) {}
+
+  // This is a pointer to the singleton object which we don't own.
+  CleanerLoggingService* logging_service_;
+
+  registry_util::RegistryOverrideManager registry_override_manager_;
+  std::unique_ptr<RegistryLogger> registry_logger_;
+
+  // Needed for the current task runner to be available.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  // |done_callback_called_| is set to true in |LoggingServiceDone| to confirm
+  // it was called appropriately.
+  bool done_callback_called_;
+  // Set with the value given to the done callback. Default to false.
+  bool upload_success_;
+
+  // UwS data for various UwS that should be logged.
+  PUPData::PUP matched_uws_;
+  PUPData::PUP removed_uws_;
+  PUPData::PUP matched_uws_slow_;
+  base::FilePath matched_uws_file_;
+  base::FilePath removed_uws_file_;
+  base::FilePath matched_uws_slow_file_;
+
+  // A temporary dir to store some temp UwS files.
+  base::ScopedTempDir uws_dir_;
+
+  // Mocked TaskScheduler.
+  TestTaskScheduler task_scheduler_;
+
+  // Overridden settings with the ExecutionMode test param.
+  std::unique_ptr<SettingsWithExecutionModeOverride> overriden_settings_;
+
+  MockHttpAgentConfig http_agent_config_;
+  std::unique_ptr<HttpAgentFactory> http_agent_factory_{
+      std::make_unique<MockHttpAgentFactory>(&http_agent_config_)};
+  MockNetworkChecker network_checker_;
+};
+
+TEST(LoggingServiceUtilTest, GetCleanerStartupFromCommandLine) {
+  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+
+  EXPECT_EQ(ChromeCleanerReport::CLEANER_STARTUP_NOT_PROMPTED,
+            GetCleanerStartupFromCommandLine(&command_line));
+
+  // Check that cleaner_startup is set to "unknown" when the chrome prompt
+  // switch is set, but has no value,
+  command_line.AppendSwitch(kChromePromptSwitch);
+  EXPECT_EQ(ChromeCleanerReport::CLEANER_STARTUP_UNKNOWN,
+            GetCleanerStartupFromCommandLine(&command_line));
+
+  // Check that cleaner_startup is set to "unknown" when the chrome prompt
+  // switch has an invalid non-integer value.
+  command_line.InitFromArgv(command_line.argv());
+  command_line.AppendSwitchASCII(kChromePromptSwitch, "blah");
+  EXPECT_EQ(ChromeCleanerReport::CLEANER_STARTUP_UNKNOWN,
+            GetCleanerStartupFromCommandLine(&command_line));
+
+  // Check that cleaner_startup is set according to the value of the chrome
+  // prompt switch when the integer is a valid CleanerStartup value.
+  for (int flag_value = 0;
+       flag_value <= ChromeCleanerReport::CleanerStartup_MAX; ++flag_value) {
+    command_line.InitFromArgv(command_line.argv());
+    command_line.AppendSwitchASCII(kChromePromptSwitch,
+                                   base::IntToString(flag_value));
+    if (flag_value == ChromeCleanerReport::CLEANER_STARTUP_UNSPECIFIED ||
+        flag_value == ChromeCleanerReport::CLEANER_STARTUP_NOT_PROMPTED) {
+      EXPECT_EQ(ChromeCleanerReport::CLEANER_STARTUP_UNKNOWN,
+                GetCleanerStartupFromCommandLine(&command_line));
+    } else {
+      EXPECT_EQ(static_cast<ChromeCleanerReport::CleanerStartup>(flag_value),
+                GetCleanerStartupFromCommandLine(&command_line));
+    }
+  }
+}
+
+TEST_P(CleanerLoggingServiceTest, Disabled) {
+  logging_service_->EnableUploads(false, registry_logger_.get());
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when not enabled.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+  // That should NOT have registered a retry.
+  task_scheduler_.ExpectRegisterTaskCalled(false);
+}
+
+TEST_P(CleanerLoggingServiceTest, Empty) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when there is nothing to report.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+  // That should NOT have registered a retry.
+  task_scheduler_.ExpectRegisterTaskCalled(false);
+}
+
+TEST_P(CleanerLoggingServiceTest, OnlyUwS) {
+  CreateFileWithContent(matched_uws_file_, kFileContent1,
+                        sizeof(kFileContent1));
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  logging_service_->AddFoundUwS(matched_uws_.signature().name);
+
+  logging_service_->AddDetectedUwS(&matched_uws_, kUwSDetectedFlagsNone);
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  http_agent_config_.AddCalls(calls);
+
+  DoSendLogsToSafeBrowsing();
+
+  // A URLRequest should have been made even if there's just UwS to report.
+  ASSERT_NE(0UL, http_agent_config_.num_request_data());
+
+  std::string upload_data(http_agent_config_.request_data(0).body);
+  ChromeCleanerReport uploaded_report;
+  ASSERT_TRUE(uploaded_report.ParseFromString(upload_data));
+  ASSERT_TRUE(uploaded_report.intermediate_log());
+  ASSERT_EQ(1, uploaded_report.found_uws_size());
+  EXPECT_EQ(matched_uws_.signature().name, uploaded_report.found_uws(0));
+  ASSERT_EQ(1, uploaded_report.detected_uws_size());
+  CompareUwSData(matched_uws_, uploaded_report.detected_uws(0));
+  EXPECT_FALSE(
+      uploaded_report.detected_uws(0).detail_level().only_one_footprint());
+
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_TRUE(upload_success_);
+  // A safety task should have been registered.
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // And then deleted.
+  task_scheduler_.ExpectDeleteTaskCalled(true);
+  // So none should be left.
+  task_scheduler_.ExpectRegisteredTasksSize(0U);
+}
+
+TEST_P(CleanerLoggingServiceTest, FullContent) {
+  CreateFileWithContent(matched_uws_file_, kFileContent1,
+                        sizeof(kFileContent1));
+  CreateFileWithContent(removed_uws_file_, kFileContent1,
+                        sizeof(kFileContent1));
+  CreateFileWithContent(matched_uws_slow_file_, kFileContent1,
+                        sizeof(kFileContent1));
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  std::string log_line1("A log line");
+  std::string log_line2("Another log line");
+  LOG(INFO) << log_line1;
+  LOG(WARNING) << log_line2;
+
+  logging_service_->SetExitCode(RESULT_CODE_NO_PUPS_FOUND);
+  logging_service_->AddFoundUwS(matched_uws_.signature().name);
+  logging_service_->AddDetectedUwS(&matched_uws_, kUwSDetectedFlagsNone);
+
+  logging_service_->AddFoundUwS(removed_uws_.signature().name);
+  logging_service_->AddDetectedUwS(&removed_uws_,
+                                   kUwSDetectedFlagsOnlyOneFootprint);
+
+  logging_service_->AddFoundUwS(matched_uws_slow_.signature().name);
+  logging_service_->AddDetectedUwS(&matched_uws_slow_,
+                                   kUwSDetectedFlagsOnlyOneFootprint);
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  ChromeFoilResponse response;
+  response.SerializeToString(&calls.read_data_result);
+  http_agent_config_.AddCalls(calls);
+
+  DoSendLogsToSafeBrowsing();
+
+  std::string upload_data(http_agent_config_.request_data(0).body);
+  ChromeCleanerReport uploaded_report;
+  ASSERT_TRUE(uploaded_report.ParseFromString(upload_data));
+
+  EXPECT_EQ(RESULT_CODE_NO_PUPS_FOUND, static_cast<chrome_cleaner::ResultCode>(
+                                           uploaded_report.exit_code()));
+
+  ASSERT_EQ(3, uploaded_report.found_uws_size());
+  EXPECT_EQ(matched_uws_.signature().name, uploaded_report.found_uws(0));
+  EXPECT_EQ(removed_uws_.signature().name, uploaded_report.found_uws(1));
+  EXPECT_EQ(matched_uws_slow_.signature().name, uploaded_report.found_uws(2));
+
+  ASSERT_EQ(3, uploaded_report.detected_uws_size());
+  CompareUwSData(matched_uws_, uploaded_report.detected_uws(0));
+  EXPECT_FALSE(
+      uploaded_report.detected_uws(0).detail_level().only_one_footprint());
+  EXPECT_EQ(UwS::REPORT_ONLY, uploaded_report.detected_uws(0).state());
+  CompareUwSData(removed_uws_, uploaded_report.detected_uws(1));
+  EXPECT_TRUE(
+      uploaded_report.detected_uws(1).detail_level().only_one_footprint());
+  EXPECT_EQ(UwS::REMOVABLE, uploaded_report.detected_uws(1).state());
+  CompareUwSData(matched_uws_slow_, uploaded_report.detected_uws(2));
+  EXPECT_TRUE(
+      uploaded_report.detected_uws(2).detail_level().only_one_footprint());
+  EXPECT_EQ(UwS::REPORT_ONLY, uploaded_report.detected_uws(2).state());
+
+  ASSERT_EQ(2, uploaded_report.raw_log_line_size());
+  // Log lines include the \n char.
+  log_line1 += "\n";
+  log_line2 += "\n";
+  EXPECT_TRUE(base::EndsWith(uploaded_report.raw_log_line(0), log_line1,
+                             base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(base::EndsWith(uploaded_report.raw_log_line(1), log_line2,
+                             base::CompareCase::SENSITIVE));
+
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_TRUE(upload_success_);
+  // A safety task should have been registered.
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // And then deleted.
+  task_scheduler_.ExpectDeleteTaskCalled(true);
+  // So none should be left.
+  task_scheduler_.ExpectRegisteredTasksSize(0U);
+}
+
+TEST_P(CleanerLoggingServiceTest, EnableDisableChanges) {
+  // Start enable, and then disable, and then enable again.
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  std::string log_line1("Visible log line");
+  LOG(INFO) << log_line1;
+
+  logging_service_->EnableUploads(false, registry_logger_.get());
+
+  std::string log_line2("Visible log line with uploads off");
+  LOG(INFO) << log_line2;
+
+  // Logs shouldn't be sent since uploads are disabled.
+  DoSendLogsToSafeBrowsing();
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_FALSE(upload_success_);
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  std::string log_line3("Another visible log line");
+  LOG(INFO) << log_line3;
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  ChromeFoilResponse response;
+  response.SerializeToString(&calls.read_data_result);
+  http_agent_config_.AddCalls(calls);
+
+  // Reset done_callback before attempting to upload again.
+  done_callback_called_ = false;
+  DoSendLogsToSafeBrowsing();
+
+  std::string upload_data(http_agent_config_.request_data(0).body);
+  ChromeCleanerReport uploaded_report;
+  ASSERT_TRUE(uploaded_report.ParseFromString(upload_data));
+
+  ASSERT_EQ(3, uploaded_report.raw_log_line_size());
+  // Log lines include the \n char.
+  log_line1 += "\n";
+  log_line2 += "\n";
+  log_line3 += "\n";
+  EXPECT_TRUE(base::EndsWith(uploaded_report.raw_log_line(0), log_line1,
+                             base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(base::EndsWith(uploaded_report.raw_log_line(1), log_line2,
+                             base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(base::EndsWith(uploaded_report.raw_log_line(2), log_line3,
+                             base::CompareCase::SENSITIVE));
+
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_TRUE(upload_success_);
+  // A safety task should have been registered.
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // And then deleted.
+  task_scheduler_.ExpectDeleteTaskCalled(true);
+  // So none should be left.
+  task_scheduler_.ExpectRegisteredTasksSize(0U);
+}
+
+TEST_P(CleanerLoggingServiceTest, FailedAndRetry) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // First request will fail.
+  MockHttpAgentConfig::Calls calls(HttpStatus::kBadRequest);
+  http_agent_config_.AddCalls(calls);
+
+  // Second request will succeed.
+  calls.get_status_code_result = HttpStatus::kOk;
+  http_agent_config_.AddCalls(calls);
+
+  logging_service_->SetExitCode(RESULT_CODE_NO_PUPS_FOUND);
+  DoSendLogsToSafeBrowsing();
+
+  // Now the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_TRUE(upload_success_);
+
+  // A safety task should have been registered.
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // And then deleted.
+  task_scheduler_.ExpectDeleteTaskCalled(true);
+  // So none should be left.
+  task_scheduler_.ExpectRegisteredTasksSize(0U);
+
+  // And no more retries should be done.
+  EXPECT_EQ(2UL, http_agent_config_.num_request_data());
+}
+
+TEST_P(CleanerLoggingServiceTest, CompleteFailure) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kBadRequest);
+  http_agent_config_.AddCalls(calls);
+  http_agent_config_.AddCalls(calls);
+  http_agent_config_.AddCalls(calls);
+
+  logging_service_->SetExitCode(RESULT_CODE_NO_PUPS_FOUND);
+  DoSendLogsToSafeBrowsing();
+
+  // Now the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_FALSE(upload_success_);
+
+  // A safety task should have been registered.
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // The safety task should not have been deleted this time.
+  task_scheduler_.ExpectDeleteTaskCalled(false);
+  // So task should still be registered.
+  task_scheduler_.ExpectRegisteredTasksSize(1U);
+
+  // And no more retries should be done.
+  EXPECT_EQ(3UL, http_agent_config_.num_request_data());
+  base::FilePath log_file;
+
+  // Now forget about the scheduled logs upload retry.
+  registry_logger_->GetNextLogFilePath(&log_file);
+  ASSERT_FALSE(log_file.empty());
+  bool success = base::DeleteFile(log_file, false);
+  EXPECT_TRUE(success) << "Failed to delete " << log_file.value();
+  bool more_log_files = registry_logger_->RemoveLogFilePath(log_file);
+  EXPECT_FALSE(more_log_files);
+  task_scheduler_.DeleteTask(L"");
+}
+
+TEST_P(CleanerLoggingServiceTest, RawReportContent) {
+  ChromeCleanerReport raw_report;
+  // Start with the current state of the LoggingService which adds stuff
+  // initially like the Environment object.
+  ASSERT_TRUE(raw_report.ParseFromString(logging_service_->RawReportContent()));
+
+  logging_service_->SetExitCode(RESULT_CODE_NO_PUPS_FOUND);
+  raw_report.set_exit_code(RESULT_CODE_NO_PUPS_FOUND);
+  raw_report.set_intermediate_log(false);
+
+  logging_service_->AddFoundUwS(matched_uws_.signature().name);
+  raw_report.add_found_uws(matched_uws_.signature().name);
+  logging_service_->AddFoundUwS(removed_uws_.signature().name);
+  raw_report.add_found_uws(removed_uws_.signature().name);
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  EXPECT_EQ(raw_report.SerializeAsString(), report.SerializeAsString());
+}
+
+TEST_P(CleanerLoggingServiceTest, ReadContentFromFile) {
+  ChromeCleanerReport raw_report;
+
+  raw_report.set_exit_code(RESULT_CODE_NO_PUPS_FOUND);
+  raw_report.add_found_uws("UwS-42");
+  raw_report.add_found_uws("UwS-trio");
+
+  std::string raw_report_string;
+  ASSERT_TRUE(raw_report.SerializeToString(&raw_report_string));
+
+  base::FilePath test_file;
+  base::ScopedTempDir test_dir;
+  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
+  base::CreateTemporaryFileInDir(test_dir.GetPath(), &test_file);
+  ASSERT_TRUE(base::PathExists(test_file));
+  ASSERT_GT(base::WriteFile(test_file, raw_report_string.c_str(),
+                            raw_report_string.size()),
+            0);
+
+  EXPECT_TRUE(logging_service_->ReadContentFromFile(test_file));
+
+  std::string report_string(logging_service_->RawReportContent());
+  EXPECT_EQ(raw_report_string, report_string);
+}
+
+TEST_P(CleanerLoggingServiceTest, ReadContentFromNonexistentFile) {
+  base::ScopedTempDir test_dir;
+  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
+  // Only the folder was actually created, so Bla is always nonexistent.
+  base::FilePath nonexistent_file(test_dir.GetPath().Append(L"Bla"));
+  ASSERT_FALSE(base::PathExists(nonexistent_file));
+
+  EXPECT_FALSE(logging_service_->ReadContentFromFile(nonexistent_file));
+}
+
+TEST_P(CleanerLoggingServiceTest, ReadContentFromEmptyFile) {
+  base::FilePath test_file;
+  base::ScopedTempDir test_dir;
+  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
+  base::CreateTemporaryFileInDir(test_dir.GetPath(), &test_file);
+  ASSERT_TRUE(base::PathExists(test_file));
+
+  EXPECT_FALSE(logging_service_->ReadContentFromFile(test_file));
+}
+
+TEST_P(CleanerLoggingServiceTest, ReadContentFromInvalidFile) {
+  base::FilePath test_file;
+  base::ScopedTempDir test_dir;
+  ASSERT_TRUE(test_dir.CreateUniqueTempDir());
+  base::CreateTemporaryFileInDir(test_dir.GetPath(), &test_file);
+  ASSERT_TRUE(base::PathExists(test_file));
+  ASSERT_GT(base::WriteFile(test_file, "bla", 3), 0);
+
+  EXPECT_FALSE(logging_service_->ReadContentFromFile(test_file));
+}
+
+TEST_P(CleanerLoggingServiceTest, ScheduleFallbackLogsUpload) {
+  // Shouldn't schedule a task if uploads are not enabled.
+  logging_service_->ScheduleFallbackLogsUpload(registry_logger_.get(),
+                                               RESULT_CODE_SUCCESS);
+
+  task_scheduler_.ExpectRegisterTaskCalled(false);
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+  logging_service_->ScheduleFallbackLogsUpload(registry_logger_.get(),
+                                               RESULT_CODE_SUCCESS);
+
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+  // The safety task should not have been deleted.
+  task_scheduler_.ExpectDeleteTaskCalled(false);
+  // A single task should still be registered.
+  task_scheduler_.ExpectRegisteredTasksSize(1U);
+
+  // Also make sure that opting out of logs upload will clear any pending logs
+  // upload.
+  logging_service_->EnableUploads(false, registry_logger_.get());
+  task_scheduler_.ExpectDeleteTaskCalled(true);
+  // No task should be registered anymore.
+  task_scheduler_.ExpectRegisteredTasksSize(0U);
+}
+
+TEST_P(CleanerLoggingServiceTest, AppendMatchedFile) {
+  MatchedFile matched_file;
+  FileInformation* proto_file_information =
+      matched_file.mutable_file_information();
+  proto_file_information->set_path(base::UTF16ToUTF8(kFileInformation1.path));
+  proto_file_information->set_creation_date(kFileInformation1.creation_date);
+  proto_file_information->set_last_modified_date(
+      kFileInformation1.last_modified_date);
+  proto_file_information->set_sha256(kFileInformation1.sha256);
+  proto_file_information->set_size(kFileInformation1.size);
+  proto_file_information->set_company_name(
+      base::UTF16ToUTF8(kFileInformation1.company_name));
+  proto_file_information->set_company_short_name(
+      base::UTF16ToUTF8(kFileInformation1.company_short_name));
+  proto_file_information->set_product_name(
+      base::UTF16ToUTF8(kFileInformation1.product_name));
+  proto_file_information->set_product_short_name(
+      base::UTF16ToUTF8(kFileInformation1.product_short_name));
+  proto_file_information->set_internal_name(
+      base::UTF16ToUTF8(kFileInformation1.internal_name));
+  proto_file_information->set_original_filename(
+      base::UTF16ToUTF8(kFileInformation1.original_filename));
+  proto_file_information->set_file_description(
+      base::UTF16ToUTF8(kFileInformation1.file_description));
+  proto_file_information->set_file_version(
+      base::UTF16ToUTF8(kFileInformation1.file_version));
+  proto_file_information->set_active_file(kFileInformation1.active_file);
+  MessageBuilder builder;
+  AppendMatchedFile(matched_file, &builder);
+  EXPECT_EQ(kMatchedFileToStringExpectedString, builder.content());
+}
+
+TEST_P(CleanerLoggingServiceTest, AppendFolderInformation) {
+  constexpr char kFolderPath[] = "some/path/something";
+  constexpr char kCreationDate[] = "3/1/2016";
+  constexpr char kLastModifiedDate[] = "3/3/2016";
+
+  constexpr wchar_t kExpectedFolderString[] =
+      L"path = 'some/path/something', folder_creation_date = "
+      L"'3/1/2016', folder_last_modified_date = '3/3/2016'";
+
+  FolderInformation folder_information;
+  MessageBuilder builder;
+  AppendFolderInformation(folder_information, &builder);
+  EXPECT_TRUE(builder.content().empty());
+
+  folder_information.set_path(kFolderPath);
+  folder_information.set_creation_date(kCreationDate);
+  folder_information.set_last_modified_date(kLastModifiedDate);
+  AppendFolderInformation(folder_information, &builder);
+
+  EXPECT_EQ(kExpectedFolderString, builder.content());
+}
+
+TEST_P(CleanerLoggingServiceTest, AppendMatchedRegistryEntry) {
+  MatchedRegistryEntry matched_registry_entry;
+  matched_registry_entry.set_key_path(
+      base::UTF16ToUTF8(kMatchedRegistryEntryKey));
+  matched_registry_entry.set_value_name(
+      base::UTF16ToUTF8(kMatchedRegistryEntryValueName));
+  matched_registry_entry.set_value_substring(
+      base::UTF16ToUTF8(kMatchedRegistryEntryValueSubstring));
+  MessageBuilder builder;
+  AppendMatchedRegistryEntry(matched_registry_entry, &builder);
+  EXPECT_EQ(kMatchedRegistryEntryToStringExpectedString, builder.content());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddLoadedModule) {
+  static const wchar_t kLoadedModuleName1[] = L"SomeLoadedModuleName1";
+  static const wchar_t kLoadedModuleName2[] = L"SomeLoadedModuleName2";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  EXPECT_EQ(report.system_report().loaded_modules_size(), 0);
+
+  logging_service_->AddLoadedModule(kLoadedModuleName1, ModuleHost::CHROME,
+                                    kFileInformation1);
+  logging_service_->AddLoadedModule(
+      kLoadedModuleName2, ModuleHost::CHROME_CLEANUP_TOOL, kFileInformation2);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().loaded_modules_size(), 2);
+
+  ChromeCleanerReport_SystemReport_LoadedModule loaded_module_chrome =
+      report.system_report().loaded_modules(0);
+  ExpectFileInformationEqualToProtoObj(kFileInformation1,
+                                       loaded_module_chrome.file_information());
+  EXPECT_EQ(base::WideToUTF8(kLoadedModuleName1), loaded_module_chrome.name());
+  EXPECT_EQ(ModuleHost::CHROME, loaded_module_chrome.host());
+
+  ChromeCleanerReport_SystemReport_LoadedModule loaded_module_cct =
+      report.system_report().loaded_modules(1);
+  ExpectFileInformationEqualToProtoObj(kFileInformation2,
+                                       loaded_module_cct.file_information());
+  EXPECT_EQ(base::WideToUTF8(kLoadedModuleName2), loaded_module_cct.name());
+  EXPECT_EQ(ModuleHost::CHROME_CLEANUP_TOOL, loaded_module_cct.host());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddService) {
+  static const wchar_t kServiceDisplayName[] = L"ServiceDisplayName";
+  static const wchar_t kServiceName[] = L"ServiceName";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().services_size(), 0);
+
+  logging_service_->AddService(kServiceDisplayName, kServiceName,
+                               kFileInformation1);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().services_size(), 1);
+
+  ChromeCleanerReport_SystemReport_Service added_service =
+      report.system_report().services(0);
+
+  ExpectFileInformationEqualToProtoObj(kFileInformation1,
+                                       added_service.file_information());
+  EXPECT_EQ(base::WideToUTF8(kServiceDisplayName),
+            added_service.display_name());
+  EXPECT_EQ(base::WideToUTF8(kServiceName), added_service.service_name());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddInstalledProgram) {
+  constexpr wchar_t kInstalledProgramName[] = L"some_installed_program";
+
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  base::FilePath installed_program_path;
+  ASSERT_TRUE(base::CreateTemporaryDirInDir(
+      temp_dir.GetPath(), kInstalledProgramName, &installed_program_path));
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().installed_programs_size(), 0);
+
+  logging_service_->AddInstalledProgram(installed_program_path);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().installed_programs_size(), 1);
+
+  FolderInformation folder_information =
+      report.system_report().installed_programs(0).folder_information();
+
+  const base::string16 sanitized_path = SanitizePath(installed_program_path);
+  EXPECT_EQ(base::UTF16ToUTF8(sanitized_path), folder_information.path());
+  EXPECT_FALSE(folder_information.creation_date().empty());
+  EXPECT_FALSE(folder_information.last_modified_date().empty());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddProcess) {
+  static const wchar_t kProcessName[] = L"SomeProcessName";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  EXPECT_EQ(report.system_report().processes_size(), 0);
+
+  logging_service_->AddProcess(kProcessName, kFileInformation1);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().processes_size(), 1);
+
+  ChromeCleanerReport_SystemReport_Process added_process =
+      report.system_report().processes(0);
+  ExpectFileInformationEqualToProtoObj(kFileInformation1,
+                                       added_process.file_information());
+  EXPECT_EQ(base::WideToUTF8(kProcessName), added_process.name());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddRegistryValue) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  EXPECT_EQ(report.system_report().registry_values_size(), 0);
+
+  // Add a registry value with no FileInformation.
+  std::vector<internal::FileInformation> empty_file_informations;
+  logging_service_->AddRegistryValue(kRegistryValue1, empty_file_informations);
+
+  // Add a registry value with many FileInformation.
+  std::vector<internal::FileInformation> multiple_file_informations;
+  multiple_file_informations.push_back(kFileInformation1);
+  multiple_file_informations.push_back(kFileInformation2);
+  logging_service_->AddRegistryValue(kRegistryValue2,
+                                     multiple_file_informations);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().registry_values_size(), 2);
+
+  RegistryValue registry_value1 = report.system_report().registry_values(0);
+  RegistryValue registry_value2 = report.system_report().registry_values(1);
+
+  ExpectRegistryValueEqualToProtoObj(kRegistryValue1, registry_value1);
+  EXPECT_EQ(registry_value1.file_informations_size(), 0);
+  ExpectRegistryValueEqualToProtoObj(kRegistryValue2, registry_value2);
+  ASSERT_EQ(registry_value2.file_informations_size(), 2);
+  ExpectFileInformationEqualToProtoObj(kFileInformation1,
+                                       registry_value2.file_informations(0));
+  ExpectFileInformationEqualToProtoObj(kFileInformation2,
+                                       registry_value2.file_informations(1));
+}
+
+TEST_P(CleanerLoggingServiceTest, AddLayeredServiceProvider) {
+  static const wchar_t kGuid1[] = L"Guid1";
+  static const wchar_t kGuid2[] = L"Guid2";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(0, report.system_report().layered_service_providers_size());
+
+  std::vector<base::string16> guids;
+  guids.push_back(kGuid1);
+  guids.push_back(kGuid2);
+  logging_service_->AddLayeredServiceProvider(guids, kFileInformation1);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(1, report.system_report().layered_service_providers_size());
+
+  ChromeCleanerReport_SystemReport_LayeredServiceProvider
+      layered_service_provider =
+          report.system_report().layered_service_providers(0);
+
+  ASSERT_EQ(2, layered_service_provider.guids_size());
+
+  EXPECT_EQ(base::UTF16ToUTF8(guids.at(0)), layered_service_provider.guids(0));
+  EXPECT_EQ(base::UTF16ToUTF8(guids.at(1)), layered_service_provider.guids(1));
+
+  ExpectFileInformationEqualToProtoObj(
+      kFileInformation1, layered_service_provider.file_information());
+}
+
+TEST_P(CleanerLoggingServiceTest, SetWinInetProxySettings) {
+  static const wchar_t kSystemProxySettingsAutoConfigURL[] =
+      L"https://somewhere.test/proxy.pac";
+
+  const bool kAutodetect = true;
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_FALSE(report.system_report().has_win_inet_proxy_settings());
+
+  logging_service_->SetWinInetProxySettings(
+      kSystemProxySettingsConfig, kSystemProxySettingsBypass,
+      kSystemProxySettingsAutoConfigURL, kAutodetect);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_TRUE(report.system_report().has_win_inet_proxy_settings());
+
+  ChromeCleanerReport_SystemReport_SystemProxySettings win_inet_proxy_settings =
+      report.system_report().win_inet_proxy_settings();
+  EXPECT_EQ(base::WideToUTF8(kSystemProxySettingsConfig),
+            win_inet_proxy_settings.config());
+  EXPECT_EQ(base::WideToUTF8(kSystemProxySettingsBypass),
+            win_inet_proxy_settings.bypass());
+  EXPECT_EQ(base::WideToUTF8(kSystemProxySettingsAutoConfigURL),
+            win_inet_proxy_settings.auto_config_url());
+  EXPECT_EQ(kAutodetect, win_inet_proxy_settings.autodetect());
+}
+
+TEST_P(CleanerLoggingServiceTest, SetWinHttpProxySettings) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_FALSE(report.system_report().has_win_http_proxy_settings());
+
+  logging_service_->SetWinHttpProxySettings(kSystemProxySettingsConfig,
+                                            kSystemProxySettingsBypass);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_TRUE(report.system_report().has_win_http_proxy_settings());
+
+  ChromeCleanerReport_SystemReport_SystemProxySettings win_http_proxy_settings =
+      report.system_report().win_http_proxy_settings();
+  EXPECT_EQ(base::WideToUTF8(kSystemProxySettingsConfig),
+            win_http_proxy_settings.config());
+  EXPECT_EQ(base::WideToUTF8(kSystemProxySettingsBypass),
+            win_http_proxy_settings.bypass());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddInstalledExtension) {
+  static const wchar_t kExtensionId[] = L"ababababcdcdcdcdefefefefghghghgh";
+  static const wchar_t kExtensionId2[] = L"aaaabbbbccccddddeeeeffffgggghhhh";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().installed_extensions_size(), 0);
+
+  logging_service_->AddInstalledExtension(
+      kExtensionId, ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST);
+  logging_service_->AddInstalledExtension(
+      kExtensionId2, ExtensionInstallMethod::POLICY_MASTER_PREFERENCES);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(report.system_report().installed_extensions_size(), 2);
+
+  ChromeCleanerReport_SystemReport_InstalledExtension installed_extension =
+      report.system_report().installed_extensions(0);
+  EXPECT_EQ(base::WideToUTF8(kExtensionId), installed_extension.extension_id());
+  EXPECT_EQ(ExtensionInstallMethod::POLICY_EXTENSION_FORCELIST,
+            installed_extension.install_method());
+
+  installed_extension = report.system_report().installed_extensions(1);
+  EXPECT_EQ(base::WideToUTF8(kExtensionId2),
+            installed_extension.extension_id());
+  EXPECT_EQ(ExtensionInstallMethod::POLICY_MASTER_PREFERENCES,
+            installed_extension.install_method());
+}
+
+TEST_P(CleanerLoggingServiceTest, AddScheduledTask) {
+  static const wchar_t kScheduledTaskName[] = L"SomeTaskName";
+  static const wchar_t kScheduledTaskDescription[] = L"Description";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(0, report.system_report().scheduled_tasks_size());
+
+  std::vector<internal::FileInformation> actions;
+  actions.push_back(kFileInformation1);
+  actions.push_back(kFileInformation2);
+  logging_service_->AddScheduledTask(kScheduledTaskName,
+                                     kScheduledTaskDescription, actions);
+
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(1, report.system_report().scheduled_tasks_size());
+
+  ScheduledTask added_scheduled_task =
+      report.system_report().scheduled_tasks(0);
+  ASSERT_EQ(2, added_scheduled_task.actions_size());
+
+  ExpectFileInformationEqualToProtoObj(
+      kFileInformation1, added_scheduled_task.actions(0).file_information());
+  ExpectFileInformationEqualToProtoObj(
+      kFileInformation2, added_scheduled_task.actions(1).file_information());
+  EXPECT_EQ(base::WideToUTF8(kScheduledTaskName), added_scheduled_task.name());
+  EXPECT_EQ(base::WideToUTF8(kScheduledTaskDescription),
+            added_scheduled_task.description());
+}
+
+TEST_P(CleanerLoggingServiceTest, LogProcessInformation) {
+  base::IoCounters io_counters;
+  io_counters.ReadOperationCount = 1;
+  io_counters.WriteOperationCount = 2;
+  io_counters.OtherOperationCount = 3;
+  io_counters.ReadTransferCount = 4;
+  io_counters.WriteTransferCount = 5;
+  io_counters.OtherTransferCount = 6;
+  SystemResourceUsage usage = {io_counters, base::TimeDelta::FromSeconds(10),
+                               base::TimeDelta::FromSeconds(20), 123456};
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+  logging_service_->LogProcessInformation(SandboxType::kNonSandboxed, usage);
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ASSERT_EQ(1, report.process_information_size());
+  EXPECT_EQ(ProcessInformation::MAIN, report.process_information(0).process());
+
+  ProcessInformation::SystemResourceUsage usage_msg =
+      report.process_information(0).resource_usage();
+  EXPECT_TRUE(IoCountersEqual(io_counters, usage_msg));
+  EXPECT_EQ(10U, usage_msg.user_time());
+  EXPECT_EQ(20U, usage_msg.kernel_time());
+  EXPECT_EQ(123456U, usage_msg.peak_working_set_size());
+}
+
+namespace {
+
+// Add a matched file entry to |uws| with path given by SanitizePath(|path|)
+// and removal status "matched only".
+void AddFileToUwS(const base::FilePath& path, UwS* uws) {
+  MatchedFile* file = uws->add_files();
+  file->mutable_file_information()->set_path(
+      base::UTF16ToUTF8(SanitizePath(path)));
+  file->mutable_file_information()->set_active_file(
+      PathHasActiveExtension(path));
+  file->set_removal_status(REMOVAL_STATUS_MATCHED_ONLY);
+}
+
+// Add a matched folder entry to |uws| with path given by SanitizePath(|path|)
+// and removal status "matched only".
+void AddFolderToUwS(const base::FilePath& path, UwS* uws) {
+  MatchedFolder* folder = uws->add_folders();
+  folder->mutable_folder_information()->set_path(
+      base::UTF16ToUTF8(SanitizePath(path)));
+  folder->set_removal_status(REMOVAL_STATUS_MATCHED_ONLY);
+}
+
+// Checks that all files and folders for all UwS entries in |report| have
+// removal status |expected_status|.
+void ExpectRemovalStatus(const ChromeCleanerReport& report,
+                         RemovalStatus expected_status) {
+  for (const UwS& uws : report.detected_uws()) {
+    for (const MatchedFile& file : uws.files())
+      EXPECT_EQ(expected_status, file.removal_status());
+    for (const MatchedFolder& folder : uws.folders())
+      EXPECT_EQ(expected_status, folder.removal_status());
+  }
+}
+
+// Checks that the given path appears in the unknown UwS field with the expected
+// removal status.
+void ExpectUnknownRemovalStatus(const ChromeCleanerReport& report,
+                                const base::FilePath& path,
+                                RemovalStatus expected_status) {
+  bool found = false;
+  std::string normalized_path = base::UTF16ToUTF8(SanitizePath(path));
+  for (const auto& file : report.unknown_uws().files()) {
+    if (file.file_information().path() == normalized_path) {
+      EXPECT_EQ(file.removal_status(), expected_status);
+      EXPECT_FALSE(found)
+          << normalized_path
+          << " appeared in the unknown UwS files more than once";
+      found = true;
+    }
+  }
+  EXPECT_TRUE(found) << normalized_path
+                     << " didn't appear in the unknown UwS files";
+}
+
+}  // namespace
+
+TEST_P(CleanerLoggingServiceTest, InitializeResetsRemovalStatus) {
+  EXPECT_TRUE(
+      FileRemovalStatusUpdater::GetInstance()->GetAllRemovalStatuses().empty());
+}
+
+TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus) {
+  constexpr wchar_t kFile1[] = L"C:\\Program Files\\My Dear UwS\\File1.exe";
+  constexpr wchar_t kFile2[] =
+      L"C:\\Program Files\\My least favority UwS\\File2.exe";
+  constexpr wchar_t kFolder1[] = L"C:\\Program Files\\My Dear UwS";
+  constexpr wchar_t kFolder2[] = L"C:\\Program Files\\Nasty UwS";
+
+  const std::vector<base::FilePath> paths{
+      base::FilePath(kFile1), base::FilePath(kFile2), base::FilePath(kFolder1),
+      base::FilePath(kFolder2),
+  };
+
+  // Creates a vector of all RemovalStatus enum values to improve readability
+  // of loops in this test. Loops start from RemovalStatus_MIN + 1 to avoid
+  // testing default value REMOVAL_STATUS_UNSPECIFIED, that is not written
+  // by the cleaner code. Also, ensures that all RemovalStatus enumerators are
+  // checked.
+  std::vector<RemovalStatus> all_removal_status;
+  for (int i = RemovalStatus_MIN + 1; i <= RemovalStatus_MAX; ++i)
+    all_removal_status.push_back(static_cast<RemovalStatus>(i));
+
+  FileRemovalStatusUpdater* removal_status_updater =
+      FileRemovalStatusUpdater::GetInstance();
+  for (RemovalStatus removal_status : all_removal_status) {
+    for (RemovalStatus new_removal_status : all_removal_status) {
+      SCOPED_TRACE(::testing::Message()
+                   << "removal_status " << removal_status
+                   << ", new_removal_status " << new_removal_status);
+      logging_service_->EnableUploads(true, registry_logger_.get());
+
+      // Add two UwS to the logged proto with a shared file and a shared folder
+      // to ensure that removal status is set for all occurrences of the same
+      // file/folder.
+      UwS uws;
+      uws.set_id(1);
+      AddFileToUwS(base::FilePath(kFile1), &uws);
+      AddFolderToUwS(base::FilePath(kFolder1), &uws);
+      logging_service_->AddDetectedUwS(uws);
+
+      uws.set_id(2);  // kFile1 and kFolder1 are in both UwS.
+      AddFileToUwS(base::FilePath(kFile2), &uws);
+      AddFolderToUwS(base::FilePath(kFolder2), &uws);
+      logging_service_->AddDetectedUwS(uws);
+
+      // Initially, files and folders have removal status "matched only", which
+      // was given in the proto passed to AddDetectedUwS().
+      ChromeCleanerReport report;
+      ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+      ExpectRemovalStatus(report, REMOVAL_STATUS_MATCHED_ONLY);
+
+      // Any removal status can override "matched only".
+      for (const base::FilePath& path : paths)
+        removal_status_updater->UpdateRemovalStatus(path, removal_status);
+      ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+      ExpectRemovalStatus(report, removal_status);
+
+      // Tests if attempts to override removal status obey the rules specified
+      // by GetRemovalStatusOverridePermissionMap().
+      for (const base::FilePath& path : paths)
+        removal_status_updater->UpdateRemovalStatus(path, new_removal_status);
+      ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+      const internal::RemovalStatusOverridePermissionMap& decisions_map =
+          internal::GetRemovalStatusOverridePermissionMap();
+      const bool can_override = decisions_map.find(removal_status)
+                                    ->second.find(new_removal_status)
+                                    ->second == internal::kOkToOverride;
+      ExpectRemovalStatus(report,
+                          can_override ? new_removal_status : removal_status);
+
+      // Reset the logging service, so one the current test doesn't interfere
+      // with the next one.
+      logging_service_->Terminate();
+      logging_service_->Initialize(registry_logger_.get());
+    }
+  }
+}
+
+TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus_UwSAdded) {
+  constexpr wchar_t kFile1[] = L"C:\\Program Files\\My Dear UwS\\File1.exe";
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  FileRemovalStatusUpdater* removal_status_updater =
+      FileRemovalStatusUpdater::GetInstance();
+
+  // Create an UwS and update the removal status of one of its files.
+  UwS uws;
+  uws.set_id(1);
+  AddFileToUwS(base::FilePath(kFile1), &uws);
+  logging_service_->AddDetectedUwS(uws);
+
+  removal_status_updater->UpdateRemovalStatus(
+      base::FilePath(kFile1), REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ExpectRemovalStatus(report, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+
+  // Create another UwS with the same file. The file should already be
+  // scheduled for removal.
+  UwS uws2;
+  uws2.set_id(2);
+  AddFileToUwS(base::FilePath(kFile1), &uws2);
+  logging_service_->AddDetectedUwS(uws2);
+
+  ChromeCleanerReport report2;
+  ASSERT_TRUE(report2.ParseFromString(logging_service_->RawReportContent()));
+  ExpectRemovalStatus(report2, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+}
+
+TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus_UnknownUwS) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  FileRemovalStatusUpdater* removal_status_updater =
+      FileRemovalStatusUpdater::GetInstance();
+
+  // Update the removal status of a real file.
+  base::FilePath real_file = uws_dir_.GetPath().Append(L"real_file.exe");
+  EXPECT_TRUE(CreateEmptyFile(real_file));
+  removal_status_updater->UpdateRemovalStatus(real_file,
+                                              REMOVAL_STATUS_MATCHED_ONLY);
+  ChromeCleanerReport report;
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ExpectUnknownRemovalStatus(report, real_file, REMOVAL_STATUS_MATCHED_ONLY);
+
+  removal_status_updater->UpdateRemovalStatus(
+      real_file, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ExpectUnknownRemovalStatus(report, real_file,
+                             REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+
+  // Update the removeal status of a non-existant file and ensure it is still
+  // recorded.
+  base::FilePath fake_file = uws_dir_.GetPath().Append(L"fake_file.exe");
+  removal_status_updater->UpdateRemovalStatus(fake_file,
+                                              REMOVAL_STATUS_MATCHED_ONLY);
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ExpectUnknownRemovalStatus(report, fake_file, REMOVAL_STATUS_MATCHED_ONLY);
+
+  removal_status_updater->UpdateRemovalStatus(
+      fake_file, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+  ASSERT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
+  ExpectUnknownRemovalStatus(report, fake_file,
+                             REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+}
+
+TEST_P(CleanerLoggingServiceTest, AllExpectedRemovalsConfirmed) {
+  const base::FilePath kFile1(L"C:\\Program Files\\uws.exe");
+  const base::FilePath kFile2(L"C:\\Program Files\\virus.exe");
+  const base::FilePath kFile3(L"C:\\Program Files\\malware.exe");
+  const base::FilePath kFile4(L"C:\\Program Files\\inactive.txt");
+
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  FileRemovalStatusUpdater* removal_status_updater =
+      FileRemovalStatusUpdater::GetInstance();
+
+  // No detected UwS -> nothing to remove.
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+
+  UwS uws1;
+  uws1.set_id(1);
+  uws1.set_state(UwS::REMOVABLE);
+  AddFileToUwS(kFile1, &uws1);
+  logging_service_->AddDetectedUwS(uws1);
+  EXPECT_FALSE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(kFile1,
+                                              REMOVAL_STATUS_MATCHED_ONLY);
+  EXPECT_FALSE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(kFile1,
+                                              REMOVAL_STATUS_FAILED_TO_REMOVE);
+  EXPECT_FALSE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(
+      kFile1, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL_FALLBACK);
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(
+      kFile1, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+
+  UwS uws2;
+  uws2.set_id(2);
+  uws2.set_state(UwS::REPORT_ONLY);
+  AddFileToUwS(kFile2, &uws2);
+  logging_service_->AddDetectedUwS(uws2);
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+
+  UwS uws3;
+  uws3.set_id(3);
+  uws3.set_state(UwS::REMOVABLE);
+  AddFileToUwS(kFile2, &uws3);
+  AddFileToUwS(kFile3, &uws3);
+
+  // Since this is inactive, it is not expected to be removed.
+  AddFileToUwS(kFile4, &uws3);
+
+  logging_service_->AddDetectedUwS(uws3);
+  EXPECT_FALSE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(kFile2, REMOVAL_STATUS_REMOVED);
+  EXPECT_FALSE(logging_service_->AllExpectedRemovalsConfirmed());
+  removal_status_updater->UpdateRemovalStatus(kFile3, REMOVAL_STATUS_NOT_FOUND);
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+
+  // When another UwS is found with all files already scheduled for removal,
+  // the removals should already be confirmed.
+  UwS uws4;
+  uws4.set_id(4);
+  uws4.set_state(UwS::REMOVABLE);
+  AddFileToUwS(kFile1, &uws4);
+  EXPECT_TRUE(logging_service_->AllExpectedRemovalsConfirmed());
+}
+
+// TODO(csharp) add multi-thread tests.
+
+INSTANTIATE_TEST_CASE_P(All,
+                        CleanerLoggingServiceTest,
+                        testing::Values(ExecutionMode::kScanning,
+                                        ExecutionMode::kCleanup),
+                        GetParamNameForTest());
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/detailed_info_sampler.cc b/chrome/chrome_cleaner/logging/detailed_info_sampler.cc
new file mode 100644
index 0000000..88b60dc4
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/detailed_info_sampler.cc
@@ -0,0 +1,37 @@
+// Copyright 2018 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/chrome_cleaner/logging/detailed_info_sampler.h"
+
+#include <algorithm>
+#include <random>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/rand_util.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+
+namespace chrome_cleaner {
+
+DetailedInfoSampler::DetailedInfoSampler(int max_files)
+    : max_files_(max_files) {}
+
+void DetailedInfoSampler::SelectPathSetToSample(const FilePathSet& file_paths,
+                                                FilePathSet* paths_to_sample) {
+  std::vector<base::FilePath> active_paths = file_paths.ToVector();
+
+  std::shuffle(active_paths.begin(), active_paths.end(),
+               std::default_random_engine(base::RandDouble()));
+
+  // There might already be some files in |path_to_sample|, hence we use
+  // |selected_files| to count how many files we are adding.
+  int selected_files = 0;
+  for (auto it = active_paths.begin();
+       selected_files < max_files_ && it != active_paths.end(); it++) {
+    paths_to_sample->Insert(*it);
+    selected_files++;
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/detailed_info_sampler.h b/chrome/chrome_cleaner/logging/detailed_info_sampler.h
new file mode 100644
index 0000000..bedc418
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/detailed_info_sampler.h
@@ -0,0 +1,31 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_DETAILED_INFO_SAMPLER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_DETAILED_INFO_SAMPLER_H_
+
+#include <memory>
+
+#include "chrome/chrome_cleaner/logging/info_sampler.h"
+#include "chrome/chrome_cleaner/os/file_path_set.h"
+
+namespace chrome_cleaner {
+
+class DetailedInfoSampler : public InfoSampler {
+ public:
+  // Maximum number of files for which we will collect detailed information by
+  // default.
+  static constexpr int kDefaultMaxFiles = 5;
+
+  explicit DetailedInfoSampler(int max_files);
+  void SelectPathSetToSample(const FilePathSet& file_paths,
+                             FilePathSet* paths_to_sample) override;
+
+ private:
+  int max_files_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_DETAILED_INFO_SAMPLER_H_
diff --git a/chrome/chrome_cleaner/logging/detailed_info_sampler_unittest.cc b/chrome/chrome_cleaner/logging/detailed_info_sampler_unittest.cc
new file mode 100644
index 0000000..a72804b
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/detailed_info_sampler_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2018 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/chrome_cleaner/logging/detailed_info_sampler.h"
+
+#include "base/files/file_path.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+const wchar_t kBinaryPath1[] = L"C:\\Folder\\Binary1.exe";
+const wchar_t kBinaryPath2[] = L"C:\\Folder\\Binary2.exe";
+const wchar_t kBinaryPath3[] = L"C:\\Folder\\Binary3.exe";
+const wchar_t kBinaryPath4[] = L"C:\\Folder\\Binary4.exe";
+const unsigned int kMaxNumberOfFiles = 3;
+
+}  // namespace
+
+TEST(DetailedInfoSamplingTest, SampleOneFile) {
+  FilePathSet files_to_sample;
+  files_to_sample.Insert(base::FilePath(kBinaryPath1));
+
+  FilePathSet selected_paths;
+  DetailedInfoSampler sampler(kMaxNumberOfFiles);
+  sampler.SelectPathSetToSample(files_to_sample, &selected_paths);
+  EXPECT_EQ(1UL, selected_paths.size());
+}
+
+TEST(DetailedInfoSamplingTest, SampleMaxFiles) {
+  FilePathSet files_to_sample;
+  files_to_sample.Insert(base::FilePath(kBinaryPath1));
+  files_to_sample.Insert(base::FilePath(kBinaryPath2));
+  files_to_sample.Insert(base::FilePath(kBinaryPath3));
+  files_to_sample.Insert(base::FilePath(kBinaryPath4));
+
+  ASSERT_LT(kMaxNumberOfFiles, files_to_sample.size());
+  DetailedInfoSampler sampler(kMaxNumberOfFiles);
+  FilePathSet selected_paths;
+  sampler.SelectPathSetToSample(files_to_sample, &selected_paths);
+
+  EXPECT_EQ(kMaxNumberOfFiles, selected_paths.size());
+  EXPECT_EQ(4UL, files_to_sample.size());
+  // Count how many of the original list were found in out newly populated set.
+  size_t nb_found = 0;
+  for (const auto& path : files_to_sample.ToVector()) {
+    if (selected_paths.Contains(path))
+      nb_found++;
+  }
+  EXPECT_EQ(kMaxNumberOfFiles, nb_found);
+}
+
+TEST(DetailedInfoSamplingTest, SampleEmptyVector) {
+  FilePathSet files_to_sample;
+  FilePathSet selected_paths;
+  DetailedInfoSampler sampler(kMaxNumberOfFiles);
+  sampler.SelectPathSetToSample(files_to_sample, &selected_paths);
+  EXPECT_EQ(0UL, selected_paths.size());
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/dummy_api_keys.cc b/chrome/chrome_cleaner/logging/dummy_api_keys.cc
new file mode 100644
index 0000000..c0ac308b
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/dummy_api_keys.cc
@@ -0,0 +1,17 @@
+// Copyright 2018 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/chrome_cleaner/logging/api_keys.h"
+
+namespace chrome_cleaner {
+
+const char* kSafeBrowsingCleanerUrl =
+    "https://sb-ssl.google.com/safebrowsing/clientreport/chrome-cct"
+    "?key=DUMMY_KEY";
+
+const char* kSafeBrowsingReporterUrl =
+    "https://sb-ssl.google.com/safebrowsing/clientreport/chrome-sw-reporter"
+    "?key=DUMMY_KEY";
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/info_sampler.h b/chrome/chrome_cleaner/logging/info_sampler.h
new file mode 100644
index 0000000..cd6a305
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/info_sampler.h
@@ -0,0 +1,20 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_INFO_SAMPLER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_INFO_SAMPLER_H_
+
+#include "chrome/chrome_cleaner/os/file_path_set.h"
+
+namespace chrome_cleaner {
+
+class InfoSampler {
+ public:
+  virtual void SelectPathSetToSample(const FilePathSet& file_paths,
+                                     FilePathSet* sampled_file_paths) = 0;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_INFO_SAMPLER_H_
diff --git a/chrome/chrome_cleaner/logging/interface_log_service.cc b/chrome/chrome_cleaner/logging/interface_log_service.cc
new file mode 100644
index 0000000..fa4377e8
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/interface_log_service.cc
@@ -0,0 +1,139 @@
+// Copyright 2018 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.
+
+// Simple class that takes care of logging all the calls to a given interface.
+//
+// This should run on the IPC thread.
+
+#include "chrome/chrome_cleaner/logging/interface_log_service.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "chrome/chrome_cleaner/logging/proto/interface_logger.pb.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+base::FilePath GetPathForLogFile(const base::string16& log_file_name) {
+  base::FilePath app_data_path;
+  GetAppDataProductDirectory(&app_data_path);
+  base::FilePath log_file_path = app_data_path.Append(log_file_name);
+  return log_file_path;
+}
+
+}  // namespace
+
+base::TimeDelta InterfaceLogService::GetTicksSinceCreation() const {
+  return base::TimeTicks::Now() - ticks_at_creation_;
+}
+
+LogInformation::LogInformation(std::string function_name, std::string file_name)
+    : function_name(function_name), file_name(file_name) {}
+
+// static
+std::unique_ptr<InterfaceLogService> InterfaceLogService::Create(
+    const base::string16& file_name) {
+  if (file_name.empty())
+    return nullptr;
+
+  base::FilePath file_path = GetPathForLogFile(file_name);
+
+  std::string file_path_utf8;
+  if (!base::UTF16ToUTF8(file_path.value().c_str(), file_path.value().size(),
+                         &file_path_utf8)) {
+    LOG(ERROR) << "Can't open interface log file " << file_path.value()
+               << ": name is invalid" << std::endl;
+    return nullptr;
+  }
+
+  std::ofstream stream(file_path_utf8);
+  if (!stream.is_open()) {
+    PLOG(ERROR) << "Can't open interface log file " << file_path_utf8;
+    return nullptr;
+  }
+
+  return base::WrapUnique<InterfaceLogService>(
+      new InterfaceLogService(file_name, std::move(stream)));
+}
+
+InterfaceLogService::~InterfaceLogService() = default;
+
+void InterfaceLogService::AddCall(
+    const LogInformation& log_information,
+    const std::map<std::string, std::string>& params) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
+  if (log_information.function_name.empty())
+    return;
+
+  int64_t microseconds_since_start = GetTicksSinceCreation().InMicroseconds();
+
+  APICall* new_call = call_record_.add_api_calls();
+  new_call->set_function_name(log_information.function_name);
+  new_call->set_file_name(log_information.file_name);
+  new_call->set_microseconds_since_start(microseconds_since_start);
+  new_call->mutable_parameters()->insert(params.begin(), params.end());
+
+  csv_stream_ << microseconds_since_start << "," << log_information.file_name
+              << "," << log_information.function_name << ",";
+
+  for (const auto& name_value : params)
+    csv_stream_ << name_value.first << "=" << name_value.second << ";";
+
+  csv_stream_ << std::endl;
+}
+
+void InterfaceLogService::AddCall(const LogInformation& log_information) {
+  AddCall(log_information, {});
+}
+
+std::vector<APICall> InterfaceLogService::GetCallHistory() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_check_);
+  std::vector<APICall> call_history;
+  for (auto it = call_record_.api_calls().begin();
+       it != call_record_.api_calls().end(); it++) {
+    call_history.push_back(*it);
+  }
+  return call_history;
+}
+
+std::string InterfaceLogService::GetBuildVersion() const {
+  return call_record_.build_version();
+}
+
+base::FilePath InterfaceLogService::GetLogFilePath() const {
+  return GetPathForLogFile(log_file_name_);
+}
+
+InterfaceLogService::InterfaceLogService(const base::string16& file_name,
+                                         std::ofstream csv_stream)
+    : log_file_name_(file_name), csv_stream_(std::move(csv_stream)) {
+  DETACH_FROM_SEQUENCE(sequence_check_);
+  base::string16 build_version_utf16 = LASTCHANGE_STRING;
+  std::string build_version_utf8;
+  if (!base::UTF16ToUTF8(build_version_utf16.c_str(),
+                         build_version_utf16.size(), &build_version_utf8)) {
+    LOG(ERROR) << "Cannot convert build version to utf8";
+    build_version_utf8 = "UNKNOWN";
+  }
+  call_record_.set_build_version(build_version_utf8);
+
+  // Write build version and column headers.
+  csv_stream_ << "buildVersion," << build_version_utf8 << std::endl;
+  csv_stream_ << "timeCalledTicks,fileName,functionName,functionArguments"
+              << std::endl;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/interface_log_service.h b/chrome/chrome_cleaner/logging/interface_log_service.h
new file mode 100644
index 0000000..b413d3d3
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/interface_log_service.h
@@ -0,0 +1,78 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_INTERFACE_LOG_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_INTERFACE_LOG_SERVICE_H_
+
+#include <fstream>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string16.h"
+#include "chrome/chrome_cleaner/logging/proto/interface_logger.pb.h"
+
+namespace chrome_cleaner {
+
+struct LogInformation {
+  LogInformation(std::string function_name, std::string file_name);
+  std::string function_name;
+  std::string file_name;
+};
+
+class InterfaceLogService {
+ public:
+  static std::unique_ptr<InterfaceLogService> Create(
+      const base::string16& file_name);
+
+  ~InterfaceLogService();
+
+  // Logs a call to |function_name| from the given |class_name| and also logs
+  // the passed parameters recorded on |params|.
+  void AddCall(const LogInformation& log_information,
+               const std::map<std::string, std::string>& params);
+
+  // Logs a call to |function_name| without parameters.
+  void AddCall(const LogInformation& log_information);
+
+  // Exposes the underlying call_record_, this is for testing purposes and
+  // to provide a way to print or log the recorded calls.
+  std::vector<APICall> GetCallHistory() const;
+
+  // Returns the build version of all the logged function calls.
+  std::string GetBuildVersion() const;
+
+  base::FilePath GetLogFilePath() const;
+
+ private:
+  InterfaceLogService(const base::string16& file_name,
+                      std::ofstream csv_stream);
+
+  // TODO(joenotcharles): Currently the CallHistory is only used in the unit
+  // test. Decide whether it's worth keeping.
+  CallHistory call_record_;
+
+  base::string16 log_file_name_;
+  SEQUENCE_CHECKER(sequence_check_);
+
+  // Stream to output CSV records to.
+  std::ofstream csv_stream_;
+
+  // Time at the creation of the object
+  base::TimeTicks ticks_at_creation_{base::TimeTicks::Now()};
+
+  base::TimeDelta GetTicksSinceCreation() const;
+
+  DISALLOW_COPY_AND_ASSIGN(InterfaceLogService);
+};
+
+// Define a macro to make easier the use of AddCall.
+#define CURRENT_FILE_AND_METHOD LogInformation(__func__, __FILE__)
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_INTERFACE_LOG_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/interface_log_service_unittest.cc b/chrome/chrome_cleaner/logging/interface_log_service_unittest.cc
new file mode 100644
index 0000000..705afc0
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/interface_log_service_unittest.cc
@@ -0,0 +1,288 @@
+// Copyright 2018 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/chrome_cleaner/logging/interface_log_service.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+class InterfaceLogServiceTest : public testing::Test {
+ public:
+  void SetUp() override {
+    log_service_ = InterfaceLogService::Create(kLogFileName);
+    expected_file_size_ = 0LL;
+  }
+
+  void InvalidFunction() {
+    // Try to log nothing.
+    log_service_->AddCall(LogInformation("", ""));
+  }
+
+  void CheckFileSizeIncreased() {
+    int64_t file_size;
+    GetFileSize(&file_size);
+    EXPECT_GT(file_size, expected_file_size_)
+        << log_service_->GetLogFilePath()
+        << " did not grow between calls to CheckFileSizeIncreased";
+
+    // Next time this is called the size should be greater than it currently is.
+    expected_file_size_ = file_size;
+  }
+
+  void CheckFileSizeUnchanged() {
+    int64_t file_size;
+    GetFileSize(&file_size);
+    EXPECT_EQ(file_size, expected_file_size_)
+        << log_service_->GetLogFilePath()
+        << " grew unexpectedly after last call to CheckFileSizeIncreased";
+  }
+
+  void GetFileSize(int64_t* size) {
+    // This function uses ASSERT to return early on error. This will only abort
+    // the current function, not the whole test, but will still record a test
+    // failure. If this happens treat the file size as 0.
+    *size = 0LL;
+
+    base::FilePath log_file_path = log_service_->GetLogFilePath();
+    base::File file(log_file_path,
+                    base::File::FLAG_OPEN | base::File::FLAG_READ);
+    ASSERT_TRUE(file.IsValid())
+        << log_file_path << " couldn't be opened: "
+        << base::File::ErrorToString(file.GetLastFileError());
+
+    base::File::Info info;
+    ASSERT_TRUE(file.GetInfo(&info))
+        << log_file_path << " couldn't be sized: "
+        << base::File::ErrorToString(file.GetLastFileError());
+
+    *size = info.size;
+  }
+
+  int64_t expected_file_size_;
+  std::unique_ptr<InterfaceLogService> log_service_;
+  const base::string16 kLogFileName = L"interface_log_service_test";
+  const std::string kFileName = __FILE__;
+  base::string16 build_version_ = LASTCHANGE_STRING;
+};
+
+class TestClass1 {
+ public:
+  explicit TestClass1(InterfaceLogService* log_service)
+      : log_service_(log_service) {}
+  // Auxiliary functions, they do nothing but add a new entry to the
+  // call history.
+  void function1(std::string parameter1, int32_t parameter2) {
+    std::map<std::string, std::string> params;
+    params["parameter1"] = parameter1;
+    std::string s_parameter2 = base::IntToString(parameter2);
+    params["parameter2"] = s_parameter2;
+    log_service_->AddCall(CURRENT_FILE_AND_METHOD, params);
+  }
+
+  void function3() { log_service_->AddCall(CURRENT_FILE_AND_METHOD); }
+
+ private:
+  InterfaceLogService* log_service_;
+};
+
+class TestClass2 {
+ public:
+  explicit TestClass2(InterfaceLogService* log_service)
+      : log_service_(log_service) {}
+
+  void function2(std::map<std::string, int32_t> map_parameter) {
+    // There is no currently supported way to log the value of an object
+    // into the InterfaceLogService, so the params can go empty
+    log_service_->AddCall(CURRENT_FILE_AND_METHOD);
+  }
+
+ private:
+  InterfaceLogService* log_service_;
+};
+
+TEST_F(InterfaceLogServiceTest, LogAndRecoverTest) {
+  const std::string kString1 = "hola amigo";
+  const std::string kString2 = "second call";
+  const int kInt1 = 45;
+  const int kInt2 = 43;
+  TestClass1 test_object1(log_service_.get());
+  TestClass2 test_object2(log_service_.get());
+
+  // Some headers are written at the top of the log file, so its size should
+  // already be >0.
+  CheckFileSizeIncreased();
+
+  // First make some calls and check that the log file grows after each.
+  test_object1.function1(kString1, kInt1);
+  CheckFileSizeIncreased();
+
+  test_object1.function3();
+  CheckFileSizeIncreased();
+
+  test_object1.function1(kString2, kInt2);
+  CheckFileSizeIncreased();
+
+  std::map<std::string, int32_t> unused_map;
+  test_object2.function2(unused_map);
+  CheckFileSizeIncreased();
+
+  test_object1.function3();
+  CheckFileSizeIncreased();
+
+  // This function should be ignored on the log service.
+  InvalidFunction();
+  CheckFileSizeUnchanged();
+
+  // Read back the log file and check that the lines contain the expected calls.
+  std::string file_contents;
+  ASSERT_TRUE(
+      base::ReadFileToString(log_service_->GetLogFilePath(), &file_contents));
+  std::vector<std::string> lines = base::SplitString(
+      file_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  enum {
+    kReadingVersion,
+    kReadingHeader,
+    kReadingCalls,
+  } state = kReadingVersion;
+
+  CallHistory call_history_from_file;
+
+  for (const auto& line : lines) {
+    std::vector<std::string> tokens = base::SplitString(
+        line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+    switch (state) {
+      case kReadingVersion:
+        // Check that we have the same build version.
+        ASSERT_EQ(tokens.size(), 2ULL);
+        EXPECT_EQ(tokens[0], "buildVersion");
+        call_history_from_file.set_build_version(tokens[1]);
+        state = kReadingHeader;
+        break;
+      case kReadingHeader:
+        EXPECT_THAT(tokens, ::testing::ElementsAre("timeCalledTicks",
+                                                   "fileName", "functionName",
+                                                   "functionArguments"));
+        state = kReadingCalls;
+        break;
+      case kReadingCalls:
+        ASSERT_EQ(tokens.size(), 4ULL);
+        {
+          int64_t microseconds_since_start;
+          ASSERT_TRUE(
+              base::StringToInt64(tokens[0], &microseconds_since_start));
+
+          APICall* new_call = call_history_from_file.add_api_calls();
+          new_call->set_microseconds_since_start(microseconds_since_start);
+          new_call->set_file_name(tokens[1]);
+          new_call->set_function_name(tokens[2]);
+
+          std::vector<std::pair<std::string, std::string>> params;
+          EXPECT_TRUE(
+              base::SplitStringIntoKeyValuePairs(tokens[3], '=', ';', &params));
+          for (const auto& name_value : params) {
+            (*new_call->mutable_parameters())[name_value.first] =
+                name_value.second;
+          }
+        }
+        break;
+      default:
+        FAIL() << "Invalid state " << state;
+        break;
+    }
+  }
+  EXPECT_EQ(state, kReadingCalls);
+
+  // Make sure the file contents and the data held in log_service_ are equal.
+  std::string build_version_utf8;
+  ASSERT_TRUE(base::UTF16ToUTF8(build_version_.c_str(), build_version_.size(),
+                                &build_version_utf8));
+  EXPECT_EQ(log_service_->GetBuildVersion(), build_version_utf8);
+  EXPECT_EQ(call_history_from_file.build_version(), build_version_utf8);
+
+  std::vector<APICall> call_record = log_service_->GetCallHistory();
+  EXPECT_EQ(call_record.size(), 5UL);
+  ASSERT_EQ(static_cast<size_t>(call_history_from_file.api_calls_size()),
+            call_record.size());
+  for (size_t i = 0; i < call_record.size(); ++i) {
+    const APICall& call_from_file = call_history_from_file.api_calls(i);
+    EXPECT_EQ(call_record[i].function_name(), call_from_file.function_name());
+    EXPECT_EQ(call_record[i].file_name(), call_from_file.file_name());
+    EXPECT_EQ(call_record[i].microseconds_since_start(),
+              call_from_file.microseconds_since_start());
+  }
+
+  // Make sure that the call record that was retrieved from log_service_
+  // contains the expected data. (This means the file will contain the expected
+  // data too, since we already checked that they're equal.)
+
+  // Check that the order is correct
+  EXPECT_EQ(call_record[0].function_name(), "function1");
+  EXPECT_EQ(call_record[0].file_name(), kFileName);
+  EXPECT_EQ(2U, call_record[0].parameters().size());
+  EXPECT_EQ(kString1, call_record[0].parameters().at("parameter1"));
+  EXPECT_EQ(base::IntToString(kInt1),
+            call_record[0].parameters().at("parameter2"));
+
+  EXPECT_EQ(call_record[1].function_name(), "function3");
+  EXPECT_EQ(call_record[1].file_name(), kFileName);
+
+  EXPECT_EQ(call_record[2].function_name(), "function1");
+  EXPECT_EQ(call_record[2].file_name(), kFileName);
+  EXPECT_EQ(kString2, call_record[2].parameters().at("parameter1"));
+  EXPECT_EQ(base::IntToString(kInt2),
+            call_record[2].parameters().at("parameter2"));
+
+  EXPECT_EQ(call_record[3].function_name(), "function2");
+  EXPECT_EQ(call_record[3].file_name(), kFileName);
+  EXPECT_EQ(call_record[4].function_name(), "function3");
+  EXPECT_EQ(call_record[4].file_name(), kFileName);
+
+  // Check that the times are increasing.
+  int64_t time_call1 = call_record[0].microseconds_since_start();
+  int64_t time_call2 = call_record[1].microseconds_since_start();
+  int64_t time_call3 = call_record[2].microseconds_since_start();
+  int64_t time_call4 = call_record[3].microseconds_since_start();
+  int64_t time_call5 = call_record[4].microseconds_since_start();
+
+  EXPECT_LE(time_call1, time_call2);
+  EXPECT_LE(time_call2, time_call3);
+  EXPECT_LE(time_call3, time_call4);
+  EXPECT_LE(time_call4, time_call5);
+
+  // Finally check that for the functions that have parameters they are
+  // untouched.
+  std::map<std::string, std::string> parameters1;
+  parameters1.insert(call_record[0].parameters().begin(),
+                     call_record[0].parameters().end());
+  EXPECT_EQ(parameters1["parameter1"], kString1);
+  EXPECT_EQ(parameters1["parameter2"], base::IntToString(kInt1));
+
+  std::map<std::string, std::string> parameters2;
+  parameters2.insert(call_record[2].parameters().begin(),
+                     call_record[2].parameters().end());
+  EXPECT_EQ(parameters2["parameter1"], kString2);
+  EXPECT_EQ(parameters2["parameter2"], base::IntToString(kInt2));
+}
+
+TEST_F(InterfaceLogServiceTest, EmptyLogFileTest) {
+  EXPECT_FALSE(InterfaceLogService::Create(L""));
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/logging_definitions.h b/chrome/chrome_cleaner/logging/logging_definitions.h
new file mode 100644
index 0000000..95fcb3a
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/logging_definitions.h
@@ -0,0 +1,19 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_LOGGING_DEFINITIONS_H_
+#define CHROME_CHROME_CLEANER_LOGGING_LOGGING_DEFINITIONS_H_
+
+// All executable targets build in this project should contain one
+// *_logging_definitions.cc file to ensure that this function is defined.
+
+namespace chrome_cleaner {
+
+class LoggingServiceAPI;
+
+LoggingServiceAPI* GetLoggingServiceForCurrentBuild();
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_LOGGING_DEFINITIONS_H_
diff --git a/chrome/chrome_cleaner/logging/logging_service_api.cc b/chrome/chrome_cleaner/logging/logging_service_api.cc
new file mode 100644
index 0000000..13bc16b
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/logging_service_api.cc
@@ -0,0 +1,59 @@
+// Copyright 2018 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/chrome_cleaner/logging/logging_service_api.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/logging/logging_definitions.h"
+#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+const wchar_t kProtoExtension[] = L"pb";
+
+}  // namespace
+
+// static
+LoggingServiceAPI* LoggingServiceAPI::logging_service_for_testing_ = nullptr;
+
+// static
+LoggingServiceAPI* LoggingServiceAPI::GetInstance() {
+  if (logging_service_for_testing_)
+    return logging_service_for_testing_;
+  return GetLoggingServiceForCurrentBuild();
+}
+
+// static
+void LoggingServiceAPI::SetInstanceForTesting(
+    LoggingServiceAPI* logging_service) {
+  logging_service_for_testing_ = logging_service;
+}
+
+void LoggingServiceAPI::MaybeSaveLogsToFile(const base::string16& tag) {
+#if !defined(NDEBUG)
+  // Always dump the raw logs in debug builds.
+  const bool dump_raw_logs = true;
+#else
+  const bool dump_raw_logs =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kDumpRawLogsSwitch);
+#endif
+  if (dump_raw_logs) {
+    base::FilePath exe_file_path =
+        PreFetchedPaths::GetInstance()->GetExecutablePath();
+
+    base::FilePath log_file_path(exe_file_path.ReplaceExtension(kProtoExtension)
+                                     .InsertBeforeExtension(tag));
+
+    std::string logs_proto = RawReportContent();
+    base::WriteFile(log_file_path, logs_proto.c_str(), logs_proto.length());
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/logging_service_api.h b/chrome/chrome_cleaner/logging/logging_service_api.h
new file mode 100644
index 0000000..14a4121
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/logging_service_api.h
@@ -0,0 +1,200 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_LOGGING_SERVICE_API_H_
+#define CHROME_CHROME_CLEANER_LOGGING_LOGGING_SERVICE_API_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "chrome/chrome_cleaner/logging/utils.h"
+#include "chrome/chrome_cleaner/os/process.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "chrome/chrome_cleaner/settings/settings_types.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace base {
+class FilePath;
+}  // namespace base
+
+namespace chrome_cleaner {
+
+class RegistryLogger;
+
+namespace internal {
+
+struct FileInformation;
+struct RegistryValue;
+
+}  // namespace internal
+
+// Manage where the logs are sent, and expose an API for more specific logging.
+class LoggingServiceAPI {
+ public:
+  typedef base::RepeatingCallback<void(bool)> UploadResultCallback;
+
+  virtual ~LoggingServiceAPI() {}
+
+  // Return the singleton instance which will get destroyed by the AtExitMgr.
+  // The instance must have been initialized with |Initialize| before
+  // being used, and |Terminate| must be called before releasing (or destroying)
+  // the object.
+  // Tests can force their own logging service implemenation with
+  // SetInstanceForTesting().
+  static LoggingServiceAPI* GetInstance();
+
+  // Sets the logging service instance for testing purposes only.
+  // Using |logging_service| as null resets GetInstance() to its default
+  // behaviour.
+  // Recommended usage:
+  //   void SetUp() override {
+  //     LoggingServiceAPI::SetInstance(&my_logging_service_);
+  //     LoggingServiceAPI::GetInstance()->Initialize(&my_registry_logger_);
+  //   }
+  //   void TearDown() override {
+  //     LoggingServiceAPI::GetInstance()->Terminate();
+  //     LoggingServiceAPI::SetInstance(nullptr);
+  //   }
+  // Note: when my_logging_service_ is a MockLoggingService, Initialize() and
+  //       Terminate() don't need to be called.
+  static void SetInstanceForTesting(LoggingServiceAPI* logging_service);
+
+  // All of the following functions must be called from the main UI thread:
+
+  // Start and stop intercepting logs. The function |Terminate| disables uploads
+  // and flushes current content of the logging report. |registry_logger| is
+  // needed to make sure there are no logs upload scheduled tasks left active
+  // when logs upload is disabled. It can be nullptr when caller is positive
+  // that there are none, i.e., when initializing for the first time.
+  virtual void Initialize(RegistryLogger* registry_logger) = 0;
+  virtual void Terminate() = 0;
+
+  // Send the current state of |chrome_cleaner_report_| to the Safe Browsing API
+  // and clear it. |registry_logger| is specified by the caller so that it can
+  // be mocked for testing.
+  virtual void SendLogsToSafeBrowsing(const UploadResultCallback& done_callback,
+                                      RegistryLogger* registry_logger) = 0;
+
+  // Cancel all current and future waits, to speed up system shutdown.
+  virtual void CancelWaitForShutdown() = 0;
+
+  // Enable / disable uploads of logs. |registry_logger| is needed to make sure
+  // there are no logs upload scheduled tasks left active when logs upload is
+  // disabled. It can be nullptr when caller is positive that there are none,
+  // i.e., when enabling for the first time.
+  virtual void EnableUploads(bool enabled, RegistryLogger* registry_logger) = 0;
+
+  // Schedule a task to upload logs in case we fail to progress beyond the point
+  // from where this is called, which can be identified by |result_code|. These
+  // fall back logs use the current state of raw log lines without flushing
+  // them, and use |result_code| as the protobuf exit code.
+  virtual void ScheduleFallbackLogsUpload(RegistryLogger* registry_logger,
+                                          ResultCode result_code) = 0;
+
+  // All of the following functions can be called from any thread:
+
+  virtual bool uploads_enabled() const = 0;
+
+  // Set |detailed_system_report| to |chrome_cleaner_report_|.
+  virtual void SetDetailedSystemReport(bool detailed_system_report) = 0;
+  virtual bool detailed_system_report_enabled() const = 0;
+
+  // Add |found_uws_name| to |chrome_cleaner_report_|.
+  virtual void AddFoundUwS(const std::string& found_uws_name) = 0;
+
+  // Add |found_uws| to |chrome_cleaner_report_| with the detail level of the
+  // UwS set according to |flags|.
+  // Can be called from any thread.
+  virtual void AddDetectedUwS(const PUPData::PUP* found_uws,
+                              UwSDetectedFlags flags) = 0;
+
+  // Adds a converted UwS proto to the list of matched UwS.
+  virtual void AddDetectedUwS(const UwS& uws) = 0;
+
+  // Set |exit_code| to |chrome_cleaner_report_|. Can be called from any thread.
+  // Must not be called except when really done.
+  virtual void SetExitCode(ResultCode exit_code) = 0;
+
+  // Add a loaded module to the system report.
+  virtual void AddLoadedModule(
+      const base::string16& name,
+      ModuleHost host,
+      const internal::FileInformation& file_information) = 0;
+
+  // Add a running service to the system report.
+  virtual void AddService(
+      const base::string16& display_name,
+      const base::string16& service_name,
+      const internal::FileInformation& file_information) = 0;
+
+  // Add an installed program to the system report.
+  virtual void AddInstalledProgram(const base::FilePath& folder_path) = 0;
+
+  // Add a running process to the system report.
+  virtual void AddProcess(
+      const base::string16& name,
+      const internal::FileInformation& file_information) = 0;
+
+  // Add a registry value |registry_value| which may have |file_informations|
+  // associated with it to the system report.
+  virtual void AddRegistryValue(
+      const internal::RegistryValue& registry_value,
+      const std::vector<internal::FileInformation>& file_informations) = 0;
+
+  // Add a layered service provider to the system report.
+  virtual void AddLayeredServiceProvider(
+      const std::vector<base::string16>& guids,
+      const internal::FileInformation& file_information) = 0;
+
+  // Set the WinInetProxy settings of the system report.
+  virtual void SetWinInetProxySettings(const base::string16& config,
+                                       const base::string16& bypass,
+                                       const base::string16& auto_config_url,
+                                       bool autodetect) = 0;
+
+  // Set the WinHttpProxy settings of the system report.
+  virtual void SetWinHttpProxySettings(const base::string16& config,
+                                       const base::string16& bypass) = 0;
+
+  // Add an installed extension to the system report.
+  virtual void AddInstalledExtension(const base::string16& extension_id,
+                                     ExtensionInstallMethod install_method) = 0;
+
+  // Add a scheduled task to the system report.
+  virtual void AddScheduledTask(
+      const base::string16& name,
+      const base::string16& description,
+      const std::vector<internal::FileInformation>& actions) = 0;
+
+  // Log resource usage of a Chrome Cleanup process identified by
+  // |process_type|.
+  virtual void LogProcessInformation(SandboxType process_type,
+                                     const SystemResourceUsage& usage) = 0;
+
+  // Returns whether all files detected for removable UwS were successfully
+  // deleted.
+  virtual bool AllExpectedRemovalsConfirmed() const = 0;
+
+  // Return a raw representation of the current state of the report.
+  virtual std::string RawReportContent() = 0;
+
+  // Read the content of |log_file| as a protocol buffer and replace current
+  // state with it. Return false on failures.
+  virtual bool ReadContentFromFile(const base::FilePath& log_file) = 0;
+
+  // If in debug mode or switch --dump-raw-logs is present, save the serialized
+  // report proto to |executable||tag|.pb, where |executable| is the current
+  // binary name.
+  virtual void MaybeSaveLogsToFile(const base::string16& tag);
+
+ private:
+  static LoggingServiceAPI* logging_service_for_testing_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_LOGGING_SERVICE_API_H_
diff --git a/chrome/chrome_cleaner/logging/message_builder.cc b/chrome/chrome_cleaner/logging/message_builder.cc
new file mode 100644
index 0000000..cb70ae5
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/message_builder.cc
@@ -0,0 +1,82 @@
+// Copyright 2018 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/chrome_cleaner/logging/message_builder.h"
+
+#include <utility>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace chrome_cleaner {
+
+MessageBuilder::MessageItem::MessageItem(base::StringPiece16 value)
+    : value_(value.as_string()) {}
+
+MessageBuilder::MessageItem::MessageItem(base::StringPiece value)
+    : value_(base::UTF8ToUTF16(value.as_string())) {}
+
+MessageBuilder::MessageItem::MessageItem(int value)
+    : value_(base::IntToString16(value)) {}
+
+MessageBuilder::ScopedIndent::ScopedIndent(MessageBuilder* builder)
+    : builder_(builder) {
+  builder_->IncreaseIdentationLevel();
+}
+
+MessageBuilder::ScopedIndent::ScopedIndent(
+    MessageBuilder::ScopedIndent&& other) {
+  operator=(std::move(other));  // Should call the move assignment operator.
+}
+
+MessageBuilder::ScopedIndent& MessageBuilder::ScopedIndent::operator=(
+    MessageBuilder::ScopedIndent&& other) {
+  std::swap(builder_, other.builder_);
+  return *this;
+}
+
+MessageBuilder::ScopedIndent::~ScopedIndent() {
+  builder_->DecreaseIdentationLevel();
+}
+
+void MessageBuilder::IncreaseIdentationLevel() {
+  ++indentation_level_;
+  if (!content_.empty() && content_.back() != L'\n')
+    NewLine();
+}
+
+void MessageBuilder::DecreaseIdentationLevel() {
+  DCHECK_GT(indentation_level_, 0);
+  --indentation_level_;
+  if (!content_.empty() && content_.back() != L'\n')
+    NewLine();
+}
+
+MessageBuilder& MessageBuilder::NewLine() {
+  content_ += L"\n";
+  return *this;
+}
+
+void MessageBuilder::AddInternal(std::initializer_list<MessageItem> values) {
+  for (auto value : values)
+    content_ += value.value();
+}
+
+void MessageBuilder::IndentIfNewLine() {
+  if (content_.empty() || content_.back() != L'\n')
+    return;
+  for (int i = 0; i < indentation_level_; ++i)
+    content_ += L"\t";
+}
+
+MessageBuilder& MessageBuilder::AddHeaderLine(base::StringPiece16 title) {
+  Add(title, L":").NewLine();
+  return *this;
+}
+
+MessageBuilder::ScopedIndent MessageBuilder::Indent() {
+  return MessageBuilder::ScopedIndent(this);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/message_builder.h b/chrome/chrome_cleaner/logging/message_builder.h
new file mode 100644
index 0000000..baaecfd5
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/message_builder.h
@@ -0,0 +1,151 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_MESSAGE_BUILDER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_MESSAGE_BUILDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+
+namespace chrome_cleaner {
+
+// Provides a fluent interface for building messages to be presented to the
+// user with functions to add values of basic types and common line patterns.
+// The class also provides an interface for automatically increasing the
+// indentation level and decrease it back when the current scope ends.
+//
+// Usage example:
+//   MessageBuilder builder;
+//   builder.AddHeaderLine(L"Main header");
+//   {
+//     MessageBuilder::ScopedIndent scoped_indent(&builder);
+//     builder
+//         .AddFieldValueLine(L"String16 field", L"abc")
+//         .AddFieldValueLine(L"Int field", 10)
+//         .AddFieldValueLine(L"String field", "xyz");
+//     {
+//       MessageBuilder::ScopedIndent scoped_indent(&builder);
+//       builder.Add(L"abc ", "xyz ", 10).NewLine();
+//       builder.AddFieldValueLine(L"Another field", L"pqr")
+
+//       MessageBuilder::ScopedIndent scoped_indent_2(&builder);
+//       builder.Add(1, L" ", 2, L" ", 4).NewLine();
+//     }
+//     builder.AddFieldValueLine(L"Last field", L"xyz")
+//   }
+//
+// At the end, builder.content() will contain (| represents the start of the
+// line):
+//     |Main header:
+//     |\tString16 field: abc
+//     |\tInt field: 10
+//     |\tString field: xyz
+//     |\t\tabc xyz 10
+//     |\t\tAnother field: pqr
+//     |\t\t\t1 2 4
+//     |\tLast field: xyz
+//
+// Note: scoped indentation can also be introduced by the following idiom:
+//     auto scoped_indent = builder.Indent();
+class MessageBuilder {
+ public:
+  // Increases the indentation level for |builder| in the current scope:
+  //   - on construction, this object will indentation level is increased by 1,
+  //     so all new lines will start with an additional tab;
+  //   - on destruction, indentation level is restored to its previous value.
+  // For convenience, an EOL symbol is appended to the result string whenever
+  // the indentation level changes and the last appended character is not EOL.
+  class ScopedIndent {
+   public:
+    explicit ScopedIndent(MessageBuilder* builder);
+    ScopedIndent(ScopedIndent&& other);
+    ~ScopedIndent();
+
+    ScopedIndent& operator=(ScopedIndent&& other);
+
+   private:
+    MessageBuilder* builder_;
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedIndent);
+  };
+
+  MessageBuilder() = default;
+
+  // Appends an EOL character to the result string.
+  MessageBuilder& NewLine();
+
+  // Appends a list of values to the result string.
+  template <typename... Values>
+  MessageBuilder& Add(const Values&... values) {
+    IndentIfNewLine();
+    AddInternal({MessageItem(values)...});
+    return *this;
+  }
+
+  // Appends a list of values to the result string and then appends an EOL.
+  // Equivalent to:
+  //   Add(...).NewLine()
+  template <typename... Values>
+  MessageBuilder& AddLine(const Values&... values) {
+    Add(values...).NewLine();
+    return *this;
+  }
+
+  // Adds a new line with |title| indented with |indentation_level| tabs.
+  // Equivalent to:
+  //   Add(title, ":").NewLine()
+  MessageBuilder& AddHeaderLine(base::StringPiece16 title);
+
+  // AddFieldValueLine adds a new line for a pair (|field_name|, |value|)
+  // indented with |indentation_level| tabs.
+  // Equivalent to:
+  //   Add(field_name, ": ", value).NewLine()
+  template <typename Value>
+  MessageBuilder& AddFieldValueLine(base::StringPiece16 field_name,
+                                    const Value& value) {
+    Add(field_name, L": ", value).NewLine();
+    return *this;
+  }
+
+  MessageBuilder::ScopedIndent Indent();
+
+  base::string16 content() const { return content_; }
+
+ protected:
+  // Updates the current indentation level and appends a L'\n' if it's not the
+  // last symbol appended to the result string.
+  void IncreaseIdentationLevel();
+  void DecreaseIdentationLevel();
+
+ private:
+  // Internal representation of a value that can be appended to the result
+  // string, so that values of different types can be added to the initializer
+  // list in AddInternal().
+  class MessageItem {
+   public:
+    explicit MessageItem(base::StringPiece16 value);
+    explicit MessageItem(base::StringPiece value);
+    explicit MessageItem(int value);
+
+    const base::string16& value() const { return value_; }
+
+   private:
+    base::string16 value_;
+  };
+
+  void AddInternal(std::initializer_list<MessageItem> values);
+  void IndentIfNewLine();
+
+  base::string16 content_;
+  int indentation_level_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(MessageBuilder);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_MESSAGE_BUILDER_H_
diff --git a/chrome/chrome_cleaner/logging/message_builder_unittest.cc b/chrome/chrome_cleaner/logging/message_builder_unittest.cc
new file mode 100644
index 0000000..162e65b
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/message_builder_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright 2018 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/chrome_cleaner/logging/message_builder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+TEST(MessageBuilderTest, NewLine) {
+  MessageBuilder builder;
+  builder.NewLine();
+  EXPECT_EQ(L"\n", builder.content());
+
+  builder.NewLine().NewLine();
+  EXPECT_EQ(L"\n\n\n", builder.content());
+}
+
+TEST(MessageBuilderTest, Add) {
+  MessageBuilder builder;
+  builder.Add(L"abc").Add(L" ").Add("xyz").Add(" ").Add(10);
+  EXPECT_EQ(L"abc xyz 10", builder.content());
+
+  builder.Add(" ").Add(true).Add(" ").Add(false);
+  EXPECT_EQ(L"abc xyz 10 1 0", builder.content());
+}
+
+TEST(MessageBuilderTest, AddLine) {
+  MessageBuilder builder;
+  builder.AddLine(L"abc", L" ", 10).AddLine("xyz", L" ", false);
+  base::string16 expected = L"abc 10\nxyz 0\n";
+  EXPECT_EQ(expected, builder.content());
+
+  builder.AddLine(" test ", true).AddLine(false);
+  expected += L" test 1\n0\n";
+  EXPECT_EQ(expected, builder.content());
+}
+
+TEST(MessageBuilderTest, ScopedIndentation) {
+  MessageBuilder builder;
+  builder.Add(L"*", L"*").NewLine();
+  base::string16 expected = L"**\n";
+  EXPECT_EQ(expected, builder.content());
+
+  {
+    MessageBuilder::ScopedIndent scoped_indent(&builder);
+    builder.Add(L"*", L"*").AddLine(L"*");
+    expected += L"\t***\n";
+    EXPECT_EQ(expected, builder.content());
+
+    {
+      auto scoped_indent = builder.Indent();
+      builder.Add(L"*", L"*", L"*").NewLine();
+      expected += L"\t\t***\n";
+      EXPECT_EQ(expected, builder.content());
+      builder.AddLine(L"*", L"*");
+      expected += L"\t\t**\n";
+      EXPECT_EQ(expected, builder.content());
+
+      MessageBuilder::ScopedIndent scoped_indent_2(&builder);
+      builder.Add(L"*", L"*").NewLine();
+      expected += L"\t\t\t**\n";
+      EXPECT_EQ(expected, builder.content());
+    }
+
+    builder.Add(L"*", L"*").NewLine();
+    expected += L"\t**\n";
+    EXPECT_EQ(expected, builder.content());
+  }
+
+  builder.Add(L"*");
+  expected += L"*";
+  EXPECT_EQ(expected, builder.content());
+
+  {
+    MessageBuilder::ScopedIndent scoped_indent(&builder);
+    expected += L"\n";  // Added due to change in indentation level.
+    EXPECT_EQ(expected, builder.content());
+
+    builder.Add(L"*", L"*");
+    expected += L"\t**";
+    EXPECT_EQ(expected, builder.content());
+  }
+
+  expected += L"\n";  // Added due to change in indentation level.
+  EXPECT_EQ(expected, builder.content());
+
+  builder.AddLine(L"*", L"*");
+  expected += L"**\n";
+  EXPECT_EQ(expected, builder.content());
+
+  {
+    auto scoped_indent = builder.Indent();
+    builder.AddLine(L"*", L"*");
+    expected += L"\t**\n";
+    EXPECT_EQ(expected, builder.content());
+  }
+
+  builder.Add(L"*", L"*").Add(L"*", L"*", L"*").NewLine();
+  expected += L"*****\n";
+  EXPECT_EQ(expected, builder.content());
+}
+
+TEST(MessageBuilderTest, AddHeaderLine) {
+  MessageBuilder builder;
+  builder.AddHeaderLine(L"Header1").AddHeaderLine(L"Header2");
+  base::string16 expected = L"Header1:\nHeader2:\n";
+  EXPECT_EQ(expected, builder.content());
+
+  MessageBuilder::ScopedIndent scoped_indent(&builder);
+  builder.AddHeaderLine(L"Header3");
+  expected += L"\tHeader3:\n";
+  EXPECT_EQ(expected, builder.content());
+
+  MessageBuilder::ScopedIndent scoped_indent_2(&builder);
+  builder.AddHeaderLine(L"Header4").AddHeaderLine(L"Header5");
+  expected += L"\t\tHeader4:\n\t\tHeader5:\n";
+  EXPECT_EQ(expected, builder.content());
+}
+
+TEST(MessageBuilderTest, AddFieldValueLine) {
+  MessageBuilder builder;
+
+  builder.AddFieldValueLine(L"Field1", "abc")
+      .AddFieldValueLine(L"Field2", L"xyz");
+  base::string16 expected = L"Field1: abc\nField2: xyz\n";
+  EXPECT_EQ(expected, builder.content());
+
+  MessageBuilder::ScopedIndent scoped_indent(&builder);
+  builder.AddFieldValueLine(L"Field3", 10);
+  expected += L"\tField3: 10\n";
+  EXPECT_EQ(expected, builder.content());
+
+  MessageBuilder::ScopedIndent scoped_indent_2(&builder);
+  builder.AddFieldValueLine(L"Field4", true)
+      .AddFieldValueLine(L"Field5", false);
+  expected += L"\t\tField4: 1\n\t\tField5: 0\n";
+  EXPECT_EQ(expected, builder.content());
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/mock_logging_service.cc b/chrome/chrome_cleaner/logging/mock_logging_service.cc
new file mode 100644
index 0000000..0a6491eb
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/mock_logging_service.cc
@@ -0,0 +1,13 @@
+// Copyright 2018 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/chrome_cleaner/logging/mock_logging_service.h"
+
+namespace chrome_cleaner {
+
+MockLoggingService::MockLoggingService() = default;
+
+MockLoggingService::~MockLoggingService() = default;
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/mock_logging_service.h b/chrome/chrome_cleaner/logging/mock_logging_service.h
new file mode 100644
index 0000000..ec0609f
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/mock_logging_service.h
@@ -0,0 +1,94 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_MOCK_LOGGING_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_MOCK_LOGGING_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "base/values.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/logging/utils.h"
+#include "chrome/chrome_cleaner/os/disk_util_types.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chrome_cleaner {
+
+// Mock for the LoggingService API.
+class MockLoggingService : public LoggingServiceAPI {
+ public:
+  MockLoggingService();
+  ~MockLoggingService() override;
+
+  // LoggingServiceAPI
+  MOCK_METHOD1(Initialize, void(RegistryLogger* registry_logger));
+  MOCK_METHOD0(Terminate, void());
+
+  MOCK_METHOD2(SendLogsToSafeBrowsing,
+               void(const UploadResultCallback& done_callback,
+                    RegistryLogger* registry_logger));
+  MOCK_METHOD0(CancelWaitForShutdown, void());
+  MOCK_METHOD2(EnableUploads,
+               void(bool enabled, RegistryLogger* registry_logger));
+  MOCK_CONST_METHOD0(uploads_enabled, bool());
+  MOCK_METHOD1(SetDetailedSystemReport, void(bool detailed_system_report));
+  MOCK_CONST_METHOD0(detailed_system_report_enabled, bool());
+  MOCK_METHOD1(AddFoundUwS, void(const std::string& found_uws_name));
+  MOCK_METHOD2(AddDetectedUwS,
+               void(const PUPData::PUP* found_uws, UwSDetectedFlags flags));
+  MOCK_METHOD1(AddDetectedUwS, void(const UwS& uws));
+  MOCK_METHOD1(SetExitCode, void(ResultCode exit_code));
+  MOCK_METHOD3(AddLoadedModule,
+               void(const base::string16& name,
+                    ModuleHost host,
+                    const internal::FileInformation& file_information));
+  MOCK_METHOD1(AddInstalledProgram, void(const base::FilePath& folder_path));
+  MOCK_METHOD3(AddService,
+               void(const base::string16& display_name,
+                    const base::string16& service_name,
+                    const internal::FileInformation& file_information));
+  MOCK_METHOD2(AddProcess,
+               void(const base::string16& name,
+                    const internal::FileInformation& file_information));
+  MOCK_METHOD2(
+      AddRegistryValue,
+      void(const internal::RegistryValue& registry_value,
+           const std::vector<internal::FileInformation>& file_informations));
+  MOCK_METHOD2(AddLayeredServiceProvider,
+               void(const std::vector<base::string16>& guids,
+                    const internal::FileInformation& file_information));
+  MOCK_METHOD4(SetWinInetProxySettings,
+               void(const base::string16& config,
+                    const base::string16& bypass,
+                    const base::string16& auto_config_url,
+                    bool autodetect));
+  MOCK_METHOD2(SetWinHttpProxySettings,
+               void(const base::string16& config,
+                    const base::string16& bypass));
+  MOCK_METHOD2(AddInstalledExtension,
+               void(const base::string16& extension_id,
+                    ExtensionInstallMethod install_method));
+  MOCK_METHOD3(AddScheduledTask,
+               void(const base::string16& name,
+                    const base::string16& description,
+                    const std::vector<internal::FileInformation>& actions));
+  MOCK_METHOD2(LogProcessInformation,
+               void(SandboxType process_type,
+                    const SystemResourceUsage& usage));
+  MOCK_CONST_METHOD0(AllExpectedRemovalsConfirmed, bool());
+  MOCK_METHOD0(RawReportContent, std::string());
+  MOCK_METHOD1(ReadContentFromFile, bool(const base::FilePath& log_file));
+  MOCK_METHOD2(ScheduleFallbackLogsUpload,
+               void(RegistryLogger* registry_logger, ResultCode result_code));
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_MOCK_LOGGING_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/network_checker.h b/chrome/chrome_cleaner/logging/network_checker.h
new file mode 100644
index 0000000..c06ab18e
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/network_checker.h
@@ -0,0 +1,34 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_NETWORK_CHECKER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_NETWORK_CHECKER_H_
+
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace chrome_cleaner {
+
+// Base class that provides methods to test whether Safe Browsing is reachable
+// or not, and to wait for it to be reachable.
+class NetworkChecker {
+ public:
+  virtual ~NetworkChecker() = default;
+
+  // Returns whether Safe Browsing is currently reachable or not.
+  virtual bool IsSafeBrowsingReachable(const GURL& upload_url) const = 0;
+
+  // Blocks until Safe Browsing is reachable, up to |wait_time|, whichever
+  // happens first. Returns whether Safe Browsing is reachable or not when this
+  // function exits.
+  virtual bool WaitForSafeBrowsing(const GURL& upload_url,
+                                   const base::TimeDelta& wait_time) = 0;
+
+  // Cancels all current and future waits, to speed up system shutdown.
+  virtual void CancelWaitForShutdown() = 0;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_NETWORK_CHECKER_H_
diff --git a/chrome/chrome_cleaner/logging/noop_logging_service.cc b/chrome/chrome_cleaner/logging/noop_logging_service.cc
new file mode 100644
index 0000000..9e028a8a
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/noop_logging_service.cc
@@ -0,0 +1,125 @@
+// Copyright 2018 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/chrome_cleaner/logging/noop_logging_service.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+
+namespace chrome_cleaner {
+
+NoOpLoggingService* NoOpLoggingService::GetInstance() {
+  return base::Singleton<NoOpLoggingService>::get();
+}
+
+NoOpLoggingService::NoOpLoggingService() {}
+
+void NoOpLoggingService::Initialize(RegistryLogger* registry_logger) {}
+
+void NoOpLoggingService::Terminate() {}
+
+void NoOpLoggingService::SendLogsToSafeBrowsing(
+    const UploadResultCallback& done_callback,
+    RegistryLogger* registry_logger) {
+  done_callback.Run(false);
+}
+
+void NoOpLoggingService::CancelWaitForShutdown() {}
+
+void NoOpLoggingService::EnableUploads(bool enabled,
+                                       RegistryLogger* registry_logger) {
+  DCHECK(!enabled);
+}
+
+bool NoOpLoggingService::uploads_enabled() const {
+  return false;
+}
+
+void NoOpLoggingService::SetDetailedSystemReport(
+    bool /*detailed_system_report*/) {}
+
+bool NoOpLoggingService::detailed_system_report_enabled() const {
+  return false;
+}
+
+void NoOpLoggingService::AddFoundUwS(const std::string& /*found_uws_name*/) {}
+
+void NoOpLoggingService::AddDetectedUwS(const PUPData::PUP* /*found_uws*/,
+                                        UwSDetectedFlags flags) {}
+
+void NoOpLoggingService::AddDetectedUwS(const UwS& uws) {}
+
+void NoOpLoggingService::SetExitCode(ResultCode /*exit_code*/) {}
+
+void NoOpLoggingService::AddLoadedModule(
+    const base::string16& /*name*/,
+    ModuleHost /*host*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void NoOpLoggingService::AddService(
+    const base::string16& /*display_name*/,
+    const base::string16& /*service_name*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void NoOpLoggingService::AddInstalledProgram(
+    const base::FilePath& /*folder_path*/) {}
+
+void NoOpLoggingService::AddProcess(
+    const base::string16& /*name*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void NoOpLoggingService::AddRegistryValue(
+    const internal::RegistryValue& /*registry_value*/,
+    const std::vector<internal::FileInformation>& /*file_informations*/) {}
+
+void NoOpLoggingService::AddLayeredServiceProvider(
+    const std::vector<base::string16>& /*guids*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void NoOpLoggingService::SetWinInetProxySettings(
+    const base::string16& /*config*/,
+    const base::string16& /*bypass*/,
+    const base::string16& /*auto_config_url*/,
+    bool /*autodetect*/) {}
+
+void NoOpLoggingService::SetWinHttpProxySettings(
+    const base::string16& /*config*/,
+    const base::string16& /*bypass*/) {}
+
+void NoOpLoggingService::AddInstalledExtension(
+    const base::string16& extension_id,
+    ExtensionInstallMethod install_method) {}
+
+void NoOpLoggingService::AddScheduledTask(
+    const base::string16& /*name*/,
+    const base::string16& /*description*/,
+    const std::vector<internal::FileInformation>& /*actions*/) {}
+
+void NoOpLoggingService::LogProcessInformation(
+    SandboxType /*process_type*/,
+    const SystemResourceUsage& /*usage*/) {}
+
+bool NoOpLoggingService::AllExpectedRemovalsConfirmed() const {
+  // This function should never be called on no-op logging service as it's used
+  // only in the reporter. Return |false| as the default value to indicate error
+  // if it ever happens.
+  NOTREACHED();
+  return false;
+}
+
+std::string NoOpLoggingService::RawReportContent() {
+  return std::string();
+}
+
+bool NoOpLoggingService::ReadContentFromFile(const base::FilePath& log_file) {
+  return true;
+}
+
+void NoOpLoggingService::ScheduleFallbackLogsUpload(
+    RegistryLogger* registry_logger,
+    ResultCode result_code) {}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/noop_logging_service.h b/chrome/chrome_cleaner/logging/noop_logging_service.h
new file mode 100644
index 0000000..c062731
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/noop_logging_service.h
@@ -0,0 +1,93 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_NOOP_LOGGING_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_NOOP_LOGGING_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}  // namespace base
+
+namespace chrome_cleaner {
+
+// Manage where the logs are sent, and expose an API for more specific logging.
+class NoOpLoggingService : public LoggingServiceAPI {
+ public:
+  // Return the singleton instance which will get destroyed by the AtExitMgr.
+  static NoOpLoggingService* GetInstance();
+
+  // LoggingServiceAPI.
+  void Initialize(RegistryLogger* registry_logger) override;
+  void Terminate() override;
+
+  void SendLogsToSafeBrowsing(const UploadResultCallback& done_callback,
+                              RegistryLogger* registry_logger) override;
+  void CancelWaitForShutdown() override;
+  void EnableUploads(bool enabled, RegistryLogger* registry_logger) override;
+  bool uploads_enabled() const override;
+  void SetDetailedSystemReport(bool detailed_system_report) override;
+  bool detailed_system_report_enabled() const override;
+  void AddFoundUwS(const std::string& found_uws_name) override;
+  void AddDetectedUwS(const PUPData::PUP* found_uws,
+                      UwSDetectedFlags flags) override;
+  void AddDetectedUwS(const UwS& uws) override;
+  void SetExitCode(ResultCode exit_code) override;
+  void AddLoadedModule(
+      const base::string16& name,
+      ModuleHost host,
+      const internal::FileInformation& file_information) override;
+  void AddInstalledProgram(const base::FilePath& folder_path) override;
+  void AddService(const base::string16& display_name,
+                  const base::string16& service_name,
+                  const internal::FileInformation& file_information) override;
+  void AddProcess(const base::string16& name,
+                  const internal::FileInformation& file_information) override;
+  void AddRegistryValue(
+      const internal::RegistryValue& registry_value,
+      const std::vector<internal::FileInformation>& file_informations) override;
+  void AddLayeredServiceProvider(
+      const std::vector<base::string16>& guids,
+      const internal::FileInformation& file_information) override;
+  void SetWinInetProxySettings(const base::string16& config,
+                               const base::string16& bypass,
+                               const base::string16& auto_config_url,
+                               bool autodetect) override;
+  void SetWinHttpProxySettings(const base::string16& config,
+                               const base::string16& bypass) override;
+  void AddInstalledExtension(const base::string16& extension_id,
+                             ExtensionInstallMethod install_method) override;
+  void AddScheduledTask(
+      const base::string16& name,
+      const base::string16& description,
+      const std::vector<internal::FileInformation>& actions) override;
+  void LogProcessInformation(SandboxType process_type,
+                             const SystemResourceUsage& usage) override;
+  bool AllExpectedRemovalsConfirmed() const override;
+  std::string RawReportContent() override;
+  bool ReadContentFromFile(const base::FilePath& log_file) override;
+  void ScheduleFallbackLogsUpload(RegistryLogger* registry_logger,
+                                  ResultCode result_code) override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<NoOpLoggingService>;
+  NoOpLoggingService();
+
+  DISALLOW_COPY_AND_ASSIGN(NoOpLoggingService);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_NOOP_LOGGING_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/other_logging_definitions.cc b/chrome/chrome_cleaner/logging/other_logging_definitions.cc
new file mode 100644
index 0000000..f245bf93
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/other_logging_definitions.cc
@@ -0,0 +1,19 @@
+// Copyright 2018 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.
+
+// Provides the logging service instance to be used by executables other than
+// the reporter and cleaner.
+
+#include "chrome/chrome_cleaner/logging/logging_definitions.h"
+#include "chrome/chrome_cleaner/logging/noop_logging_service.h"
+
+namespace chrome_cleaner {
+
+// Returns an instance of ReporterLoggingService if logs collection is enabled,
+// or NoOpLoggingService otherwise.
+LoggingServiceAPI* GetLoggingServiceForCurrentBuild() {
+  return NoOpLoggingService::GetInstance();
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/pending_logs_service.cc b/chrome/chrome_cleaner/logging/pending_logs_service.cc
new file mode 100644
index 0000000..7e5731a
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/pending_logs_service.cc
@@ -0,0 +1,183 @@
+// Copyright 2018 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/chrome_cleaner/logging/pending_logs_service.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
+#include "chrome/chrome_cleaner/logging/registry_logger.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
+#include "chrome/chrome_cleaner/os/task_scheduler.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace chrome_cleaner {
+
+bool PendingLogsService::retrying_ = false;
+
+// static.
+base::string16 PendingLogsService::LogsUploadRetryTaskName(
+    const base::string16& product_shortname) {
+  return product_shortname + L" logs upload retry";
+}
+
+// static.
+void PendingLogsService::ScheduleLogsUploadTask(
+    const base::string16& product_shortname,
+    const ChromeCleanerReport& chrome_cleaner_report,
+    base::FilePath* log_file,
+    RegistryLogger* registry_logger) {
+  DCHECK(base::MessageLoopForUI::IsCurrent());
+  DCHECK(log_file);
+  DCHECK(registry_logger);
+  // This can happen when we fail while retrying. The logging service is not
+  // aware of this state and will call us again. Simply ignore the call.
+  if (retrying_)
+    return;
+
+  std::string chrome_cleaner_report_string;
+  if (!chrome_cleaner_report.SerializeToString(&chrome_cleaner_report_string)) {
+    PLOG(ERROR) << "SerializeToString failed!";
+    return;
+  }
+
+  base::FilePath product_app_data_path;
+  base::FilePath temp_file_path;
+  if (!GetAppDataProductDirectory(&product_app_data_path) ||
+      !CreateTemporaryFileInDir(product_app_data_path, &temp_file_path)) {
+    PLOG(ERROR) << "Create a temporary log file failed!";
+    return;
+  }
+
+  // To get rid of the temporary file if we are not going to use it.
+  base::ScopedClosureRunner delete_file_closure(base::BindRepeating(
+      IgnoreResult(&base::DeleteFile), temp_file_path, false));
+
+  if (base::WriteFile(temp_file_path, chrome_cleaner_report_string.c_str(),
+                      chrome_cleaner_report_string.size()) <= 0) {
+    PLOG(ERROR) << "Failed to write logging report to "
+                << SanitizePath(temp_file_path);
+    return;
+  }
+
+  // AppendLogFilePath already logs sufficiently on failures.
+  if (!registry_logger->AppendLogFilePath(temp_file_path))
+    return;
+
+  base::CommandLine scheduled_command_line(
+      PreFetchedPaths::GetInstance()->GetExecutablePath());
+  scheduled_command_line.AppendSwitch(kUploadLogFileSwitch);
+  // Propage the cleanup id for the current process, so we can identify the
+  // corresponding pending logs.
+  scheduled_command_line.AppendSwitchASCII(
+      kCleanupIdSwitch, Settings::GetInstance()->cleanup_id());
+  scheduled_command_line.AppendArguments(
+      *base::CommandLine::ForCurrentProcess(), false);
+  std::unique_ptr<TaskScheduler> task_scheduler(
+      TaskScheduler::CreateInstance());
+  if (!task_scheduler->RegisterTask(
+          LogsUploadRetryTaskName(product_shortname).c_str(),
+          /*task_description=*/L"", scheduled_command_line,
+          TaskScheduler::TRIGGER_TYPE_EVERY_SIX_HOURS, false)) {
+    LOG(ERROR) << "Failed to register logs upload retry task.";
+    registry_logger->RemoveLogFilePath(temp_file_path);
+    return;
+  }
+  // TODO(csharp): when we add support to upload pending logs in regular
+  // unscheduled runs, this should move up before the scheduling.
+  // Prevent the file deletion now that we confirmed it will be used.
+  delete_file_closure.Release().Reset();
+  *log_file = temp_file_path;
+}
+
+// static.
+void PendingLogsService::ClearPendingLogFile(
+    const base::string16& product_shortname,
+    const base::FilePath& log_file,
+    RegistryLogger* registry_logger) {
+  DCHECK(registry_logger);
+  // RemoveLogFilePath returns false when there are no log files left in the
+  // registry, in which case we must delete the scheduled task too.
+  if (!registry_logger->RemoveLogFilePath(log_file)) {
+    std::unique_ptr<TaskScheduler> task_scheduler(
+        TaskScheduler::CreateInstance());
+    if (!task_scheduler->DeleteTask(
+            LogsUploadRetryTaskName(product_shortname).c_str()))
+      LOG(ERROR) << "Failed to delete logs upload retry task.";
+  }
+
+  if (!base::DeleteFile(log_file, false))
+    LOG(ERROR) << "Failed to delete '" << SanitizePath(log_file) << "'.";
+}
+
+PendingLogsService::PendingLogsService() {
+  DCHECK(base::MessageLoopForUI::IsCurrent());
+}
+
+PendingLogsService::~PendingLogsService() = default;
+
+void PendingLogsService::RetryNextPendingLogsUpload(
+    const base::string16& product_shortname,
+    base::OnceCallback<void(bool)> done_callback,
+    RegistryLogger* registry_logger) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(registry_logger);
+  DCHECK(!retrying_);
+  retrying_ = true;
+
+  LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance();
+
+  // TODO(csharp): Loop through all the pending log files in one shot instead of
+  // having to wait for another scheduled task interval for the next one.
+  registry_logger->GetNextLogFilePath(&log_file_);
+  if (log_file_.empty()) {
+    LOG(ERROR) << "Got no log file path from registry";
+    logging_service->SetExitCode(RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE);
+  } else if (!logging_service->ReadContentFromFile(log_file_)) {
+    LOG(ERROR) << "Error reading log content from file";
+    ClearPendingLogFile(product_shortname, log_file_, registry_logger);
+    log_file_.clear();
+
+    // Send a plain log with just the proper result code in case it can work.
+    logging_service->SetExitCode(RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE);
+  } else {
+    // Add one more log before we upload, to specify that we are sending this
+    // log from a retry task. TODO(csharp): Maybe add this as a protobuf field.
+    LOG(INFO) << "Uploading persisted pending logs.";
+  }
+
+  done_callback_ = std::move(done_callback);
+  logging_service->SendLogsToSafeBrowsing(
+      base::BindRepeating(&PendingLogsService::UploadResultCallback,
+                          base::Unretained(this), product_shortname,
+                          registry_logger),
+      registry_logger);
+}
+
+void PendingLogsService::UploadResultCallback(
+    const base::string16& product_shortname,
+    RegistryLogger* registry_logger,
+    bool success) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(retrying_);
+  if (success && !log_file_.empty())
+    ClearPendingLogFile(product_shortname, log_file_, registry_logger);
+
+  log_file_.clear();
+  retrying_ = false;
+  std::move(done_callback_).Run(success);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/pending_logs_service.h b/chrome/chrome_cleaner/logging/pending_logs_service.h
new file mode 100644
index 0000000..1e537852
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/pending_logs_service.h
@@ -0,0 +1,82 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_PENDING_LOGS_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_PENDING_LOGS_SERVICE_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+
+namespace chrome_cleaner {
+
+class ChromeCleanerReport;
+class RegistryLogger;
+
+// A combination of static helper functions as well as a living service to
+// retry sending logs when others fail to do so successfully. All these methods
+// must be called from the main UI message loop.
+class PendingLogsService {
+ public:
+  // Returns the name of a task to run hourly, until logs upload for the
+  // product named |product_shortname| succeeded.
+  static base::string16 LogsUploadRetryTaskName(
+      const base::string16& product_shortname);
+
+  // TODO(csharp): Many of these methods receive a RegistryLogger. Maybe we
+  // should turn it into a singleton.
+
+  // Persist |chrome_cleaner_report| and schedule a task to try uploading it
+  // again. Return the path to the temporary file where the logs were saved in
+  // |log_file|. Caller specifies |registry_logger| so it can be mocked.
+  static void ScheduleLogsUploadTask(
+      const base::string16& product_shortname,
+      const ChromeCleanerReport& chrome_cleaner_report,
+      base::FilePath* log_file,
+      RegistryLogger* registry_logger);
+
+  // Clear the specified log file from pending logs upload, and also remove
+  // the scheduled task if there are no pending logs files in there. Caller
+  // specifies |registry_logger| so it can be mocked.
+  static void ClearPendingLogFile(const base::string16& product_shortname,
+                                  const base::FilePath& log_file,
+                                  RegistryLogger* registry_logger);
+
+  PendingLogsService();
+  ~PendingLogsService();
+
+  // Retry to upload the next pending log if any. |done_callback| is called with
+  // success/failure result of the logs upload, when it's done, which can be
+  // synchronously on some failures, and asynchronously upon success.  Caller
+  // specifies |registry_logger| so it can be mocked.
+  void RetryNextPendingLogsUpload(const base::string16& product_shortname,
+                                  base::OnceCallback<void(bool)> done_callback,
+                                  RegistryLogger* registry_logger);
+
+ private:
+  // Callback to be registered in the logging service.
+  void UploadResultCallback(const base::string16& product_shortname,
+                            RegistryLogger* registry_logger,
+                            bool success);
+
+  // Thread checker, to make sure |done_callback_| gets run on the right thread.
+  THREAD_CHECKER(thread_checker_);
+
+  // Our caller's callback to Run when we're done.
+  base::OnceCallback<void(bool)> done_callback_;
+
+  // The file we are currently attempting to upload.
+  base::FilePath log_file_;
+
+  // Remember whether we are currently retrying to upload logs so that we don't
+  // re-re-re-re-retry... ;-)
+  static bool retrying_;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingLogsService);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_PENDING_LOGS_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/pending_logs_service_unittest.cc b/chrome/chrome_cleaner/logging/pending_logs_service_unittest.cc
new file mode 100644
index 0000000..18305b3d
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/pending_logs_service_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright 2018 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/chrome_cleaner/logging/pending_logs_service.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_reg_util_win.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/http/http_agent_factory.h"
+#include "chrome/chrome_cleaner/http/mock_http_agent_factory.h"
+#include "chrome/chrome_cleaner/logging/cleaner_logging_service.h"
+#include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
+#include "chrome/chrome_cleaner/logging/registry_logger.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/logging/test_utils.h"
+#include "chrome/chrome_cleaner/os/task_scheduler.h"
+#include "chrome/chrome_cleaner/test/test_branding.h"
+#include "chrome/chrome_cleaner/test/test_settings_util.h"
+#include "chrome/chrome_cleaner/test/test_task_scheduler.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+class PendingLogsServiceTest : public testing::Test {
+ public:
+  PendingLogsServiceTest()
+      : logging_service_(nullptr),
+        scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI),
+        done_callback_called_(false),
+        upload_success_(false),
+        cleanup_execution_mode_settings_(ExecutionMode::kCleanup) {}
+
+  void SetUp() override {
+    // This test creates an instance of CleanerLoggingService, which requires
+    // ExecutionMode::kCleanup to log properly.
+    Settings::SetInstanceForTesting(&cleanup_execution_mode_settings_);
+
+    registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
+    // The registry logger must be created after calling OverrideRegistry.
+    registry_logger_.reset(new RegistryLogger(RegistryLogger::Mode::REMOVER));
+
+    // Make sure to clear any previous tests content, e.g., log lines.
+    // Individual tests will enable it appropriately.
+    logging_service_ = CleanerLoggingService::GetInstance();
+    LoggingServiceAPI::SetInstanceForTesting(logging_service_);
+    logging_service_->Initialize(registry_logger_.get());
+
+    // Use a mock HttpAgent instead of making real network requests.
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(
+        http_agent_factory_.get());
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(&network_checker_);
+  }
+
+  void TearDown() override {
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(nullptr);
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(nullptr);
+
+    registry_logger_.reset(nullptr);
+
+    logging_service_->Terminate();
+    LoggingServiceAPI::SetInstanceForTesting(nullptr);
+
+    Settings::SetInstanceForTesting(nullptr);
+  }
+
+  // RetryNextPendingLogsUpload callback implementation.
+  void LogsUploadCallback(base::OnceClosure run_loop_quit, bool success) {
+    done_callback_called_ = true;
+    upload_success_ = success;
+
+    // A RunLoop will be waiting for this callback to run before proceeding
+    // with the test. Now that the callback has run, we can quit the RunLoop.
+    std::move(run_loop_quit).Run();
+  }
+
+ protected:
+  // Helper method to successfully upload logs for a pending log file failure.
+  void ValidatePendingLogsUploadFailure(const base::FilePath& log_file) {
+    MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+    const size_t calls_index = http_agent_config_.AddCalls(calls);
+
+    // Set the failing file as the next pending log.
+    ASSERT_TRUE(registry_logger_->AppendLogFilePath(log_file));
+    // And now try to upload it, it should fail, but a failure logs should
+    // successfully be uploaded.
+    base::RunLoop run_loop;
+    PendingLogsService pending_logs_service;
+    pending_logs_service.RetryNextPendingLogsUpload(
+        TEST_PRODUCT_SHORTNAME_STRING,
+        base::BindRepeating(&PendingLogsServiceTest::LogsUploadCallback,
+                            base::Unretained(this), run_loop.QuitClosure()),
+        registry_logger_.get());
+    run_loop.Run();
+
+    std::string upload_data(http_agent_config_.request_data(calls_index).body);
+    ChromeCleanerReport uploaded_report;
+    ASSERT_TRUE(uploaded_report.ParseFromString(upload_data));
+
+    // Et voila!
+    EXPECT_EQ(
+        RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE,
+        static_cast<chrome_cleaner::ResultCode>(uploaded_report.exit_code()));
+
+    // All succeeded, even though the log file could not be read.
+    EXPECT_TRUE(done_callback_called_);
+    EXPECT_TRUE(upload_success_);
+
+    // And make sure the pending log that couldn't be read has been cleaned up.
+    base::FilePath empty_log_file;
+    registry_logger_->GetNextLogFilePath(&empty_log_file);
+    EXPECT_TRUE(empty_log_file.empty());
+
+    // And that there are no more task scheduled to try again.
+    task_scheduler_.ExpectDeleteTaskCalled(true);
+  }
+
+  LoggingServiceAPI* logging_service_;
+
+  registry_util::RegistryOverrideManager registry_override_manager_;
+  std::unique_ptr<RegistryLogger> registry_logger_;
+
+  // Needed for the current task runner to be available.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  // |done_callback_called_| is set to true in |LogsUploadCallback| to confirm
+  // it was called appropriately.
+  bool done_callback_called_;
+  // Set with the value given to the done callback. Default to false.
+  bool upload_success_;
+
+  // Mocked TaskScheduler.
+  TestTaskScheduler task_scheduler_;
+
+  // Overridden settings with ExecutionMode::kCleanup.
+  SettingsWithExecutionModeOverride cleanup_execution_mode_settings_;
+
+  MockHttpAgentConfig http_agent_config_;
+  std::unique_ptr<HttpAgentFactory> http_agent_factory_{
+      std::make_unique<MockHttpAgentFactory>(&http_agent_config_)};
+  MockNetworkChecker network_checker_;
+};
+
+TEST_F(PendingLogsServiceTest, SuccessfulRegistration) {
+  ChromeCleanerReport chrome_cleaner_report;
+  chrome_cleaner_report.set_exit_code(RESULT_CODE_NO_PUPS_FOUND);
+
+  base::FilePath temp_log_file;
+  PendingLogsService::ScheduleLogsUploadTask(
+      TEST_PRODUCT_SHORTNAME_STRING, chrome_cleaner_report, &temp_log_file,
+      registry_logger_.get());
+
+  // The pending log should have been registered and a task scheduled to use it.
+  base::FilePath log_file;
+  registry_logger_->GetNextLogFilePath(&log_file);
+  EXPECT_FALSE(log_file.empty());
+  EXPECT_EQ(temp_log_file, log_file);
+
+  task_scheduler_.ExpectRegisterTaskCalled(true);
+
+  // Cleanup.
+  EXPECT_FALSE(registry_logger_->RemoveLogFilePath(log_file));
+  EXPECT_TRUE(base::DeleteFile(log_file, false));
+}
+
+TEST_F(PendingLogsServiceTest, FailToRegisterScheduledTask) {
+  ChromeCleanerReport chrome_cleaner_report;
+  chrome_cleaner_report.set_exit_code(RESULT_CODE_NO_PUPS_FOUND);
+
+  // Make the scheduled task registration fail.
+  task_scheduler_.SetRegisterTaskReturnValue(false);
+
+  base::FilePath temp_log_file;
+  PendingLogsService::ScheduleLogsUploadTask(
+      TEST_PRODUCT_SHORTNAME_STRING, chrome_cleaner_report, &temp_log_file,
+      registry_logger_.get());
+  EXPECT_TRUE(temp_log_file.empty());
+
+  // The temporary file should have been deleted (as validated by the temp file
+  // watcher run from run_all_tests.bat) and not registered by the logger.
+  base::FilePath log_file;
+  registry_logger_->GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+}
+
+TEST_F(PendingLogsServiceTest, UploadPendingLogs) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Setup a simple non-empty report to send.
+  ChromeCleanerReport raw_report;
+  raw_report.set_exit_code(RESULT_CODE_NO_PUPS_FOUND);
+
+  std::string raw_report_string;
+  ASSERT_TRUE(raw_report.SerializeToString(&raw_report_string));
+
+  // Save it on disk.
+  base::FilePath log_file;
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  ASSERT_TRUE(
+      base::CreateTemporaryFileInDir(scoped_temp_dir.GetPath(), &log_file));
+  ASSERT_TRUE(base::PathExists(log_file));
+  ASSERT_GT(base::WriteFile(log_file, raw_report_string.c_str(),
+                            raw_report_string.size()),
+            0);
+
+  // Set it up as a pending log.
+  ASSERT_TRUE(registry_logger_->AppendLogFilePath(log_file));
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  const size_t calls_index = http_agent_config_.AddCalls(calls);
+
+  // And now upload it.
+  PendingLogsService pending_logs_service;
+  base::RunLoop run_loop;
+  pending_logs_service.RetryNextPendingLogsUpload(
+      TEST_PRODUCT_SHORTNAME_STRING,
+      base::BindRepeating(&PendingLogsServiceTest::LogsUploadCallback,
+                          base::Unretained(this), run_loop.QuitClosure()),
+      registry_logger_.get());
+  run_loop.Run();
+
+  ChromeCleanerReport uploaded_report;
+  ASSERT_TRUE(uploaded_report.ParseFromString(
+      http_agent_config_.request_data(calls_index).body));
+  // There should have been one raw log line added to specify a retry.
+  EXPECT_EQ(1, uploaded_report.raw_log_line_size());
+  // We need to clear it for the reports to be the same.
+  uploaded_report.clear_raw_log_line();
+  std::string uploaded_report_string;
+  ASSERT_TRUE(uploaded_report.SerializeToString(&uploaded_report_string));
+  EXPECT_EQ(raw_report_string, uploaded_report_string);
+
+  EXPECT_TRUE(done_callback_called_);
+  EXPECT_TRUE(upload_success_);
+
+  // And make sure the pending log has been cleaned up.
+  EXPECT_FALSE(base::PathExists(log_file));
+  base::FilePath empty_log_file;
+  registry_logger_->GetNextLogFilePath(&empty_log_file);
+  EXPECT_TRUE(empty_log_file.empty());
+}
+
+TEST_F(PendingLogsServiceTest, UploadPendingLogsFromNonexistentFile) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Create a nonexistent file path.
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  // Only the folder was actually created, so Bla is always nonexistent.
+  base::FilePath nonexistent_file(scoped_temp_dir.GetPath().Append(L"Bla"));
+  ASSERT_FALSE(base::PathExists(nonexistent_file));
+
+  ValidatePendingLogsUploadFailure(nonexistent_file);
+}
+
+TEST_F(PendingLogsServiceTest, UploadPendingLogsFromEmptyFile) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Create an empty file.
+  base::FilePath empty_file;
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  ASSERT_TRUE(
+      base::CreateTemporaryFileInDir(scoped_temp_dir.GetPath(), &empty_file));
+  ASSERT_TRUE(base::PathExists(empty_file));
+
+  ValidatePendingLogsUploadFailure(empty_file);
+}
+
+TEST_F(PendingLogsServiceTest, UploadPendingLogsFromInvalidFile) {
+  logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Create an invalid file.
+  base::FilePath invalid_file;
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  ASSERT_TRUE(
+      base::CreateTemporaryFileInDir(scoped_temp_dir.GetPath(), &invalid_file));
+  ASSERT_TRUE(base::PathExists(invalid_file));
+  ASSERT_GT(base::WriteFile(invalid_file, "wat", 3), 0);
+
+  ValidatePendingLogsUploadFailure(invalid_file);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.proto b/chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.proto
index dfad302..2b756e4 100644
--- a/chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.proto
+++ b/chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.proto
@@ -136,6 +136,9 @@
     reserved 10;
     // Deprecated AntiVirusProduct field that stopped being sent after 29.157.
     reserved 11;
+    // Deprecated incomplete_uws_matches that stopped being sent after
+    // 31.164.200
+    reserved 15;
 
     // Information about modules loaded into selected processes.
     // Next tag: 4.
@@ -221,10 +224,6 @@
   }
   optional SystemReport system_report = 14;
 
-  // Information found by Chrome Cleaner while running custom UwS matchers
-  // with incomplete matching results.
-  repeated IncompleteUwSMatch incomplete_uws_matches = 15;
-
   // Information about Chrome Cleaner processes and their resource usage.
   repeated ProcessInformation process_information = 19;
 }
diff --git a/chrome/chrome_cleaner/logging/proto/interface_logger.proto b/chrome/chrome_cleaner/logging/proto/interface_logger.proto
index 6cb9ba47..5bc69171 100644
--- a/chrome/chrome_cleaner/logging/proto/interface_logger.proto
+++ b/chrome/chrome_cleaner/logging/proto/interface_logger.proto
@@ -14,7 +14,7 @@
 message APICall {
   string file_name = 1;
   string function_name = 2;
-  int64 time_called_ticks = 3;
+  int64 microseconds_since_start = 3;
   map<string, string> parameters = 4;
 }
 
diff --git a/chrome/chrome_cleaner/logging/proto/shared_data.proto b/chrome/chrome_cleaner/logging/proto/shared_data.proto
index 18e67004..2bfd6e0 100644
--- a/chrome/chrome_cleaner/logging/proto/shared_data.proto
+++ b/chrome/chrome_cleaner/logging/proto/shared_data.proto
@@ -149,7 +149,7 @@
 }
 
 // All the information found by the app about an UwS during a run.
-// Next tag: 10.
+// Next tag: 11.
 message UwS {
   optional uint32 id = 6;
   optional string name = 1;
@@ -169,14 +169,12 @@
   // Documents the detail level with which this UwS was collected.
   // Next tag: 3.
   message DetailLevel {
+    // Deprecated ran_collectors field that stopped being sent after 31.163.
+    reserved 2;
+
     // Whether or not the scanner stopped after finding one active
     // footprint.
     optional bool only_one_footprint = 1;
-
-    // Whether or not collectors were run to find items on the system,
-    // such as links and registry entries, that point to matched
-    // footprints.
-    optional bool ran_collectors = 2;
   }
   optional DetailLevel detail_level = 5;
 
@@ -185,7 +183,7 @@
     UNKNOWN = 0;
     REPORT_ONLY = 1;
     REMOVABLE = 2;
-    FORCED_DETECTION = 3;
+    DEPRECATED_FORCED_DETECTION = 3;  // Deprecated after 31.163.
   }
   optional State state = 8;
 
@@ -207,32 +205,8 @@
     FOUND_IN_MODULES = 10;         // Loaded modules of a process
   }
   repeated TraceLocation trace_locations = 9;
-}
 
-// Information collected by the app when it's only able to find incomplete
-// matches for an UwS that could correspond to a new flavour that is currently
-// not tracked.
-// Next tag: 4.
-message IncompleteUwSMatch {
-  optional uint32 uws_id = 1;
-  optional string uws_name = 2;
-
-  // Clue corresponding to an incomplete UwS matching.
-  // Next tag: 5.
-  message Clue {
-    // Deprecated details field that was defined only in 11.72 but never used.
-    reserved 3;
-
-    // Title of the clue (e.g. main problem encountered when attempting
-    // a match, relevance of files and registry entries listed in the trace
-    // etc.).
-    optional string title = 1;
-
-    repeated MatchedFile files = 2;
-
-    repeated MatchedRegistryEntry registry_entries = 4;
-  }
-  repeated Clue clues = 3;
+  optional Engine.Name detected_by = 10;
 }
 
 // ChromeFoilResponse is a legacy name that must be kept because existing
diff --git a/chrome/chrome_cleaner/logging/registry_logger.cc b/chrome/chrome_cleaner/logging/registry_logger.cc
new file mode 100644
index 0000000..489410c
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/registry_logger.cc
@@ -0,0 +1,430 @@
+// Copyright 2018 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/chrome_cleaner/logging/registry_logger.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/files/file_path.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/registry.h"
+#include "chrome/chrome_cleaner/os/registry_util.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+const wchar_t kMultiSzSeparator = L'\0';  // Must be null char.
+
+// The separator using to join and split pending log files.
+const wchar_t* kPendingLogFilesSeparatorForLogs = L":";
+
+// Maximum length for a registry name, according to Microsoft documentation.
+// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872(v=vs.85).aspx
+static const size_t kMaxRegistryLength = 0x3FFF;
+
+// Encode the current tool's version number into a uint32_t suitable for
+// e.g. reporting via an UMA histogram.
+// This code is the same as in
+// chrome/browser/safe_browsing/chrome_cleaner/reporter_runner_win.cc
+// TODO(joenotcharles): Move the shared code to components/chrome_cleaner.
+uint32_t GetVersionNumber() {
+  base::Version version(CHROME_VERSION_UTF8_STRING);
+  DCHECK(!version.components().empty());
+  // The version number for X.Y.Z is X*256^3+Y*256+Z. If there are additional
+  // components, only the first three count, and if there are less than 3, the
+  // missing values are just replaced by zero. So 1 is equivalent 1.0.0.
+  DCHECK_LT(version.components()[0], 0x100U);
+  uint32_t version_number = 0x1000000 * version.components()[0];
+  if (version.components().size() >= 2) {
+    DCHECK_LT(version.components()[1], 0x10000U);
+    version_number += 0x100 * version.components()[1];
+  }
+  if (version.components().size() >= 3) {
+    DCHECK_LT(version.components()[2], 0x100U);
+    version_number += version.components()[2];
+  }
+  return version_number;
+}
+
+void CreateRegKey(base::win::RegKey* reg_key, const base::string16& path) {
+  if (reg_key->Create(HKEY_CURRENT_USER, path.c_str(),
+                      KEY_SET_VALUE | KEY_QUERY_VALUE) != ERROR_SUCCESS) {
+    PLOG(ERROR) << "Failed to open registry key" << path;
+  }
+}
+
+}  // namespace
+
+// static
+const wchar_t RegistryLogger::kPendingLogFilesValue[] = L"PendingLogs";
+
+// This is an arbitrary truncation length for log upload results, that allows
+// for 64 states encoded as 1;0;1;1;. When the series exceeds this length, it is
+// truncated to meet it.
+// static
+const size_t RegistryLogger::kMaxUploadResultLength = 128;
+
+RegistryLogger::RegistryLogger(Mode mode) : RegistryLogger(mode, "") {}
+
+RegistryLogger::RegistryLogger(Mode mode, const std::string& suffix)
+    : mode_(mode) {
+  if (mode == Mode::NOOP_FOR_TESTING)
+    return;
+
+  if (suffix.length() > kMaxRegistryLength) {
+    LOG(WARNING) << "Attempted creating registry key longer than limit ("
+                 << suffix << "). Logger not initialized.";
+    return;
+  }
+
+  if (!base::IsStringASCII(suffix)) {
+    LOG(WARNING) << "Value of registry suffix (" << suffix
+                 << ") must be ASCII. "
+                 << "Logger not initialized";
+    return;
+  }
+
+  suffix_ = base::UTF8ToUTF16(suffix);
+  CreateRegKey(&logging_key_, GetLoggingKeyPath(mode));
+  CreateRegKey(&scan_times_key_, GetScanTimesKeyPath(mode));
+}
+
+RegistryLogger::~RegistryLogger() {}
+
+void RegistryLogger::WriteVersion() {
+  if (logging_key_.Valid())
+    logging_key_.WriteValue(kVersionValueName, GetVersionNumber());
+}
+
+void RegistryLogger::WriteExitCode(int exit_code) {
+  if (logging_key_.Valid())
+    logging_key_.WriteValue(kExitCodeValueName, exit_code);
+}
+
+void RegistryLogger::ClearExitCode() {
+  if (logging_key_.Valid())
+    logging_key_.DeleteValue(kExitCodeValueName);
+}
+
+void RegistryLogger::WriteStartTime() {
+  if (logging_key_.Valid()) {
+    int64_t now = base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
+    logging_key_.WriteValue(kStartTimeValueName, &now, sizeof(now), REG_QWORD);
+  }
+}
+
+void RegistryLogger::WriteEndTime() {
+  if (logging_key_.Valid()) {
+    int64_t now = base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
+    logging_key_.WriteValue(kEndTimeValueName, &now, sizeof(now), REG_QWORD);
+  }
+}
+
+void RegistryLogger::ClearEndTime() {
+  if (logging_key_.Valid())
+    logging_key_.DeleteValue(kEndTimeValueName);
+}
+
+void RegistryLogger::ClearScanTimes() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  if (!scan_times_key_.Valid())
+    return;
+
+  if (scan_times_key_.DeleteKey(L"") != ERROR_SUCCESS) {
+    PLOG(ERROR) << "Failed to delete key: '" << GetScanTimesKeyPath(mode_)
+                << "'";
+    return;
+  }
+
+  CreateRegKey(&scan_times_key_, GetScanTimesKeyPath(mode_));
+}
+
+void RegistryLogger::WriteScanTime(UwSId pup_id,
+                                   const base::TimeDelta& scan_time) {
+  if (!scan_times_key_.Valid())
+    return;
+
+  int64_t scan_time_serialized = scan_time.InMicroseconds();
+  scan_times_key_.WriteValue(base::UintToString16(pup_id).c_str(),
+                             &scan_time_serialized,
+                             sizeof(scan_time_serialized), REG_QWORD);
+}
+
+void RegistryLogger::WriteMemoryUsage(size_t memory_used_kb) {
+  if (logging_key_.Valid()) {
+    DWORD memory = memory_used_kb;
+    LONG result = logging_key_.WriteValue(kMemoryUsedValueName, memory);
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to write memory usage to the registry. Error: " << std::hex
+        << result;
+  }
+}
+
+void RegistryLogger::AppendLogUploadResult(bool success) {
+  if (logging_key_.Valid()) {
+    base::string16 upload_results;
+    // Ignore the return value, if this fails, just overwrite what is there.
+    LONG result =
+        logging_key_.ReadValue(kUploadResultsValueName, &upload_results);
+    PLOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND)
+        << "Failed to read log upload results.";
+
+    upload_results += success ? L"1;" : L"0;";
+    if (upload_results.size() > kMaxUploadResultLength) {
+      upload_results.erase(upload_results.begin(),
+                           upload_results.begin() + (upload_results.size() -
+                                                     kMaxUploadResultLength));
+    }
+
+    result = logging_key_.WriteValue(kUploadResultsValueName,
+                                     upload_results.c_str());
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to persist log upload results.";
+  }
+}
+
+void RegistryLogger::WriteReporterLogsUploadResult(
+    SafeBrowsingReporter::Result upload_result) {
+  DCHECK(mode_ == Mode::REPORTER);
+
+  if (logging_key_.Valid()) {
+    LONG result = logging_key_.WriteValue(kLogsUploadResultValueName,
+                                          static_cast<DWORD>(upload_result));
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to persist log upload results.";
+  }
+}
+
+bool RegistryLogger::AppendLogFilePath(const base::FilePath& log_file) {
+  if (!logging_key_.Valid())
+    return false;
+
+  base::string16 registry_value;
+  std::vector<base::string16> log_files;
+  if (ReadPendingLogFiles(&log_files, nullptr)) {
+    log_files.push_back(log_file.value());
+    registry_value =
+        base::JoinString(log_files, base::StringPiece16(&kMultiSzSeparator, 1));
+  } else {
+    registry_value = log_file.value();
+  }
+
+  // REG_MULTI_SZ requires an extra \0 at the end of the string.
+  registry_value.append(1, L'\0');
+  LONG result = logging_key_.WriteValue(
+      kPendingLogFilesValue,
+      reinterpret_cast<const void*>(registry_value.c_str()),
+      registry_value.size() * sizeof(base::string16::value_type), REG_MULTI_SZ);
+  if (result != ERROR_SUCCESS) {
+    PLOG(ERROR) << "Failed to write '" << registry_value
+                << "' to pending logs registry entry. Error: " << result;
+    return false;
+  }
+  return true;
+}
+
+// TODO(csharp): Maybe optimize these, which run in O(n^2) when used
+// subsequently to remove all pending log files for example.
+void RegistryLogger::GetNextLogFilePath(base::FilePath* log_file) {
+  DCHECK(log_file);
+  log_file->clear();
+  if (!logging_key_.Valid() || !logging_key_.HasValue(kPendingLogFilesValue))
+    return;
+
+  std::vector<base::string16> log_files;
+  RegistryError registry_error = RegistryError::SUCCESS;
+  if (!ReadPendingLogFiles(&log_files, &registry_error)) {
+    PLOG(WARNING)
+        << "Failed to read the list of pending log files (registry_error = "
+        << static_cast<int>(registry_error) << ")";
+    return;
+  }
+
+  if (log_files.empty())
+    return;
+
+  *log_file = base::FilePath(log_files[0]);
+}
+
+bool RegistryLogger::RemoveLogFilePath(const base::FilePath& log_file) {
+  if (!logging_key_.Valid())
+    return false;  // Assume empty when we can't read the content.
+
+  if (!logging_key_.HasValue(kPendingLogFilesValue))
+    return false;
+
+  std::vector<base::string16> log_files;
+  RegistryError registry_error = RegistryError::SUCCESS;
+  if (!ReadPendingLogFiles(&log_files, &registry_error)) {
+    PLOG(WARNING) << "Empty pending log files registry entry when trying to "
+                  << "remove '" << log_file.value()
+                  << "' (registry_error = " << static_cast<int>(registry_error)
+                  << ").";
+    return false;
+  }
+
+  std::vector<base::string16>::const_iterator iter =
+      std::find(log_files.begin(), log_files.end(), log_file.value());
+  if (iter == log_files.end()) {
+    PLOG(WARNING) << "Requested log file '" << SanitizePath(log_file)
+                  << "', not found in registered log files '"
+                  << base::JoinString(log_files,
+                                      kPendingLogFilesSeparatorForLogs)
+                  << "'.";
+  } else {
+    iter = log_files.erase(iter);
+  }
+  if (log_files.empty()) {
+    logging_key_.DeleteValue(kPendingLogFilesValue);
+    return false;
+  }
+
+  base::string16 registry_value(
+      base::JoinString(log_files, base::StringPiece16(&kMultiSzSeparator, 1)));
+  // REG_MULTI_SZ requires an extra \0 at the end of the string.
+  registry_value.append(1, L'\0');
+  LONG result = logging_key_.WriteValue(
+      kPendingLogFilesValue,
+      reinterpret_cast<const void*>(registry_value.c_str()),
+      registry_value.size() * sizeof(base::string16::value_type), REG_MULTI_SZ);
+  if (result != ERROR_SUCCESS) {
+    LOG(ERROR) << "Failed to write '" << registry_value
+               << "' to pending logs registry entry. Error: " << std::hex
+               << result;
+    // If we fail to write to this key, might as well delete it and let the
+    // caller know that there are no more log files.
+    logging_key_.DeleteValue(kPendingLogFilesValue);
+    return false;
+  }
+
+  return true;
+}
+
+bool RegistryLogger::RecordFoundPUPs(const std::vector<UwSId>& pups_to_store) {
+  base::string16 multi_sz_value;
+  for (UwSId pup_to_store : pups_to_store) {
+    multi_sz_value += base::UintToString16(pup_to_store);
+    multi_sz_value += kMultiSzSeparator;
+  }
+  multi_sz_value += kMultiSzSeparator;
+
+  LONG result = logging_key_.WriteValue(
+      kFoundUwsValueName, reinterpret_cast<const void*>(multi_sz_value.c_str()),
+      multi_sz_value.size() * sizeof(base::string16::value_type), REG_MULTI_SZ);
+
+  if (result != ERROR_SUCCESS) {
+    LOG(ERROR) << "Failed to write '" << multi_sz_value
+               << "' to found UwS registry entry. Error: " << std::hex
+               << result;
+    return false;
+  }
+
+  return true;
+}
+
+void RegistryLogger::WriteExperimentalEngineResultCode(int exit_code) {
+  if (logging_key_.Valid()) {
+    LONG result = logging_key_.WriteValue(kEngineErrorCodeValueName,
+                                          static_cast<DWORD>(exit_code));
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to persist experimental engine error code.";
+  }
+}
+
+void RegistryLogger::RecordCompletedCleanup() {
+  if (logging_key_.Valid()) {
+    LONG result = logging_key_.WriteValue(kCleanupCompletedValueName, 1);
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to persist completed cleanup.";
+  }
+}
+
+void RegistryLogger::ResetCompletedCleanup() {
+  if (logging_key_.Valid() &&
+      logging_key_.HasValue(kCleanupCompletedValueName)) {
+    LONG result = logging_key_.DeleteValue(kCleanupCompletedValueName);
+    PLOG_IF(ERROR, result != ERROR_SUCCESS)
+        << "Failed to reset completed cleanup.";
+  }
+}
+
+base::string16 RegistryLogger::GetLoggingKeyPath(Mode mode) const {
+  base::string16 key_path = base::string16(kSoftwareRemovalToolRegistryKey);
+  if (mode == Mode::REMOVER)
+    key_path += base::string16(L"\\") + kCleanerSubKey;
+  if (!suffix_.empty())
+    key_path += base::string16(L"\\") + suffix_;
+  return key_path;
+}
+
+base::string16 RegistryLogger::GetScanTimesKeyPath(Mode mode) const {
+  return base::StrCat({GetLoggingKeyPath(mode), L"\\", kScanTimesSubKey});
+}
+
+base::string16 RegistryLogger::GetKeySuffix() const {
+  return suffix_;
+}
+
+// static.
+bool RegistryLogger::ReadValues(const base::win::RegKey& logging_key,
+                                const wchar_t* name,
+                                std::vector<base::string16>* values,
+                                RegistryError* registry_error) {
+  DCHECK(name);
+  DCHECK(values);
+  values->clear();
+
+  base::string16 content;
+  uint32_t content_type;
+  if (!ReadRegistryValue(logging_key, name, &content, &content_type,
+                         registry_error) ||
+      content_type != REG_MULTI_SZ) {
+    return false;
+  }
+
+  // Parse the double-null-terminated list of strings.
+  // Note: This code is paranoid to not read outside of |buf|, in the case where
+  // it may not be properly terminated.
+  if (!content.empty()) {
+    const wchar_t* entry = &content[0];
+    const wchar_t* buffer_end = entry + content.size();
+    while (entry < buffer_end && entry[0] != '\0') {
+      const wchar_t* entry_end = std::find(entry, buffer_end, L'\0');
+      base::string16 value(entry, entry_end);
+      DCHECK(!value.empty());
+      values->push_back(value);
+      entry = entry_end + 1;
+    }
+  }
+  return true;
+}
+
+bool RegistryLogger::ReadPendingLogFiles(std::vector<base::string16>* log_files,
+                                         RegistryError* registry_error) {
+  DCHECK(log_files);
+  if (!ReadValues(logging_key_, kPendingLogFilesValue, log_files,
+                  registry_error)) {
+    // Don't log here since in some case this is an expected behavior. Those not
+    // expecting a failure are responsible to log appropriately.
+    // Delete the value in case it is corrupted, so we can properly use it next
+    // time around.
+    logging_key_.DeleteValue(kPendingLogFilesValue);
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/registry_logger.h b/chrome/chrome_cleaner/logging/registry_logger.h
new file mode 100644
index 0000000..fa2a3aa
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/registry_logger.h
@@ -0,0 +1,149 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_REGISTRY_LOGGER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_REGISTRY_LOGGER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+#include "base/win/registry.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace chrome_cleaner {
+
+enum class RegistryError : char;
+
+// This class contains all logic related to logging data to the registry. It
+// is largely a collection of convenience methods to mutate specific registry
+// state. It also contains the serialization logic for the more complicated
+// reporting.
+class RegistryLogger {
+ public:
+  enum class Mode {
+    REMOVER,   // Writes to the registry location for the chrome cleanup tool.
+    REPORTER,  // Writes to the appropriate location for the reporter tool.
+    NOOP_FOR_TESTING,  // Drops all writes, used for testing.
+  };
+
+  // Construct a RegistryLogger that writes to the location specified by |mode|.
+  explicit RegistryLogger(Mode mode);
+  RegistryLogger(Mode mode, const std::string& suffix);
+  ~RegistryLogger();
+
+  // Write the currently running version of the tool to the key specified by
+  // |mode| at construction.
+  void WriteVersion();
+
+  // Persist |exit_code| to the key specified by |mode| at construction.
+  void WriteExitCode(int exit_code);
+
+  // Clear the persisted exit code from the key specified by |mode|.
+  void ClearExitCode();
+
+  // Persist the current time as the start time to the key specified by |mode|.
+  void WriteStartTime();
+
+  // Persist the current time as the end time to the key specified by |mode|.
+  void WriteEndTime();
+
+  // Clear the currently persisted end time from the key specified by |mode|.
+  void ClearEndTime();
+
+  // Clear the previously written scan times from the key specified by
+  // |mode|. Must be called on the same thread that created the registry logger
+  // object.
+  void ClearScanTimes();
+
+  // Write the scan time of PUP with ID |pup_id| to the key specified by |mode|.
+  void WriteScanTime(UwSId pup_id, const base::TimeDelta& scan_time);
+
+  // Write |memory_usage_kb| to the key specified by |mode|. |memory_used_kb|
+  // must be given in units of KBs.
+  void WriteMemoryUsage(size_t memory_used_kb);
+
+  // Append the last log upload result to a series of results stored in the
+  // key specified by |mode|. This sequence will be bounded in length by
+  // kMaxUploadResultLength and older entries will be truncated if this length
+  // is exceeded.
+  void AppendLogUploadResult(bool success);
+
+  // Write the result of the attempt to write logs upload from the Software
+  // Reporter.
+  void WriteReporterLogsUploadResult(
+      SafeBrowsingReporter::Result upload_result);
+
+  // Append the given full path to a persisted log to the appropriate registry
+  // value, or create the registry value if it doesn't exist yet. Return false
+  // on failures.
+  bool AppendLogFilePath(const base::FilePath& log_file);
+
+  // Retrieve the head of the list of registered log files to upload. Return an
+  // empty path when there are none.
+  void GetNextLogFilePath(base::FilePath* log_file);
+
+  // Remove |log_file| from the list of registered log files to upload. Return
+  // false if there are no more registered log files after removing |log_file|
+  // (whether there was a |log_file| to remove or not).
+  bool RemoveLogFilePath(const base::FilePath& log_file);
+
+  // Record the given list of found pup ids to the key specified by |mode|.
+  bool RecordFoundPUPs(const std::vector<UwSId>& pups_to_store);
+
+  // Writes the result of the experimental engine.
+  void WriteExperimentalEngineResultCode(int exit_code);
+
+  // Writes that a cleanup has successfully completed.
+  void RecordCompletedCleanup();
+
+  // Erases information written by previous cleaner runs that a cleanup has
+  // completed.
+  void ResetCompletedCleanup();
+
+ protected:
+  // Return the full path of the key where the cleaner / reporter info is
+  // stored. Exposed for tests.
+  base::string16 GetLoggingKeyPath(Mode mode) const;
+
+  // Return the full path of the key where the PUP scan times are
+  // stored. Exposed for tests.
+  base::string16 GetScanTimesKeyPath(Mode mode) const;
+
+  // Return the registry key suffix. Exposed for tests.
+  base::string16 GetKeySuffix() const;
+
+  // Read values from the given registry key. Exposed for tests.
+  static bool ReadValues(const base::win::RegKey& logging_key,
+                         const wchar_t* name,
+                         std::vector<base::string16>* values,
+                         RegistryError* registry_error);
+
+  // Exposed for testing.
+  static const wchar_t kPendingLogFilesValue[];
+  static const size_t kMaxUploadResultLength;
+
+ private:
+  bool ReadPendingLogFiles(std::vector<base::string16>* log_files,
+                           RegistryError* registry_error);
+
+  THREAD_CHECKER(thread_checker_);
+  base::win::RegKey logging_key_;
+  base::win::RegKey scan_times_key_;
+  Mode mode_;
+  base::string16 suffix_;
+
+  DISALLOW_COPY_AND_ASSIGN(RegistryLogger);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_REGISTRY_LOGGER_H_
diff --git a/chrome/chrome_cleaner/logging/registry_logger_unittest.cc b/chrome/chrome_cleaner/logging/registry_logger_unittest.cc
new file mode 100644
index 0000000..48475967
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/registry_logger_unittest.cc
@@ -0,0 +1,449 @@
+// Copyright 2018 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/chrome_cleaner/logging/registry_logger.h"
+
+#include <stdint.h>
+#include <set>
+
+#include "base/files/file_path.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/registry.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+const wchar_t kLogFilePath1[] = L"AFilePath";
+const wchar_t kLogFilePath2[] = L"AnotherOne";
+const wchar_t kLogFilePath3[] = L"ThirdsTheCharm";
+const char kCorruptFilePath[] = "x";
+const wchar_t kWideCorruptFilePath[] = L"x";
+const wchar_t kRawFilePath[] = L"AFilePath\0\0";
+const char kTestSuffix[] = "TestSuffix";
+const char kInvalidUTF8[] = "\xed\xa0\x80\xed\xbf\xbf";
+
+class TestRegistryLogger : public RegistryLogger {
+ public:
+  explicit TestRegistryLogger(RegistryLogger::Mode mode)
+      : RegistryLogger(mode) {}
+  TestRegistryLogger(RegistryLogger::Mode mode, std::string suffix)
+      : RegistryLogger(mode, suffix) {}
+  using RegistryLogger::GetLoggingKeyPath;
+  using RegistryLogger::GetScanTimesKeyPath;
+  using RegistryLogger::GetKeySuffix;
+  using RegistryLogger::ReadValues;
+  using RegistryLogger::kMaxUploadResultLength;
+  using RegistryLogger::kPendingLogFilesValue;
+};
+
+bool ReadFoundPUPs(std::set<UwSId>* stored_pups) {
+  DCHECK(stored_pups);
+
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+  base::win::RegKey logging_key;
+  logging_key.Open(HKEY_CURRENT_USER, logger.GetLoggingKeyPath(mode).c_str(),
+                   KEY_QUERY_VALUE);
+  if (!logging_key.Valid())
+    return false;
+
+  std::vector<base::string16> existing_pups;
+  bool success = true;
+  if (logger.ReadValues(logging_key, kFoundUwsValueName, &existing_pups,
+                        nullptr)) {
+    for (base::string16 pup_id_string : existing_pups) {
+      UwSId pup_id = 0;
+      if (base::StringToUint(pup_id_string, &pup_id))
+        stored_pups->insert(pup_id);
+      else
+        success = false;
+    }
+  }
+  return success;
+}
+
+// Open |reg_key| using GetLoggingKeyPath(). Returns true if the key is
+// created properly, false otherwise.
+bool OpenLoggingRegKey(base::win::RegKey* reg_key,
+                       const TestRegistryLogger& logger,
+                       const REGSAM access,
+                       const RegistryLogger::Mode mode) {
+  LONG result = reg_key->Open(HKEY_CURRENT_USER,
+                              logger.GetLoggingKeyPath(mode).c_str(), access);
+  return reg_key->Valid() && (result == ERROR_SUCCESS);
+}
+
+// Open |reg_key| using GetScanTimesKeyPath(). Returns true if the key is
+// created properly, false otherwise.
+bool OpenScanTimeRegKey(base::win::RegKey* reg_key,
+                        const TestRegistryLogger& logger,
+                        const REGSAM access,
+                        const RegistryLogger::Mode mode) {
+  LONG result = reg_key->Open(HKEY_CURRENT_USER,
+                              logger.GetScanTimesKeyPath(mode).c_str(), access);
+  return reg_key->Valid() && (result == ERROR_SUCCESS);
+}
+
+class RegistryLoggerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
+  }
+
+ protected:
+  registry_util::RegistryOverrideManager registry_override_manager_;
+};
+
+TEST_F(RegistryLoggerTest, AppendLogUploadResultBasic) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+  logger.AppendLogUploadResult(true);
+  logger.AppendLogUploadResult(false);
+  logger.AppendLogUploadResult(false);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+
+  base::string16 upload_results;
+  LONG result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
+  EXPECT_EQ(ERROR_SUCCESS, result);
+  EXPECT_STREQ(L"1;0;0;", upload_results.c_str());
+}
+
+TEST_F(RegistryLoggerTest, WriteReporterLogsUploadResult) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  TestRegistryLogger logger(mode);
+  logger.WriteReporterLogsUploadResult(
+      SafeBrowsingReporter::Result::UPLOAD_INTERNAL_ERROR);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+  DWORD upload_result;
+  EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kLogsUploadResultValueName,
+                                                   &upload_result));
+  EXPECT_EQ(
+      static_cast<DWORD>(SafeBrowsingReporter::Result::UPLOAD_INTERNAL_ERROR),
+      upload_result);
+}
+
+TEST_F(RegistryLoggerTest, WriteAndClearScanTimes) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  TestRegistryLogger logger(mode);
+  base::TimeDelta scan_time = base::TimeDelta::FromSeconds(3);
+  logger.WriteScanTime(42, scan_time);
+
+  base::win::RegKey scan_times_key;
+  ASSERT_TRUE(
+      OpenScanTimeRegKey(&scan_times_key, logger, KEY_QUERY_VALUE, mode));
+
+  int64_t us_scan_time = 0;
+  LONG result = scan_times_key.ReadInt64(L"42", &us_scan_time);
+  EXPECT_EQ(ERROR_SUCCESS, result);
+  base::TimeDelta read_scan_time =
+      base::TimeDelta::FromMicroseconds(us_scan_time);
+
+  EXPECT_EQ(read_scan_time, scan_time);
+
+  logger.ClearScanTimes();
+  ASSERT_TRUE(
+      OpenScanTimeRegKey(&scan_times_key, logger, KEY_QUERY_VALUE, mode));
+  EXPECT_EQ(0U, scan_times_key.GetValueCount());
+}
+
+TEST_F(RegistryLoggerTest, AppendLogUploadResultMaxLength) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
+
+  static const size_t kMaxNumUploadResults =
+      TestRegistryLogger::kMaxUploadResultLength / 2;
+
+  for (size_t i = 0; i < kMaxNumUploadResults; ++i)
+    logger.AppendLogUploadResult(true);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+
+  base::string16 upload_results;
+  LONG result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
+  EXPECT_EQ(ERROR_SUCCESS, result);
+
+  base::string16 expected_string;
+  for (size_t i = 0; i < kMaxNumUploadResults; ++i)
+    expected_string += L"1;";
+
+  EXPECT_EQ(expected_string, upload_results);
+
+  logger.AppendLogUploadResult(false);
+  result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
+  EXPECT_EQ(ERROR_SUCCESS, result);
+
+  expected_string[expected_string.length() - 2] = '0';
+
+  EXPECT_EQ(expected_string, upload_results);
+}
+
+TEST_F(RegistryLoggerTest, LogFilePath) {
+  TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
+
+  base::FilePath log_file;
+  base::FilePath log_file_path1(kLogFilePath1);
+  EXPECT_TRUE(logger.AppendLogFilePath(log_file_path1));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path1, log_file);
+
+  base::FilePath log_file_path2(kLogFilePath2);
+  EXPECT_TRUE(logger.AppendLogFilePath(log_file_path2));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path1, log_file);
+
+  // If we remove the second file, there is still the first file.
+  EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path2));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path1, log_file);
+
+  // Also test removing the first file in the list.
+  EXPECT_TRUE(logger.AppendLogFilePath(log_file_path2));
+  EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path1));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path2, log_file);
+
+  // And now middle file removal.
+  base::FilePath log_file_path3(kLogFilePath3);
+  EXPECT_TRUE(logger.AppendLogFilePath(log_file_path1));
+  EXPECT_TRUE(logger.AppendLogFilePath(log_file_path3));
+  EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path1));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path2, log_file);
+
+  EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path2));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(log_file_path3, log_file);
+  EXPECT_FALSE(logger.RemoveLogFilePath(log_file_path3));
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+}
+
+TEST_F(RegistryLoggerTest, LogFilePathFailures) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+
+  // Make sure we get an empty file path when there's no value.
+  base::FilePath log_file;
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+
+  // Create an empty value, and make sure we also get an empty file path.
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
+
+  logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue, L"");
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+  // And the value should have been wiped.
+  EXPECT_FALSE(logging_key.HasValue(TestRegistryLogger::kPendingLogFilesValue));
+
+  // Now try with an invalid value type.
+  logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue, 42);
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+  // And again, the value should have been wiped.
+  EXPECT_FALSE(logging_key.HasValue(TestRegistryLogger::kPendingLogFilesValue));
+}
+
+TEST_F(RegistryLoggerTest, LogFilePathRegistryFailure) {
+  TestRegistryLogger logger(RegistryLogger::Mode::NOOP_FOR_TESTING);
+
+  base::FilePath log_file_path1(kLogFilePath1);
+  EXPECT_FALSE(logger.AppendLogFilePath(log_file_path1));
+  EXPECT_FALSE(logger.RemoveLogFilePath(log_file_path1));
+
+  base::FilePath log_file;
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_TRUE(log_file.empty());
+}
+
+TEST_F(RegistryLoggerTest, LogFilePathWithCorruptKey) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
+
+  // Write a corrupt registry key (one byte, no ending null character).
+  logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue,
+                         kCorruptFilePath, 1, REG_MULTI_SZ);
+
+  base::FilePath log_file;
+  logger.GetNextLogFilePath(&log_file);
+  EXPECT_EQ(kWideCorruptFilePath, log_file.value());
+}
+
+TEST_F(RegistryLoggerTest, LogFilePathRawContent) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
+
+  // Try every possibility of ending trailing null characters.
+  size_t length = wcslen(kRawFilePath) * sizeof(wchar_t);
+  for (; length < sizeof(kRawFilePath); ++length) {
+    logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue,
+                           kRawFilePath, length, REG_MULTI_SZ);
+
+    base::FilePath log_file;
+    logger.GetNextLogFilePath(&log_file);
+    EXPECT_EQ(kRawFilePath, log_file.value());
+  }
+}
+
+TEST_F(RegistryLoggerTest, RecordFoundPUPs) {
+  TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
+
+  std::set<UwSId> stored_pups;
+  ReadFoundPUPs(&stored_pups);
+  EXPECT_TRUE(stored_pups.empty());
+
+  std::vector<UwSId> pup_list{1, 2, 3, 4, 5};
+  EXPECT_TRUE(logger.RecordFoundPUPs(pup_list));
+  EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
+
+  std::set<UwSId> expected_pups{1, 2, 3, 4, 5};
+  for (UwSId pup_id : stored_pups)
+    EXPECT_EQ(1UL, expected_pups.count(pup_id));
+  EXPECT_EQ(expected_pups.size(), stored_pups.size());
+
+  std::vector<UwSId> pup_list2{6};
+  EXPECT_TRUE(logger.RecordFoundPUPs(pup_list2));
+  EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
+
+  std::set<UwSId> expected_pups2{1, 2, 3, 4, 5, 6};
+  for (UwSId pup_id : stored_pups)
+    EXPECT_EQ(1UL, expected_pups2.count(pup_id));
+  EXPECT_EQ(expected_pups2.size(), stored_pups.size());
+
+  std::vector<UwSId> pup_list3{5, 6, 7, 8};
+  EXPECT_TRUE(logger.RecordFoundPUPs(pup_list3));
+  EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
+
+  std::set<UwSId> expected_pups3{1, 2, 3, 4, 5, 6, 7, 8};
+  for (UwSId pup_id : stored_pups)
+    EXPECT_EQ(1UL, expected_pups3.count(pup_id));
+  EXPECT_EQ(expected_pups3.size(), stored_pups.size());
+}
+
+TEST_F(RegistryLoggerTest, RecordFoundPUPsDuplicates) {
+  TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
+
+  std::set<UwSId> stored_pups;
+  ReadFoundPUPs(&stored_pups);
+  EXPECT_TRUE(stored_pups.empty());
+
+  std::vector<UwSId> pup_list{1, 1, 2, 2, 3};
+  EXPECT_TRUE(logger.RecordFoundPUPs(pup_list));
+  EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
+
+  std::set<UwSId> expected_pups{1, 2, 3};
+  for (UwSId pup_id : stored_pups)
+    EXPECT_EQ(1UL, expected_pups.count(pup_id));
+}
+
+// TODO(joenotcharles):
+// Have components/chrome_cleaner/public/constants/constant.cc use branding
+// instead of hardcoded strings so that we expect the correct company name here
+// on all builds
+TEST_F(RegistryLoggerTest, DISABLED_LoggingKeyPathContainsCompanyName) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  TestRegistryLogger logger(mode);
+  const base::string16 expected_name = base::StrCat(
+      {L"Software\\", COMPANY_SHORTNAME_STRING, L"\\Software Removal Tool"});
+  EXPECT_EQ(expected_name, logger.GetLoggingKeyPath(mode));
+}
+
+// TODO(joenotcharles):
+// Have components/chrome_cleaner/public/constants/constant.cc use branding
+// instead of hardcoded strings so that we expect the correct company name here
+// on all builds
+TEST_F(RegistryLoggerTest, DISABLED_UseSuffixRegistryKey) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  TestRegistryLogger logger(mode, kTestSuffix);
+  TestRegistryLogger no_suffix_logger(mode);
+
+  base::string16 key_name = no_suffix_logger.GetLoggingKeyPath(mode);
+  EXPECT_EQ(base::string16::npos,
+            key_name.find(base::UTF8ToUTF16(kTestSuffix).c_str()));
+
+  key_name = logger.GetLoggingKeyPath(mode);
+  base::string16 expected_name = base::StrCat(
+      {L"Software\\", COMPANY_SHORTNAME_STRING, L"\\Software Removal Tool\\",
+       base::UTF8ToUTF16(kTestSuffix)});
+  EXPECT_EQ(expected_name, key_name);
+
+  base::win::RegKey no_suffix_key;
+  EXPECT_TRUE(
+      OpenLoggingRegKey(&no_suffix_key, no_suffix_logger, KEY_SET_VALUE, mode));
+
+  base::win::RegKey logger_key;
+  EXPECT_TRUE(OpenLoggingRegKey(&logger_key, logger, KEY_SET_VALUE, mode));
+}
+
+TEST_F(RegistryLoggerTest, InvalidRegistrySuffixes) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  // Invalid characters in suffix.
+  TestRegistryLogger logger_invalid_utf8(mode, kInvalidUTF8);
+
+  base::win::RegKey invalid_utf8_key;
+  EXPECT_FALSE(OpenLoggingRegKey(&invalid_utf8_key, logger_invalid_utf8,
+                                 KEY_SET_VALUE, mode));
+
+  // Suffix too long.
+  std::string suffix_too_long(0x4000, 'A');
+  TestRegistryLogger logger_too_long(RegistryLogger::Mode::REPORTER,
+                                     suffix_too_long);
+  base::win::RegKey too_long_key;
+  EXPECT_FALSE(
+      OpenLoggingRegKey(&too_long_key, logger_too_long, KEY_SET_VALUE, mode));
+}
+
+TEST_F(RegistryLoggerTest, WriteExperimentalEngineResultCode) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
+  TestRegistryLogger logger(mode);
+  logger.WriteExperimentalEngineResultCode(42);
+
+  base::win::RegKey logging_key;
+  ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+  DWORD engine_error_code;
+  EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kEngineErrorCodeValueName,
+                                                   &engine_error_code));
+  EXPECT_EQ(static_cast<DWORD>(42), engine_error_code);
+}
+
+TEST_F(RegistryLoggerTest, RecordAndResetCompletedCleanup) {
+  const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
+  TestRegistryLogger logger(mode);
+
+  logger.RecordCompletedCleanup();
+  {
+    base::win::RegKey logging_key;
+    ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+    DWORD recorded_value;
+    EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kCleanupCompletedValueName,
+                                                     &recorded_value));
+    EXPECT_EQ(static_cast<DWORD>(1), recorded_value);
+  }
+
+  logger.ResetCompletedCleanup();
+  {
+    base::win::RegKey logging_key;
+    ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
+    EXPECT_FALSE(logging_key.HasValue(kCleanupCompletedValueName));
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_definitions.cc b/chrome/chrome_cleaner/logging/reporter_logging_definitions.cc
new file mode 100644
index 0000000..be905e2
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/reporter_logging_definitions.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 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.
+
+// Provides the logging service instance to be used by the reporter executable.
+
+#include "chrome/chrome_cleaner/logging/logging_definitions.h"
+#include "chrome/chrome_cleaner/logging/noop_logging_service.h"
+#include "chrome/chrome_cleaner/logging/reporter_logging_service.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+
+namespace chrome_cleaner {
+
+// Returns an instance of ReporterLoggingService if logs collection is enabled,
+// or NoOpLoggingService otherwise.
+LoggingServiceAPI* GetLoggingServiceForCurrentBuild() {
+  if (Settings::GetInstance()->logs_collection_enabled())
+    return ReporterLoggingService::GetInstance();
+  return NoOpLoggingService::GetInstance();
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_service.cc b/chrome/chrome_cleaner/logging/reporter_logging_service.cc
new file mode 100644
index 0000000..3dad88e1
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/reporter_logging_service.cc
@@ -0,0 +1,338 @@
+// Copyright 2018 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/chrome_cleaner/logging/reporter_logging_service.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/i18n.h"
+#include "base/win/windows_version.h"
+#include "chrome/chrome_cleaner/chrome_utils/chrome_util.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "chrome/chrome_cleaner/logging/api_keys.h"
+#include "chrome/chrome_cleaner/logging/noop_logging_service.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/logging/utils.h"
+#include "chrome/chrome_cleaner/os/system_util.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+
+namespace chrome_cleaner {
+
+namespace {
+// TODO(joenotcharles): refer to the report definition in the "data" section.
+constexpr net::NetworkTrafficAnnotationTag kReporterTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("unwanted_software_report", R"(
+          semantics {
+            sender: "Chrome Cleanup"
+            description:
+              "Chrome on Windows is able to detect and remove software that "
+              "violates Google's Unwanted Software Policy "
+              "(https://www.google.com/about/unwanted-software-policy.html). "
+              "When potentially unwanted software is detected in the "
+              "background, Chrome may upload information about the detected "
+              "software to Google, if the user has opted in to automatically "
+              "send system information to help detect dangerous apps and "
+              "sites. "
+              "The user can also use the settings page to ask Chrome to search "
+              "for unwanted software and remove it. In this case if the user "
+              "chooses \"Report details to Google\", information about "
+              "unwanted software that is detected can be uploaded to Google "
+              "even if the user has not opted in to automatically send system "
+              "information to help detect dangerous apps and sites."
+            trigger:
+              "Chrome detected the presence of unwanted software on the system."
+            data:
+              "The user's Chrome version, Windows version, and locale, file "
+              "metadata and system settings linked to the unwanted software "
+              "that was detected as described at "
+              "https://www.google.com/chrome/privacy/whitepaper.html#unwantedsoftware. "
+              "Contents of files are never reported. No user identifiers are "
+              "reported, and common user identifiers found in metadata are "
+              "replaced with generic strings, but it is possible some metadata "
+              "may contain personally identifiable information. This "
+              "information is a subset of the information in "
+              "\"chrome_cleanup_report\"."
+            destination: GOOGLE_OWNED_SERVICE
+          }
+          policy {
+            cookies_allowed: NO
+            setting:
+              "Reporting of unwanted software that is detected automatically "
+              "in the background can be disabled by unchecking \"Automatically "
+              "send some system information and page content to Google to help "
+              "detect dangerous apps and sites\" in the \"Privacy and "
+              "Security\" section of settings, under Advanced. This does not "
+              "disable detection of unwanted software, only the reports to "
+              "Google. "
+              "Chrome Cleanup can also be explicitly requested by the user in "
+              "\"Reset and clean up\" in settings under Advanced. To disable "
+              "this report, turn off \"Report details to Google\" before "
+              "choosing \"Find and remove harmful software\"."
+            chrome_policy {
+              SafeBrowsingExtendedReportingEnabled {
+                SafeBrowsingExtendedReportingEnabled: false
+              }
+              ChromeCleanupReportingEnabled {
+                ChromeCleanupReportingEnabled: false
+              }
+              ChromeCleanupEnabled  {
+                ChromeCleanupEnabled: false
+              }
+            }
+          }
+          comments:
+            "If ChromeCleanupEnabled is set to \"false\", Chrome Cleanup will "
+            "never run, so no reports will be uploaded to Google. Otherwise "
+            "ChromeCleanupReportingEnabled can override the \"Report details "
+            "to Google\" control and the \"Automatically send some system "
+            "information and page content to Google to help detect dangerous "
+            "apps and sites\" setting: if it is set to \"true\", reports will "
+            "always be sent, and if it is set to \"false\", reports will never "
+            "be sent. If ChromeCleanupReportingEnabled is unset, "
+            "SafeBrowsingExtendedReportingEnabled can override the "
+            "\"Automatically send some system information and page content to "
+            "Google to help detect dangerous apps and sites\" setting, but not "
+            "the \"Report details to Google\" control."
+          )");
+}  // namespace
+
+ReporterLoggingService* ReporterLoggingService::GetInstance() {
+  return base::Singleton<ReporterLoggingService>::get();
+}
+
+ReporterLoggingService::ReporterLoggingService()
+    : sampler_(DetailedInfoSampler::kDefaultMaxFiles) {}
+
+ReporterLoggingService::~ReporterLoggingService() {
+  DCHECK(!initialized_)
+      << "'Terminate' must be called before deleting ReporterLoggingService.";
+}
+
+void ReporterLoggingService::Initialize(RegistryLogger* registry_logger) {
+  DCHECK(!initialized_) << "LoggingService already initialized.";
+
+  std::vector<base::string16> languages;
+  base::win::i18n::GetUserPreferredUILanguageList(&languages);
+  base::string16 version_string;
+  bool version_string_succeeded =
+      RetrieveChromeVersionAndInstalledDomain(&version_string, nullptr);
+  int channel = 0;
+  bool has_chrome_channel = GetChromeChannelFromCommandLine(&channel);
+  Settings* settings = Settings::GetInstance();
+  base::string16 session_id = settings->session_id();
+  const Engine::Name engine = settings->engine();
+  const std::string engine_version = settings->engine_version();
+
+  {
+    base::AutoLock lock(lock_);
+
+    // Ensure that logging report starts in a cleared state.
+    reporter_logs_.Clear();
+
+    if (!session_id.empty())
+      reporter_logs_.set_session_id(base::WideToUTF8(session_id));
+
+    // If we upload logs for this run, use pending if no exit code is provided
+    // elsewhere.
+    reporter_logs_.set_exit_code(RESULT_CODE_PENDING);
+
+    // Set invariant environment / machine data.
+    FoilReporterLogs_EnvironmentData* env_data =
+        reporter_logs_.mutable_environment();
+    env_data->set_windows_version(base::win::GetVersion());
+    env_data->set_reporter_version(CHROME_VERSION_UTF8_STRING);
+    if (version_string_succeeded)
+      env_data->set_chrome_version(base::WideToUTF8(version_string));
+    if (has_chrome_channel)
+      env_data->set_chrome_channel(channel);
+    if (languages.size() > 0)
+      env_data->set_default_locale(base::WideToUTF8(languages[0]));
+    env_data->set_bitness(IsX64Process() ? 64 : 32);
+    env_data->mutable_engine()->set_name(engine);
+    if (!engine_version.empty())
+      env_data->mutable_engine()->set_version(engine_version);
+
+    initialized_ = true;
+  }
+}
+
+void ReporterLoggingService::Terminate() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(initialized_) << "Logging service is not initialized.";
+  {
+    base::AutoLock lock(lock_);
+    initialized_ = false;
+    uploads_enabled_ = false;
+  }
+}
+
+void ReporterLoggingService::SendLogsToSafeBrowsing(
+    const UploadResultCallback& done_callback,
+    RegistryLogger* registry_logger) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(initialized_);
+
+  // If reporting is not enabled or required, call |done_callback|.
+  if (!(uploads_enabled_ && IsReportingNeeded()))
+    return done_callback.Run(false);  // false since no logs were uploaded.
+
+  SafeBrowsingReporter::UploadReport(
+      base::BindRepeating(&ReporterLoggingService::OnReportUploadResult,
+                          base::Unretained(this), done_callback,
+                          registry_logger),
+      kSafeBrowsingReporterUrl, RawReportContent(), kReporterTrafficAnnotation);
+}
+
+void ReporterLoggingService::CancelWaitForShutdown() {}
+
+void ReporterLoggingService::EnableUploads(bool enabled,
+                                           RegistryLogger* registry_logger) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  uploads_enabled_ = enabled;
+}
+
+bool ReporterLoggingService::uploads_enabled() const {
+  return uploads_enabled_;
+}
+
+void ReporterLoggingService::SetDetailedSystemReport(
+    bool /*detailed_system_report*/) {}
+
+bool ReporterLoggingService::detailed_system_report_enabled() const {
+  return false;
+}
+
+void ReporterLoggingService::AddFoundUwS(
+    const std::string& /*found_uws_name*/) {}
+
+void ReporterLoggingService::AddDetectedUwS(const PUPData::PUP* found_uws,
+                                            UwSDetectedFlags flags) {
+  DCHECK(found_uws);
+  UwS detected_uws =
+      PUPToUwS(found_uws, flags, /*cleaning_files=*/false, &sampler_);
+  AddDetectedUwS(detected_uws);
+}
+
+void ReporterLoggingService::AddDetectedUwS(const UwS& uws) {
+  base::AutoLock lock(lock_);
+  *reporter_logs_.add_detected_uws() = uws;
+}
+
+void ReporterLoggingService::SetExitCode(ResultCode exit_code) {
+  ResultCode previous_exit_code = RESULT_CODE_INVALID;
+  {
+    base::AutoLock lock(lock_);
+    previous_exit_code = static_cast<ResultCode>(reporter_logs_.exit_code());
+    reporter_logs_.set_exit_code(exit_code);
+  }
+  // The DCHECK can't be under |lock_|. The only valid reason to overwrite a non
+  // pending exit code, is when we failed to read pending upload log files.
+  // Since pending logs upload is not implemented by the reporter yet, we will
+  // only guarantee that we don't override the exit code in the logs proto.
+  DCHECK(previous_exit_code == RESULT_CODE_PENDING);
+}
+
+void ReporterLoggingService::AddLoadedModule(
+    const base::string16& /*name*/,
+    ModuleHost /*host*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void ReporterLoggingService::AddService(
+    const base::string16& /*display_name*/,
+    const base::string16& /*service_name*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void ReporterLoggingService::AddInstalledProgram(
+    const base::FilePath& /*folder_path*/) {}
+
+void ReporterLoggingService::AddProcess(
+    const base::string16& /*name*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void ReporterLoggingService::AddRegistryValue(
+    const internal::RegistryValue& /*registry_value*/,
+    const std::vector<internal::FileInformation>& /*file_informations*/) {}
+
+void ReporterLoggingService::AddLayeredServiceProvider(
+    const std::vector<base::string16>& /*guids*/,
+    const internal::FileInformation& /*file_information*/) {}
+
+void ReporterLoggingService::SetWinInetProxySettings(
+    const base::string16& /*config*/,
+    const base::string16& /*bypass*/,
+    const base::string16& /*auto_config_url*/,
+    bool /*autodetect*/) {}
+
+void ReporterLoggingService::SetWinHttpProxySettings(
+    const base::string16& /*config*/,
+    const base::string16& /*bypass*/) {}
+
+void ReporterLoggingService::AddInstalledExtension(
+    const base::string16& extension_id,
+    ExtensionInstallMethod install_method) {}
+
+void ReporterLoggingService::AddScheduledTask(
+    const base::string16& /*name*/,
+    const base::string16& /*description*/,
+    const std::vector<internal::FileInformation>& /*actions*/) {}
+
+void ReporterLoggingService::LogProcessInformation(
+    SandboxType process_type,
+    const SystemResourceUsage& usage) {
+  ProcessInformation info =
+      GetProcessInformationProtoObject(process_type, usage);
+  base::AutoLock lock(lock_);
+  reporter_logs_.add_process_information()->Swap(&info);
+}
+
+bool ReporterLoggingService::AllExpectedRemovalsConfirmed() const {
+  // This function should never be called on reporter logging service as no
+  // files are removed by the reporter. Return |false| as the default value to
+  // indicate error if it ever happens.
+  NOTREACHED();
+  return false;
+}
+
+std::string ReporterLoggingService::RawReportContent() {
+  base::AutoLock lock(lock_);
+  std::string serialized_report;
+  reporter_logs_.SerializeToString(&serialized_report);
+  return serialized_report;
+}
+
+bool ReporterLoggingService::ReadContentFromFile(
+    const base::FilePath& log_file) {
+  return true;
+}
+
+void ReporterLoggingService::ScheduleFallbackLogsUpload(
+    RegistryLogger* registry_logger,
+    ResultCode result_code) {}
+
+void ReporterLoggingService::OnReportUploadResult(
+    const UploadResultCallback& done_callback,
+    RegistryLogger* registry_logger,
+    SafeBrowsingReporter::Result result,
+    const std::string& serialized_report,
+    std::unique_ptr<ChromeFoilResponse> response) {
+  registry_logger->WriteReporterLogsUploadResult(result);
+  done_callback.Run(result == SafeBrowsingReporter::Result::UPLOAD_SUCCESS);
+}
+
+bool ReporterLoggingService::IsReportingNeeded() const {
+  base::AutoLock lock(const_cast<base::Lock&>(lock_));
+  // We should only upload logs if we have explicitly set an exit code and if
+  // there is at least one UwS detected.
+  return reporter_logs_.exit_code() != RESULT_CODE_PENDING &&
+         reporter_logs_.detected_uws_size() > 0;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_service.h b/chrome/chrome_cleaner/logging/reporter_logging_service.h
new file mode 100644
index 0000000..c3170e031
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/reporter_logging_service.h
@@ -0,0 +1,128 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_REPORTER_LOGGING_SERVICE_H_
+#define CHROME_CHROME_CLEANER_LOGGING_REPORTER_LOGGING_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+#include "chrome/chrome_cleaner/logging/detailed_info_sampler.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/logging/proto/reporter_logs.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/logging/registry_logger.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+
+namespace chrome_cleaner {
+
+// Manage where the logs are sent, and expose an API for more specific logging.
+class ReporterLoggingService : public LoggingServiceAPI {
+ public:
+  // Return the singleton instance which will get destroyed by the AtExitMgr.
+  static ReporterLoggingService* GetInstance();
+
+  // LoggingServiceAPI.
+  void Initialize(RegistryLogger* registry_logger) override;
+  void Terminate() override;
+
+  void SendLogsToSafeBrowsing(const UploadResultCallback& done_callback,
+                              RegistryLogger* registry_logger) override;
+  void CancelWaitForShutdown() override;
+  void EnableUploads(bool enabled, RegistryLogger* registry_logger) override;
+  bool uploads_enabled() const override;
+  void SetDetailedSystemReport(bool detailed_system_report) override;
+  bool detailed_system_report_enabled() const override;
+  void AddFoundUwS(const std::string& found_uws_name) override;
+  void AddDetectedUwS(const PUPData::PUP* found_uws,
+                      UwSDetectedFlags flags) override;
+  void AddDetectedUwS(const UwS& uws) override;
+  void SetExitCode(ResultCode exit_code) override;
+  void AddLoadedModule(
+      const base::string16& name,
+      ModuleHost host,
+      const internal::FileInformation& file_information) override;
+  void AddInstalledProgram(const base::FilePath& folder_path) override;
+  void AddService(const base::string16& display_name,
+                  const base::string16& service_name,
+                  const internal::FileInformation& file_information) override;
+  void AddProcess(const base::string16& name,
+                  const internal::FileInformation& file_information) override;
+  void AddRegistryValue(
+      const internal::RegistryValue& registry_value,
+      const std::vector<internal::FileInformation>& file_informations) override;
+  void AddLayeredServiceProvider(
+      const std::vector<base::string16>& guids,
+      const internal::FileInformation& file_information) override;
+  void SetWinInetProxySettings(const base::string16& config,
+                               const base::string16& bypass,
+                               const base::string16& auto_config_url,
+                               bool autodetect) override;
+  void SetWinHttpProxySettings(const base::string16& config,
+                               const base::string16& bypass) override;
+  void AddInstalledExtension(const base::string16& extension_id,
+                             ExtensionInstallMethod install_method) override;
+  void AddScheduledTask(
+      const base::string16& name,
+      const base::string16& description,
+      const std::vector<internal::FileInformation>& actions) override;
+  void LogProcessInformation(SandboxType process_type,
+                             const SystemResourceUsage& usage) override;
+  bool AllExpectedRemovalsConfirmed() const override;
+  std::string RawReportContent() override;
+  bool ReadContentFromFile(const base::FilePath& log_file) override;
+  void ScheduleFallbackLogsUpload(RegistryLogger* registry_logger,
+                                  ResultCode result_code) override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<ReporterLoggingService>;
+
+  ReporterLoggingService();
+  ~ReporterLoggingService() override;
+
+  // Return true if |reporter_logs_|'s values have changed since it has
+  // been cleared.
+  bool IsReportingNeeded() const;
+
+  // Callback for |safe_browsing_reporter_|.
+  void OnReportUploadResult(const UploadResultCallback& done_callback,
+                            RegistryLogger* registry_logger,
+                            SafeBrowsingReporter::Result result,
+                            const std::string& serialized_report,
+                            std::unique_ptr<ChromeFoilResponse> response);
+
+  // Any access to |reporter_logs_| must be protected by |lock_|. While
+  // under this lock, no outside function calls should be made, and no logging
+  // (this includes DCHECK).
+  mutable base::Lock lock_;
+  FoilReporterLogs reporter_logs_;
+
+  // Whether the logging service has been initialized.
+  bool initialized_ = false;
+
+  // Default to false, so EnableUploads must be called to set it to true.
+  bool uploads_enabled_ = false;
+
+  // |uploads_enabled_| must only be set from the thread that created the
+  // ReporterLoggingService.
+  THREAD_CHECKER(thread_checker_);
+
+  // Sampler to choose which files to log detailed info for.
+  DetailedInfoSampler sampler_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReporterLoggingService);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_REPORTER_LOGGING_SERVICE_H_
diff --git a/chrome/chrome_cleaner/logging/reporter_logging_service_unittest.cc b/chrome/chrome_cleaner/logging/reporter_logging_service_unittest.cc
new file mode 100644
index 0000000..5ebc8b33
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/reporter_logging_service_unittest.cc
@@ -0,0 +1,255 @@
+// Copyright 2018 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/chrome_cleaner/logging/reporter_logging_service.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "chrome/chrome_cleaner/http/http_agent_factory.h"
+#include "chrome/chrome_cleaner/http/mock_http_agent_factory.h"
+#include "chrome/chrome_cleaner/logging/proto/reporter_logs.pb.h"
+#include "chrome/chrome_cleaner/logging/safe_browsing_reporter.h"
+#include "chrome/chrome_cleaner/logging/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+constexpr UwSId kDefaultUwSId = 1;
+
+void NoSleep(base::TimeDelta) {}
+
+PUPData::PUP CreateSimpleDetectedPUP() {
+  // Use a static signature object so that it will outlive any PUP object
+  // pointing to it.
+  static PUPData::UwSSignature signature{kDefaultUwSId,
+                                         PUPData::FLAGS_STATE_CONFIRMED_UWS,
+                                         /*name=*/"This is nasty"};
+  return PUPData::PUP(&signature);
+}
+
+}  // namespace
+
+class ReporterLoggingServiceTest : public testing::Test {
+ public:
+  ReporterLoggingServiceTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+  // LoggingServiceAPI::UploadResultCallback implementation.
+  void LoggingServiceDone(base::OnceClosure run_loop_quit, bool success) {
+    done_callback_called_ = true;
+    upload_success_ = success;
+
+    // A RunLoop will be waiting for this callback to run before proceeding
+    // with the test. Now that the callback has run, we can quit the RunLoop.
+    std::move(run_loop_quit).Run();
+  }
+
+  void SetUp() override {
+    registry_logger_.reset(new RegistryLogger(RegistryLogger::Mode::REPORTER));
+
+    reporter_logging_service_ = ReporterLoggingService::GetInstance();
+    reporter_logging_service_->Initialize(registry_logger_.get());
+
+    done_callback_called_ = false;
+    upload_success_ = false;
+
+    // Use a mock HttpAgent instead of making real network requests.
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(
+        http_agent_factory_.get());
+    SafeBrowsingReporter::SetSleepCallbackForTesting(
+        base::BindRepeating(&NoSleep));
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(&network_checker_);
+  }
+
+  void TearDown() override { reporter_logging_service_->Terminate(); }
+
+  // Sends logs to Safe Browsing, and waits for LoggingServiceDone to be called
+  // before returning.
+  void DoSendLogsToSafeBrowsing() {
+    base::RunLoop run_loop;
+    reporter_logging_service_->SendLogsToSafeBrowsing(
+        base::BindRepeating(&ReporterLoggingServiceTest::LoggingServiceDone,
+                            base::Unretained(this), run_loop.QuitClosure()),
+        registry_logger_.get());
+    run_loop.Run();
+  }
+
+  LoggingServiceAPI* reporter_logging_service_;
+
+  // Needed for the current task runner to be available.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  // |done_callback_called_| is set to true in |LoggingServiceDone| to confirm
+  // it was called appropriately.
+  bool done_callback_called_;
+
+  // Set with the value given to the done callback.
+  bool upload_success_;
+
+  std::unique_ptr<RegistryLogger> registry_logger_;
+
+  MockHttpAgentConfig http_agent_config_;
+  std::unique_ptr<HttpAgentFactory> http_agent_factory_{
+      std::make_unique<MockHttpAgentFactory>(&http_agent_config_)};
+  MockNetworkChecker network_checker_;
+};
+
+TEST_F(ReporterLoggingServiceTest, Disabled) {
+  reporter_logging_service_->EnableUploads(false, registry_logger_.get());
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when not enabled.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+}
+
+TEST_F(ReporterLoggingServiceTest, Empty) {
+  reporter_logging_service_->EnableUploads(true, registry_logger_.get());
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when there is nothing to report.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+}
+
+TEST_F(ReporterLoggingServiceTest, ExitCodeNotSet) {
+  reporter_logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Adding UwS data, but exit code is not set.
+  PUPData::PUP pup = CreateSimpleDetectedPUP();
+
+  reporter_logging_service_->AddDetectedUwS(&pup, kUwSDetectedFlagsNone);
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when the exit code has not been set.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+}
+
+TEST_F(ReporterLoggingServiceTest, NoUwSFound) {
+  reporter_logging_service_->EnableUploads(true, registry_logger_.get());
+
+  reporter_logging_service_->SetExitCode(RESULT_CODE_NO_PUPS_FOUND);
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  ChromeFoilResponse response;
+  response.SerializeToString(&calls.read_data_result);
+  http_agent_config_.AddCalls(calls);
+
+  DoSendLogsToSafeBrowsing();
+
+  // No URLRequest should have been made when no UwS was found.
+  EXPECT_EQ(0UL, http_agent_config_.num_request_data());
+
+  // But the done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With a failure.
+  EXPECT_FALSE(upload_success_);
+}
+
+TEST_F(ReporterLoggingServiceTest, BothExitCodeSetAndUwSDetected) {
+  reporter_logging_service_->EnableUploads(true, registry_logger_.get());
+
+  // Set the exit code and add detected UwS.
+  reporter_logging_service_->SetExitCode(RESULT_CODE_EXAMINED_FOR_REMOVAL_ONLY);
+  PUPData::PUP pup = CreateSimpleDetectedPUP();
+  reporter_logging_service_->AddDetectedUwS(&pup, kUwSDetectedFlagsNone);
+
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  ChromeFoilResponse response;
+  response.SerializeToString(&calls.read_data_result);
+  http_agent_config_.AddCalls(calls);
+
+  DoSendLogsToSafeBrowsing();
+
+  // One URLRequest should have been made.
+  EXPECT_EQ(1UL, http_agent_config_.num_request_data());
+
+  // The done callback should have been called.
+  EXPECT_TRUE(done_callback_called_);
+  // With success.
+  EXPECT_TRUE(upload_success_);
+
+  std::string upload_data(http_agent_config_.request_data(0).body);
+  FoilReporterLogs reporter_logs;
+  ASSERT_TRUE(reporter_logs.ParseFromString(upload_data));
+  EXPECT_EQ(RESULT_CODE_EXAMINED_FOR_REMOVAL_ONLY,
+            static_cast<chrome_cleaner::ResultCode>(reporter_logs.exit_code()));
+
+  ASSERT_EQ(1, reporter_logs.detected_uws_size());
+  const UwS& uws = reporter_logs.detected_uws(0);
+  EXPECT_EQ(pup.signature().id, uws.id());
+  EXPECT_EQ(pup.signature().name, uws.name());
+  EXPECT_EQ(UwS::REPORT_ONLY, uws.state());
+  EXPECT_FALSE(uws.detail_level().only_one_footprint());
+
+  // TODO Add tests for matched files, folders, registry entries,
+  // and scheduled tasks, once the corresponding utility functions have been
+  // moved from logging_service_unittest.cc to a shared lib.
+}
+
+TEST_F(ReporterLoggingServiceTest, AddDetectedUwS) {
+  UwS uws;
+  uws.set_id(kDefaultUwSId);
+  reporter_logging_service_->AddDetectedUwS(uws);
+
+  FoilReporterLogs report;
+  ASSERT_TRUE(
+      report.ParseFromString(reporter_logging_service_->RawReportContent()));
+  ASSERT_EQ(1, report.detected_uws_size());
+  ASSERT_EQ(kDefaultUwSId, report.detected_uws(0).id());
+}
+
+TEST_F(ReporterLoggingServiceTest, LogProcessInformation) {
+  base::IoCounters io_counters;
+  io_counters.ReadOperationCount = 1;
+  io_counters.WriteOperationCount = 2;
+  io_counters.OtherOperationCount = 3;
+  io_counters.ReadTransferCount = 4;
+  io_counters.WriteTransferCount = 5;
+  io_counters.OtherTransferCount = 6;
+  SystemResourceUsage usage = {io_counters, base::TimeDelta::FromSeconds(10),
+                               base::TimeDelta::FromSeconds(20), 123456};
+
+  reporter_logging_service_->LogProcessInformation(SandboxType::kEset, usage);
+
+  FoilReporterLogs report;
+  ASSERT_TRUE(
+      report.ParseFromString(reporter_logging_service_->RawReportContent()));
+  ASSERT_EQ(1, report.process_information_size());
+  EXPECT_EQ(ProcessInformation::ESET_SANDBOX,
+            report.process_information(0).process());
+
+  ProcessInformation::SystemResourceUsage usage_msg =
+      report.process_information(0).resource_usage();
+  EXPECT_TRUE(IoCountersEqual(io_counters, usage_msg));
+  EXPECT_EQ(10U, usage_msg.user_time());
+  EXPECT_EQ(20U, usage_msg.kernel_time());
+  EXPECT_EQ(123456U, usage_msg.peak_working_set_size());
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/safe_browsing_reporter.cc b/chrome/chrome_cleaner/logging/safe_browsing_reporter.cc
new file mode 100644
index 0000000..289aafa
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/safe_browsing_reporter.cc
@@ -0,0 +1,378 @@
+// Copyright 2018 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/chrome_cleaner/logging/safe_browsing_reporter.h"
+
+#include <windows.h>
+
+#include <iphlpapi.h>
+#include <stdint.h>
+#include <wininet.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "base/win/scoped_handle.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/http/http_agent.h"
+#include "chrome/chrome_cleaner/http/http_response.h"
+#include "chrome/chrome_cleaner/http/http_status_codes.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "url/gurl.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+// Number of times that an upload will be retried on failure.
+const unsigned int kMaxUploadAttempts = 3;
+
+// How long to wait (in seconds) before every upload attempt. The first delay
+// should be zero.
+const unsigned int kUploadAttemptDelaySeconds[kMaxUploadAttempts] = {0, 5, 300};
+
+// How long to wait for network changes (total in seconds), if Safe Browsing is
+// not reachable.
+const unsigned int kNetworkPresenceTimeoutSeconds = 300;
+
+// The maximum timeout we allow, in milliseconds.
+const DWORD kMaxTimeoutMilliseconds = 600000;
+
+// Extracts data from |http_response| and returns it as a std::string.
+std::string GetHttpResponseData(chrome_cleaner::HttpResponse* http_response) {
+  std::string response_data;
+  while (true) {
+    char buffer[8192] = {};
+    size_t count = base::size(buffer);
+    if (!http_response->ReadData(buffer, &count)) {
+      LOG(ERROR) << "ReadData failed";
+      break;
+    } else if (!count) {
+      break;
+    }
+    response_data.append(buffer, count);
+  }
+  return response_data;
+}
+
+// Returns the URL that logs should be uploaded to.
+GURL GetSafeBrowsingReportUrl(const std::string& default_url) {
+  GURL upload_url(default_url);
+
+  std::string test_url =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          kTestLoggingURLSwitch);
+  if (!test_url.empty()) {
+    LOG(INFO) << "Using test logging url: " << test_url;
+    upload_url = GURL(test_url);
+  }
+
+  DCHECK(upload_url.is_valid());
+  return upload_url;
+}
+
+class NetworkCheckerImpl : public NetworkChecker {
+ public:
+  NetworkCheckerImpl() = default;
+  ~NetworkCheckerImpl() override = default;
+
+  // TODO(olivierli) Make upload_url a member variable
+  bool IsSafeBrowsingReachable(const GURL& upload_url) const override {
+    const BOOL is_reachable =
+        ::InternetCheckConnection(base::UTF8ToWide(upload_url.spec()).c_str(),
+                                  FLAG_ICC_FORCE_CONNECTION, 0);
+    if (is_reachable == FALSE)
+      PLOG(INFO) << "Safe Browsing is not reachable";
+    return !!is_reachable;
+  }
+
+  bool WaitForSafeBrowsing(const GURL& upload_url,
+                           const base::TimeDelta& wait_time) override {
+    const base::Time start_time = base::Time::Now();
+    if (wait_time <= base::TimeDelta()) {
+      LOG(INFO) << "Past deadline, not trying to wait for Safe Browsing";
+      return IsSafeBrowsingReachable(upload_url);
+    }
+
+    HANDLE event = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
+    if (!event) {
+      PLOG(ERROR) << "Unable to create event";
+      return false;
+    }
+    base::ScopedClosureRunner close_event(
+        base::BindRepeating(base::IgnoreResult(&::CloseHandle), event));
+
+    OVERLAPPED overlapped = {0};
+    overlapped.hEvent = event;
+
+    HANDLE handle = nullptr;
+    DWORD ret = ::NotifyAddrChange(&handle, &overlapped);
+    if (ret != ERROR_IO_PENDING) {
+      PLOG(ERROR) << "Error in NotifyAddrChange (" << ret << ")";
+      return false;
+    }
+    base::ScopedClosureRunner cancel_ip_change_notify(base::BindRepeating(
+        base::IgnoreResult(&::CancelIPChangeNotify), &overlapped));
+
+    // NotifyAddrChange will only notify when there are address changes to the
+    // network interfaces, so avoid a race condition and make sure that Safe
+    // Browsing is in fact not reachable before waiting for address changes.
+    if (IsSafeBrowsingReachable(upload_url)) {
+      LOG(INFO) << "Safe Browsing is reachable, not waiting";
+      return true;
+    }
+
+    EnsureCancelWaitEventCreated();
+    HANDLE event_list[2] = {overlapped.hEvent, cancel_wait_event_.Get()};
+
+    const DWORD timeout_ms = static_cast<DWORD>(wait_time.InMilliseconds());
+    DCHECK_LT(timeout_ms, kMaxTimeoutMilliseconds)
+        << "timeout should be a reasonable value";
+    LOG(INFO) << "Waiting up to " << timeout_ms << "ms for Internet connection";
+    ret = ::WaitForMultipleObjects(2, event_list, FALSE, timeout_ms);
+    if (ret == WAIT_OBJECT_0) {
+      // The event was signaled by NotifyAddrChange. Cancel the change
+      // notification so we don't receive notifications on this particular
+      // OVERLAPPED structure again.
+      cancel_ip_change_notify.RunAndReset();
+      if (IsSafeBrowsingReachable(upload_url)) {
+        LOG(INFO) << "Safe Browsing now reachable";
+        return true;
+      }
+    } else if (ret == (WAIT_OBJECT_0 + 1)) {
+      LOG(INFO) << "Wait canceled";
+      return false;
+    } else {
+      LOG(INFO) << "WaitForMultipleObjects returned " << ret;
+    }
+
+    LOG(INFO) << "Trying to wait for Safe Browsing to be reachable again";
+
+    // TODO(csharp): In cases where we timed out above. this recursive call
+    //               will immediately return because (start_time + wait_time)
+    //               - base::Time::Now() should ~0. This may or may not be
+    //               intended. Refactor this to not use recursion to make it
+    //               clearer how this is supposed to work.
+    return WaitForSafeBrowsing(upload_url,
+                               (start_time + wait_time) - base::Time::Now());
+  }
+
+  void CancelWaitForShutdown() override {
+    EnsureCancelWaitEventCreated();
+    ::SetEvent(cancel_wait_event_.Get());
+  }
+
+ private:
+  void EnsureCancelWaitEventCreated() {
+    base::AutoLock lock(cancel_wait_event_lock_);
+    if (!cancel_wait_event_.IsValid())
+      cancel_wait_event_.Set(::CreateEvent(nullptr, TRUE, FALSE, nullptr));
+  }
+
+  // Manual-reset event, which will remain set once set.
+  base::win::ScopedHandle cancel_wait_event_;
+  base::Lock cancel_wait_event_lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkCheckerImpl);
+};
+
+NetworkChecker* current_network_checker{nullptr};
+const HttpAgentFactory* current_http_agent_factory{nullptr};
+
+NetworkChecker* GetNetworkChecker() {
+  // This is "leaked" on purpose to avoid static destruction order woes.
+  // Neither NetworkChecker nor its parent classes dtors do any work.
+  static NetworkChecker* network_checker = new NetworkCheckerImpl();
+
+  if (!current_network_checker) {
+    current_network_checker = network_checker;
+  }
+
+  return current_network_checker;
+}
+
+const HttpAgentFactory* GetHttpAgentFactory() {
+  // This is "leaked" on purpose to avoid static destruction order woes.
+  // Neither HttpAgentFactory nor its parent classes dtors do any work.
+  static HttpAgentFactory* http_agent_factory = new HttpAgentFactory();
+
+  if (!current_http_agent_factory) {
+    current_http_agent_factory = http_agent_factory;
+  }
+
+  return current_http_agent_factory;
+}
+
+}  // namespace
+
+base::RepeatingCallback<void(base::TimeDelta)>
+    SafeBrowsingReporter::sleep_callback_(
+        base::BindRepeating(&base::PlatformThread::Sleep));
+
+SafeBrowsingReporter::~SafeBrowsingReporter() = default;
+
+// static
+void SafeBrowsingReporter::SetHttpAgentFactoryForTesting(
+    const HttpAgentFactory* factory) {
+  current_http_agent_factory = factory;
+}
+
+// static
+void SafeBrowsingReporter::SetSleepCallbackForTesting(
+    base::RepeatingCallback<void(base::TimeDelta)> callback) {
+  sleep_callback_ = callback;
+}
+
+// static
+void SafeBrowsingReporter::SetNetworkCheckerForTesting(
+    NetworkChecker* checker) {
+  current_network_checker = checker;
+}
+
+// static
+void SafeBrowsingReporter::UploadReport(
+    const OnResultCallback& done_callback,
+    const std::string& default_url,
+    const std::string& report,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  // SafeBrowsingReporter will PostTask to WorkerPool, giving that task
+  // ownership of the object, which will get destructed on task completion.
+  new SafeBrowsingReporter(done_callback, GetSafeBrowsingReportUrl(default_url),
+                           report, traffic_annotation,
+                           base::ThreadTaskRunnerHandle::Get());
+}
+
+// static
+void SafeBrowsingReporter::CancelWaitForShutdown() {
+  GetNetworkChecker()->CancelWaitForShutdown();
+}
+
+SafeBrowsingReporter::SafeBrowsingReporter(
+    const OnResultCallback& done_callback,
+    const GURL& upload_url,
+    const std::string& serialized_report,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
+    scoped_refptr<base::TaskRunner> done_callback_runner)
+    : upload_url_(upload_url),
+      done_callback_runner_(done_callback_runner),
+      done_callback_(done_callback) {
+  DCHECK(done_callback_runner);
+  base::PostTaskWithTraits(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+      base::BindRepeating(&SafeBrowsingReporter::UploadWithRetry,
+                          base::Owned(this), serialized_report,
+                          traffic_annotation));
+}
+
+void SafeBrowsingReporter::UploadWithRetry(
+    const std::string& serialized_report,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  std::unique_ptr<ChromeFoilResponse> response(new ChromeFoilResponse);
+  Result result = Result::UPLOAD_NO_NETWORK;
+  if (GetNetworkChecker()->WaitForSafeBrowsing(
+          upload_url_,
+          base::TimeDelta::FromSeconds(kNetworkPresenceTimeoutSeconds))) {
+    result = PerformUploadWithRetries(serialized_report, response.get(),
+                                      traffic_annotation);
+    if (result != Result::UPLOAD_SUCCESS) {
+      LOG(WARNING) << "Failed to upload report to Safe Browsing API, "
+                   << static_cast<int>(result);
+    }
+  }
+
+  LOG(INFO) << "Calling done_callback_ with result: "
+            << static_cast<int>(result);
+  done_callback_runner_->PostTask(
+      FROM_HERE, base::BindRepeating(done_callback_, result, serialized_report,
+                                     base::Passed(&response)));
+}
+
+SafeBrowsingReporter::Result SafeBrowsingReporter::PerformUploadWithRetries(
+    const std::string& serialized_report,
+    ChromeFoilResponse* response,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(response);
+
+  SafeBrowsingReporter::Result result = Result::UPLOAD_INTERNAL_ERROR;
+  for (unsigned int attempt = 0; attempt < kMaxUploadAttempts; ++attempt) {
+    sleep_callback_.Run(
+        base::TimeDelta::FromSeconds(kUploadAttemptDelaySeconds[attempt]));
+    result = PerformUpload(serialized_report, response, traffic_annotation);
+    if (result == Result::UPLOAD_SUCCESS)
+      break;
+  }
+
+  return result;
+}
+
+SafeBrowsingReporter::Result SafeBrowsingReporter::PerformUpload(
+    const std::string& serialized_report,
+    ChromeFoilResponse* response,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(response);
+
+  // TODO(csharp): Add an entry point to the appspot test logging server to
+  // generate errors, to replace this command line switch.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kForceLogsUploadFailureSwitch)) {
+    LOG(WARNING) << "Forcing a logs upload failure.";
+    return Result::UPLOAD_INTERNAL_ERROR;
+  }
+
+  std::unique_ptr<chrome_cleaner::HttpAgent> http_agent =
+      GetHttpAgentFactory()->CreateHttpAgent();
+  std::unique_ptr<chrome_cleaner::HttpResponse> http_response =
+      http_agent->Post(base::UTF8ToWide(upload_url_.host()),
+                       static_cast<uint16_t>(upload_url_.EffectiveIntPort()),
+                       base::UTF8ToWide(upload_url_.PathForRequest()),
+                       upload_url_.SchemeIsCryptographic(),
+                       L"",  // No extra headers.
+                       serialized_report, traffic_annotation);
+
+  if (!http_response.get())
+    return Result::UPLOAD_REQUEST_FAILED;
+
+  Result result = Result::UPLOAD_INTERNAL_ERROR;
+
+  uint16_t status_code = 0;
+  if (http_response->GetStatusCode(&status_code)) {
+    DCHECK_NE(status_code, 0);
+    if (status_code == static_cast<uint16_t>(HttpStatus::kOk)) {
+      std::string response_data = GetHttpResponseData(http_response.get());
+      if (response->ParseFromString(response_data)) {
+        result = Result::UPLOAD_SUCCESS;
+        LOG(INFO) << "Upload response token: " << response->token();
+      } else {
+        result = Result::UPLOAD_INVALID_RESPONSE;
+      }
+    } else {
+      LOG(WARNING) << "HttpResponse status: " << status_code;
+      if (status_code ==
+          static_cast<uint16_t>(HttpStatus::kRequestEntityTooLarge))
+        result = Result::UPLOAD_ERROR_TOO_LARGE;
+      else if (status_code == static_cast<uint16_t>(HttpStatus::kNotFound))
+        result = Result::UPLOAD_REQUEST_FAILED;
+    }
+  } else {
+    LOG(ERROR) << "Failed to retrieve data from HttpResponse";
+  }
+
+  return result;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/safe_browsing_reporter.h b/chrome/chrome_cleaner/logging/safe_browsing_reporter.h
new file mode 100644
index 0000000..9813ccf
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/safe_browsing_reporter.h
@@ -0,0 +1,128 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_SAFE_BROWSING_REPORTER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_SAFE_BROWSING_REPORTER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "chrome/chrome_cleaner/http/http_agent.h"
+#include "chrome/chrome_cleaner/http/http_agent_factory.h"
+#include "chrome/chrome_cleaner/logging/network_checker.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "url/gurl.h"
+
+namespace chrome_cleaner {
+
+// Class that uploads serialized protos to Safe Browsing. The upload
+// operation will run on a separate thread (via a WorkerPool), and results will
+// be posted back on the thread that initially requested the upload.
+class SafeBrowsingReporter {
+ public:
+  // The result of a report upload.
+  enum class Result {
+    UPLOAD_SUCCESS,           // A response was received.
+    UPLOAD_REQUEST_FAILED,    // Upload failed.
+    UPLOAD_INVALID_RESPONSE,  // The response was not recognized.
+    UPLOAD_TIMED_OUT,         // Upload timed out.
+    UPLOAD_INTERNAL_ERROR,    // Internal error.
+    UPLOAD_ERROR_TOO_LARGE,   // Entity too large.
+    UPLOAD_NO_NETWORK,        // No network or Safe Browsing is not reachable.
+    NUM_UPLOAD_RESULTS
+  };
+
+  // A callback run by the uploader upon success or failure. The first argument
+  // indicates the result of the upload, the second the report that was uploaded
+  // while the third contains the response received, if any.
+  typedef base::RepeatingCallback<void(Result,
+                                       const std::string& serialized_report,
+                                       std::unique_ptr<ChromeFoilResponse>)>
+      OnResultCallback;
+
+  virtual ~SafeBrowsingReporter();
+
+  // Replaces the HttpAgent factory with a new factory. Exposed so tests can
+  // create mock HttpAgent objects. Passing an empty factory will reset to the
+  // default factory. This method is not thread-safe.
+  static void SetHttpAgentFactoryForTesting(const HttpAgentFactory* factory);
+
+  // Sets which Callback should be executed when the code needs to Sleep for a
+  // period of time.
+  static void SetSleepCallbackForTesting(
+      base::RepeatingCallback<void(base::TimeDelta)> callback);
+
+  // Replaces the NetworkChecker with a new instance. Passing a NULL |checker|
+  // will reset to the default network checker. Exposed so tests can provide
+  // implementations that do not actually check the network presence.
+  static void SetNetworkCheckerForTesting(NetworkChecker* checker);
+
+  // Starts the process to upload a report to |default_url|, unless it's
+  // overriden by --test-logging-url. |done_callback| will be run when the
+  // upload is complete. The callback will always be run (success or failure) on
+  // the same thread that was used when this method was called.
+  static void UploadReport(
+      const OnResultCallback& done_callback,
+      const std::string& default_url,
+      const std::string& serialized_report,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // Cancels all current and future waits, to speed up system shutdown.
+  static void CancelWaitForShutdown();
+
+ protected:
+  // Initializes SafeBrowsingReporter and posts a task to WorkerPool which will
+  // perform the upload on a separate thread. |done_callback| will be run in all
+  // cases, on the |done_callback_runner| TaskRunner. Declared protected so
+  // tests which override this class can call this.
+  SafeBrowsingReporter(
+      const OnResultCallback& done_callback,
+      const GURL& upload_url,
+      const std::string& serialized_report,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
+      scoped_refptr<base::TaskRunner> done_callback_runner);
+
+ private:
+  // Attempts to upload |serialized_report_|. This method runs on a WorkerPool
+  // thread.
+  void UploadWithRetry(
+      const std::string& serialized_report,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // Attempts to upload a report synchronously, retrying up to a fixed number of
+  // times on failure.
+  Result PerformUploadWithRetries(
+      const std::string& serialized_report,
+      ChromeFoilResponse* response,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // Attempts to upload a report synchronously.
+  Result PerformUpload(
+      const std::string& serialized_report,
+      ChromeFoilResponse* response,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // Callback to be run when the code needs to sleep for some amount of time.
+  static base::RepeatingCallback<void(base::TimeDelta)> sleep_callback_;
+
+  // The URL to upload logs to.
+  GURL upload_url_;
+
+  // The TaskRunner on which |done_callback_| must be run.
+  scoped_refptr<base::TaskRunner> done_callback_runner_;
+
+  // The callback by which results are returned.
+  OnResultCallback done_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingReporter);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_SAFE_BROWSING_REPORTER_H_
diff --git a/chrome/chrome_cleaner/logging/safe_browsing_reporter_unittest.cc b/chrome/chrome_cleaner/logging/safe_browsing_reporter_unittest.cc
new file mode 100644
index 0000000..a02c14c0
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/safe_browsing_reporter_unittest.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 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/chrome_cleaner/logging/safe_browsing_reporter.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "chrome/chrome_cleaner/http/mock_http_agent_factory.h"
+#include "chrome/chrome_cleaner/logging/test_utils.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+namespace {
+const char kSerializedReport[] = "I'm a serialized report!";
+const char kTestSafeBrowsingUrl[] = "https://sb.google.com/yay";
+
+void NoSleep(base::TimeDelta) {}
+}  // namespace
+
+using ::testing::_;
+using ::testing::Return;
+
+class SafeBrowsingReporterTest : public testing::Test {
+ public:
+  // SafeBrowsingReporter::OnResultCallback:
+  void OnReportUploadResult(base::OnceClosure run_loop_quit,
+                            SafeBrowsingReporter::Result result,
+                            const std::string& serialized_report,
+                            std::unique_ptr<ChromeFoilResponse> response) {
+    result_ = result;
+    report_upload_result_called_ = true;
+    if (response.get())
+      response->SerializeToString(&response_string_);
+    std::move(run_loop_quit).Run();
+  }
+
+  void SetUp() override {
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(
+        http_agent_factory_.get());
+    SafeBrowsingReporter::SetSleepCallbackForTesting(
+        base::BindRepeating(&NoSleep));
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(&network_checker_);
+  }
+
+  void TearDown() override {
+    SafeBrowsingReporter::SetNetworkCheckerForTesting(nullptr);
+    SafeBrowsingReporter::SetHttpAgentFactoryForTesting(nullptr);
+  }
+
+ protected:
+  SafeBrowsingReporterTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
+
+  // Uploads a report and waits for OnReportUploadResult to be called.
+  void DoUploadReport(const std::string& serialized_report) {
+    base::RunLoop run_loop;
+    SafeBrowsingReporter::UploadReport(
+        base::BindRepeating(&SafeBrowsingReporterTest::OnReportUploadResult,
+                            base::Unretained(this), run_loop.QuitClosure()),
+        kTestSafeBrowsingUrl, serialized_report, TRAFFIC_ANNOTATION_FOR_TESTS);
+    run_loop.Run();
+  }
+
+  // Needed for the current task runner to be available.
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  // |result_| and |response_string_| are set in |OnReportUploadResult| and used
+  // to confirm that the upload succeeded or failed appropriately.
+  SafeBrowsingReporter::Result result_{
+      SafeBrowsingReporter::Result::UPLOAD_INTERNAL_ERROR};
+  std::string response_string_;
+
+  MockHttpAgentConfig config_;
+  std::unique_ptr<HttpAgentFactory> http_agent_factory_{
+      std::make_unique<MockHttpAgentFactory>(&config_)};
+  MockNetworkChecker network_checker_;
+
+  // Confirm the execution of |OnReportUploadResult| and that |result_| and
+  // |response_string_| are valid.
+  bool report_upload_result_called_{false};
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SafeBrowsingReporterTest);
+};
+
+TEST_F(SafeBrowsingReporterTest, Success) {
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+
+  ChromeFoilResponse response;
+  response.set_token("Token");
+  std::string response_string;
+  response.SerializeToString(&response_string);
+  calls.read_data_result = response_string;
+
+  config_.AddCalls(calls);
+
+  DoUploadReport(kSerializedReport);
+
+  ASSERT_GT(config_.num_request_data(), 0u);
+  EXPECT_EQ(kSerializedReport, config_.request_data(0).body);
+
+  EXPECT_EQ(SafeBrowsingReporter::Result::UPLOAD_SUCCESS, result_);
+  EXPECT_EQ(response_string_, response_string);
+
+  EXPECT_TRUE(report_upload_result_called_);
+}
+
+TEST_F(SafeBrowsingReporterTest, Failure) {
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+  calls.request_succeeds = false;
+  // Request fails on all tries. No retry without log lines.
+  config_.AddCalls(calls);
+  config_.AddCalls(calls);
+  config_.AddCalls(calls);
+
+  DoUploadReport(kSerializedReport);
+
+  EXPECT_EQ(SafeBrowsingReporter::Result::UPLOAD_REQUEST_FAILED, result_);
+  EXPECT_TRUE(response_string_.empty());
+  EXPECT_EQ(config_.num_request_data(), 3UL);
+
+  EXPECT_TRUE(report_upload_result_called_);
+}
+
+TEST_F(SafeBrowsingReporterTest, RetryOnlyOnceWhenFailing) {
+  MockHttpAgentConfig::Calls calls_failed(HttpStatus::kNotFound);
+  // Fail on every try with the same error.
+  config_.AddCalls(calls_failed);
+  config_.AddCalls(calls_failed);
+  config_.AddCalls(calls_failed);
+
+  DoUploadReport(kSerializedReport);
+
+  EXPECT_EQ(SafeBrowsingReporter::Result::UPLOAD_REQUEST_FAILED, result_);
+  EXPECT_TRUE(report_upload_result_called_);
+
+  ASSERT_EQ(3u, config_.num_request_data());
+
+  // Expect all requests to have the same contents.
+  EXPECT_EQ(config_.request_data(0).body, config_.request_data(1).body);
+  EXPECT_EQ(config_.request_data(0).body, config_.request_data(2).body);
+}
+
+TEST_F(SafeBrowsingReporterTest, WaitForSafeBrowsing) {
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+
+  ChromeFoilResponse response;
+  response.set_token("Token");
+  std::string response_string;
+  response.SerializeToString(&response_string);
+  calls.read_data_result = response_string;
+
+  config_.AddCalls(calls);
+
+  // Safe Browsing is initially not reachable, but waiting for it result in
+  // it becoming reachable. Everything should succeed.
+  network_checker_.SetIsSafeBrowsingReachableResult(false);
+  network_checker_.SetWaitForSafeBrowsingResult(true);
+
+  DoUploadReport(kSerializedReport);
+
+  ASSERT_GT(config_.num_request_data(), 0u);
+  EXPECT_EQ(kSerializedReport, config_.request_data(0).body);
+
+  EXPECT_EQ(SafeBrowsingReporter::Result::UPLOAD_SUCCESS, result_);
+  EXPECT_EQ(response_string_, response_string);
+
+  EXPECT_TRUE(report_upload_result_called_);
+}
+
+TEST_F(SafeBrowsingReporterTest, NoNetwork) {
+  MockHttpAgentConfig::Calls calls(HttpStatus::kOk);
+
+  config_.AddCalls(calls);
+
+  // Safe Browsing never becomes reachable, and WaitForSafeBrowsing times
+  // out. UPLOAD_NO_NETWORK should be the result code, and the callback should
+  // have run anyway.
+  network_checker_.SetIsSafeBrowsingReachableResult(false);
+  network_checker_.SetWaitForSafeBrowsingResult(false);
+
+  DoUploadReport(kSerializedReport);
+
+  ASSERT_EQ(config_.num_request_data(), 0u);
+  EXPECT_EQ(SafeBrowsingReporter::Result::UPLOAD_NO_NETWORK, result_);
+  EXPECT_TRUE(report_upload_result_called_);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/scoped_logging.cc b/chrome/chrome_cleaner/logging/scoped_logging.cc
new file mode 100644
index 0000000..9d2befd
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/scoped_logging.cc
@@ -0,0 +1,112 @@
+// Copyright 2018 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/chrome_cleaner/logging/scoped_logging.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/file_version_info.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging_win.h"
+#include "base/win/current_module.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/constants/version.h"
+#include "chrome/chrome_cleaner/logging/logging_service_api.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+// {985388DD-5F6A-40A9-A4D2-86D8547EFB52}
+const GUID kChromeCleanerTraceProviderName = {
+    0x985388DD,
+    0x5F6A,
+    0x40A9,
+    {0xA4, 0xD2, 0x86, 0xD8, 0x54, 0x7E, 0xFB, 0x52}};
+
+// The log file extension.
+const wchar_t kLogFileExtension[] = L"log";
+
+}  // namespace
+
+ScopedLogging::ScopedLogging(base::FilePath::StringPieceType suffix) {
+  // Log to an ETW facility for convenience.
+  // This swallows all log lines it gets, so we need to initialize it before
+  // LoggingServiceAPI so LoggingServiceAPI can see the logs first, which it
+  // then passes on to LogEventProvider.
+  logging::LogEventProvider::Initialize(kChromeCleanerTraceProviderName);
+
+  // Initialize the logging global state.
+  LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance();
+  logging_service->Initialize(nullptr);
+
+  const base::FilePath log_file_path = GetLogFilePath(suffix);
+
+  // Truncate log files to 40kB. This should be enough to cover logs of the
+  // previous run (99th percentile of uploaded raw log line size is 36kB).
+  TruncateLogFileToTail(log_file_path, 40 * 1000);
+
+  logging::LoggingSettings logging_settings;
+  logging_settings.logging_dest = logging::LOG_TO_FILE;
+  logging_settings.log_file = log_file_path.value().c_str();
+
+  bool success = logging::InitLogging(logging_settings);
+  DCHECK(success);
+  LOG(INFO) << "Starting logs for version: " << CHROME_VERSION_STRING;
+
+#if defined(CHROME_CLEANER_OFFICIAL_BUILD)
+  // Official builds are opt-out, unless no logs upload is specified.
+  bool enable_uploads =
+      chrome_cleaner::Settings::GetInstance()->logs_upload_allowed();
+#else   // if CHROME_CLEANER_OFFICIAL_BUILD
+  // Other builds are opt-in, unless a test logging URL is specified.
+  bool enable_uploads =
+      base::CommandLine::ForCurrentProcess()->HasSwitch(kTestLoggingURLSwitch);
+#endif  // else of if CHROME_CLEANER_OFFICIAL_BUILD
+  logging_service->EnableUploads(enable_uploads, nullptr);
+}
+
+ScopedLogging::~ScopedLogging() {
+  // Terminate the service to avoid work being done in the destructor called by
+  // the AtExitManager.
+  LoggingServiceAPI::GetInstance()->Terminate();
+
+  // Kill our ETW provider.
+  logging::LogEventProvider::Uninitialize();
+}
+
+// static
+base::FilePath ScopedLogging::GetLogFilePath(
+    base::FilePath::StringPieceType suffix) {
+  // Initialize the logging settings to set a specific log file name.
+  std::unique_ptr<FileVersionInfo> version(
+      FileVersionInfo::CreateFileVersionInfoForModule(CURRENT_MODULE()));
+
+  // Test executables don't have version resources.
+  base::FilePath original_filename;
+  if (version.get()) {
+    original_filename = base::FilePath(version->original_filename());
+  } else {
+    original_filename =
+        PreFetchedPaths::GetInstance()->GetExecutablePath().BaseName();
+  }
+
+  if (!suffix.empty())
+    original_filename = original_filename.InsertBeforeExtension(suffix);
+
+  base::FilePath log_file_path =
+      original_filename.ReplaceExtension(kLogFileExtension);
+  base::FilePath product_app_data_path;
+  if (GetAppDataProductDirectory(&product_app_data_path))
+    log_file_path = product_app_data_path.Append(log_file_path);
+
+  return log_file_path;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/scoped_logging.h b/chrome/chrome_cleaner/logging/scoped_logging.h
new file mode 100644
index 0000000..5caacee
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/scoped_logging.h
@@ -0,0 +1,33 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_SCOPED_LOGGING_H_
+#define CHROME_CHROME_CLEANER_LOGGING_SCOPED_LOGGING_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+namespace chrome_cleaner {
+
+// A utility to print the "Null" string for null wchar_t*.
+base::string16 ConvertIfNull(const wchar_t* str);
+
+// Un/Initialize the logging machinery. A |suffix| can be appended to the log
+// file name if necessary.
+class ScopedLogging {
+ public:
+  explicit ScopedLogging(base::FilePath::StringPieceType suffix);
+  ~ScopedLogging();
+
+  // Returns the path to the log file for the current process.
+  static base::FilePath GetLogFilePath(base::FilePath::StringPieceType suffix);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedLogging);
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_SCOPED_LOGGING_H_
diff --git a/chrome/chrome_cleaner/logging/scoped_timed_task_logger.cc b/chrome/chrome_cleaner/logging/scoped_timed_task_logger.cc
new file mode 100644
index 0000000..3ee7948
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/scoped_timed_task_logger.cc
@@ -0,0 +1,38 @@
+// Copyright 2018 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/chrome_cleaner/logging/scoped_timed_task_logger.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+
+namespace chrome_cleaner {
+
+// static.
+void ScopedTimedTaskLogger::LogIfExceedThreshold(
+    const char* logging_text,
+    const base::TimeDelta& threshold,
+    const base::TimeDelta& elapsed_time) {
+  DCHECK(logging_text);
+  if (elapsed_time >= threshold) {
+    LOG(WARNING) << logging_text << " took '" << elapsed_time.InSeconds()
+                 << "' seconds.";
+  }
+}
+
+ScopedTimedTaskLogger::ScopedTimedTaskLogger(TimerCallback timer_callback)
+    : start_time_(base::Time::NowFromSystemTime()),
+      timer_callback_(std::move(timer_callback)) {}
+
+ScopedTimedTaskLogger::ScopedTimedTaskLogger(const char* logging_text)
+    : ScopedTimedTaskLogger(
+          base::BindRepeating(&LogIfExceedThreshold,
+                              logging_text,
+                              base::TimeDelta::FromSeconds(1))) {}
+
+ScopedTimedTaskLogger::~ScopedTimedTaskLogger() {
+  std::move(timer_callback_).Run(base::Time::NowFromSystemTime() - start_time_);
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/scoped_timed_task_logger.h b/chrome/chrome_cleaner/logging/scoped_timed_task_logger.h
new file mode 100644
index 0000000..6f4755eb
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/scoped_timed_task_logger.h
@@ -0,0 +1,45 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_SCOPED_TIMED_TASK_LOGGER_H_
+#define CHROME_CHROME_CLEANER_LOGGING_SCOPED_TIMED_TASK_LOGGER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+
+namespace chrome_cleaner {
+
+// A class to compute the time it takes to execute a task within its lifespan
+// and then call the given callback passing it the measured time.
+class ScopedTimedTaskLogger {
+ public:
+  typedef base::OnceCallback<void(const base::TimeDelta&)> TimerCallback;
+
+  // If |elapsed_time| exceeds |threshold|, then log
+  // "|logging_text| took '|elapsed_time|' seconds."
+  static void LogIfExceedThreshold(const char* logging_text,
+                                   const base::TimeDelta& threshold,
+                                   const base::TimeDelta& elapsed_time);
+
+  // Start tracking the elapsed time between construction and destruction of
+  // this object. |timer_callback| is called in the destructor with the measured
+  // time.
+  explicit ScopedTimedTaskLogger(TimerCallback timer_callback);
+  // Convenience constructor that uses |LogIfExceedThreshold| with
+  // |logging_text| and a threshold of one second as the callback. The ownership
+  // of |logging_text| is retained by the caller, who must ensure that
+  // |logging_text| outlives the |ScopedTimedTaskLogger| object.
+  explicit ScopedTimedTaskLogger(const char* logging_text);
+  ~ScopedTimedTaskLogger();
+
+ private:
+  base::Time start_time_;
+  TimerCallback timer_callback_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_SCOPED_TIMED_TASK_LOGGER_H_
diff --git a/chrome/chrome_cleaner/logging/scoped_timed_task_logger_unittest.cc b/chrome/chrome_cleaner/logging/scoped_timed_task_logger_unittest.cc
new file mode 100644
index 0000000..4f31b5f
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/scoped_timed_task_logger_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2018 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/chrome_cleaner/logging/scoped_timed_task_logger.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "chrome/chrome_cleaner/test/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+namespace {
+
+class ScopedTimedTaskLoggerTest : public testing::Test {
+ public:
+  // Intercepts all log messages.
+  static bool LogMessageHandler(int severity,
+                                const char* file,
+                                int line,
+                                size_t message_start,
+                                const std::string& str) {
+    DCHECK(active_logging_messages_);
+    active_logging_messages_->push_back(str);
+    return false;
+  }
+
+  bool LoggingMessagesContain(const std::string& sub_string) {
+    std::vector<std::string>::const_iterator line = logging_messages_.begin();
+    for (; line != logging_messages_.end(); ++line) {
+      if (StringContainsCaseInsensitive(*line, sub_string))
+        return true;
+    }
+    return false;
+  }
+
+  void FlushMessages() { logging_messages_.clear(); }
+
+  void SetUp() override {
+    DCHECK(active_logging_messages_ == nullptr);
+    active_logging_messages_ = &logging_messages_;
+    logging::SetLogMessageHandler(&LogMessageHandler);
+  }
+
+  void TearDown() override {
+    logging::SetLogMessageHandler(nullptr);
+    logging_messages_.clear();
+    DCHECK(active_logging_messages_ == &logging_messages_);
+    active_logging_messages_ = nullptr;
+  }
+
+  void timer_callback(const base::TimeDelta&) { callback_called_ = true; }
+
+  std::vector<std::string> logging_messages_;
+  static std::vector<std::string>* active_logging_messages_;
+  bool callback_called_ = false;
+};
+
+std::vector<std::string>* ScopedTimedTaskLoggerTest::active_logging_messages_ =
+    nullptr;
+
+}  // namespace
+
+TEST_F(ScopedTimedTaskLoggerTest, CallbackCalled) {
+  {
+    ScopedTimedTaskLogger timer(base::BindRepeating(
+        &ScopedTimedTaskLoggerTest::timer_callback, base::Unretained(this)));
+  }
+  EXPECT_TRUE(callback_called_);
+}
+
+TEST_F(ScopedTimedTaskLoggerTest, NoLog) {
+  static const char kNoShow[] = "Should not show up";
+  {
+    ScopedTimedTaskLogger no_logs(
+        base::BindRepeating(ScopedTimedTaskLogger::LogIfExceedThreshold,
+                            kNoShow, base::TimeDelta::FromDays(1)));
+  }
+  EXPECT_FALSE(LoggingMessagesContain(kNoShow));
+}
+
+TEST_F(ScopedTimedTaskLoggerTest, Log) {
+  static const char kShow[] = "Should show up";
+  {
+    ScopedTimedTaskLogger logs(
+        base::BindRepeating(ScopedTimedTaskLogger::LogIfExceedThreshold, kShow,
+                            base::TimeDelta::FromMilliseconds(0)));
+    ::Sleep(2);
+  }
+  EXPECT_TRUE(LoggingMessagesContain(kShow));
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/test_utils.cc b/chrome/chrome_cleaner/logging/test_utils.cc
new file mode 100644
index 0000000..248312f
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/test_utils.cc
@@ -0,0 +1,37 @@
+// Copyright 2018 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/chrome_cleaner/logging/test_utils.h"
+
+namespace chrome_cleaner {
+
+MockNetworkChecker::MockNetworkChecker() = default;
+
+MockNetworkChecker::~MockNetworkChecker() = default;
+
+bool MockNetworkChecker::IsSafeBrowsingReachable(const GURL& upload_url) const {
+  return is_safe_browsing_reachable_;
+}
+
+bool MockNetworkChecker::WaitForSafeBrowsing(const GURL& upload_url,
+                                             const base::TimeDelta&) {
+  return wait_for_safe_browsing_;
+}
+
+void MockNetworkChecker::CancelWaitForShutdown() {}
+
+bool IoCountersEqual(
+    const base::IoCounters& io_stats,
+    const ProcessInformation::SystemResourceUsage& resource_usage) {
+  return resource_usage.read_operation_count() == io_stats.ReadOperationCount &&
+         resource_usage.write_operation_count() ==
+             io_stats.WriteOperationCount &&
+         resource_usage.other_operation_count() ==
+             io_stats.OtherOperationCount &&
+         resource_usage.read_transfer_count() == io_stats.ReadTransferCount &&
+         resource_usage.write_transfer_count() == io_stats.WriteTransferCount &&
+         resource_usage.other_transfer_count() == io_stats.OtherTransferCount;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/test_utils.h b/chrome/chrome_cleaner/logging/test_utils.h
new file mode 100644
index 0000000..f34fc01
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/test_utils.h
@@ -0,0 +1,51 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_TEST_UTILS_H_
+#define CHROME_CHROME_CLEANER_LOGGING_TEST_UTILS_H_
+
+// This must be added before process_metrics.h for base::IoCounters to be
+// available.
+#include <windows.h>
+
+#include "base/process/process_metrics_iocounters.h"
+#include "base/time/time.h"
+#include "chrome/chrome_cleaner/logging/network_checker.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "url/gurl.h"
+
+namespace chrome_cleaner {
+
+// Provides a mock NetworkChecker.
+class MockNetworkChecker : public NetworkChecker {
+ public:
+  MockNetworkChecker();
+  ~MockNetworkChecker() override;
+
+  void SetIsSafeBrowsingReachableResult(bool result) {
+    is_safe_browsing_reachable_ = result;
+  }
+  void SetWaitForSafeBrowsingResult(bool result) {
+    wait_for_safe_browsing_ = result;
+  }
+
+  // NetworkChecker:
+  bool IsSafeBrowsingReachable(const GURL& upload_url) const override;
+  bool WaitForSafeBrowsing(const GURL& upload_url,
+                           const base::TimeDelta&) override;
+  void CancelWaitForShutdown() override;
+
+ private:
+  bool is_safe_browsing_reachable_{true};
+  bool wait_for_safe_browsing_{true};
+};
+
+// Returns true if IO counters in |resource_usage| are equal to |io_stats|.
+bool IoCountersEqual(
+    const base::IoCounters& io_counters,
+    const ProcessInformation::SystemResourceUsage& resource_usage);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_TEST_UTILS_H_
diff --git a/chrome/chrome_cleaner/logging/utils.cc b/chrome/chrome_cleaner/logging/utils.cc
new file mode 100644
index 0000000..a2811cbd
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/utils.cc
@@ -0,0 +1,272 @@
+// Copyright 2018 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/chrome_cleaner/logging/utils.h"
+
+#include <algorithm>
+#include <random>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+
+namespace chrome_cleaner {
+
+bool GetChromeChannelFromCommandLine(int* channel) {
+  const base::CommandLine* const command_line =
+      base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(kChromeChannelSwitch))
+    return false;
+  std::string channel_string =
+      command_line->GetSwitchValueASCII(kChromeChannelSwitch);
+  return base::StringToInt(channel_string, channel) && *channel >= 0;
+}
+
+bool RetrieveFolderInformation(const base::FilePath& folder_path,
+                               FolderInformation* folder_information) {
+  base::FilePath expanded_path;
+  if (!TryToExpandPath(folder_path, &expanded_path) || expanded_path.empty() ||
+      !base::DirectoryExists(expanded_path)) {
+    return false;
+  }
+
+  internal::FileInformation file_information;
+  RetrievePathInformation(expanded_path, &file_information);
+
+  folder_information->set_path(base::UTF16ToUTF8(file_information.path));
+  folder_information->set_creation_date(file_information.creation_date);
+  folder_information->set_last_modified_date(
+      file_information.last_modified_date);
+  return true;
+}
+
+void LogFileInformation(const base::FilePath& file_path,
+                        bool log_detailed_info,
+                        bool is_active,
+                        UwS* detected_uws) {
+  internal::FileInformation file_information;
+  if (!RetrieveFileInformation(file_path, log_detailed_info,
+                               &file_information)) {
+    return;
+  }
+
+  file_information.active_file = is_active;
+  MatchedFile* matched_file = detected_uws->add_files();
+  FileInformationToProtoObject(file_information,
+                               matched_file->mutable_file_information());
+  matched_file->set_removal_status(REMOVAL_STATUS_MATCHED_ONLY);
+}
+
+UwS PUPToUwS(const PUPData::PUP* found_uws,
+             UwSDetectedFlags flags,
+             bool cleaning_files,
+             InfoSampler* sampler) {
+  DCHECK(found_uws);
+  const PUPData::UwSSignature& found_signature = found_uws->signature();
+
+  UwS detected_uws;
+  detected_uws.set_id(found_signature.id);
+  if (found_signature.name != nullptr)
+    detected_uws.set_name(found_signature.name);
+  UwS::DetailLevel* detail_level = detected_uws.mutable_detail_level();
+  bool only_one_footprint = (flags & kUwSDetectedFlagsOnlyOneFootprint) != 0;
+  detail_level->set_only_one_footprint(only_one_footprint);
+
+  if (PUPData::HasRemovalFlag(found_signature.flags)) {
+    detected_uws.set_state(UwS::REMOVABLE);
+  } else {
+    detected_uws.set_state(UwS::REPORT_ONLY);
+  }
+  detected_uws.set_detected_by(PUPData::GetEngine(found_signature.id));
+
+  // Get information on each folder, and split the files into active and
+  // inactive lists.
+  FilePathSet active_files;
+  FilePathSet inactive_files;
+  for (const auto& disk_footprint :
+       found_uws->expanded_disk_footprints.file_paths()) {
+    if (base::DirectoryExists(disk_footprint)) {
+      FolderInformation folder_information;
+      if (RetrieveFolderInformation(disk_footprint, &folder_information)) {
+        MatchedFolder* matched_folder = detected_uws.add_folders();
+        *matched_folder->mutable_folder_information() = folder_information;
+        matched_folder->set_removal_status(REMOVAL_STATUS_MATCHED_ONLY);
+      }
+    } else if (PathHasActiveExtension(disk_footprint)) {
+      active_files.Insert(disk_footprint);
+    } else {
+      inactive_files.Insert(disk_footprint);
+    }
+  }
+
+  // Both cleaning and reporting mode will log details of a sample of
+  // inactive files.
+  FilePathSet collected_files = inactive_files;
+
+  if (cleaning_files) {
+    // Log details for all active files.
+    for (const auto& disk_footprint : active_files.file_paths()) {
+      LogFileInformation(disk_footprint,
+                         /*log_detailed_info=*/true, /*is_active=*/true,
+                         &detected_uws);
+    }
+  } else {
+    // Add the active files to the set of files to sample.
+    collected_files.CopyFrom(active_files);
+  }
+
+  // Select a subset of files that haven't been logged yet for detailed
+  // information sampling. Only log basic information for the rest.
+  FilePathSet files_to_sample;
+  DCHECK(sampler);
+  sampler->SelectPathSetToSample(collected_files, &files_to_sample);
+  for (const auto& file_path : collected_files.ToVector()) {
+    LogFileInformation(file_path, files_to_sample.Contains(file_path),
+                       active_files.Contains(file_path), &detected_uws);
+  }
+
+  // Collect information on registry footprints.
+  for (const auto& registry_footprint :
+       found_uws->expanded_registry_footprints) {
+    MatchedRegistryEntry* entry = detected_uws.add_registry_entries();
+    MatchedRegistryEntryToProtoObject(registry_footprint, entry);
+  }
+
+  // Collect information on scheduled tasks.
+  for (const auto& expanded_scheduled_task :
+       found_uws->expanded_scheduled_tasks) {
+    ScheduledTask* reported_task =
+        detected_uws.add_scheduled_tasks()->mutable_scheduled_task();
+    reported_task->set_name(base::UTF16ToUTF8(expanded_scheduled_task));
+  }
+
+  // Collect trace locations.
+  std::set<UwS::TraceLocation> trace_locations;
+  for (const auto& path_info_it : found_uws->disk_footprints_info.map()) {
+    trace_locations.insert(path_info_it.second.found_in.begin(),
+                           path_info_it.second.found_in.end());
+  }
+  for (UwS::TraceLocation location : trace_locations)
+    detected_uws.add_trace_locations(location);
+
+  return detected_uws;
+}
+
+void FileInformationToProtoObject(
+    const internal::FileInformation& file_information,
+    FileInformation* proto_file_information) {
+  DCHECK(proto_file_information);
+  if (file_information.path.empty())
+    return;
+  proto_file_information->set_path(base::UTF16ToUTF8(file_information.path));
+  if (!file_information.creation_date.empty())
+    proto_file_information->set_creation_date(file_information.creation_date);
+  if (!file_information.last_modified_date.empty()) {
+    proto_file_information->set_last_modified_date(
+        file_information.last_modified_date);
+  }
+  if (!file_information.sha256.empty())
+    proto_file_information->set_sha256(file_information.sha256);
+  proto_file_information->set_size(file_information.size);
+  if (!file_information.company_name.empty()) {
+    proto_file_information->set_company_name(
+        base::UTF16ToUTF8(file_information.company_name));
+  }
+  if (!file_information.company_short_name.empty()) {
+    proto_file_information->set_company_short_name(
+        base::UTF16ToUTF8(file_information.company_short_name));
+  }
+  if (!file_information.product_name.empty()) {
+    proto_file_information->set_product_name(
+        base::UTF16ToUTF8(file_information.product_name));
+  }
+  if (!file_information.product_short_name.empty()) {
+    proto_file_information->set_product_short_name(
+        base::UTF16ToUTF8(file_information.product_short_name));
+  }
+  if (!file_information.internal_name.empty()) {
+    proto_file_information->set_internal_name(
+        base::UTF16ToUTF8(file_information.internal_name));
+  }
+  if (!file_information.original_filename.empty()) {
+    proto_file_information->set_original_filename(
+        base::UTF16ToUTF8(file_information.original_filename));
+  }
+  if (!file_information.file_description.empty()) {
+    proto_file_information->set_file_description(
+        base::UTF16ToUTF8(file_information.file_description));
+  }
+  if (!file_information.file_version.empty()) {
+    proto_file_information->set_file_version(
+        base::UTF16ToUTF8(file_information.file_version));
+  }
+  proto_file_information->set_active_file(file_information.active_file);
+}
+
+void MatchedRegistryEntryToProtoObject(
+    const PUPData::RegistryFootprint& registry_footprint,
+    MatchedRegistryEntry* entry) {
+  DCHECK(entry);
+  auto full_key_path = registry_footprint.key_path.FullPath();
+  if (full_key_path.empty())
+    return;
+  entry->set_key_path(base::WideToUTF8(full_key_path));
+  if (!registry_footprint.value_name.empty())
+    entry->set_value_name(base::WideToUTF8(registry_footprint.value_name));
+  if (!registry_footprint.value_substring.empty()) {
+    entry->set_value_substring(
+        base::WideToUTF8(registry_footprint.value_substring));
+  }
+}
+
+bool GetFileInformationProtoObject(const base::FilePath& file_path,
+                                   bool include_details,
+                                   FileInformation* file_information) {
+  internal::FileInformation internal_file_information;
+  if (!RetrieveFileInformation(file_path, include_details,
+                               &internal_file_information))
+    return false;
+
+  FileInformationToProtoObject(internal_file_information, file_information);
+  return true;
+}
+
+ProcessInformation GetProcessInformationProtoObject(
+    SandboxType process_type,
+    const SystemResourceUsage& usage) {
+  ProcessInformation process_info;
+  switch (process_type) {
+    case SandboxType::kNonSandboxed:
+      process_info.set_process(ProcessInformation::MAIN);
+      break;
+    case SandboxType::kEset:
+      process_info.set_process(ProcessInformation::ESET_SANDBOX);
+      break;
+    default:
+      NOTREACHED() << "Unknown sandbox type " << static_cast<int>(process_type);
+  }
+
+  ProcessInformation::SystemResourceUsage* usage_msg =
+      process_info.mutable_resource_usage();
+  usage_msg->set_user_time(usage.user_time.InSeconds());
+  usage_msg->set_kernel_time(usage.kernel_time.InSeconds());
+  usage_msg->set_peak_working_set_size(usage.peak_working_set_size);
+  usage_msg->set_read_operation_count(usage.io_counters.ReadOperationCount);
+  usage_msg->set_write_operation_count(usage.io_counters.WriteOperationCount);
+  usage_msg->set_other_operation_count(usage.io_counters.OtherOperationCount);
+  usage_msg->set_read_transfer_count(usage.io_counters.ReadTransferCount);
+  usage_msg->set_write_transfer_count(usage.io_counters.WriteTransferCount);
+  usage_msg->set_other_transfer_count(usage.io_counters.OtherTransferCount);
+
+  return process_info;
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/logging/utils.h b/chrome/chrome_cleaner/logging/utils.h
new file mode 100644
index 0000000..ad8bfcdc
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/utils.h
@@ -0,0 +1,80 @@
+// Copyright 2018 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_CHROME_CLEANER_LOGGING_UTILS_H_
+#define CHROME_CHROME_CLEANER_LOGGING_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/time/time.h"
+#include "chrome/chrome_cleaner/logging/info_sampler.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/process.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "chrome/chrome_cleaner/settings/settings_types.h"
+
+namespace chrome_cleaner {
+#define LOG_PUP(severity, pup) \
+  LOG(severity) << "UwS " << PUPData::GetPUPName(pup) << "(" << pup->id << ")"
+// Sets |*channel| as the user's Chrome channel if defined by the
+// --chrome-channel switch. Returns true if the channel can be determined.
+bool GetChromeChannelFromCommandLine(int* channel);
+
+// Retrieve the folder information path and dates into |folder_information|.
+// Returns true if |folder_path| could be expanded.
+bool RetrieveFolderInformation(const base::FilePath& folder_path,
+                               FolderInformation* folder_information);
+
+// Enum type for the flags used to log the detected UwS.
+// |only_one_footprint| should be set to true if the scanner stopped at the
+// first found active footprint
+using UwSDetectedFlags = uint32_t;
+enum : UwSDetectedFlags {
+  kUwSDetectedFlagsNone = 0,
+  kUwSDetectedFlagsOnlyOneFootprint = 1 << 0,
+};
+
+// Returns a new UwS proto with matching information in |found_uws|. For each
+// |file_path|, if |cleaning_files| is set to true, detailed information is
+// collected from any active and forced active files and a random sample of
+// information is returned for the rest of files. If |cleaning_files| is set to
+// false, gather a random sample of detailed information. |sampler| is used to
+// choose the sample.
+UwS PUPToUwS(const PUPData::PUP* found_uws,
+             UwSDetectedFlags flags,
+             bool cleaning_files,
+             InfoSampler* sampler);
+
+// Convert an internal::FileInformation struct to its corresponding
+// FileInformation proto object.
+void FileInformationToProtoObject(
+    const internal::FileInformation& file_information,
+    FileInformation* proto_file_information);
+
+// Convert an internal::RegistryFootprint struct to its corresponding
+// MatchedRegistryEntry proto object.
+void MatchedRegistryEntryToProtoObject(
+    const PUPData::RegistryFootprint& registry_footprint,
+    MatchedRegistryEntry* entry);
+
+// Returns a FileInformation proto object with basic information for the file
+// with path |file_path|. If |include_details|, detailed information (including
+// the digest) will be included. Otherwise, only basic information will be
+// retrieved.
+bool GetFileInformationProtoObject(const base::FilePath& file_path,
+                                   bool include_details,
+                                   FileInformation* file_information);
+
+// Returns ProcessInformation message with process resource usage stats.
+ProcessInformation GetProcessInformationProtoObject(
+    SandboxType process_type,
+    const SystemResourceUsage& usage);
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_LOGGING_UTILS_H_
diff --git a/chrome/chrome_cleaner/logging/utils_unittest.cc b/chrome/chrome_cleaner/logging/utils_unittest.cc
new file mode 100644
index 0000000..ea63e160
--- /dev/null
+++ b/chrome/chrome_cleaner/logging/utils_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2018 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/chrome_cleaner/logging/utils.h"
+
+#include <shlobj.h>
+#include <map>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_path_override.h"
+#include "chrome/chrome_cleaner/logging/info_sampler.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/file_path_set.h"
+#include "chrome/chrome_cleaner/test/test_file_util.h"
+#include "chrome/chrome_cleaner/test/test_pup_data.h"
+#include "chrome/chrome_cleaner/test/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+TEST(DiskUtilTests, RetrieveFolderInformation) {
+  base::ScopedPathOverride appdata_override(
+      CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA));
+  base::FilePath appdata_folder;
+
+  ASSERT_TRUE(base::PathService::Get(CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA),
+                                     &appdata_folder));
+  base::FilePath temp_folder;
+  base::CreateTemporaryDirInDir(appdata_folder, L"temp_folder123",
+                                &temp_folder);
+
+  FolderInformation folder_information;
+  EXPECT_TRUE(RetrieveFolderInformation(temp_folder, &folder_information));
+
+  // The expected file path value should be sanitized.
+  EXPECT_EQ(base::UTF16ToUTF8(SanitizePath(temp_folder)),
+            folder_information.path());
+  EXPECT_FALSE(folder_information.creation_date().empty());
+  EXPECT_FALSE(folder_information.last_modified_date().empty());
+}
+
+TEST(DiskUtilTests, RetrieveFolderInformationNoFile) {
+  base::ScopedPathOverride appdata_override(
+      CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA));
+  base::FilePath appdata_folder;
+  ASSERT_TRUE(base::PathService::Get(CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA),
+                                     &appdata_folder));
+
+  base::FilePath non_existent_folder(
+      appdata_folder.DirName().Append(L"non-existent-folder"));
+
+  FolderInformation folder_information;
+  EXPECT_FALSE(
+      RetrieveFolderInformation(non_existent_folder, &folder_information));
+
+  EXPECT_TRUE(folder_information.path().empty());
+  EXPECT_TRUE(folder_information.creation_date().empty());
+  EXPECT_TRUE(folder_information.last_modified_date().empty());
+}
+
+// Returns all files in |files| that start with a fixed pattern.
+class PrefixInfoSampler : public InfoSampler {
+ public:
+  explicit PrefixInfoSampler(const base::string16& prefix) : prefix_(prefix) {}
+
+  void SelectPathSetToSample(const FilePathSet& files,
+                             FilePathSet* sampled_file_paths) override {
+    for (const base::FilePath& file : files.file_paths()) {
+      if (base::StartsWith(file.BaseName().value(), prefix_,
+                           base::CompareCase::INSENSITIVE_ASCII)) {
+        sampled_file_paths->Insert(file);
+      }
+    }
+  }
+
+ private:
+  const base::string16 prefix_;
+};
+
+class PUPToUwSTest : public ::testing::TestWithParam<bool> {
+ public:
+  static constexpr UwSId kTestPUPId = 12321;
+
+  enum class FileCategory {
+    kInactive,  // File should be inactive based on the file name.
+    kActive,    // File should be active based on the file name.
+  };
+
+  struct FileFlags {
+    FileCategory category;
+    bool has_details;
+  };
+
+  PUPToUwSTest() : is_cleaning_(GetParam()), sampler_(L"sampled_") {
+    expectations_ = {
+        // If cleaning, active files and sampled files should have details.
+        // Otherwise only sampled files should have details.
+        {L"active_file.exe", {FileCategory::kActive, is_cleaning_}},
+        {L"inactive_file.txt", {FileCategory::kInactive, false}},
+        // Only files beginning with "sampled_" will be chosen by the
+        // sampler.
+        {L"sampled_active_file.exe", {FileCategory::kActive, true}},
+        {L"sampled_inactive_file.txt", {FileCategory::kInactive, true}},
+    };
+  }
+
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    // Create a PUPData entry that would match the expected files.
+    test_pup_data_.AddPUP(kTestPUPId, PUPData::FLAGS_NONE, nullptr,
+                          PUPData::kMaxFilesToRemoveSmallUwS);
+    for (const auto& expectation : expectations_) {
+      const base::string16 filename = expectation.first;
+      test_pup_data_.AddDiskFootprint(kTestPUPId, CSIDL_STARTUP,
+                                      filename.c_str(),
+                                      PUPData::DISK_MATCH_ANY_FILE);
+
+      // Files can't be matched unless they exist on disk.
+      ASSERT_TRUE(CreateFileInFolder(temp_dir_.GetPath(), filename.c_str()));
+    }
+  }
+
+  const PUPData::PUP* GetPUPWithExpectedFiles() const {
+    // Create a found PUP that matched the files.
+    PUPData::PUP* pup = PUPData::GetPUP(kTestPUPId);
+    DCHECK(pup);
+    for (const auto& expectation : expectations_) {
+      const base::string16 filename = expectation.first;
+      pup->AddDiskFootprint(temp_dir_.GetPath().Append(filename));
+    }
+    return pup;
+  }
+
+ protected:
+  bool is_cleaning_;
+
+  PrefixInfoSampler sampler_;
+  std::map<base::string16, FileFlags> expectations_;
+  base::ScopedTempDir temp_dir_;
+  TestPUPData test_pup_data_;
+};
+
+INSTANTIATE_TEST_CASE_P(PUPToUwS, PUPToUwSTest, ::testing::Values(false, true));
+
+TEST_P(PUPToUwSTest, PUPToUwS) {
+  UwS uws = PUPToUwS(GetPUPWithExpectedFiles(), kUwSDetectedFlagsNone,
+                     is_cleaning_, &sampler_);
+
+  // Loop through all converted files and make sure each was converted
+  // correctly.
+  std::vector<base::string16> converted_files;
+  for (int i = 0; i < uws.files_size(); ++i) {
+    ASSERT_TRUE(uws.files(i).has_file_information());
+    const FileInformation& file_info = uws.files(i).file_information();
+    ASSERT_TRUE(file_info.has_path());
+    base::FilePath file_path(base::UTF8ToUTF16(file_info.path()));
+    base::string16 uws_filename =
+        base::ToLowerASCII(file_path.BaseName().value());
+    converted_files.push_back(uws_filename);
+
+    SCOPED_TRACE(uws_filename);
+
+    auto expectation = expectations_.find(uws_filename);
+    ASSERT_FALSE(expectation == expectations_.end());
+    EXPECT_EQ(expectation->second.category != FileCategory::kInactive,
+              file_info.active_file());
+    EXPECT_EQ(expectation->second.has_details, file_info.has_sha256());
+  }
+
+  std::vector<base::string16> expected_files;
+  for (const auto& expectation : expectations_)
+    expected_files.push_back(expectation.first);
+  EXPECT_THAT(converted_files,
+              ::testing::UnorderedElementsAreArray(expected_files));
+
+  EXPECT_NE(Engine::UNKNOWN, uws.detected_by());
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/settings/BUILD.gn b/chrome/chrome_cleaner/settings/BUILD.gn
index 6dd920a..28a1c541 100644
--- a/chrome/chrome_cleaner/settings/BUILD.gn
+++ b/chrome/chrome_cleaner/settings/BUILD.gn
@@ -15,6 +15,7 @@
   ]
 
   deps = [
+    ":settings_definitions",
     ":settings_types",
     "//base:base",
     "//chrome/chrome_cleaner/constants:common_strings",
@@ -28,6 +29,12 @@
   ]
 }
 
+source_set("settings_definitions") {
+  sources = [
+    "settings_definitions.h",
+  ]
+}
+
 source_set("matching_options") {
   sources = [
     "matching_options.cc",
@@ -62,6 +69,7 @@
 
   deps = [
     ":settings",
+    ":settings_definitions",
     "//base:base",
     "//base/test:test_support",
     "//chrome/chrome_cleaner/constants:common_strings",
diff --git a/chrome/chrome_cleaner/test/BUILD.gn b/chrome/chrome_cleaner/test/BUILD.gn
index fb34d5f..9769775 100644
--- a/chrome/chrome_cleaner/test/BUILD.gn
+++ b/chrome/chrome_cleaner/test/BUILD.gn
@@ -65,6 +65,10 @@
     "test_name_helper.h",
     "test_registry_util.cc",
     "test_registry_util.h",
+    "test_settings_util.cc",
+    "test_settings_util.h",
+    "test_task_scheduler.cc",
+    "test_task_scheduler.h",
     "test_util.cc",
     "test_util.h",
   ]
@@ -78,6 +82,7 @@
     "//chrome/chrome_cleaner/os:cleaner_os",
     "//chrome/chrome_cleaner/os:common_os",
     "//chrome/chrome_cleaner/pup_data:pup_data_base",
+    "//chrome/chrome_cleaner/settings:settings",
     "//chrome/chrome_cleaner/strings",
     "//components/chrome_cleaner/public/constants:constants",
     "//sandbox/win:sandbox",
@@ -165,6 +170,7 @@
   testonly = true
 
   sources = [
+    "//chrome/chrome_cleaner/engines/dummy_engine_resources.cc",
     "test_process_main.cc",
   ]
 
@@ -174,6 +180,8 @@
     "//base:base",
     "//base/test:test_support",
     "//build/win:default_exe_manifest",
+    "//chrome/chrome_cleaner:other_executable_definitions",
+    "//chrome/chrome_cleaner/engines:resources",
     "//chrome/chrome_cleaner/os:common_os",
   ]
 }
diff --git a/chrome/chrome_cleaner/test/test_settings_util.cc b/chrome/chrome_cleaner/test/test_settings_util.cc
new file mode 100644
index 0000000..beb3f197
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_settings_util.cc
@@ -0,0 +1,24 @@
+// Copyright 2018 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/chrome_cleaner/test/test_settings_util.h"
+
+namespace chrome_cleaner {
+
+SettingsWithExecutionModeOverride::SettingsWithExecutionModeOverride(
+    ExecutionMode execution_mode)
+    : execution_mode_(execution_mode) {}
+
+SettingsWithExecutionModeOverride::~SettingsWithExecutionModeOverride() =
+    default;
+
+ExecutionMode SettingsWithExecutionModeOverride::execution_mode() const {
+  return execution_mode_;
+}
+
+MockSettings::MockSettings() = default;
+
+MockSettings::~MockSettings() = default;
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/test/test_settings_util.h b/chrome/chrome_cleaner/test/test_settings_util.h
new file mode 100644
index 0000000..0e67846
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_settings_util.h
@@ -0,0 +1,57 @@
+// Copyright 2018 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_CHROME_CLEANER_TEST_TEST_SETTINGS_UTIL_H_
+#define CHROME_CHROME_CLEANER_TEST_TEST_SETTINGS_UTIL_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/settings/settings.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chrome_cleaner {
+
+// Dummy Settings instance where the value to be returned by execution_mode()
+// is defined during object construction. For use with
+// |Settings::SetInstanceForTesting|.
+class SettingsWithExecutionModeOverride : public Settings {
+ public:
+  explicit SettingsWithExecutionModeOverride(ExecutionMode execution_mode);
+  ~SettingsWithExecutionModeOverride() override;
+
+  ExecutionMode execution_mode() const override;
+
+ private:
+  ExecutionMode execution_mode_;
+};
+
+class MockSettings : public Settings {
+ public:
+  MockSettings();
+  ~MockSettings() override;
+
+  MOCK_CONST_METHOD0(allow_crash_report_upload, bool());
+  MOCK_CONST_METHOD0(session_id, base::string16());
+  MOCK_CONST_METHOD0(cleanup_id, std::string());
+  MOCK_CONST_METHOD0(engine, Engine::Name());
+  MOCK_CONST_METHOD0(is_stub_engine, bool());
+  MOCK_CONST_METHOD0(logs_upload_allowed, bool());
+  MOCK_CONST_METHOD0(logs_collection_enabled, bool());
+  MOCK_CONST_METHOD0(logs_allowed_in_cleanup_mode, bool());
+  MOCK_METHOD1(set_logs_allowed_in_cleanup_mode, void(bool));
+  MOCK_CONST_METHOD0(metrics_enabled, bool());
+  MOCK_CONST_METHOD0(sber_enabled, bool());
+  MOCK_CONST_METHOD0(chrome_mojo_pipe_token, const std::string&());
+  MOCK_CONST_METHOD0(has_parent_pipe_handle, bool());
+  MOCK_CONST_METHOD0(execution_mode, ExecutionMode());
+  MOCK_CONST_METHOD0(locations_to_scan,
+                     const std::vector<UwS::TraceLocation>&());
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_TEST_TEST_SETTINGS_UTIL_H_
diff --git a/chrome/chrome_cleaner/test/test_task_scheduler.cc b/chrome/chrome_cleaner/test/test_task_scheduler.cc
new file mode 100644
index 0000000..b973ce6
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_task_scheduler.cc
@@ -0,0 +1,142 @@
+// Copyright 2018 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/chrome_cleaner/test/test_task_scheduler.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chrome_cleaner {
+
+TestTaskScheduler::TestTaskScheduler() {
+  TaskScheduler::SetMockDelegateForTesting(this);
+}
+
+TestTaskScheduler::~TestTaskScheduler() {
+  TaskScheduler::SetMockDelegateForTesting(nullptr);
+}
+
+bool TestTaskScheduler::IsTaskRegistered(const wchar_t* task_name) {
+  ADD_FAILURE() << "TestTaskScheduler::IsTaskRegistered is not implemented.";
+  return false;
+}
+
+bool TestTaskScheduler::SetTaskEnabled(const wchar_t* task_name, bool enabled) {
+  ADD_FAILURE() << "TestTaskScheduler::SetTaskEnabled is not implemented.";
+  return false;
+}
+
+bool TestTaskScheduler::IsTaskEnabled(const wchar_t* task_name) {
+  DCHECK(task_name);
+
+  // We currently assume that all tasks are enabled. This function needs to be
+  // adapted when implementing |SetTaskEnabled| in this mock class.
+  return tasks_.find(task_name) != tasks_.end();
+}
+
+bool TestTaskScheduler::DeleteTask(const wchar_t* task_name) {
+  DCHECK(task_name);
+
+  tasks_.erase(task_name);
+  delete_task_called_ = true;
+  return true;
+}
+
+bool TestTaskScheduler::GetNextTaskRunTime(const wchar_t* task_name,
+                                           base::Time* next_run_time) {
+  ADD_FAILURE() << "TestTaskScheduler::GetNextTaskRunTime is not implemented.";
+  return false;
+}
+
+bool TestTaskScheduler::GetTaskNameList(
+    std::vector<base::string16>* task_names) {
+  DCHECK(task_names);
+
+  for (const auto& task : tasks_)
+    task_names->push_back(task.first);
+  return true;
+}
+
+bool TestTaskScheduler::GetTaskInfo(const wchar_t* task_name,
+                                    TaskScheduler::TaskInfo* info) {
+  DCHECK(task_name);
+  DCHECK(info);
+
+  const auto& task_iterator = tasks_.find(task_name);
+  if (task_iterator != tasks_.end()) {
+    *info = task_iterator->second;
+    return true;
+  }
+  return false;
+}
+
+bool TestTaskScheduler::RegisterTask(const wchar_t* task_name,
+                                     const wchar_t* task_description,
+                                     const base::CommandLine& run_command,
+                                     TriggerType trigger_type,
+                                     bool hidden) {
+  DCHECK(task_name);
+  DCHECK(task_description);
+
+  if (!register_task_return_value_)
+    return false;
+
+  TaskExecAction task_action = {
+      /* .application_path = */ run_command.GetProgram(),
+      /* .working_dir = */ base::FilePath(),
+      /* .arguments = */ run_command.GetArgumentsString()};
+
+  std::vector<TaskExecAction> task_actions = {task_action};
+
+  TaskInfo task_info;
+  task_info.name = task_name;
+  task_info.description = task_description;
+  task_info.exec_actions = task_actions;
+
+  tasks_[task_name] = task_info;
+
+  register_task_called_ = true;
+  return true;
+}
+
+bool TestTaskScheduler::AddTaskAction(const wchar_t* task_name,
+                                      const base::CommandLine& run_command) {
+  DCHECK(task_name);
+
+  auto task_iterator = tasks_.find(task_name);
+  if (task_iterator != tasks_.end()) {
+    TaskExecAction task_action = {
+        /* .application_path = */ run_command.GetProgram(),
+        /* .working_dir = */ run_command.GetProgram().DirName(),
+        /* .arguments = */ run_command.GetArgumentsString()};
+    task_iterator->second.exec_actions.push_back(task_action);
+    return true;
+  }
+  return false;
+}
+
+void TestTaskScheduler::SetRegisterTaskReturnValue(bool value) {
+  register_task_return_value_ = value;
+}
+
+void TestTaskScheduler::ExpectRegisterTaskCalled(bool called) const {
+  if (register_task_called_ != called)
+    ADD_FAILURE() << "TestTaskScheduler::ExpectRegisterTaskCalled failed.";
+}
+
+void TestTaskScheduler::ExpectDeleteTaskCalled(bool called) const {
+  if (delete_task_called_ != called)
+    ADD_FAILURE() << "TestTaskScheduler::ExpectDeleteTaskCalled failed.";
+}
+
+void TestTaskScheduler::ExpectRegisteredTasksSize(size_t count) const {
+  if (tasks_.size() != count) {
+    ADD_FAILURE()
+        << "TestTaskScheduler::ExpectRegisteredTasksSize failed: expected "
+        << count << " task but got " << tasks_.size() << " tasks.";
+  }
+}
+
+}  // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/test/test_task_scheduler.h b/chrome/chrome_cleaner/test/test_task_scheduler.h
new file mode 100644
index 0000000..0baa6a4
--- /dev/null
+++ b/chrome/chrome_cleaner/test/test_task_scheduler.h
@@ -0,0 +1,53 @@
+// Copyright 2018 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_CHROME_CLEANER_TEST_TEST_TASK_SCHEDULER_H_
+#define CHROME_CHROME_CLEANER_TEST_TEST_TASK_SCHEDULER_H_
+
+#include <map>
+#include <vector>
+
+#include "chrome/chrome_cleaner/os/task_scheduler.h"
+
+namespace chrome_cleaner {
+
+class TestTaskScheduler : public TaskScheduler {
+ public:
+  TestTaskScheduler();
+  ~TestTaskScheduler() override;
+
+  // TaskScheduler:
+  bool IsTaskRegistered(const wchar_t* task_name) override;
+  bool SetTaskEnabled(const wchar_t* task_name, bool enabled) override;
+  bool IsTaskEnabled(const wchar_t* task_name) override;
+  bool DeleteTask(const wchar_t* task_name) override;
+  bool GetNextTaskRunTime(const wchar_t* task_name,
+                          base::Time* next_run_time) override;
+  bool GetTaskNameList(std::vector<base::string16>* task_names) override;
+  bool GetTaskInfo(const wchar_t* task_name,
+                   TaskScheduler::TaskInfo* info) override;
+  bool RegisterTask(const wchar_t* task_name,
+                    const wchar_t* task_description,
+                    const base::CommandLine& run_command,
+                    TriggerType trigger_type,
+                    bool hidden) override;
+
+  // Used by tests to add a new action to an existing task.
+  void SetRegisterTaskReturnValue(bool value);
+  bool AddTaskAction(const wchar_t* task_name,
+                     const base::CommandLine& run_command);
+  void ExpectRegisterTaskCalled(bool called) const;
+  void ExpectDeleteTaskCalled(bool called) const;
+  void ExpectRegisteredTasksSize(size_t count) const;
+
+ private:
+  bool delete_task_called_ = false;
+  bool register_task_called_ = false;
+  bool register_task_return_value_ = true;
+  std::map<base::string16, TaskInfo> tasks_;
+};
+
+}  // namespace chrome_cleaner
+
+#endif  // CHROME_CHROME_CLEANER_TEST_TEST_TASK_SCHEDULER_H_
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 6ece51fd..2e25c8f7 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -556,7 +556,7 @@
 // Enables or disabled committed interstitials for Supervised User
 // interstitials.
 const base::Feature kSupervisedUserCommittedInterstitials{
-    "SupervisedUserCommittedInterstitials", base::FEATURE_DISABLED_BY_DEFAULT};
+    "SupervisedUserCommittedInterstitials", base::FEATURE_ENABLED_BY_DEFAULT};
 
 #if defined(OS_CHROMEOS)
 // Enables or disables chrome://sys-internals.
@@ -655,6 +655,9 @@
 // Please note that a Chrome policy must also be set, for this to have effect.
 extern const base::Feature kWebRtcRemoteEventLog{
     "WebRtcRemoteEventLog", base::FEATURE_DISABLED_BY_DEFAULT};
+// Compress remote-bound WebRTC event logs (if used; see kWebRtcRemoteEventLog).
+extern const base::Feature kWebRtcRemoteEventLogGzipped{
+    "WebRtcRemoteEventLogGzipped", base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
 #if defined(OS_WIN)
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index c9847971..f22cae0 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -355,6 +355,7 @@
 
 #if !defined(OS_ANDROID)
 extern const base::Feature kWebRtcRemoteEventLog;
+extern const base::Feature kWebRtcRemoteEventLogGzipped;
 #endif
 
 #if defined(OS_WIN)
diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl
index 0d8b11d..b2051bc 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -175,6 +175,7 @@
     imageMap,
     inlineTextBox,
     inputTime,
+    keyboard,
     labelText,
     layoutTable,
     layoutTableCell,
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index b85dd6a4..7675377 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -875,6 +875,11 @@
 // Shares for Chrome OS feature.
 const char kNetworkFileSharesAllowed[] = "network_file_shares.allowed";
 
+// Boolean pref indicating whether the currently running public session runs in
+// the old standard "public session" mode (false), or in the new "managed
+// session" mode which has lifted restrictions (true).
+const char kManagedSessionEnabled[] = "managed_session.enabled";
+
 #endif  // defined(OS_CHROMEOS)
 
 // A boolean pref set to true if a Home button to open the Home pages should be
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 5a8c73f..7bef8d9 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -282,6 +282,7 @@
 extern const char kScreenTimeLastState[];
 extern const char kEnableSyncConsent[];
 extern const char kNetworkFileSharesAllowed[];
+extern const char kManagedSessionEnabled[];
 #endif  // defined(OS_CHROMEOS)
 extern const char kShowHomeButton[];
 extern const char kSpeechRecognitionFilterProfanities[];
diff --git a/chrome/renderer/benchmarking_extension.cc b/chrome/renderer/benchmarking_extension.cc
index 955cbd5..b0fe829 100644
--- a/chrome/renderer/benchmarking_extension.cc
+++ b/chrome/renderer/benchmarking_extension.cc
@@ -53,9 +53,11 @@
   v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
       v8::Isolate* isolate,
       v8::Local<v8::String> name) override {
-    if (name->Equals(v8::String::NewFromUtf8(isolate, "IsSingleProcess"))) {
+    if (name->StringEquals(
+            v8::String::NewFromUtf8(isolate, "IsSingleProcess"))) {
       return v8::FunctionTemplate::New(isolate, IsSingleProcess);
-    } else if (name->Equals(v8::String::NewFromUtf8(isolate, "HiResTime"))) {
+    } else if (name->StringEquals(
+                   v8::String::NewFromUtf8(isolate, "HiResTime"))) {
       return v8::FunctionTemplate::New(isolate, HiResTime);
     }
 
diff --git a/chrome/renderer/extensions/app_bindings.cc b/chrome/renderer/extensions/app_bindings.cc
index 8fb336ae..434c2ac 100644
--- a/chrome/renderer/extensions/app_bindings.cc
+++ b/chrome/renderer/extensions/app_bindings.cc
@@ -46,7 +46,7 @@
     const v8::FunctionCallbackInfo<v8::Value>& args) {
   CHECK_EQ(1, args.Length());
   CHECK(args[0]->IsInt32());
-  int callback_id = args[0]->Int32Value();
+  int callback_id = args[0].As<v8::Int32>()->Value();
 
   app_core_.GetInstallState(
       context(), base::BindOnce(&AppBindings::OnAppInstallStateResponse,
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index 1474f15..1bb7be4 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -158,7 +158,10 @@
     if (args.Length() != 1 || !args[0]->IsNumber())
       ThrowInvalidArgumentsException(automation_bindings_);
 
-    int tree_id = args[0]->Int32Value();
+    int tree_id =
+        args[0]
+            ->Int32Value(automation_bindings_->context()->v8_context())
+            .FromMaybe(0);
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
     if (!tree_wrapper)
@@ -203,8 +206,10 @@
     if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber())
       ThrowInvalidArgumentsException(automation_bindings_);
 
-    int tree_id = args[0]->Int32Value();
-    int node_id = args[1]->Int32Value();
+    v8::Local<v8::Context> context =
+        automation_bindings_->context()->v8_context();
+    int tree_id = args[0]->Int32Value(context).FromMaybe(0);
+    int node_id = args[1]->Int32Value(context).FromMaybe(0);
 
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
@@ -255,8 +260,10 @@
       ThrowInvalidArgumentsException(automation_bindings_);
     }
 
-    int tree_id = args[0]->Int32Value();
-    int node_id = args[1]->Int32Value();
+    v8::Local<v8::Context> context =
+        automation_bindings_->context()->v8_context();
+    int tree_id = args[0]->Int32Value(context).FromMaybe(0);
+    int node_id = args[1]->Int32Value(context).FromMaybe(0);
     std::string attribute = *v8::String::Utf8Value(isolate, args[2]);
 
     AutomationAXTreeWrapper* tree_wrapper =
@@ -309,10 +316,12 @@
       ThrowInvalidArgumentsException(automation_bindings_);
     }
 
-    int tree_id = args[0]->Int32Value();
-    int node_id = args[1]->Int32Value();
-    int start = args[2]->Int32Value();
-    int end = args[3]->Int32Value();
+    v8::Local<v8::Context> context =
+        automation_bindings_->context()->v8_context();
+    int tree_id = args[0]->Int32Value(context).FromMaybe(0);
+    int node_id = args[1]->Int32Value(context).FromMaybe(0);
+    int start = args[2]->Int32Value(context).FromMaybe(0);
+    int end = args[3]->Int32Value(context).FromMaybe(0);
 
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
@@ -358,10 +367,12 @@
       ThrowInvalidArgumentsException(automation_bindings_);
     }
 
-    int tree_id = args[0]->Int32Value();
-    int node_id = args[1]->Int32Value();
+    v8::Local<v8::Context> context =
+        automation_bindings_->context()->v8_context();
+    int tree_id = args[0]->Int32Value(context).FromMaybe(0);
+    int node_id = args[1]->Int32Value(context).FromMaybe(0);
     std::string str_val = *v8::String::Utf8Value(isolate, args[2]);
-    bool bool_val = args[3]->BooleanValue();
+    bool bool_val = args[3].As<v8::Boolean>()->Value();
 
     AutomationAXTreeWrapper* tree_wrapper =
         automation_bindings_->GetAutomationAXTreeWrapperFromTreeID(tree_id);
@@ -1210,7 +1221,7 @@
     return;
   }
 
-  int tree_id = args[0]->Int32Value();
+  int tree_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
   auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
   if (iter == tree_id_to_tree_wrapper_map_.end())
     return;
@@ -1228,7 +1239,7 @@
   }
 
   TreeChangeObserver observer;
-  observer.id = args[0]->Int32Value();
+  observer.id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
   std::string filter_str = *v8::String::Utf8Value(args.GetIsolate(), args[1]);
   observer.filter = api::automation::ParseTreeChangeObserverFilter(filter_str);
 
@@ -1245,7 +1256,7 @@
     return;
   }
 
-  int observer_id = args[0]->Int32Value();
+  int observer_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   for (auto iter = tree_change_observers_.begin();
        iter != tree_change_observers_.end(); ++iter) {
@@ -1313,7 +1324,7 @@
     return;
   }
 
-  int tree_id = args[0]->Int32Value();
+  int tree_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
   AutomationAXTreeWrapper* tree_wrapper =
       GetAutomationAXTreeWrapperFromTreeID(tree_id);
   if (!tree_wrapper)
@@ -1336,8 +1347,8 @@
   if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  int tree_id = args[0]->Int32Value();
-  int node_id = args[1]->Int32Value();
+  int tree_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
+  int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
       GetAutomationAXTreeWrapperFromTreeID(tree_id);
@@ -1360,8 +1371,8 @@
   if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber())
     ThrowInvalidArgumentsException(this);
 
-  int tree_id = args[0]->Int32Value();
-  int node_id = args[1]->Int32Value();
+  int tree_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
+  int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   AutomationAXTreeWrapper* tree_wrapper =
       GetAutomationAXTreeWrapperFromTreeID(tree_id);
@@ -1581,8 +1592,8 @@
     return;
   }
 
-  int tree_id = args[0]->Int32Value();
-  int node_id = args[1]->Int32Value();
+  int tree_id = args[0]->Int32Value(context()->v8_context()).FromMaybe(0);
+  int node_id = args[1]->Int32Value(context()->v8_context()).FromMaybe(0);
 
   const auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
   if (iter == tree_id_to_tree_wrapper_map_.end())
@@ -1593,7 +1604,7 @@
   if (!node)
     return;
 
-  int index = args[2]->Int32Value();
+  int index = args[2]->Int32Value(context()->v8_context()).FromMaybe(0);
   int child_id;
 
   // Check for a child tree, which is guaranteed to always be the only child.
diff --git a/chrome/renderer/extensions/cast_streaming_native_handler.cc b/chrome/renderer/extensions/cast_streaming_native_handler.cc
index 1b9fb2b5..eea058b 100644
--- a/chrome/renderer/extensions/cast_streaming_native_handler.cc
+++ b/chrome/renderer/extensions/cast_streaming_native_handler.cc
@@ -892,7 +892,7 @@
 
   const int max_width = args[3]->ToInt32(args.GetIsolate())->Value();
   const int max_height = args[4]->ToInt32(args.GetIsolate())->Value();
-  const double fps = args[5]->NumberValue();
+  const double fps = args[5].As<v8::Number>()->Value();
 
   if (fps <= 1) {
     args.GetIsolate()->ThrowException(v8::Exception::TypeError(
diff --git a/chrome/renderer/extensions/page_capture_custom_bindings.cc b/chrome/renderer/extensions/page_capture_custom_bindings.cc
index 9bfb8443..445ff1df 100644
--- a/chrome/renderer/extensions/page_capture_custom_bindings.cc
+++ b/chrome/renderer/extensions/page_capture_custom_bindings.cc
@@ -35,7 +35,7 @@
   blink::WebString path(
       blink::WebString::FromUTF8(*v8::String::Utf8Value(isolate, args[0])));
   blink::WebBlob blob =
-      blink::WebBlob::CreateFromFile(path, args[1]->Int32Value());
+      blink::WebBlob::CreateFromFile(path, args[1].As<v8::Int32>()->Value());
   args.GetReturnValue().Set(
       blob.ToV8Value(context()->v8_context()->Global(), isolate));
 }
@@ -48,7 +48,7 @@
   content::RenderFrame* render_frame = context()->GetRenderFrame();
   if (render_frame) {
     render_frame->Send(new ExtensionHostMsg_ResponseAck(
-        render_frame->GetRoutingID(), args[0]->Int32Value()));
+        render_frame->GetRoutingID(), args[0].As<v8::Int32>()->Value()));
   }
 }
 
diff --git a/chrome/renderer/extensions/webstore_bindings.cc b/chrome/renderer/extensions/webstore_bindings.cc
index aefece3..ff95be9 100644
--- a/chrome/renderer/extensions/webstore_bindings.cc
+++ b/chrome/renderer/extensions/webstore_bindings.cc
@@ -123,10 +123,10 @@
   // or download progress listeners.
   int listener_mask = 0;
   CHECK(args[0]->IsBoolean());
-  if (args[0]->BooleanValue())
+  if (args[0].As<v8::Boolean>()->Value())
     listener_mask |= api::webstore::INSTALL_STAGE_LISTENER;
   CHECK(args[1]->IsBoolean());
-  if (args[1]->BooleanValue())
+  if (args[1].As<v8::Boolean>()->Value())
     listener_mask |= api::webstore::DOWNLOAD_PROGRESS_LISTENER;
 
   std::string preferred_store_link_url;
diff --git a/chrome/renderer/loadtimes_extension_bindings.cc b/chrome/renderer/loadtimes_extension_bindings.cc
index 6dae101..dda7d71 100644
--- a/chrome/renderer/loadtimes_extension_bindings.cc
+++ b/chrome/renderer/loadtimes_extension_bindings.cc
@@ -64,9 +64,9 @@
   v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
       v8::Isolate* isolate,
       v8::Local<v8::String> name) override {
-    if (name->Equals(v8::String::NewFromUtf8(isolate, "GetLoadTimes"))) {
+    if (name->StringEquals(v8::String::NewFromUtf8(isolate, "GetLoadTimes"))) {
       return v8::FunctionTemplate::New(isolate, GetLoadTimes);
-    } else if (name->Equals(v8::String::NewFromUtf8(isolate, "GetCSI"))) {
+    } else if (name->StringEquals(v8::String::NewFromUtf8(isolate, "GetCSI"))) {
       return v8::FunctionTemplate::New(isolate, GetCSI);
     }
     return v8::Local<v8::FunctionTemplate>();
diff --git a/chrome/renderer/net_benchmarking_extension.cc b/chrome/renderer/net_benchmarking_extension.cc
index 814bdd9..a43add3 100644
--- a/chrome/renderer/net_benchmarking_extension.cc
+++ b/chrome/renderer/net_benchmarking_extension.cc
@@ -48,15 +48,15 @@
   v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
       v8::Isolate* isolate,
       v8::Local<v8::String> name) override {
-    if (name->Equals(v8::String::NewFromUtf8(isolate, "ClearCache"))) {
+    if (name->StringEquals(v8::String::NewFromUtf8(isolate, "ClearCache"))) {
       return v8::FunctionTemplate::New(isolate, ClearCache);
-    } else if (name->Equals(v8::String::NewFromUtf8(
+    } else if (name->StringEquals(v8::String::NewFromUtf8(
                    isolate, "ClearHostResolverCache"))) {
       return v8::FunctionTemplate::New(isolate, ClearHostResolverCache);
-    } else if (name->Equals(
+    } else if (name->StringEquals(
                    v8::String::NewFromUtf8(isolate, "ClearPredictorCache"))) {
       return v8::FunctionTemplate::New(isolate, ClearPredictorCache);
-    } else if (name->Equals(
+    } else if (name->StringEquals(
                    v8::String::NewFromUtf8(isolate, "CloseConnections"))) {
       return v8::FunctionTemplate::New(isolate, CloseConnections);
     }
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index a5c37cb..02b274a 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -74,7 +74,6 @@
 const char kCSSBackgroundPositionCenter[] = "center";
 const char kCSSBackgroundPositionLeft[] = "left";
 const char kCSSBackgroundPositionTop[] = "top";
-const char kCSSBackgroundPositionCenterCover[] = "center/cover";
 const char kCSSBackgroundPositionRight[] = "right";
 const char kCSSBackgroundPositionBottom[] = "bottom";
 
@@ -334,6 +333,8 @@
     }
   }
 
+  // If a custom background has been set provide the relevant information to the
+  // page.
   if (theme_info.using_default_theme &&
       !theme_info.custom_background_url.is_empty()) {
     builder.Set("alternateLogo", true);
@@ -341,19 +342,15 @@
     builder.Set("textColorRgba",
                 internal::RGBAColorToArray(isolate, whiteTextRgba));
     builder.Set("customBackgroundConfigured", true);
-    builder.Set("imageUrl",
-                "url('" + theme_info.custom_background_url.spec() + "')");
-    builder.Set("imageTiling", std::string(kCSSBackgroundRepeatNo));
-    builder.Set("imageHorizontalAlignment",
-                std::string(kCSSBackgroundPositionCenter));
-    builder.Set("imageVerticalAlignment",
-                std::string(kCSSBackgroundPositionCenterCover));
+    builder.Set("imageUrl", theme_info.custom_background_url.spec());
     builder.Set("attributionActionUrl",
                 theme_info.custom_background_attribution_action_url.spec());
     builder.Set("attribution1",
                 theme_info.custom_background_attribution_line_1);
     builder.Set("attribution2",
                 theme_info.custom_background_attribution_line_2);
+  } else {
+    builder.Set("customBackgroundConfigured", false);
   }
 
   return builder.Build();
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6f3ceac..66d0fdc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -660,6 +660,8 @@
       "../browser/page_load_metrics/observers/multi_tab_loading_page_load_metrics_observer_browsertest.cc",
       "../browser/page_load_metrics/observers/security_state_page_load_metrics_observer_browsertest.cc",
       "../browser/page_load_metrics/page_load_metrics_browsertest.cc",
+      "../browser/page_load_metrics/page_load_metrics_test_waiter.cc",
+      "../browser/page_load_metrics/page_load_metrics_test_waiter.h",
       "../browser/password_manager/credential_manager_browsertest.cc",
       "../browser/password_manager/password_manager_browsertest.cc",
       "../browser/pdf/pdf_extension_test.cc",
@@ -1890,6 +1892,8 @@
         "//third_party/ocmock",
       ]
       sources += [
+        "../browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.h",
+        "../browser/policy/cloud/machine_level_user_cloud_policy_browsertest_mac_util.mm",
         "../browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_browsertest.mm",
         "../browser/spellchecker/spell_check_host_chrome_impl_mac_browsertest.cc",
         "../browser/ui/cocoa/applescript/bookmark_applescript_utils_test.h",
@@ -2300,6 +2304,7 @@
     "../browser/android/contextualsearch/contextual_search_field_trial_unittest.cc",
     "../browser/android/digital_asset_links/digital_asset_links_handler_unittest.cc",
     "../browser/android/download/download_manager_service_unittest.cc",
+    "../browser/android/explore_sites/explore_sites_feature_unittest.cc",
     "../browser/android/explore_sites/ntp_json_fetcher_unittest.cc",
     "../browser/android/history_report/data_observer_unittest.cc",
     "../browser/android/history_report/delta_file_backend_leveldb_unittest.cc",
@@ -2985,7 +2990,10 @@
       "../browser/media/router/discovery/discovery_network_monitor_metric_observer_unittest.cc",
       "../browser/media/router/discovery/discovery_network_monitor_unittest.cc",
       "../browser/media/webrtc/tab_desktop_media_list_unittest.cc",
+      "../browser/media/webrtc/webrtc_event_log_manager_common_unittest.cc",
       "../browser/media/webrtc/webrtc_event_log_manager_unittest.cc",
+      "../browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.cc",
+      "../browser/media/webrtc/webrtc_event_log_manager_unittest_helpers.h",
       "../browser/media/webrtc/webrtc_event_log_uploader_impl_unittest.cc",
       "../browser/media_galleries/fileapi/native_media_file_util_unittest.cc",
       "../browser/media_galleries/gallery_watch_manager_unittest.cc",
@@ -3022,6 +3030,7 @@
       "../browser/resource_coordinator/local_site_characteristics_webcontents_observer_unittest.cc",
       "../browser/resource_coordinator/page_signal_receiver_unittest.cc",
       "../browser/resource_coordinator/performance_measurement_manager_unittest.cc",
+      "../browser/resource_coordinator/render_process_probe_unittest.cc",
       "../browser/resource_coordinator/session_restore_policy_unittest.cc",
       "../browser/resource_coordinator/tab_activity_watcher_unittest.cc",
       "../browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc",
@@ -4975,6 +4984,7 @@
         "../browser/chromeos/accessibility/select_to_speak_browsertest.cc",
         "../browser/chromeos/accessibility/spoken_feedback_browsertest.cc",
         "../browser/chromeos/accessibility/sticky_keys_browsertest.cc",
+        "../browser/chromeos/accessibility/switch_access_browsertest.cc",
         "../browser/chromeos/input_method/textinput_browsertest.cc",
         "../browser/chromeos/input_method/textinput_surroundingtext_browsertest.cc",
         "../browser/chromeos/input_method/textinput_test_helper.cc",
@@ -5436,6 +5446,7 @@
       "../browser/sync/test/integration/two_client_app_list_sync_test.cc",
       "../browser/sync/test/integration/two_client_apps_sync_test.cc",
       "../browser/sync/test/integration/two_client_arc_package_sync_test.cc",
+      "../browser/sync/test/integration/two_client_autocomplete_sync_test.cc",
       "../browser/sync/test/integration/two_client_autofill_sync_test.cc",
       "../browser/sync/test/integration/two_client_bookmarks_sync_test.cc",
       "../browser/sync/test/integration/two_client_dictionary_sync_test.cc",
@@ -5671,6 +5682,8 @@
       "../browser/autofill/autofill_uitest.h",
       "../browser/autofill/autofill_uitest_util.cc",
       "../browser/autofill/autofill_uitest_util.h",
+      "../browser/autofill/captured_sites_test_utils.cc",
+      "../browser/autofill/captured_sites_test_utils.h",
       "base/interactive_test_utils.cc",
       "base/interactive_test_utils.h",
       "base/interactive_test_utils_common_views.cc",
diff --git a/chrome/test/chromedriver/test/run_java_tests.py b/chrome/test/chromedriver/test/run_java_tests.py
index 3bbf01c3..2c1b40f 100755
--- a/chrome/test/chromedriver/test/run_java_tests.py
+++ b/chrome/test/chromedriver/test/run_java_tests.py
@@ -24,6 +24,8 @@
 import chrome_paths
 import test_environment
 import util
+import glob
+import time
 
 if util.IsLinux():
   sys.path.insert(0, os.path.join(chrome_paths.GetSrc(), 'build', 'android'))
@@ -81,22 +83,6 @@
   Returns:
     A list of |TestResult|s.
   """
-  test_dir = util.MakeTempDir()
-  keystore_path = ('java', 'client', 'test', 'keystore')
-  required_dirs = [keystore_path[:-1],
-                   ('javascript',),
-                   ('third_party', 'closure', 'goog'),
-                   ('third_party', 'js')]
-  for required_dir in required_dirs:
-    os.makedirs(os.path.join(test_dir, *required_dir))
-
-  test_jar = 'test-standalone.jar'
-  class_path = test_jar
-  shutil.copyfile(os.path.join(java_tests_src_dir, 'keystore'),
-                  os.path.join(test_dir, *keystore_path))
-  util.Unzip(os.path.join(java_tests_src_dir, 'common.zip'), test_dir)
-  shutil.copyfile(os.path.join(java_tests_src_dir, test_jar),
-                  os.path.join(test_dir, test_jar))
 
   sys_props = ['selenium.browser=chrome',
                'webdriver.chrome.driver=' + os.path.abspath(chromedriver_path)]
@@ -104,7 +90,7 @@
     if util.IsLinux() and android_package_key is None:
       # Workaround for crbug.com/611886 and
       # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1695
-      chrome_wrapper_path = os.path.join(test_dir, 'chrome-wrapper-no-sandbox')
+      chrome_wrapper_path = os.path.join(java_tests_src_dir, 'chrome-wrapper-no-sandbox')
       with open(chrome_wrapper_path, 'w') as f:
         f.write('#!/bin/sh\n')
         f.write('exec %s --no-sandbox --disable-gpu "$@"\n' %
@@ -114,7 +100,7 @@
     elif util.IsMac():
       # Use srgb color profile, otherwise the default color profile on Mac
       # causes some color adjustments, so screenshots have unexpected colors.
-      chrome_wrapper_path = os.path.join(test_dir, 'chrome-wrapper')
+      chrome_wrapper_path = os.path.join(java_tests_src_dir, 'chrome-wrapper')
       with open(chrome_wrapper_path, 'w') as f:
         f.write('#!/bin/sh\n')
         f.write('exec %s --force-color-profile=srgb "$@"\n' %
@@ -146,18 +132,14 @@
       transport = 'dt_shmem'
     jvm_args += ['-agentlib:jdwp=transport=%s,server=y,suspend=y,'
                  'address=33081' % transport]
-    # Unpack the sources into the test directory and add to the class path
-    # for ease of debugging, particularly with jdb.
-    util.Unzip(os.path.join(java_tests_src_dir, 'test-nodeps-srcs.jar'),
-               test_dir)
-    class_path += ':' + test_dir
 
-  return _RunAntTest(
-      test_dir, 'org.openqa.selenium.chrome.ChromeDriverTests',
-      class_path, sys_props, jvm_args, verbose)
+  return _RunAntTest(java_tests_src_dir, test_filter, chromedriver_path,
+                     chrome_path, log_path, android_package_key,
+                     jvm_args, verbose, debug, sys_props)
 
-
-def _RunAntTest(test_dir, test_class, class_path, sys_props, jvm_args, verbose):
+def _RunAntTest(java_tests_src_dir, test_filter, chromedriver_path,
+                    chrome_path, log_path, android_package_key,
+                    jvm_args, verbose, debug, sys_props):
   """Runs a single Ant JUnit test suite and returns the |TestResult|s.
 
   Args:
@@ -171,27 +153,37 @@
   Returns:
     A list of |TestResult|s.
   """
-  def _CreateBuildConfig(test_name, results_file, class_path, junit_props,
-                         sys_props, jvm_args):
+  def _CreateBuildConfig(java_tests_src_dir, jvm_args, sys_props):
+    path_element = []
+
+    for name in glob.glob(java_tests_src_dir + "/jar/*.jar"):
+      path_element.append('\t<pathelement location=\"%s\" />' % name)
+
+    build_xml = '\n'.join([
+    '<project>',
+    ' <property name="test.class.name" value="org.openqa.selenium.chrome.ChromeDriverTests" />',
+    ' <path id="test.classpath">',
+    '\n'.join(path_element),
+    '</path>'])
+
     def _SystemPropToXml(prop):
       key, value = prop.split('=')
       return '<sysproperty key="%s" value="%s"/>' % (key, value)
     def _JvmArgToXml(arg):
       return '<jvmarg value="%s"/>' % arg
-    return '\n'.join([
-        '<project>',
-        '  <target name="test">',
-        '    <junit %s>' % ' '.join(junit_props),
-        '      <formatter type="xml"/>',
-        '      <classpath>',
-        '        <pathelement path="%s"/>' % class_path,
-        '      </classpath>',
-        '      ' + '\n      '.join(map(_SystemPropToXml, sys_props)),
-        '      ' + '\n      '.join(map(_JvmArgToXml, jvm_args)),
-        '      <test name="%s" outfile="%s"/>' % (test_name, results_file),
-        '    </junit>',
-        '  </target>',
-        '</project>'])
+    build_xml += '\n'.join([
+      '  <target name="test">',
+      '    <junit %s>' % ' '.join(junit_props),
+      '      <formatter type="xml"/>',
+      '      ' + '\n      '.join(map(_SystemPropToXml, sys_props)),
+      '      ' + '\n      '.join(map(_JvmArgToXml, jvm_args)),
+      '      <test name="%s" outfile="%s"/>' % ("org.openqa.selenium.chrome.ChromeDriverTests", "results"),
+      '      <classpath refid="test.classpath" />',
+      '    </junit>',
+      '  </target>',
+      '</project>'])
+
+    return build_xml
 
   def _ProcessResults(results_path):
     doc = minidom.parse(results_path)
@@ -216,20 +208,21 @@
   if verbose:
     junit_props += ['showoutput="yes"']
 
-  ant_file = open(os.path.join(test_dir, 'build.xml'), 'w')
-  ant_file.write(_CreateBuildConfig(
-      test_class, 'results', class_path, junit_props, sys_props, jvm_args))
+  ant_file = open(os.path.join(java_tests_src_dir, 'build.xml'), 'w')
+  file_contents = _CreateBuildConfig(java_tests_src_dir, jvm_args, sys_props)
+
+  ant_file.write(file_contents)
   ant_file.close()
 
   if util.IsWindows():
     ant_name = 'ant.bat'
   else:
     ant_name = 'ant'
-  code = util.RunCommand([ant_name, 'test'], cwd=test_dir)
+  code = util.RunCommand([ant_name, 'test'], java_tests_src_dir)
   if code != 0:
-    print 'FAILED to run java tests of %s through ant' % test_class
+    print 'FAILED to run java tests of ChromeDriverTests through ant'
     return
-  return _ProcessResults(os.path.join(test_dir, 'results.xml'))
+  return _ProcessResults(os.path.join(java_tests_src_dir, 'results.xml'))
 
 
 def PrintTestResults(results):
@@ -351,7 +344,12 @@
     return PrintTestResults(results)
   finally:
     environment.GlobalTearDown()
-
+    os.remove(os.path.join(java_tests_src_dir, "build.xml"))
+    os.remove(os.path.join(java_tests_src_dir, "results.xml"))
+    if(os.path.exists(os.path.join(java_tests_src_dir, "chrome-wrapper-no-sandbox"))):
+      os.remove(os.path.join(java_tests_src_dir, "chrome-wrapper-no-sandbox"))
+    if(os.path.exists(os.path.join(java_tests_src_dir, "chrome-wrapper"))):
+      os.remove(os.path.join(java_tests_src_dir, "chrome-wrapper"))
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index d777d9b..5a9d49e6 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -23,7 +23,7 @@
     'BasicMouseInterfaceTest.testMovingIntoAnImageEnclosedInALink',
     'BasicMouseInterfaceTest.testMovingMouseBackAndForthPastViewPort',
     'BasicMouseInterfaceTest.testMovingMousePastViewPort',
-    'ChromeOptionsFunctionalTest.canStartChromeWithCustomOptions',
+    'ChromeOptionsFunctionalTest.*',
     'ClickScrollingTest.testShouldBeAbleToClickOnAnElementHiddenByDoubleOverflow',
     'ClickScrollingTest.testShouldNotBeAbleToClickElementThatIsOutOfViewInANonScrollableFrame',
     'ClickScrollingTest.testShouldNotScrollIfAlreadyScrolledAndElementIsInView',
@@ -102,20 +102,100 @@
     'AlertsTest.testPromptShouldUseDefaultValueIfNoKeysSent',
     'UnexpectedAlertBehaviorTest.canAcceptUnhandledAlert',
     'UnexpectedAlertBehaviorTest.canSpecifyUnhandledAlertBehaviourUsingCapabilities',
+
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+    'ClickTest.testShouldBeAbleToClickOnAPartiallyOverlappedLinkThatWrapsToTheNextLine',
+    'ClickTest.testShouldSetRelatedTargetForMouseOut',
+
+    'ContentEditableTest.testShouldBeAbleToTypeIntoContentEditableElementWithExistingValue',
+    'ContentEditableTest.testShouldAppendToTinyMCE',
+    'CorrectEventFiringTest.testNativelyClickOverlappingElements',
+    'CorrectEventFiringTest.testClickPartiallyOverlappingElements',
+
+    'ElementFindingTest.testShouldBeAbleToFindElementByXPathInXmlDocument',
+
+    'ExecutingJavascriptTest.shouldReturnDocumentElementIfDocumentIsReturned',
+    'ExecutingJavascriptTest.testShouldBeAbleToReturnADateObject',
+    'ExecutingJavascriptTest.shouldHandleRecursiveStructures',
+
+    'I18nTest.testEnteringSupplementaryCharacters',
+
+    'PageLoadingTest.testEagerStrategyShouldNotWaitForResourcesOnRefresh',
+    'PageLoadingTest.testEagerStrategyShouldWaitForDocumentToBeLoaded',
+    'PageLoadingTest.testShouldTimeoutIfAPageTakesTooLongToRefresh',
+    'PageLoadingTest.testShouldNotStopLoadingPageAfterTimeout',
+    'PageLoadingTest.testEagerStrategyShouldNotWaitForResources',
+
+    'PositionAndSizeTest.testShouldHandleNonIntegerPositionAndSize',
+
+    'ProxySettingTest.canConfigureProxyThroughPACFile',
+    'ProxySettingTest.canUsePACThatOnlyProxiesCertainHosts',
+    'ProxySettingTest.canConfigureManualHttpProxy',
+    'ProxySettingTest.canConfigureNoProxy',
+
+    'SelectElementTest.shouldNotAllowInvisibleOptionsToBeSelectedByVisibleText',
+    'SelectElementTest.shouldNotAllowUserToDeselectOptionsByInvisibleText',
+
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfAnElement',
+    'TakesScreenshotTest.testShouldCaptureScreenshotAtIFramePageAfterSwitching',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfPageWithTooLongX',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfPageWithTooLongY',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfPageWithLongX',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfPageWithLongY',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfPageWithTooLongXandY',
+
+    'TextHandlingTest.testShouldNotReturnLtrMarks',
+    'TextHandlingTest.testShouldTrimTextWithMultiByteWhitespaces',
+
+    'UnexpectedAlertBehaviorTest.canDismissUnhandledAlert',
+    'UnexpectedAlertBehaviorTest.canSilentlyAcceptUnhandledAlert',
+    'UnexpectedAlertBehaviorTest.canDismissUnhandledAlertsByDefault',
+    'UnexpectedAlertBehaviorTest.canSilentlyDismissUnhandledAlert',
+
+    'UploadTest.testClickFileInput',
+
+    'PageLoadingTest.testNoneStrategyShouldNotWaitForPageToRefresh',
+    'BasicMouseInterfaceTest.testHoverPersists',
+
+    'AlertsTest.testShouldHandleAlertOnPageLoad',
+    'AlertsTest.testShouldAllowAUserToSetTheValueOfAPrompt',
+    'AlertsTest.testHandlesTwoAlertsFromOneInteraction',
+    'AlertsTest.testCanQuitWhenAnAlertIsPresent',
+    'AlertsTest.testShouldImplicitlyHandleAlertOnPageBeforeUnload',
+    'AlertsTest.testSwitchingToMissingAlertInAClosedWindowThrows',
+    'AlertsTest.testShouldAllowAUserToAcceptAPrompt',
+    'AlertsTest.testShouldAllowUsersToAcceptAnAlertManually',
+    'AlertsTest.testShouldThrowIllegalArgumentExceptionWhenKeysNull',
+    'AlertsTest.testShouldAllowAUserToDismissAPrompt',
+    'AlertsTest.testShouldAllowUsersToAcceptAnAlertWithNoTextManually',
+    'AlertsTest.shouldHandleAlertOnFormSubmit',
+    'AlertsTest.testShouldBeAbleToOverrideTheWindowAlertMethod',
+    'ElementAttributeTest.testShouldReturnInnerHtml',
+    'ExecutingAsyncJavascriptTest.shouldBeAbleToMakeXMLHttpRequestsAndWaitForTheResponse',
+    'MiscTest.testShouldReturnTheSourceOfAPage',
+    'TextHandlingTest.testReadALargeAmountOfData',
+    'TypingTest.testNumericShiftKeys',
+    'TypingTest.testAllPrintableKeys',
+    'TypingTest.testChordControlCutAndPaste',
 ]
 
 _OS_NEGATIVE_FILTER = {}
 _OS_NEGATIVE_FILTER['win'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+    'ClickScrollingTest.testClickingOnAnchorScrollsPage',
 ]
 _OS_NEGATIVE_FILTER['linux'] = [
 ]
 _OS_NEGATIVE_FILTER['mac'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+    'PageLoadingTest.testNoneStrategyShouldNotWaitForPageToRefresh',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfCurrentViewport',
 ]
 
 _SPECIFIC_OS_REVISION_NEGATIVE_FILTER = {}
 
 _OS_NEGATIVE_FILTER['android:chrome'] = [
-    'ChromeOptionsFunctionalTest.canStartChromeWithCustomOptions',
+    'ChromeOptionsFunctionalTest.*',
     'ClickScrollingTest.testShouldBeAbleToClickElementThatIsOutOfViewInANestedFrame',
 
     'ClickTest.testShouldOnlyFollowHrefOnce',
@@ -214,39 +294,41 @@
     'BasicMouseInterfaceTest.testMoveAndClick',
     'BasicMouseInterfaceTest.testShouldClickElementInIFrame',
     'CombinedInputActionsTest.testCanClickOnLinks',
+
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2376
+    'ImplicitWaitTest.testShouldRetainImplicitlyWaitFromTheReturnedWebDriverOfFrameSwitchTo',
+
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+    'CorrectEventFiringTest.testSendingKeysToAnotherElementShouldCauseTheBlurEventToFireInNonTopmostWindow',
+    'CorrectEventFiringTest.testClickAnElementThatDisappear',
+    'ImplicitWaitTest.testShouldImplicitlyWaitForASingleElement',
+    'ReferrerTest.crossDomainHistoryNavigationWithADirectProxy',
+    'ReferrerTest.navigationWhenProxyInterceptsASpecificUrl',
+    'ReferrerTest.crossDomainHistoryNavigationWithoutAProxy',
+    'ReferrerTest.basicHistoryNavigationWithoutAProxy',
+    'ReferrerTest.basicHistoryNavigationWithADirectProxy',
+    'ReferrerTest.crossDomainHistoryNavigationWhenProxyInterceptsHostRequests',
+    'ReferrerTest.crossDomainHistoryNavigationWithAProxiedHost',
+    'TakesScreenshotTest.testShouldCaptureScreenshotOfCurrentViewport',
+    'UploadTest.testUploadingWithHiddenFileInput',
+    'CombinedInputActionsTest.testControlClickingOnMultiSelectionList',
+    'CombinedInputActionsTest.testShiftClickingOnMultiSelectionList',
+    'CombinedInputActionsTest.testPlainClickingOnMultiSelectionList',
 ]
+
 _OS_NEGATIVE_FILTER['android:chrome_stable'] = (
     _OS_NEGATIVE_FILTER['android:chrome'] + [
     ]
 )
 _OS_NEGATIVE_FILTER['android:chrome_beta'] = (
     _OS_NEGATIVE_FILTER['android:chrome'] + [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+    'CorrectEventFiringTest.testClickingAnUnfocusableChildShouldNotBlurTheParent',
+    'CorrectEventFiringTest.testShouldFireFocusEventInNonTopmostWindow',
     ]
 )
 _OS_NEGATIVE_FILTER['android:chromium'] = (
     _OS_NEGATIVE_FILTER['android:chrome'] + [
-        # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2286
-        'CorrectEventFiringTest.*',
-        'FormHandlingTest.testShouldSubmitAFormUsingTheEnterKey',
-        'FormHandlingTest.testShouldSubmitAFormUsingTheNewlineLiteral',
-        'JavascriptEnabledDriverTest.testShouldBeAbleToFindElementAfterJavascriptCausesANewPageToLoad',
-        'JavascriptEnabledDriverTest.testShouldBeAbleToDetermineTheLocationOfAnElement',
-        'JavascriptEnabledDriverTest.testShouldBeAbleToSwitchToFocusedElement',
-        'JavascriptEnabledDriverTest.testIfNoElementHasFocusTheActiveElementIsTheBody',
-        'JavascriptEnabledDriverTest.testShouldWaitForLoadsToCompleteAfterJavascriptCausesANewPageToLoad',
-        'TypingTest.testShouldBeAbleToUseArrowKeys',
-        'TypingTest.testShouldReportKeyCodeOfArrowKeysUpDownEvents',
-        'TypingTest.testShouldBeAbleToMixUpperAndLowerCaseLetters',
-        'BasicMouseInterfaceTest.testDoubleClickThenGet',
-        'MiscTest.testShouldReturnTheSourceOfAPage',
-        'MiscTest.testClickingShouldNotTrampleWOrHInGlobalScope',
-        'MiscTest.testShouldReportTheCurrentUrlCorrectly',
-        'MiscTest.testStimulatesStrangeOnloadInteractionInFirefox',
-        'ElementAttributeTest.testShouldCorrectlyReportValueOfColspan',
-        # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2376
-        'ImplicitWaitTest.*',
-        # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2401
-        'JavascriptEnabledDriverTest.testIssue80ClickShouldGenerateClickEvent',
     ]
 )
 _OS_NEGATIVE_FILTER['android:chromedriver_webview_shell'] = (
@@ -255,7 +337,10 @@
         'VisibilityTest.testShouldModifyTheVisibilityOfAnElementDynamically',
          # Not applicable on ChromeDriverWebViewShell (doesn't support tabs).
         'WindowSwitchingTest.*',
-        'TakesScreenshotTest.testShouldCaptureScreenshot',
+        # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2480
+        'TakesScreenshotTest.*',
+        # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2376
+        'ImplicitWaitTest.testShouldRetainImplicitlyWaitFromTheReturnedWebDriverOfFrameSwitchTo',
     ]
 )
 
diff --git a/chrome/test/data/autofill/captured_sites/amazon.test b/chrome/test/data/autofill/captured_sites/amazon.test
index b8e720a..995fe0d3 100644
--- a/chrome/test/data/autofill/captured_sites/amazon.test
+++ b/chrome/test/data/autofill/captured_sites/amazon.test
@@ -65,7 +65,7 @@
     {
       "selector": "//*[@id=\"enterAddressPhoneNumber\"]",
       "context": [],
-      "expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
+      "expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
       "expectedValue": "5125551234",
       "type": "validateField"
     },
diff --git a/chrome/test/data/autofill/captured_sites/walmart.test b/chrome/test/data/autofill/captured_sites/walmart.test
index c6a53b7..221b87c 100644
--- a/chrome/test/data/autofill/captured_sites/walmart.test
+++ b/chrome/test/data/autofill/captured_sites/walmart.test
@@ -50,7 +50,7 @@
       "selectorType": "xpath",
       "selector": "//input[@type='text' and @name='phone']",
       "context": [],
-      "expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
+      "expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
       "expectedValue": "5125551234",
       "type": "validateField"
     },
diff --git a/chrome/test/data/autofill/captured_sites/zappos.test b/chrome/test/data/autofill/captured_sites/zappos.test
index 3c9c2c9..51ee0ed 100644
--- a/chrome/test/data/autofill/captured_sites/zappos.test
+++ b/chrome/test/data/autofill/captured_sites/zappos.test
@@ -65,7 +65,7 @@
     {
       "selector": "//*[@id=\"AddressForm_PHONE_NUMBER\"]",
       "context": [],
-      "expectedAutofillType": "PHONE_HOME_CITY_AND_NUMBER",
+      "expectedAutofillType": "PHONE_HOME_WHOLE_NUMBER",
       "expectedValue": "5125551234",
       "type": "validateField"
     },
diff --git a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
index df89d8a..e42cf06 100644
--- a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
+++ b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
@@ -284,7 +284,8 @@
             HexSSID: "7769666931",
             Security: 'WEP-PSK',
             SignalStrength: 40,
-            SSID: "wifi1"
+            SSID: "wifi1",
+            TetheringState: "NotDetected"
           }
         }, {
           GUID: 'stub_wifi2_guid',
@@ -297,7 +298,8 @@
             Frequency: 5000,
             HexSSID: "77696669325F50534B",
             Security: 'WPA-PSK',
-            SSID: "wifi2_PSK"
+            SSID: "wifi2_PSK",
+            TetheringState: "NotDetected"
           }
         }], result);
 
@@ -320,7 +322,8 @@
                 HexSSID: "7769666931",
                 Security: 'WEP-PSK',
                 SignalStrength: 40,
-                SSID: "wifi1"
+                SSID: "wifi1",
+                TetheringState: "NotDetected"
               }
             }], result);
 
@@ -375,7 +378,8 @@
             HexSSID: "7769666931",
             Security: 'WEP-PSK',
             SignalStrength: 40,
-            SSID: "wifi1"
+            SSID: "wifi1",
+            TetheringState: "NotDetected"
           }
         }, {
           Connectable: true,
@@ -425,7 +429,8 @@
             HexSSID: "77696669325F50534B",
             Security: 'WPA-PSK',
             SignalStrength: 80,
-            SSID: "wifi2_PSK"
+            SSID: "wifi2_PSK",
+            TetheringState: "NotDetected"
           }
         }], result);
       }));
@@ -448,7 +453,8 @@
             HexSSID: "7769666931",
             Security: 'WEP-PSK',
             SignalStrength: 40,
-            SSID: "wifi1"
+            SSID: "wifi1",
+            TetheringState: "NotDetected"
           }
         }, {
           Connectable: true,
@@ -464,7 +470,8 @@
             HexSSID: "77696669325F50534B",
             Security: 'WPA-PSK',
             SignalStrength: 80,
-            SSID: "wifi2_PSK"
+            SSID: "wifi2_PSK",
+            TetheringState: "NotDetected"
           }
         }], result);
       }));
@@ -563,7 +570,8 @@
             FrequencyList: [2400],
             SSID: 'wifi1',
             Security: 'WEP-PSK',
-            SignalStrength: 40
+            SignalStrength: 40,
+            TetheringState: "NotDetected"
           }
         }, result);
       }));
@@ -689,6 +697,7 @@
               UserPolicy: 'WPA-PSK'
             },
             SignalStrength: 80,
+            TetheringState: "NotDetected"
           }
         }, result);
       }));
@@ -813,7 +822,8 @@
             HexSSID: "77696669325F50534B",
             Security: 'WPA-PSK',
             SignalStrength: 80,
-            SSID: "wifi2_PSK"
+            SSID: "wifi2_PSK",
+            TetheringState: "NotDetected"
           }
         }, result);
       }));
diff --git a/chrome/test/data/extensions/platform_apps/picture_in_picture/bear.webm b/chrome/test/data/extensions/platform_apps/picture_in_picture/bear.webm
new file mode 100644
index 0000000..422df3f
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/picture_in_picture/bear.webm
Binary files differ
diff --git a/chrome/test/data/extensions/platform_apps/picture_in_picture/main.html b/chrome/test/data/extensions/platform_apps/picture_in_picture/main.html
new file mode 100644
index 0000000..d5585a5
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/picture_in_picture/main.html
@@ -0,0 +1,10 @@
+<!--
+ * Copyright 2018 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.
+-->
+<html>
+<body>
+<script src="main.js"></script>
+</body>
+</html>
diff --git a/chrome/test/data/extensions/platform_apps/picture_in_picture/main.js b/chrome/test/data/extensions/platform_apps/picture_in_picture/main.js
new file mode 100644
index 0000000..29ed7be
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/picture_in_picture/main.js
@@ -0,0 +1,29 @@
+// Copyright 2018 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.
+
+const video = document.createElement('video');
+video.src = chrome.runtime.getURL('bear.webm');
+video.load();
+video.addEventListener('loadedmetadata', function() {
+  chrome.test.sendMessage('Launched');
+});
+
+function enterPictureInPicture() {
+  video.requestPictureInPicture()
+      .catch(error => { window.domAutomationController.send(false); });
+
+  video.addEventListener('enterpictureinpicture', function(pipWindow) {
+    window.domAutomationController.send(
+        pipWindow.width != 0 && pipWindow.height != 0);
+  }, { once: true });
+}
+
+function exitPictureInPicture() {
+  document.exitPictureInPicture()
+      .catch(error => { window.domAutomationController.send(false); });
+
+  video.addEventListener('leavepictureinpicture', function() {
+    window.domAutomationController.send(true);
+  }, { once: true });
+}
diff --git a/chrome/test/data/extensions/platform_apps/picture_in_picture/manifest.json b/chrome/test/data/extensions/platform_apps/picture_in_picture/manifest.json
new file mode 100644
index 0000000..8ef849ad
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/picture_in_picture/manifest.json
@@ -0,0 +1,10 @@
+{
+  "name": "Platform App Test: Picture-in-Picture",
+  "manifest_version": 2,
+  "version": "1",
+  "app": {
+    "background": {
+      "scripts": ["test.js"]
+    }
+  }
+}
diff --git a/chrome/test/data/extensions/platform_apps/picture_in_picture/test.js b/chrome/test/data/extensions/platform_apps/picture_in_picture/test.js
new file mode 100644
index 0000000..506e9d3
--- /dev/null
+++ b/chrome/test/data/extensions/platform_apps/picture_in_picture/test.js
@@ -0,0 +1,7 @@
+// Copyright 2018 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.
+
+chrome.app.runtime.onLaunched.addListener(function() {
+  chrome.app.window.create('main.html', {}, function () {});
+});
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html
index ec7721b2..abae33d 100644
--- a/chrome/test/data/local_ntp/local_ntp_browsertest.html
+++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -15,6 +15,7 @@
   <script src="test_utils.js" charset="utf-8"></script>
   <script src="local_ntp_browsertest.js" charset="utf-8"></script>
   <template id="local-ntp-template">
+    <div id="custom-bg"></div>
     <div id="ntp-contents">
       <div id="logo">
         <!-- The logo that is displayed in the absence of a doodle. -->
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 867e342aa..63c7b9d 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3363,6 +3363,9 @@
   "DeviceLocalAccountAutoLoginBailoutEnabled": {
   },
 
+  "DeviceLocalAccountManagedSessionEnabled": {
+  },
+
   "DeviceLocalAccountPromptForNetworkWhenOffline": {
   },
 
diff --git a/chrome/test/nacl/nacl_browsertest.cc b/chrome/test/nacl/nacl_browsertest.cc
index c7257ac..98148db 100644
--- a/chrome/test/nacl/nacl_browsertest.cc
+++ b/chrome/test/nacl/nacl_browsertest.cc
@@ -376,9 +376,9 @@
       "pnacl_debug_url.html?nmf_file=pnacl_has_debug_flag_off.nmf"));
 }
 
-// NaClBrowserTestPnacl.PnaclErrorHandling is flaky on Linux.
-// http://crbug.com/704980
-#if defined(OS_LINUX)
+// NaClBrowserTestPnacl.PnaclErrorHandling is flaky on Win and Linux.
+// http://crbug.com/704980, http://crbug.com/870309
+#if defined(OS_WIN) || defined(OS_LINUX)
 #define MAYBE_PnaclErrorHandling DISABLED_PnaclErrorHandling
 #else
 #define MAYBE_PnaclErrorHandling PnaclErrorHandling
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index e4f12ef1..6997891 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -29,6 +29,7 @@
 #include "chromecast/browser/cast_browser_context.h"
 #include "chromecast/browser/cast_browser_main_parts.h"
 #include "chromecast/browser/cast_browser_process.h"
+#include "chromecast/browser/cast_http_user_agent_settings.h"
 #include "chromecast/browser/cast_navigation_ui_data.h"
 #include "chromecast/browser/cast_network_delegate.h"
 #include "chromecast/browser/cast_quota_permission_context.h"
@@ -474,6 +475,11 @@
   }
 }
 
+std::string CastContentBrowserClient::GetAcceptLangs(
+    content::BrowserContext* context) {
+  return CastHttpUserAgentSettings::AcceptLanguage();
+}
+
 void CastContentBrowserClient::OverrideWebkitPrefs(
     content::RenderViewHost* render_view_host,
     content::WebPreferences* prefs) {
diff --git a/chromecast/browser/cast_content_browser_client.h b/chromecast/browser/cast_content_browser_client.h
index e708e762..0b3592af 100644
--- a/chromecast/browser/cast_content_browser_client.h
+++ b/chromecast/browser/cast_content_browser_client.h
@@ -133,6 +133,7 @@
   void SiteInstanceGotProcess(content::SiteInstance* site_instance) override;
   void AppendExtraCommandLineSwitches(base::CommandLine* command_line,
                                       int child_process_id) override;
+  std::string GetAcceptLangs(content::BrowserContext* context) override;
   void OverrideWebkitPrefs(content::RenderViewHost* render_view_host,
                            content::WebPreferences* prefs) override;
   void ResourceDispatcherHostCreated() override;
diff --git a/chromecast/browser/cast_http_user_agent_settings.cc b/chromecast/browser/cast_http_user_agent_settings.cc
index f4eeba9d..2d7916a 100644
--- a/chromecast/browser/cast_http_user_agent_settings.cc
+++ b/chromecast/browser/cast_http_user_agent_settings.cc
@@ -17,6 +17,32 @@
 #include "base/android/locale_utils.h"
 #endif  // defined(OS_ANDROID)
 
+namespace {
+
+std::string GetLocale() {
+#if defined(OS_ANDROID)
+  // TODO(byungchul): Use transient locale set when new app starts.
+  return base::android::GetDefaultLocaleString();
+#else
+  return base::i18n::GetConfiguredLocale();
+#endif
+}
+
+std::string LocaleToAcceptLanguage(const std::string locale) {
+  return net::HttpUtil::GenerateAcceptLanguageHeader(
+#if defined(OS_ANDROID)
+      locale
+#else
+      // Ignoring |locale| here is a bit weird, but locale is still used to
+      // avoid a l10n_util::GetStringUTF8() call when GetAcceptLanguage() is
+      // called.
+      l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES)
+#endif
+      );
+}
+
+}  // namespace
+
 namespace chromecast {
 namespace shell {
 
@@ -30,23 +56,10 @@
 
 std::string CastHttpUserAgentSettings::GetAcceptLanguage() const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
-  std::string new_locale(
-#if defined(OS_ANDROID)
-      // TODO(byungchul): Use transient locale set when new app starts.
-      base::android::GetDefaultLocaleString()
-#else
-      base::i18n::GetConfiguredLocale()
-#endif
-      );
+  std::string new_locale(GetLocale());
   if (new_locale != last_locale_ || accept_language_.empty()) {
     last_locale_ = new_locale;
-    accept_language_ = net::HttpUtil::GenerateAcceptLanguageHeader(
-#if defined(OS_ANDROID)
-        last_locale_
-#else
-        l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES)
-#endif
-        );
+    accept_language_ = LocaleToAcceptLanguage(new_locale);
     LOG(INFO) << "Locale changed: accept_language=" << accept_language_;
   }
   return accept_language_;
@@ -56,5 +69,9 @@
   return chromecast::shell::GetUserAgent();
 }
 
+std::string CastHttpUserAgentSettings::AcceptLanguage() {
+  return LocaleToAcceptLanguage(GetLocale());
+}
+
 }  // namespace shell
 }  // namespace chromecast
diff --git a/chromecast/browser/cast_http_user_agent_settings.h b/chromecast/browser/cast_http_user_agent_settings.h
index 8deb7f2..97f9a6b2 100644
--- a/chromecast/browser/cast_http_user_agent_settings.h
+++ b/chromecast/browser/cast_http_user_agent_settings.h
@@ -5,6 +5,8 @@
 #ifndef CHROMECAST_BROWSER_CAST_HTTP_USER_AGENT_SETTINGS_H_
 #define CHROMECAST_BROWSER_CAST_HTTP_USER_AGENT_SETTINGS_H_
 
+#include <string>
+
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "net/url_request/http_user_agent_settings.h"
@@ -21,6 +23,10 @@
   std::string GetAcceptLanguage() const override;
   std::string GetUserAgent() const override;
 
+  // Returns the same value as GetAcceptLanguage(), but is static and can be
+  // called on any thread.
+  static std::string AcceptLanguage();
+
  private:
   mutable std::string last_locale_;
   mutable std::string accept_language_;
diff --git a/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc b/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
index 542f926..b35b45f 100644
--- a/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
+++ b/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
@@ -24,7 +24,6 @@
     ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
     ::media::CHANNEL_LAYOUT_STEREO,
     ::media::AudioParameters::kAudioCDSampleRate,
-    16,
     256);
 
 void OnLogMessage(const std::string& message) {}
diff --git a/chromeos/BUILD.gn b/chromeos/BUILD.gn
index 3ba13f0..c75527a 100644
--- a/chromeos/BUILD.gn
+++ b/chromeos/BUILD.gn
@@ -836,10 +836,32 @@
     # need the toolchain in the data.
     need_toolchain = true
   }
+
+  group("cros_chrome_deploy") {
+    # The following run-time dependencies are needed to deploy chrome to a
+    # ChromeOS device. See the link for the full list:
+    # https://codesearch.chromium.org/chromium/src/third_party/chromite/lib/chrome_util.py?l=341
+    # Most of these are copy targets, for which GN doesn't add their outputs
+    # as runtime-deps. See the link below for more details:
+    # https://chromium.googlesource.com/chromium/src/+/master/tools/gn/docs/reference.md#actions-and-copies
+    data_deps = [
+      "//chrome:xdg_mime",
+      "//mojo/core:shared_library_arc32",
+      "//mojo/core:shared_library_arc64",
+    ]
+
+    # TODO(bpastene): Figure out what's generating resources/chromeos/ and
+    # declare it as a dep instead of adding the dir directly.
+    data = [
+      "$root_out_dir/resources/chromeos/",
+    ]
+  }
+
   group("cros_vm_sanity_test") {
     testonly = true
     write_runtime_deps = "$root_out_dir/cros_vm_sanity_test.runtime_deps"
     data_deps = [
+      ":cros_chrome_deploy",
       "//:chromiumos_preflight",  # Builds the browser.
       ":cros_vm_sanity_test_wrapper",  # Builds the test wrapper.
     ]
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 95889da..4e62ccee 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-10928.0.0
\ No newline at end of file
+10933.0.0
\ No newline at end of file
diff --git a/chromeos/components/drivefs/drivefs_host.cc b/chromeos/components/drivefs/drivefs_host.cc
index a89a69e6..3678191 100644
--- a/chromeos/components/drivefs/drivefs_host.cc
+++ b/chromeos/components/drivefs/drivefs_host.cc
@@ -23,6 +23,7 @@
 constexpr char kMountScheme[] = "drivefs://";
 constexpr char kDataPath[] = "GCache/v2";
 constexpr char kIdentityConsumerId[] = "drivefs";
+const base::TimeDelta kMountTimeout = base::TimeDelta::FromSeconds(10);
 
 class MojoConnectionDelegateImpl : public DriveFsHost::MojoConnectionDelegate {
  public:
@@ -85,9 +86,13 @@
         mojo::MakeProxy(mojo_connection_delegate_->InitializeMojoConnection());
     mojom::DriveFsDelegatePtr delegate;
     binding_.Bind(mojo::MakeRequest(&delegate));
+    binding_.set_connection_error_handler(
+        base::BindOnce(&MountState::OnConnectionError, base::Unretained(this)));
     bootstrap->Init(
         {base::in_place, host_->delegate_->GetAccountId().GetUserEmail()},
         mojo::MakeRequest(&drivefs_), std::move(delegate));
+    drivefs_.set_connection_error_handler(
+        base::BindOnce(&MountState::OnConnectionError, base::Unretained(this)));
 
     // If unconsumed, the registration is cleaned up when |this| is destructed.
     PendingConnectionManager::Get().ExpectOpenIpcChannel(
@@ -100,10 +105,15 @@
         base::StrCat({"drivefs-", host_->delegate_->GetObfuscatedAccountId()}),
         {datadir_option}, chromeos::MOUNT_TYPE_NETWORK_STORAGE,
         chromeos::MOUNT_ACCESS_MODE_READ_WRITE);
+
+    host_->timer_->Start(
+        FROM_HERE, kMountTimeout,
+        base::BindOnce(&MountState::OnTimedOut, base::Unretained(this)));
   }
 
   ~MountState() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(host_->sequence_checker_);
+    host_->timer_->Stop();
     if (pending_token_) {
       PendingConnectionManager::Get().CancelExpectedOpenIpcChannel(
           pending_token_);
@@ -184,12 +194,14 @@
   void OnMountFailed(base::Optional<base::TimeDelta> remount_delay) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(host_->sequence_checker_);
     drivefs_has_mounted_ = false;
+    drivefs_has_terminated_ = true;
     host_->delegate_->OnMountFailed(std::move(remount_delay));
   }
 
   void OnUnmounted(base::Optional<base::TimeDelta> remount_delay) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(host_->sequence_checker_);
     drivefs_has_mounted_ = false;
+    drivefs_has_terminated_ = true;
     host_->delegate_->OnUnmounted(std::move(remount_delay));
   }
 
@@ -210,8 +222,25 @@
     }
   }
 
+  void OnConnectionError() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(host_->sequence_checker_);
+    if (!drivefs_has_terminated_) {
+      if (mounted())
+        host_->delegate_->OnUnmounted({});
+      else
+        host_->delegate_->OnMountFailed({});
+      drivefs_has_terminated_ = true;
+    }
+  }
+
+  void OnTimedOut() {
+    host_->timer_->Stop();
+    OnMountFailed({});
+  }
+
   void MaybeNotifyDelegateOnMounted() {
     if (mounted()) {
+      host_->timer_->Stop();
       host_->delegate_->OnMounted(mount_path());
     }
   }
@@ -283,14 +312,17 @@
   mojo::Binding<mojom::DriveFsDelegate> binding_;
 
   bool drivefs_has_mounted_ = false;
+  bool drivefs_has_terminated_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(MountState);
 };
 
 DriveFsHost::DriveFsHost(const base::FilePath& profile_path,
-                         DriveFsHost::Delegate* delegate)
+                         DriveFsHost::Delegate* delegate,
+                         std::unique_ptr<base::OneShotTimer> timer)
     : profile_path_(profile_path),
-      delegate_(delegate) {
+      delegate_(delegate),
+      timer_(std::move(timer)) {
   chromeos::disks::DiskMountManager::GetInstance()->AddObserver(this);
 }
 
@@ -338,6 +370,7 @@
   }
   return mount_state_->GetDriveFsInterface();
 }
+
 void DriveFsHost::OnMountEvent(
     chromeos::disks::DiskMountManager::MountEvent event,
     chromeos::MountError error_code,
diff --git a/chromeos/components/drivefs/drivefs_host.h b/chromeos/components/drivefs/drivefs_host.h
index e7fd42b..98af5aaa 100644
--- a/chromeos/components/drivefs/drivefs_host.h
+++ b/chromeos/components/drivefs/drivefs_host.h
@@ -13,6 +13,7 @@
 #include "base/files/file_path.h"
 #include "base/files/scoped_file.h"
 #include "base/macros.h"
+#include "base/timer/timer.h"
 #include "chromeos/components/drivefs/mojom/drivefs.mojom.h"
 #include "chromeos/disks/disk_mount_manager.h"
 #include "components/account_id/account_id.h"
@@ -78,7 +79,8 @@
   };
 
   DriveFsHost(const base::FilePath& profile_path,
-              Delegate* delegate);
+              Delegate* delegate,
+              std::unique_ptr<base::OneShotTimer> timer);
   ~DriveFsHost() override;
 
   void AddObserver(DriveFsHostObserver* observer);
@@ -118,6 +120,8 @@
 
   Delegate* const delegate_;
 
+  std::unique_ptr<base::OneShotTimer> timer_;
+
   // State specific to the current mount, or null if not mounted.
   std::unique_ptr<MountState> mount_state_;
 
diff --git a/chromeos/components/drivefs/drivefs_host_unittest.cc b/chromeos/components/drivefs/drivefs_host_unittest.cc
index c80797f..0cb3b28d 100644
--- a/chromeos/components/drivefs/drivefs_host_unittest.cc
+++ b/chromeos/components/drivefs/drivefs_host_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_split.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/timer/mock_timer.h"
 #include "chromeos/components/drivefs/drivefs_host_observer.h"
 #include "chromeos/components/drivefs/pending_connection_manager.h"
 #include "chromeos/disks/mock_disk_mount_manager.h"
@@ -252,7 +253,10 @@
             std::make_unique<FakeIdentityService>(&mock_identity_manager_));
     host_delegate_ = std::make_unique<TestingDriveFsHostDelegate>(
         connector_factory_->CreateConnector(), account_id_);
-    host_ = std::make_unique<DriveFsHost>(profile_path_, host_delegate_.get());
+    auto timer = std::make_unique<base::MockOneShotTimer>();
+    timer_ = timer.get();
+    host_ = std::make_unique<DriveFsHost>(profile_path_, host_delegate_.get(),
+                                          std::move(timer));
   }
 
   void TearDown() override {
@@ -350,6 +354,7 @@
   std::unique_ptr<service_manager::TestConnectorFactory> connector_factory_;
   std::unique_ptr<TestingDriveFsHostDelegate> host_delegate_;
   std::unique_ptr<DriveFsHost> host_;
+  base::MockOneShotTimer* timer_;
 
   mojo::Binding<mojom::DriveFsBootstrap> bootstrap_binding_;
   mojom::DriveFsRequest drive_fs_request_;
@@ -533,6 +538,35 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(DriveFsHostTest, BreakConnectionAfterMount) {
+  ASSERT_NO_FATAL_FAILURE(DoMount());
+  base::Optional<base::TimeDelta> empty;
+  EXPECT_CALL(*host_delegate_, OnUnmounted(empty));
+  delegate_ptr_.reset();
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(DriveFsHostTest, BreakConnectionBeforeMount) {
+  auto token = StartMount();
+  DispatchMountSuccessEvent(token);
+  EXPECT_FALSE(host_->IsMounted());
+
+  base::Optional<base::TimeDelta> empty;
+  EXPECT_CALL(*host_delegate_, OnMountFailed(empty));
+  delegate_ptr_.reset();
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(DriveFsHostTest, MountTimeout) {
+  auto token = StartMount();
+  DispatchMountSuccessEvent(token);
+  EXPECT_FALSE(host_->IsMounted());
+
+  base::Optional<base::TimeDelta> empty;
+  EXPECT_CALL(*host_delegate_, OnMountFailed(empty));
+  timer_->Fire();
+}
+
 TEST_F(DriveFsHostTest, UnsupportedAccountTypes) {
   EXPECT_CALL(*disk_manager_, MountPath(_, _, _, _, _, _)).Times(0);
   const AccountId unsupported_accounts[] = {
@@ -543,7 +577,9 @@
   for (auto& account : unsupported_accounts) {
     host_delegate_ = std::make_unique<TestingDriveFsHostDelegate>(
         connector_factory_->CreateConnector(), account);
-    host_ = std::make_unique<DriveFsHost>(profile_path_, host_delegate_.get());
+    host_ = std::make_unique<DriveFsHost>(
+        profile_path_, host_delegate_.get(),
+        std::make_unique<base::MockOneShotTimer>());
     EXPECT_FALSE(host_->Mount());
     EXPECT_FALSE(host_->IsMounted());
   }
diff --git a/chromeos/dbus/machine_learning_client.cc b/chromeos/dbus/machine_learning_client.cc
index 26c1bf1..e17ff9ba 100644
--- a/chromeos/dbus/machine_learning_client.cc
+++ b/chromeos/dbus/machine_learning_client.cc
@@ -37,8 +37,9 @@
  protected:
   // DBusClient:
   void Init(dbus::Bus* const bus) override {
-    ml_service_proxy_ = bus->GetObjectProxy(
-        ml::kMlServiceName, dbus::ObjectPath(ml::kMlServicePath));
+    ml_service_proxy_ =
+        bus->GetObjectProxy(ml::kMachineLearningServiceName,
+                            dbus::ObjectPath(ml::kMachineLearningServicePath));
   }
 
  private:
@@ -58,7 +59,7 @@
     }
 
     // Call the bootstrap D-Bus method.
-    dbus::MethodCall method_call(ml::kMlServiceName,
+    dbus::MethodCall method_call(ml::kMachineLearningInterfaceName,
                                  ml::kBootstrapMojoConnectionMethod);
     dbus::MessageWriter writer(&method_call);
     writer.AppendFileDescriptor(fd.get());
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index 6486e029..71c743d 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -295,6 +295,8 @@
     dictionary->SetKey(shill::kEapMethodProperty, base::Value(eap_method()));
     dictionary->SetKey(shill::kWifiFrequency, base::Value(frequency_));
     dictionary->SetKey(shill::kWifiHexSsid, base::Value(GetHexSsid()));
+    dictionary->SetKey(shill::kTetheringProperty,
+                       base::Value(tethering_state_));
   }
 
   // Mobile properties
diff --git a/chromeos/network/onc/onc_signature.cc b/chromeos/network/onc/onc_signature.cc
index 85e2e0d9..77ecfbd 100644
--- a/chromeos/network/onc/onc_signature.cc
+++ b/chromeos/network/onc/onc_signature.cc
@@ -239,6 +239,7 @@
     {::onc::wifi::kFrequency, &kIntegerSignature},
     {::onc::wifi::kFrequencyList, &kIntegerListSignature},
     {::onc::wifi::kSignalStrength, &kIntegerSignature},
+    {::onc::wifi::kTetheringState, &kStringSignature},
     {NULL}};
 
 const OncFieldSignature wimax_fields[] = {
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc
index 22ad9831..4fec42d5 100644
--- a/chromeos/network/onc/onc_translation_tables.cc
+++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -148,6 +148,7 @@
     // This field is converted during translation, see onc_translator_*.
     // { ::onc::wifi::kSecurity, shill::kSecurityClassProperty },
     {::onc::wifi::kSignalStrength, shill::kSignalStrengthProperty},
+    {::onc::wifi::kTetheringState, shill::kTetheringProperty},
     {NULL}};
 
 const FieldTranslationEntry wimax_fields[] = {
@@ -383,6 +384,15 @@
     {::onc::cellular::kRoamingRoaming, shill::kRoamingStateRoaming},
     {NULL}};
 
+const StringTranslationEntry kTetheringStateTable[] = {
+    {::onc::tethering_state::kTetheringConfirmedState,
+     shill::kTetheringConfirmedState},
+    {::onc::tethering_state::kTetheringNotDetectedState,
+     shill::kTetheringNotDetectedState},
+    {::onc::tethering_state::kTetheringSuspectedState,
+     shill::kTetheringSuspectedState},
+    {NULL}};
+
 // This must contain only Shill Device properties and no Service properties.
 // For Service properties see cellular_fields.
 const FieldTranslationEntry kCellularDeviceTable[] = {
diff --git a/chromeos/network/onc/onc_translation_tables.h b/chromeos/network/onc/onc_translation_tables.h
index 058c4f23..1eb01a18 100644
--- a/chromeos/network/onc/onc_translation_tables.h
+++ b/chromeos/network/onc/onc_translation_tables.h
@@ -35,6 +35,7 @@
 CHROMEOS_EXPORT extern const StringTranslationEntry kActivationStateTable[];
 CHROMEOS_EXPORT extern const StringTranslationEntry kNetworkTechnologyTable[];
 CHROMEOS_EXPORT extern const StringTranslationEntry kRoamingStateTable[];
+CHROMEOS_EXPORT extern const StringTranslationEntry kTetheringStateTable[];
 
 // A separate translation table for cellular properties that are stored in a
 // Shill Device instead of a Service. The |shill_property_name| entries
diff --git a/chromeos/services/assistant/BUILD.gn b/chromeos/services/assistant/BUILD.gn
index dd0ec832..bb7b675e 100644
--- a/chromeos/services/assistant/BUILD.gn
+++ b/chromeos/services/assistant/BUILD.gn
@@ -27,7 +27,6 @@
     "//build/util:webkit_version",
     "//chromeos",
     "//chromeos/assistant:buildflags",
-    "//chromeos/services/assistant/public/cpp:cpp",
     "//chromeos/services/assistant/public/mojom",
     "//components/account_id",
     "//services/device/public/mojom",
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index 193f538..13bdc08 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -57,6 +57,9 @@
       background_thread_("background thread"),
       weak_factory_(this) {
   background_thread_.Start();
+  background_thread_.task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&AssistantManagerServiceImpl::SetVolumeHack,
+                                base::Unretained(this)));
   connector->BindInterface(ash::mojom::kServiceName,
                            &voice_interaction_controller_);
 
@@ -484,11 +487,6 @@
   platform_api_.OnHotwordEnabled(enabled);
 }
 
-void AssistantManagerServiceImpl::OnVoiceInteractionSetupCompleted(
-    bool completed) {
-  UpdateDeviceLocale(completed);
-}
-
 void AssistantManagerServiceImpl::StartAssistantInternal(
     const std::string& access_token,
     const std::string& arc_version) {
@@ -566,27 +564,8 @@
   device_settings_update->set_assistant_device_type(
       assistant::AssistantDevice::CROS);
 
-  // Device settings update result is not handled because it is not included in
-  // the SettingsUiUpdateResult.
-  SendUpdateSettingsUiRequest(update.SerializeAsString(), base::DoNothing());
-
-  // Update device locale if voice interaction setup is completed.
-  AssistantManagerServiceImpl::IsVoiceInteractionSetupCompleted(
-      base::BindOnce(&AssistantManagerServiceImpl::UpdateDeviceLocale,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void AssistantManagerServiceImpl::UpdateDeviceLocale(bool is_setup_completed) {
-  DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
-
-  if (!is_setup_completed)
-    return;
-
-  // Update device locale.
-  assistant::SettingsUiUpdate update;
-  assistant::AssistantDeviceSettingsUpdate* device_settings_update =
-      update.mutable_assistant_device_settings_update()
-          ->add_assistant_device_settings_update();
+  VLOG(1) << "Update assistant device locale: "
+          << base::i18n::GetConfiguredLocale();
   device_settings_update->mutable_device_settings()->set_locale(
       base::i18n::GetConfiguredLocale());
 
@@ -616,24 +595,15 @@
   interaction_subscribers_.ForAllPtrs([is_mic_open](auto* ptr) {
     ptr->OnInteractionStarted(/*is_voice_interaction=*/is_mic_open);
   });
-  platform_api_.GetAudioInputProvider()
-      .GetAudioInput()
-      .OnConversationTurnStarted();
 }
 
 void AssistantManagerServiceImpl::OnConversationTurnFinishedOnMainThread(
-    assistant_client::ConversationStateListener::Resolution resolution) {
-  platform_api_.GetAudioInputProvider()
-      .GetAudioInput()
-      .OnConversationTurnFinished();
+    Resolution resolution) {
   switch (resolution) {
     // Interaction ended normally.
-    // Note that TIMEOUT here does not refer to server timeout, but rather mic
-    // timeout due to speech inactivity. As this case does not require special
-    // UI logic, it is treated here as a normal interaction completion.
     case Resolution::NORMAL:
     case Resolution::NORMAL_WITH_FOLLOW_ON:
-    case Resolution::TIMEOUT:
+    case Resolution::NO_RESPONSE:
       interaction_subscribers_.ForAllPtrs([](auto* ptr) {
         ptr->OnInteractionFinished(
             mojom::AssistantInteractionResolution::kNormal);
@@ -647,11 +617,11 @@
             mojom::AssistantInteractionResolution::kInterruption);
       });
       break;
-    // Interaction ended due to multi-device hotword loss.
-    case Resolution::NO_RESPONSE:
+    // Interaction ended due to mic timeout.
+    case Resolution::TIMEOUT:
       interaction_subscribers_.ForAllPtrs([](auto* ptr) {
         ptr->OnInteractionFinished(
-            mojom::AssistantInteractionResolution::kMultiDeviceHotwordLoss);
+            mojom::AssistantInteractionResolution::kMicTimeout);
       });
       break;
     // Interaction ended due to error.
@@ -783,5 +753,10 @@
   std::move(callback).Run();
 }
 
+void AssistantManagerServiceImpl::SetVolumeHack() {
+  DCHECK(background_thread_.task_runner()->BelongsToCurrentThread());
+  platform_api_.GetFileProvider().WriteFile("assistant/volume/system", "1.0");
+}
+
 }  // namespace assistant
 }  // namespace chromeos
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index 23ab027..9ec1938 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -135,7 +135,7 @@
   void OnVoiceInteractionSettingsEnabled(bool enabled) override;
   void OnVoiceInteractionContextEnabled(bool enabled) override;
   void OnVoiceInteractionHotwordEnabled(bool enabled) override;
-  void OnVoiceInteractionSetupCompleted(bool completed) override;
+  void OnVoiceInteractionSetupCompleted(bool completed) override {}
   void OnAssistantFeatureAllowedChanged(
       ash::mojom::AssistantAllowedState state) override {}
 
@@ -149,13 +149,9 @@
 
   std::string BuildUserAgent(const std::string& arc_version) const;
 
-  // Update device id, type, and call |UpdateDeviceLocale| when assistant
-  // service starts.
+  // Update device id, type and locale
   void UpdateDeviceSettings();
 
-  // Update device locale if |is_setup_completed| is true;
-  void UpdateDeviceLocale(bool is_setup_completed);
-
   void HandleGetSettingsResponse(
       base::RepeatingCallback<void(const std::string&)> callback,
       const std::string& settings);
@@ -197,6 +193,10 @@
   void OnAssistantScreenshotReceived(base::OnceClosure on_done,
                                      const std::vector<uint8_t>& jpg_image);
 
+  // Writes a file to assistant's path to force assistant volume to 100%.
+  // TODO(muyuanli): Remove once AudioOutputImpl is checked in.
+  void SetVolumeHack();
+
   State state_ = State::STOPPED;
   PlatformApiImpl platform_api_;
   bool enable_hotword_;
diff --git a/chromeos/services/assistant/platform/audio_input_provider_impl.cc b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
index 7c68223..bac0eb9 100644
--- a/chromeos/services/assistant/platform/audio_input_provider_impl.cc
+++ b/chromeos/services/assistant/platform/audio_input_provider_impl.cc
@@ -6,7 +6,6 @@
 
 #include "base/logging.h"
 #include "base/stl_util.h"
-#include "chromeos/services/assistant/public/cpp/features.h"
 #include "libassistant/shared/public/platform_audio_buffer.h"
 #include "media/audio/audio_device_description.h"
 #include "media/base/audio_parameters.h"
@@ -25,72 +24,6 @@
     16000 /* sample_rate */, assistant_client::INTERLEAVED_S32, 1 /* channels */
 };
 
-class DefaultHotwordStateManager : public AudioInputImpl::HotwordStateManager {
- public:
-  DefaultHotwordStateManager() = default;
-  ~DefaultHotwordStateManager() override = default;
-
-  void OnConversationTurnStarted() override {}
-  void OnConversationTurnFinished() override {}
-  void OnCaptureDataArrived() override {}
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DefaultHotwordStateManager);
-};
-
-class DspHotwordStateManager : public AudioInputImpl::HotwordStateManager {
- public:
-  DspHotwordStateManager(scoped_refptr<base::SequencedTaskRunner> task_runner,
-                         AudioInputImpl* input)
-      : task_runner_(task_runner), input_(input) {
-    second_phase_timer_.SetTaskRunner(task_runner_);
-  }
-
-  // HotwordStateManager overrides:
-  void OnConversationTurnStarted() override {
-    if (second_phase_timer_.IsRunning()) {
-      DCHECK(stream_state_ == StreamState::HOTWORD);
-      second_phase_timer_.Stop();
-      stream_state_ = StreamState::NORMAL;
-    } else {
-      // Handles user click on mic button.
-      input_->RecreateAudioInputStream(false /* hotword */);
-    }
-  }
-
-  void OnConversationTurnFinished() override {
-    input_->RecreateAudioInputStream(true /* hotword */);
-    stream_state_ = StreamState::HOTWORD;
-  }
-
-  void OnCaptureDataArrived() override {
-    if (stream_state_ == StreamState::HOTWORD &&
-        !second_phase_timer_.IsRunning()) {
-      // 1s from now, if OnConversationTurnStarted is not called, we assume that
-      // libassistant has rejected the hotword supplied by DSP. Thus, we reset
-      // and reopen the device on hotword state.
-      second_phase_timer_.Start(
-          FROM_HERE, base::TimeDelta::FromSeconds(1),
-          base::BindRepeating(
-              &DspHotwordStateManager::OnConversationTurnFinished,
-              base::Unretained(this)));
-    }
-  }
-
- private:
-  enum class StreamState {
-    HOTWORD,
-    NORMAL,
-  };
-
-  StreamState stream_state_ = StreamState::HOTWORD;
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  base::OneShotTimer second_phase_timer_;
-  AudioInputImpl* input_;
-
-  DISALLOW_COPY_AND_ASSIGN(DspHotwordStateManager);
-};
-
 }  // namespace
 
 AudioInputBufferImpl::AudioInputBufferImpl(const void* data,
@@ -116,24 +49,28 @@
   return frame_count_;
 }
 
-AudioInputImpl::AudioInputImpl(service_manager::Connector* connector,
-                               bool default_on)
-    : default_on_(default_on),
+AudioInputImpl::AudioInputImpl(
+    std::unique_ptr<service_manager::Connector> connector,
+    bool default_on)
+    : source_(audio::CreateInputDevice(
+          std::move(connector),
+          media::AudioDeviceDescription::kDefaultDeviceId)),
+      default_on_(default_on),
       task_runner_(base::ThreadTaskRunnerHandle::Get()),
       weak_factory_(this) {
   DETACH_FROM_SEQUENCE(observer_sequence_checker_);
-
-  if (IsDspHotwordEnabled()) {
-    state_manager_ =
-        std::make_unique<DspHotwordStateManager>(task_runner_, this);
-  } else {
-    state_manager_ = std::make_unique<DefaultHotwordStateManager>();
-  }
+  // AUDIO_PCM_LINEAR and AUDIO_PCM_LOW_LATENCY are the same on CRAS.
+  source_->Initialize(
+      media::AudioParameters(
+          media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+          media::CHANNEL_LAYOUT_MONO, kFormat.sample_rate,
+          kFormat.sample_rate / 10 /* buffer size for 100 ms */),
+      this);
 }
 
 AudioInputImpl::~AudioInputImpl() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  source_.reset();
+  source_->Stop();
 }
 
 void AudioInputImpl::Capture(const media::AudioBus* audio_source,
@@ -141,9 +78,6 @@
                              double volume,
                              bool key_pressed) {
   DCHECK_EQ(kFormat.num_channels, audio_source->channels());
-
-  state_manager_->OnCaptureDataArrived();
-
   std::vector<int32_t> buffer(kFormat.num_channels * audio_source->frames());
   audio_source->ToInterleaved<media::SignedInt32SampleTypeTraits>(
       audio_source->frames(), buffer.data());
@@ -216,16 +150,6 @@
   }
 }
 
-void AudioInputImpl::OnConversationTurnStarted() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  state_manager_->OnConversationTurnStarted();
-}
-
-void AudioInputImpl::OnConversationTurnFinished() {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  state_manager_->OnConversationTurnFinished();
-}
-
 void AudioInputImpl::OnHotwordEnabled(bool enable) {
   default_on_ = enable;
   if (default_on_)
@@ -236,44 +160,22 @@
 
 void AudioInputImpl::StartRecording() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(!source_);
-  RecreateAudioInputStream(IsDspHotwordEnabled());
+  source_->Start();
 }
 
 void AudioInputImpl::StopRecording() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  if (source_) {
-    source_->Stop();
-    source_.reset();
-  }
-}
-
-void AudioInputImpl::RecreateAudioInputStream(bool hotword) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  StopRecording();
-
-  source_ = audio::CreateInputDevice(
-      connector_->Clone(), media::AudioDeviceDescription::kDefaultDeviceId);
-  // AUDIO_PCM_LINEAR and AUDIO_PCM_LOW_LATENCY are the same on CRAS.
-  auto param = media::AudioParameters(
-      media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-      kFormat.sample_rate,
-      kFormat.sample_rate / 10 /* buffer size for 100 ms */);
-  if (hotword) {
-    param.set_effects(media::AudioParameters::PlatformEffectsMask::HOTWORD);
-  }
-  source_->Initialize(param, this);
-  source_->Start();
+  source_->Stop();
 }
 
 AudioInputProviderImpl::AudioInputProviderImpl(
     service_manager::Connector* connector,
     bool default_on)
-    : audio_input_(connector, default_on) {}
+    : audio_input_(connector->Clone(), default_on) {}
 
 AudioInputProviderImpl::~AudioInputProviderImpl() = default;
 
-AudioInputImpl& AudioInputProviderImpl::GetAudioInput() {
+assistant_client::AudioInput& AudioInputProviderImpl::GetAudioInput() {
   return audio_input_;
 }
 
diff --git a/chromeos/services/assistant/platform/audio_input_provider_impl.h b/chromeos/services/assistant/platform/audio_input_provider_impl.h
index 1ddb8ef7..050a914 100644
--- a/chromeos/services/assistant/platform/audio_input_provider_impl.h
+++ b/chromeos/services/assistant/platform/audio_input_provider_impl.h
@@ -49,17 +49,10 @@
 class AudioInputImpl : public assistant_client::AudioInput,
                        public media::AudioCapturerSource::CaptureCallback {
  public:
-  AudioInputImpl(service_manager::Connector* connector, bool default_on);
+  AudioInputImpl(std::unique_ptr<service_manager::Connector> connector,
+                 bool default_on);
   ~AudioInputImpl() override;
 
-  class HotwordStateManager {
-   public:
-    virtual ~HotwordStateManager() = default;
-    virtual void OnConversationTurnStarted() = 0;
-    virtual void OnConversationTurnFinished() = 0;
-    virtual void OnCaptureDataArrived() = 0;
-  };
-
   // media::AudioCapturerSource::CaptureCallback overrides:
   void Capture(const media::AudioBus* audio_source,
                int audio_delay_milliseconds,
@@ -78,10 +71,6 @@
 
   // Called when the mic state associated with the interaction is changed.
   void SetMicState(bool mic_open);
-  void OnConversationTurnStarted();
-  void OnConversationTurnFinished();
-
-  void RecreateAudioInputStream(bool hotword);
 
   // Called when hotword enabled status changed.
   void OnHotwordEnabled(bool enable);
@@ -104,12 +93,8 @@
   // sequence.
   SEQUENCE_CHECKER(observer_sequence_checker_);
 
-  service_manager::Connector* connector_;
-
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
-  std::unique_ptr<HotwordStateManager> state_manager_;
-
   base::WeakPtrFactory<AudioInputImpl> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(AudioInputImpl);
 };
@@ -121,7 +106,7 @@
   ~AudioInputProviderImpl() override;
 
   // assistant_client::AudioInputProvider overrides:
-  AudioInputImpl& GetAudioInput() override;
+  assistant_client::AudioInput& GetAudioInput() override;
   int64_t GetCurrentAudioTime() override;
 
   // Called when the mic state associated with the interaction is changed.
diff --git a/chromeos/services/assistant/platform_api_impl.cc b/chromeos/services/assistant/platform_api_impl.cc
index 9025073a..94de7bb 100644
--- a/chromeos/services/assistant/platform_api_impl.cc
+++ b/chromeos/services/assistant/platform_api_impl.cc
@@ -81,7 +81,7 @@
 
 PlatformApiImpl::~PlatformApiImpl() = default;
 
-AudioInputProviderImpl& PlatformApiImpl::GetAudioInputProvider() {
+AudioInputProvider& PlatformApiImpl::GetAudioInputProvider() {
   return audio_input_provider_;
 }
 
diff --git a/chromeos/services/assistant/platform_api_impl.h b/chromeos/services/assistant/platform_api_impl.h
index 736321a..06d473e 100644
--- a/chromeos/services/assistant/platform_api_impl.h
+++ b/chromeos/services/assistant/platform_api_impl.h
@@ -39,7 +39,7 @@
   ~PlatformApiImpl() override;
 
   // assistant_client::PlatformApi overrides
-  AudioInputProviderImpl& GetAudioInputProvider() override;
+  assistant_client::AudioInputProvider& GetAudioInputProvider() override;
   assistant_client::AudioOutputProvider& GetAudioOutputProvider() override;
   assistant_client::AuthProvider& GetAuthProvider() override;
   assistant_client::FileProvider& GetFileProvider() override;
diff --git a/chromeos/services/assistant/public/cpp/BUILD.gn b/chromeos/services/assistant/public/cpp/BUILD.gn
deleted file mode 100644
index c81f2fa..0000000
--- a/chromeos/services/assistant/public/cpp/BUILD.gn
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2018 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.
-
-# C++ headers and sources that can be used outside ash.
-component("cpp") {
-  sources = [
-    "features.cc",
-    "features.h",
-  ]
-  public_deps = [
-    "//base",
-  ]
-}
diff --git a/chromeos/services/assistant/public/cpp/features.cc b/chromeos/services/assistant/public/cpp/features.cc
deleted file mode 100644
index a088759..0000000
--- a/chromeos/services/assistant/public/cpp/features.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 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 "chromeos/services/assistant/public/cpp/features.h"
-
-#include "base/feature_list.h"
-#include "base/metrics/field_trial_params.h"
-
-namespace chromeos {
-namespace assistant {
-
-const base::Feature kEnableDspHotword{"EnableDspHotword",
-                                      base::FEATURE_DISABLED_BY_DEFAULT};
-
-bool IsDspHotwordEnabled() {
-  return base::FeatureList::IsEnabled(kEnableDspHotword);
-}
-
-}  // namespace assistant
-}  // namespace chromeos
diff --git a/chromeos/services/assistant/public/cpp/features.h b/chromeos/services/assistant/public/cpp/features.h
deleted file mode 100644
index 69242ab..0000000
--- a/chromeos/services/assistant/public/cpp/features.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2018 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 CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_FEATURES_H_
-#define CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_FEATURES_H_
-
-namespace base {
-struct Feature;
-}  // namespace base
-
-namespace chromeos {
-namespace assistant {
-
-// Enables DSP for hotword detection.
-extern const base::Feature kEnableDspHotword;
-
-bool IsDspHotwordEnabled();
-
-}  // namespace assistant
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_ASSISTANT_PUBLIC_CPP_FEATURES_H_
diff --git a/chromeos/services/assistant/public/mojom/assistant.mojom b/chromeos/services/assistant/public/mojom/assistant.mojom
index 28b2cc6..9738affa 100644
--- a/chromeos/services/assistant/public/mojom/assistant.mojom
+++ b/chromeos/services/assistant/public/mojom/assistant.mojom
@@ -170,8 +170,8 @@
   kInterruption,
   // Assistant interaction completed due to error.
   kError,
-  // Assistant interaction completed due to multi-device hotword loss.
-  kMultiDeviceHotwordLoss,
+  // Assistant interaction completed due to mic timeout.
+  kMicTimeout,
 };
 
 // Models an Assistant suggestion.
diff --git a/chromeos/services/machine_learning/public/cpp/service_connection.cc b/chromeos/services/machine_learning/public/cpp/service_connection.cc
index 3a2e604..2a214742 100644
--- a/chromeos/services/machine_learning/public/cpp/service_connection.cc
+++ b/chromeos/services/machine_learning/public/cpp/service_connection.cc
@@ -21,10 +21,11 @@
   return service_connection.get();
 }
 
-void ServiceConnection::BindModelProvider(mojom::ModelProviderRequest request) {
+void ServiceConnection::LoadModel(mojom::ModelSpecPtr spec,
+                                  mojom::ModelRequest request) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   BindMachineLearningServiceIfNeeded();
-  machine_learning_service_->GetModelProvider(std::move(request));
+  machine_learning_service_->LoadModel(std::move(spec), std::move(request));
 }
 
 void ServiceConnection::BindMachineLearningServiceIfNeeded() {
diff --git a/chromeos/services/machine_learning/public/cpp/service_connection.h b/chromeos/services/machine_learning/public/cpp/service_connection.h
index da57df94..a992515 100644
--- a/chromeos/services/machine_learning/public/cpp/service_connection.h
+++ b/chromeos/services/machine_learning/public/cpp/service_connection.h
@@ -16,18 +16,20 @@
 // Encapsulates a connection to the Chrome OS ML Service daemon via its Mojo
 // interface.
 // Usage:
-//   chromeos::machine_learning::mojom::ModelProviderPtr model_provider;
+//   chromeos::machine_learning::mojom::ModelPtr model;
+//   chromeos::machine_learning::mojom::ModelSpec spec;
 //   chromeos::machine_learning::ServiceConnection::GetInstance()
-//       ->BindModelProvider(mojom::MakeRequest(&model_provider));
-//   // Use model_provider ...
+//       ->LoadModel(spec, mojom::MakeRequest(&model));
+//   // Use model ...
 // Sequencing: Must be used on a single sequence (may be created on another).
 class ServiceConnection {
  public:
   static ServiceConnection* GetInstance();
 
-  // Instruct ML daemon to bind a ModelProvider implementation to |request|.
+  // Instruct ML daemon to load the model specified in |spec|, binding a Model
+  // implementation to |request|.
   // Bootstraps the initial Mojo connection to the daemon if necessary.
-  void BindModelProvider(mojom::ModelProviderRequest request);
+  void LoadModel(mojom::ModelSpecPtr spec, mojom::ModelRequest request);
 
  private:
   friend class base::NoDestructor<ServiceConnection>;
diff --git a/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc b/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc
index 7ed2d7d..4d2626b 100644
--- a/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc
+++ b/chromeos/services/machine_learning/public/cpp/service_connection_unittest.cc
@@ -42,9 +42,10 @@
 
 // Tests that BindModelProvider runs OK (no crash) in a basic Mojo environment.
 TEST_F(ServiceConnectionTest, BindModelProvider) {
-  mojom::ModelProviderPtr model_provider;
-  ServiceConnection::GetInstance()->BindModelProvider(
-      mojo::MakeRequest(&model_provider));
+  mojom::ModelPtr model;
+  mojom::ModelSpecPtr spec = mojom::ModelSpec::New(mojom::ModelId::UNKNOWN);
+  ServiceConnection::GetInstance()->LoadModel(std::move(spec),
+                                              mojo::MakeRequest(&model));
 }
 
 }  // namespace
diff --git a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
index 3095454..13e7166f9 100644
--- a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
+++ b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
@@ -13,12 +13,8 @@
 
 import "chromeos/services/machine_learning/public/mojom/model.mojom";
 
-interface ModelProvider {
-  // The ModelId inside ModelSpec is used to specify the model to be loaded.
-  LoadModel(ModelSpec spec, Model& request);
-};
-
 // Top-level interface between Chromium and the ML Service daemon.
 interface MachineLearningService {
-  GetModelProvider(ModelProvider& request);
+  // The ModelId inside ModelSpec is used to specify the model to be loaded.
+  LoadModel(ModelSpec spec, Model& request);
 };
diff --git a/components/OWNERS b/components/OWNERS
index 973887a..4544b2b 100644
--- a/components/OWNERS
+++ b/components/OWNERS
@@ -13,6 +13,7 @@
 per-file dom_distiller_strings.grdp=file://components/dom_distiller/OWNERS
 per-file error_page_strings.grdp=file://components/error_page/OWNERS
 per-file ntp_snippets_strings.grdp=file://components/ntp_snippets/OWNERS
+per-file nux_google_apps_strings.grdp=file://components/nux_google_apps/OWNERS
 per-file omnibox_strings.grdp=file://components/omnibox/OWNERS
 per-file page_info_strings.grdp=file://chrome/browser/ui/page_info/OWNERS
 per-file page_info_strings_grdp=file://chrome/browser/ui/page_info/OWNERS
diff --git a/components/arc/common/net.mojom b/components/arc/common/net.mojom
index c1d51d9..80de17f 100644
--- a/components/arc/common/net.mojom
+++ b/components/arc/common/net.mojom
@@ -65,6 +65,19 @@
   WPA_EAP,
 };
 
+// Tethering state of a |NetworkConfiguration| as a client.
+[Extensible]
+enum TetheringClientState {
+  // Tethering state is detected and confirmed.
+  CONFIRMED,
+
+  // Tethering state is not detected.
+  NOT_DETECTED,
+
+  // Tethering data is suspected and can be |CONFIRMED| in the future.
+  SUSPECTED,
+};
+
 struct WiFi {
   string bssid;
   int32 frequency;
@@ -93,6 +106,7 @@
   string? mac_address;
   NetworkType type;
   WiFi? wifi;
+  [MinVersion=8] TetheringClientState tethering_client_state;
 };
 
 struct WifiConfiguration {
diff --git a/components/arc/net/arc_net_host_impl.cc b/components/arc/net/arc_net_host_impl.cc
index 2b8d8fd6..4c60b32 100644
--- a/components/arc/net/arc_net_host_impl.cc
+++ b/components/arc/net/arc_net_host_impl.cc
@@ -84,6 +84,18 @@
   return arc::mojom::SecurityType::NONE;
 }
 
+arc::mojom::TetheringClientState TranslateTetheringState(
+    const std::string& tethering_state) {
+  if (tethering_state == onc::tethering_state::kTetheringConfirmedState)
+    return arc::mojom::TetheringClientState::CONFIRMED;
+  else if (tethering_state == onc::tethering_state::kTetheringNotDetectedState)
+    return arc::mojom::TetheringClientState::NOT_DETECTED;
+  else if (tethering_state == onc::tethering_state::kTetheringSuspectedState)
+    return arc::mojom::TetheringClientState::SUSPECTED;
+  NOTREACHED() << "Invalid tethering state: " << tethering_state;
+  return arc::mojom::TetheringClientState::NOT_DETECTED;
+}
+
 arc::mojom::WiFiPtr TranslateONCWifi(const base::DictionaryValue* dict) {
   arc::mojom::WiFiPtr wifi = arc::mojom::WiFi::New();
 
@@ -106,6 +118,14 @@
   return wifi;
 }
 
+// Extracts WiFi's tethering client state from a dictionary of WiFi properties.
+arc::mojom::TetheringClientState GetWifiTetheringClientState(
+    const base::DictionaryValue* dict) {
+  std::string tethering_state;
+  dict->GetString(onc::wifi::kTetheringState, &tethering_state);
+  return TranslateTetheringState(tethering_state);
+}
+
 std::vector<std::string> TranslateStringArray(const base::ListValue* list) {
   std::vector<std::string> strings;
 
@@ -181,6 +201,9 @@
                                     arc::mojom::NetworkConfiguration* mojo) {
   std::string type = GetStringFromOncDictionary(
       dict, onc::network_config::kType, true /* required */);
+  // This property will be updated as required by the relevant network types
+  // below.
+  mojo->tethering_client_state = arc::mojom::TetheringClientState::NOT_DETECTED;
   if (type == onc::network_type::kCellular) {
     mojo->type = arc::mojom::NetworkType::CELLULAR;
   } else if (type == onc::network_type::kEthernet) {
@@ -189,11 +212,11 @@
     mojo->type = arc::mojom::NetworkType::VPN;
   } else if (type == onc::network_type::kWiFi) {
     mojo->type = arc::mojom::NetworkType::WIFI;
-
     const base::DictionaryValue* wifi_dict = nullptr;
     dict->GetDictionary(onc::network_config::kWiFi, &wifi_dict);
     DCHECK(wifi_dict);
     mojo->wifi = TranslateONCWifi(wifi_dict);
+    mojo->tethering_client_state = GetWifiTetheringClientState(wifi_dict);
   } else if (type == onc::network_type::kWimax) {
     mojo->type = arc::mojom::NetworkType::WIMAX;
   } else {
diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc
index 9a5ca3c3..7669483c 100644
--- a/components/autofill/content/renderer/password_generation_agent.cc
+++ b/components/autofill/content/renderer/password_generation_agent.cc
@@ -628,13 +628,14 @@
   }
 
   if (element.Value().IsEmpty()) {
+    // The call may pop up a generation prompt.
+    MaybeOfferAutomaticGeneration();
+    // Tell the browser that the state isn't "editing" anymore. The browser
+    // should hide the editing prompt if it wasn't replaced above.
     if (password_is_generated_) {
       // User generated a password and then deleted it.
       PasswordNoLongerGenerated();
     }
-
-    // Offer generation again.
-    MaybeOfferAutomaticGeneration();
   } else if (password_is_generated_) {
     password_edited_ = true;
     // Mirror edits to any confirmation password fields.
diff --git a/components/autofill/core/browser/autofill_handler.cc b/components/autofill/core/browser/autofill_handler.cc
index 6255c45..480c1fa 100644
--- a/components/autofill/core/browser/autofill_handler.cc
+++ b/components/autofill/core/browser/autofill_handler.cc
@@ -136,6 +136,65 @@
   driver_->SendFormDataToRenderer(query_id, action, data);
 }
 
+bool AutofillHandler::GetCachedFormAndField(const FormData& form,
+                                            const FormFieldData& field,
+                                            FormStructure** form_structure,
+                                            AutofillField** autofill_field) {
+  // Find the FormStructure that corresponds to |form|.
+  // If we do not have this form in our cache but it is parseable, we'll add it
+  // in the call to |UpdateCachedForm()|.
+  if (!FindCachedForm(form, form_structure) &&
+      !FormStructure(form).ShouldBeParsed()) {
+    return false;
+  }
+
+  // Update the cached form to reflect any dynamic changes to the form data, if
+  // necessary.
+  if (!UpdateCachedForm(form, *form_structure, form_structure))
+    return false;
+
+  // No data to return if there are no auto-fillable fields.
+  if (!(*form_structure)->autofill_count())
+    return false;
+
+  // Find the AutofillField that corresponds to |field|.
+  *autofill_field = nullptr;
+  for (const auto& current : **form_structure) {
+    if (current->SameFieldAs(field)) {
+      *autofill_field = current.get();
+      break;
+    }
+  }
+
+  // Even though we always update the cache, the field might not exist if the
+  // website disables autocomplete while the user is interacting with the form.
+  // See http://crbug.com/160476
+  return *autofill_field != nullptr;
+}
+
+bool AutofillHandler::UpdateCachedForm(const FormData& live_form,
+                                       const FormStructure* cached_form,
+                                       FormStructure** updated_form) {
+  bool needs_update =
+      (!cached_form || live_form.fields.size() != cached_form->field_count());
+  for (size_t i = 0; !needs_update && i < cached_form->field_count(); ++i)
+    needs_update = !cached_form->field(i)->SameFieldAs(live_form.fields[i]);
+
+  if (!needs_update)
+    return true;
+
+  // Note: We _must not_ remove the original version of the cached form from
+  // the list of |form_structures_|. Otherwise, we break parsing of the
+  // crowdsourcing server's response to our query.
+  if (!ParseForm(live_form, cached_form, updated_form))
+    return false;
+
+  // Annotate the updated form with its predicted types.
+  driver_->SendAutofillTypePredictionsToRenderer({*updated_form});
+
+  return true;
+}
+
 bool AutofillHandler::FindCachedForm(const FormData& form,
                                      FormStructure** form_structure) const {
   // Find the FormStructure that corresponds to |form|.
diff --git a/components/autofill/core/browser/autofill_handler.h b/components/autofill/core/browser/autofill_handler.h
index 74e76a5..8a9ec8f0 100644
--- a/components/autofill/core/browser/autofill_handler.h
+++ b/components/autofill/core/browser/autofill_handler.h
@@ -21,6 +21,7 @@
 
 namespace autofill {
 
+class AutofillField;
 struct FormData;
 struct FormFieldData;
 class FormStructure;
@@ -160,6 +161,23 @@
 
   AutofillDriver* driver() { return driver_; }
 
+  // Fills |form_structure| and |autofill_field| with the cached elements
+  // corresponding to |form| and |field|.  This might have the side-effect of
+  // updating the cache.  Returns false if the |form| is not autofillable, or if
+  // it is not already present in the cache and the cache is full.
+  bool GetCachedFormAndField(const FormData& form,
+                             const FormFieldData& field,
+                             FormStructure** form_structure,
+                             AutofillField** autofill_field) WARN_UNUSED_RESULT;
+
+  // Re-parses |live_form| and adds the result to |form_structures_|.
+  // |cached_form| should be a pointer to the existing version of the form, or
+  // NULL if no cached version exists.  The updated form is then written into
+  // |updated_form|.  Returns false if the cache could not be updated.
+  bool UpdateCachedForm(const FormData& live_form,
+                        const FormStructure* cached_form,
+                        FormStructure** updated_form) WARN_UNUSED_RESULT;
+
   // Fills |form_structure| cached element corresponding to |form|.
   // Returns false if the cached element was not found.
   bool FindCachedForm(const FormData& form,
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 590e2daa..f8af31b 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -1411,42 +1411,6 @@
   return submitted_form;
 }
 
-bool AutofillManager::GetCachedFormAndField(const FormData& form,
-                                            const FormFieldData& field,
-                                            FormStructure** form_structure,
-                                            AutofillField** autofill_field) {
-  // Find the FormStructure that corresponds to |form|.
-  // If we do not have this form in our cache but it is parseable, we'll add it
-  // in the call to |UpdateCachedForm()|.
-  if (!FindCachedForm(form, form_structure) &&
-      !FormStructure(form).ShouldBeParsed()) {
-    return false;
-  }
-
-  // Update the cached form to reflect any dynamic changes to the form data, if
-  // necessary.
-  if (!UpdateCachedForm(form, *form_structure, form_structure))
-    return false;
-
-  // No data to return if there are no auto-fillable fields.
-  if (!(*form_structure)->autofill_count())
-    return false;
-
-  // Find the AutofillField that corresponds to |field|.
-  *autofill_field = nullptr;
-  for (const auto& current : **form_structure) {
-    if (current->SameFieldAs(field)) {
-      *autofill_field = current.get();
-      break;
-    }
-  }
-
-  // Even though we always update the cache, the field might not exist if the
-  // website disables autocomplete while the user is interacting with the form.
-  // See http://crbug.com/160476
-  return *autofill_field != nullptr;
-}
-
 AutofillField* AutofillManager::GetAutofillField(const FormData& form,
                                                  const FormFieldData& field) {
   if (!personal_data_)
@@ -1475,29 +1439,6 @@
   return false;
 }
 
-bool AutofillManager::UpdateCachedForm(const FormData& live_form,
-                                       const FormStructure* cached_form,
-                                       FormStructure** updated_form) {
-  bool needs_update =
-      (!cached_form || live_form.fields.size() != cached_form->field_count());
-  for (size_t i = 0; !needs_update && i < cached_form->field_count(); ++i)
-    needs_update = !cached_form->field(i)->SameFieldAs(live_form.fields[i]);
-
-  if (!needs_update)
-    return true;
-
-  // Note: We _must not_ remove the original version of the cached form from
-  // the list of |form_structures_|. Otherwise, we break parsing of the
-  // crowdsourcing server's response to our query.
-  if (!ParseForm(live_form, cached_form, updated_form))
-    return false;
-
-  // Annotate the updated form with its predicted types.
-  driver()->SendAutofillTypePredictionsToRenderer({*updated_form});
-
-  return true;
-}
-
 std::vector<Suggestion> AutofillManager::GetProfileSuggestions(
     const FormStructure& form,
     const FormFieldData& field,
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 649daf0..6e936a9 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -409,15 +409,6 @@
   // or personal data.
   std::unique_ptr<FormStructure> ValidateSubmittedForm(const FormData& form);
 
-  // Fills |form_structure| and |autofill_field| with the cached elements
-  // corresponding to |form| and |field|.  This might have the side-effect of
-  // updating the cache.  Returns false if the |form| is not autofillable, or if
-  // it is not already present in the cache and the cache is full.
-  bool GetCachedFormAndField(const FormData& form,
-                             const FormFieldData& field,
-                             FormStructure** form_structure,
-                             AutofillField** autofill_field) WARN_UNUSED_RESULT;
-
   // Returns the field corresponding to |form| and |field| that can be
   // autofilled. Returns NULL if the field cannot be autofilled.
   AutofillField* GetAutofillField(const FormData& form,
@@ -428,14 +419,6 @@
   // |FieldTypeGroup|.
   bool FormHasAddressField(const FormData& form) WARN_UNUSED_RESULT;
 
-  // Re-parses |live_form| and adds the result to |form_structures_|.
-  // |cached_form| should be a pointer to the existing version of the form, or
-  // NULL if no cached version exists.  The updated form is then written into
-  // |updated_form|.  Returns false if the cache could not be updated.
-  bool UpdateCachedForm(const FormData& live_form,
-                        const FormStructure* cached_form,
-                        FormStructure** updated_form) WARN_UNUSED_RESULT;
-
   // Returns a list of values from the stored profiles that match |type| and the
   // value of |field| and returns the labels of the matching profiles. |labels|
   // is filled with the Profile label.
diff --git a/components/autofill/core/browser/credit_card.cc b/components/autofill/core/browser/credit_card.cc
index 70f44e2..bf5ff7af 100644
--- a/components/autofill/core/browser/credit_card.cc
+++ b/components/autofill/core/browser/credit_card.cc
@@ -208,89 +208,86 @@
   // MIR                    2200-2204                                  16
   // UnionPay               62                                         16-19
 
-  // Check for prefixes of length 1.
-  if (number.empty())
-    return kGenericCard;
-
-  if (number[0] == '4')
-    return kVisaCard;
-
-  // Check for prefixes of length 2.
-  if (number.size() < 2)
-    return kGenericCard;
-
-  int first_two_digits = 0;
-  if (!base::StringToInt(number.substr(0, 2), &first_two_digits))
-    return kGenericCard;
-
-  if (first_two_digits == 34 || first_two_digits == 37)
-    return kAmericanExpressCard;
-
-  if (first_two_digits == 36 ||
-      first_two_digits == 38 ||
-      first_two_digits == 39)
-    return kDinersCard;
-
-  if (first_two_digits >= 51 && first_two_digits <= 55)
-    return kMasterCard;
-
-  if (first_two_digits == 62)
-    return kUnionPay;
-
-  if (first_two_digits == 65)
-    return kDiscoverCard;
-
-  // Check for prefixes of length 3.
-  if (number.size() < 3)
-    return kGenericCard;
-
-  int first_three_digits = 0;
-  if (!base::StringToInt(number.substr(0, 3), &first_three_digits))
-    return kGenericCard;
-
-  if ((first_three_digits >= 300 && first_three_digits <= 305) ||
-      first_three_digits == 309)
-    return kDinersCard;
-
-  if (first_three_digits >= 644 && first_three_digits <= 649)
-    return kDiscoverCard;
-
-  // Check for prefixes of length 4.
-  if (number.size() < 4)
-    return kGenericCard;
-
-  int first_four_digits = 0;
-  if (!base::StringToInt(number.substr(0, 4), &first_four_digits))
-    return kGenericCard;
-
-  if (first_four_digits >= 2200 && first_four_digits <= 2204)
-    return kMirCard;
-
-  if (first_four_digits >= 2221 && first_four_digits <= 2720)
-    return kMasterCard;
-
-  if (first_four_digits >= 3528 && first_four_digits <= 3589)
-    return kJCBCard;
-
-  if (first_four_digits == 5067 || first_four_digits == 5090)
-    return kEloCard;
-
-  if (first_four_digits == 6011)
-    return kDiscoverCard;
+  // Determine the network for the given |number| by going from the longest
+  // (most specific) prefix to the shortest (most general) prefix.
+  base::string16 stripped_number = CreditCard::StripSeparators(number);
 
   // Check for prefixes of length 6.
-  if (number.size() < 6)
+  if (stripped_number.size() >= 6) {
+    int first_six_digits = 0;
+    if (!base::StringToInt(stripped_number.substr(0, 6), &first_six_digits))
+      return kGenericCard;
+
+    if (first_six_digits == 431274 || first_six_digits == 451416 ||
+        first_six_digits == 627780 || first_six_digits == 636297)
+      return kEloCard;
+  }
+
+  // Check for prefixes of length 4.
+  if (stripped_number.size() >= 4) {
+    int first_four_digits = 0;
+    if (!base::StringToInt(stripped_number.substr(0, 4), &first_four_digits))
+      return kGenericCard;
+
+    if (first_four_digits >= 2200 && first_four_digits <= 2204)
+      return kMirCard;
+
+    if (first_four_digits >= 2221 && first_four_digits <= 2720)
+      return kMasterCard;
+
+    if (first_four_digits >= 3528 && first_four_digits <= 3589)
+      return kJCBCard;
+
+    if (first_four_digits == 5067 || first_four_digits == 5090)
+      return kEloCard;
+
+    if (first_four_digits == 6011)
+      return kDiscoverCard;
+  }
+
+  // Check for prefixes of length 3.
+  if (stripped_number.size() >= 3) {
+    int first_three_digits = 0;
+    if (!base::StringToInt(stripped_number.substr(0, 3), &first_three_digits))
+      return kGenericCard;
+
+    if ((first_three_digits >= 300 && first_three_digits <= 305) ||
+        first_three_digits == 309)
+      return kDinersCard;
+
+    if (first_three_digits >= 644 && first_three_digits <= 649)
+      return kDiscoverCard;
+  }
+
+  // Check for prefixes of length 2.
+  if (stripped_number.size() >= 2) {
+    int first_two_digits = 0;
+    if (!base::StringToInt(stripped_number.substr(0, 2), &first_two_digits))
+      return kGenericCard;
+
+    if (first_two_digits == 34 || first_two_digits == 37)
+      return kAmericanExpressCard;
+
+    if (first_two_digits == 36 || first_two_digits == 38 ||
+        first_two_digits == 39)
+      return kDinersCard;
+
+    if (first_two_digits >= 51 && first_two_digits <= 55)
+      return kMasterCard;
+
+    if (first_two_digits == 62)
+      return kUnionPay;
+
+    if (first_two_digits == 65)
+      return kDiscoverCard;
+  }
+
+  // Check for prefixes of length 1.
+  if (stripped_number.empty())
     return kGenericCard;
 
-  int first_six_digits = 0;
-  if (!base::StringToInt(number.substr(0, 6), &first_six_digits))
-    return kGenericCard;
-
-  if (first_six_digits == 431274 ||
-      first_six_digits == 451416 ||
-      first_six_digits == 627780 ||
-      first_six_digits == 636297)
-    return kEloCard;
+  if (stripped_number[0] == '4')
+    return kVisaCard;
 
   return kGenericCard;
 }
diff --git a/components/autofill/core/browser/credit_card_unittest.cc b/components/autofill/core/browser/credit_card_unittest.cc
index 4503172..05f85fe4 100644
--- a/components/autofill/core/browser/credit_card_unittest.cc
+++ b/components/autofill/core/browser/credit_card_unittest.cc
@@ -884,25 +884,7 @@
         GetCardNetworkTestCase{"622384452162063648", kUnionPay, true},
         GetCardNetworkTestCase{"2204883716636153", kMirCard, true},
         GetCardNetworkTestCase{"2200111234567898", kMirCard, true},
-        GetCardNetworkTestCase{"2200481349288130", kMirCard, true},
-
-        // The relevant sample numbers from
-        // https://www.bincodes.com/bank-creditcard-generator/ and
-        // https://www.ebanx.com/business/en/developers/integrations/testing/credit-card-test-numbers
-        GetCardNetworkTestCase{"5067001446391275", kEloCard, true},
-        GetCardNetworkTestCase{"6362970000457013", kEloCard, true},
-
-        // Empty string
-        GetCardNetworkTestCase{"", kGenericCard, false},
-
-        // Non-numeric
-        GetCardNetworkTestCase{"garbage", kGenericCard, false},
-        GetCardNetworkTestCase{"4garbage", kVisaCard, false},
-
-        // Fails Luhn check.
-        GetCardNetworkTestCase{"4111111111111112", kVisaCard, false},
-        GetCardNetworkTestCase{"6247130048162413", kUnionPay, false},
-        GetCardNetworkTestCase{"2204883716636154", kMirCard, false}));
+        GetCardNetworkTestCase{"2200481349288130", kMirCard, true}));
 
 class GetCardNetworkTestBatch2
     : public testing::TestWithParam<GetCardNetworkTestCase> {};
@@ -919,61 +901,43 @@
     CreditCardTest,
     GetCardNetworkTestBatch2,
     testing::Values(
+        // The relevant sample numbers from
+        // https://www.bincodes.com/bank-creditcard-generator/ and
+        // https://www.ebanx.com/business/en/developers/integrations/testing/credit-card-test-numbers
+        GetCardNetworkTestCase{"5067001446391275", kEloCard, true},
+        GetCardNetworkTestCase{"6362970000457013", kEloCard, true},
+
+        // These sample numbers were created by taking the expected card prefix,
+        // filling out the required number of digits, and editing the last digit
+        // so that the full number passes a Luhn check.
+        GetCardNetworkTestCase{"4312741111111112", kEloCard, true},
+        GetCardNetworkTestCase{"4514161111111119", kEloCard, true},
+        GetCardNetworkTestCase{"5090111111111113", kEloCard, true},
+        GetCardNetworkTestCase{"6277801111111112", kEloCard, true},
+
+        // Existence of separators should not change the result, especially for
+        // prefixes that go past the first separator.
+        GetCardNetworkTestCase{"4111 1111 1111 1111", kVisaCard, true},
+        GetCardNetworkTestCase{"4111-1111-1111-1111", kVisaCard, true},
+        GetCardNetworkTestCase{"4312 7411 1111 1112", kEloCard, true},
+        GetCardNetworkTestCase{"4312-7411-1111-1112", kEloCard, true},
+
+        // Empty string
+        GetCardNetworkTestCase{"", kGenericCard, false},
+
+        // Non-numeric
+        GetCardNetworkTestCase{"garbage", kGenericCard, false},
+        GetCardNetworkTestCase{"4garbage", kGenericCard, false},
+
+        // Fails Luhn check.
+        GetCardNetworkTestCase{"4111111111111112", kVisaCard, false},
+        GetCardNetworkTestCase{"6247130048162413", kUnionPay, false},
+        GetCardNetworkTestCase{"2204883716636154", kMirCard, false},
+
         // Invalid length.
         GetCardNetworkTestCase{"3434343434343434", kAmericanExpressCard, false},
         GetCardNetworkTestCase{"411111111111116", kVisaCard, false},
-        GetCardNetworkTestCase{"220011123456783", kMirCard, false},
-
-        // Issuer Identification Numbers (IINs) that Chrome recognizes.
-        GetCardNetworkTestCase{"4", kVisaCard, false},
-        GetCardNetworkTestCase{"2200", kMirCard, false},
-        GetCardNetworkTestCase{"2202", kMirCard, false},
-        GetCardNetworkTestCase{"2204", kMirCard, false},
-        GetCardNetworkTestCase{"2221", kMasterCard, false},
-        GetCardNetworkTestCase{"2720", kMasterCard, false},
-        GetCardNetworkTestCase{"34", kAmericanExpressCard, false},
-        GetCardNetworkTestCase{"37", kAmericanExpressCard, false},
-        GetCardNetworkTestCase{"300", kDinersCard, false},
-        GetCardNetworkTestCase{"301", kDinersCard, false},
-        GetCardNetworkTestCase{"302", kDinersCard, false},
-        GetCardNetworkTestCase{"303", kDinersCard, false},
-        GetCardNetworkTestCase{"304", kDinersCard, false},
-        GetCardNetworkTestCase{"305", kDinersCard, false},
-        GetCardNetworkTestCase{"309", kDinersCard, false},
-        GetCardNetworkTestCase{"36", kDinersCard, false},
-        GetCardNetworkTestCase{"38", kDinersCard, false},
-        GetCardNetworkTestCase{"39", kDinersCard, false},
-        GetCardNetworkTestCase{"6011", kDiscoverCard, false},
-        GetCardNetworkTestCase{"644", kDiscoverCard, false},
-        GetCardNetworkTestCase{"645", kDiscoverCard, false},
-        GetCardNetworkTestCase{"646", kDiscoverCard, false},
-        GetCardNetworkTestCase{"647", kDiscoverCard, false},
-        GetCardNetworkTestCase{"648", kDiscoverCard, false},
-        GetCardNetworkTestCase{"649", kDiscoverCard, false},
-        GetCardNetworkTestCase{"65", kDiscoverCard, false},
-        GetCardNetworkTestCase{"5067", kEloCard, false},
-        GetCardNetworkTestCase{"5090", kEloCard, false},
-        GetCardNetworkTestCase{"636297", kEloCard, false},
-        GetCardNetworkTestCase{"3528", kJCBCard, false},
-        GetCardNetworkTestCase{"3531", kJCBCard, false},
-        GetCardNetworkTestCase{"3589", kJCBCard, false},
-        GetCardNetworkTestCase{"51", kMasterCard, false},
-        GetCardNetworkTestCase{"52", kMasterCard, false},
-        GetCardNetworkTestCase{"53", kMasterCard, false},
-        GetCardNetworkTestCase{"54", kMasterCard, false},
-        GetCardNetworkTestCase{"55", kMasterCard, false},
-        GetCardNetworkTestCase{"62", kUnionPay, false},
-
-        // Not enough data to determine an IIN uniquely.
-        GetCardNetworkTestCase{"2", kGenericCard, false},
-        GetCardNetworkTestCase{"3", kGenericCard, false},
-        GetCardNetworkTestCase{"30", kGenericCard, false},
-        GetCardNetworkTestCase{"35", kGenericCard, false},
-        GetCardNetworkTestCase{"5", kGenericCard, false},
-        GetCardNetworkTestCase{"6", kGenericCard, false},
-        GetCardNetworkTestCase{"60", kGenericCard, false},
-        GetCardNetworkTestCase{"601", kGenericCard, false},
-        GetCardNetworkTestCase{"64", kGenericCard, false}));
+        GetCardNetworkTestCase{"220011123456783", kMirCard, false}));
 
 class GetCardNetworkTestBatch3
     : public testing::TestWithParam<GetCardNetworkTestCase> {};
@@ -990,6 +954,77 @@
     CreditCardTest,
     GetCardNetworkTestBatch3,
     testing::Values(
+        // Issuer Identification Numbers (IINs) that Chrome recognizes.
+        GetCardNetworkTestCase{"2200", kMirCard, false},
+        GetCardNetworkTestCase{"2201", kMirCard, false},
+        GetCardNetworkTestCase{"2202", kMirCard, false},
+        GetCardNetworkTestCase{"2203", kMirCard, false},
+        GetCardNetworkTestCase{"2204", kMirCard, false},
+        GetCardNetworkTestCase{"2221", kMasterCard, false},
+        GetCardNetworkTestCase{"2720", kMasterCard, false},
+        GetCardNetworkTestCase{"300", kDinersCard, false},
+        GetCardNetworkTestCase{"301", kDinersCard, false},
+        GetCardNetworkTestCase{"302", kDinersCard, false},
+        GetCardNetworkTestCase{"303", kDinersCard, false},
+        GetCardNetworkTestCase{"304", kDinersCard, false},
+        GetCardNetworkTestCase{"305", kDinersCard, false},
+        GetCardNetworkTestCase{"309", kDinersCard, false},
+        GetCardNetworkTestCase{"34", kAmericanExpressCard, false},
+        GetCardNetworkTestCase{"3528", kJCBCard, false},
+        GetCardNetworkTestCase{"3531", kJCBCard, false},
+        GetCardNetworkTestCase{"3589", kJCBCard, false},
+        GetCardNetworkTestCase{"36", kDinersCard, false},
+        GetCardNetworkTestCase{"37", kAmericanExpressCard, false},
+        GetCardNetworkTestCase{"38", kDinersCard, false},
+        GetCardNetworkTestCase{"39", kDinersCard, false},
+        GetCardNetworkTestCase{"4", kVisaCard, false},
+        GetCardNetworkTestCase{"431274", kEloCard, false},
+        GetCardNetworkTestCase{"451416", kEloCard, false},
+        GetCardNetworkTestCase{"5067", kEloCard, false},
+        GetCardNetworkTestCase{"5090", kEloCard, false},
+        GetCardNetworkTestCase{"51", kMasterCard, false},
+        GetCardNetworkTestCase{"52", kMasterCard, false},
+        GetCardNetworkTestCase{"53", kMasterCard, false},
+        GetCardNetworkTestCase{"54", kMasterCard, false},
+        GetCardNetworkTestCase{"55", kMasterCard, false},
+        GetCardNetworkTestCase{"6011", kDiscoverCard, false},
+        GetCardNetworkTestCase{"62", kUnionPay, false},
+        GetCardNetworkTestCase{"627780", kEloCard, false},
+        GetCardNetworkTestCase{"636297", kEloCard, false},
+        GetCardNetworkTestCase{"644", kDiscoverCard, false},
+        GetCardNetworkTestCase{"645", kDiscoverCard, false},
+        GetCardNetworkTestCase{"646", kDiscoverCard, false},
+        GetCardNetworkTestCase{"647", kDiscoverCard, false},
+        GetCardNetworkTestCase{"648", kDiscoverCard, false},
+        GetCardNetworkTestCase{"649", kDiscoverCard, false},
+        GetCardNetworkTestCase{"65", kDiscoverCard, false}));
+
+class GetCardNetworkTestBatch4
+    : public testing::TestWithParam<GetCardNetworkTestCase> {};
+
+TEST_P(GetCardNetworkTestBatch4, GetCardNetwork) {
+  auto test_case = GetParam();
+  base::string16 card_number = ASCIIToUTF16(test_case.card_number);
+  SCOPED_TRACE(card_number);
+  EXPECT_EQ(test_case.issuer_network, CreditCard::GetCardNetwork(card_number));
+  EXPECT_EQ(test_case.is_valid, IsValidCreditCardNumber(card_number));
+}
+
+INSTANTIATE_TEST_CASE_P(
+    CreditCardTest,
+    GetCardNetworkTestBatch4,
+    testing::Values(
+        // Not enough data to determine an IIN uniquely.
+        GetCardNetworkTestCase{"2", kGenericCard, false},
+        GetCardNetworkTestCase{"3", kGenericCard, false},
+        GetCardNetworkTestCase{"30", kGenericCard, false},
+        GetCardNetworkTestCase{"35", kGenericCard, false},
+        GetCardNetworkTestCase{"5", kGenericCard, false},
+        GetCardNetworkTestCase{"6", kGenericCard, false},
+        GetCardNetworkTestCase{"60", kGenericCard, false},
+        GetCardNetworkTestCase{"601", kGenericCard, false},
+        GetCardNetworkTestCase{"64", kGenericCard, false},
+
         // Unknown IINs.
         GetCardNetworkTestCase{"0", kGenericCard, false},
         GetCardNetworkTestCase{"1", kGenericCard, false},
diff --git a/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc b/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
index 088149b..664b9e7 100644
--- a/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autocomplete_sync_bridge.cc
@@ -50,7 +50,7 @@
     return ret_val;                       \
   }
 
-void* UserDataKey() {
+void* AutocompleteSyncBridgeUserDataKey() {
   // Use the address of a static that COMDAT folding won't ever collide
   // with something else.
   static int user_data_key = 0;
@@ -283,7 +283,7 @@
     AutofillWebDataService* web_data_service,
     AutofillWebDataBackend* web_data_backend) {
   web_data_service->GetDBUserData()->SetUserData(
-      UserDataKey(),
+      AutocompleteSyncBridgeUserDataKey(),
       std::make_unique<AutocompleteSyncBridge>(
           web_data_backend,
           std::make_unique<ClientTagBasedModelTypeProcessor>(
@@ -294,7 +294,8 @@
 ModelTypeSyncBridge* AutocompleteSyncBridge::FromWebDataService(
     AutofillWebDataService* web_data_service) {
   return static_cast<AutocompleteSyncBridge*>(
-      web_data_service->GetDBUserData()->GetUserData(UserDataKey()));
+      web_data_service->GetDBUserData()->GetUserData(
+          AutocompleteSyncBridgeUserDataKey()));
 }
 
 AutocompleteSyncBridge::AutocompleteSyncBridge(
diff --git a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc
index c83262a..c9d5b41 100644
--- a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc
+++ b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc
@@ -42,7 +42,7 @@
   return sanitized_value;
 }
 
-void* UserDataKey() {
+void* AutofillProfileSyncableServiceUserDataKey() {
   // Use the address of a static that COMDAT folding won't ever fold
   // with something else.
   static int user_data_key = 0;
@@ -74,8 +74,9 @@
     AutofillWebDataBackend* webdata_backend,
     const std::string& app_locale) {
   web_data_service->GetDBUserData()->SetUserData(
-      UserDataKey(), base::WrapUnique(new AutofillProfileSyncableService(
-                         webdata_backend, app_locale)));
+      AutofillProfileSyncableServiceUserDataKey(),
+      base::WrapUnique(
+          new AutofillProfileSyncableService(webdata_backend, app_locale)));
 }
 
 // static
@@ -83,7 +84,8 @@
 AutofillProfileSyncableService::FromWebDataService(
     AutofillWebDataService* web_data_service) {
   return static_cast<AutofillProfileSyncableService*>(
-      web_data_service->GetDBUserData()->GetUserData(UserDataKey()));
+      web_data_service->GetDBUserData()->GetUserData(
+          AutofillProfileSyncableServiceUserDataKey()));
 }
 
 AutofillProfileSyncableService::AutofillProfileSyncableService()
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc
index 25c1520..1d447d7 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc
@@ -33,7 +33,7 @@
 
 namespace {
 
-void* UserDataKey() {
+void* AutofillWalletMetadataSyncableServiceUserDataKey() {
   // Use the address of a static so that COMDAT folding won't ever fold
   // with something else.
   static int user_data_key = 0;
@@ -496,8 +496,9 @@
     AutofillWebDataBackend* web_data_backend,
     const std::string& app_locale) {
   web_data_service->GetDBUserData()->SetUserData(
-      UserDataKey(), base::WrapUnique(new AutofillWalletMetadataSyncableService(
-                         web_data_backend, app_locale)));
+      AutofillWalletMetadataSyncableServiceUserDataKey(),
+      base::WrapUnique(new AutofillWalletMetadataSyncableService(
+          web_data_backend, app_locale)));
 }
 
 // static
@@ -505,7 +506,8 @@
 AutofillWalletMetadataSyncableService::FromWebDataService(
     AutofillWebDataService* web_data_service) {
   return static_cast<AutofillWalletMetadataSyncableService*>(
-      web_data_service->GetDBUserData()->GetUserData(UserDataKey()));
+      web_data_service->GetDBUserData()->GetUserData(
+          AutofillWalletMetadataSyncableServiceUserDataKey()));
 }
 
 AutofillWalletMetadataSyncableService::AutofillWalletMetadataSyncableService(
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_syncable_service.cc b/components/autofill/core/browser/webdata/autofill_wallet_syncable_service.cc
index 0e5da82..cdfdc71 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_syncable_service.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_syncable_service.cc
@@ -26,7 +26,7 @@
 
 namespace {
 
-void* UserDataKey() {
+void* AutofillWalletSyncableServiceUserDataKey() {
   // Use the address of a static so that COMDAT folding won't ever fold
   // with something else.
   static int user_data_key = 0;
@@ -242,8 +242,9 @@
     AutofillWebDataBackend* webdata_backend,
     const std::string& app_locale) {
   web_data_service->GetDBUserData()->SetUserData(
-      UserDataKey(), base::WrapUnique(new AutofillWalletSyncableService(
-                         webdata_backend, app_locale)));
+      AutofillWalletSyncableServiceUserDataKey(),
+      base::WrapUnique(
+          new AutofillWalletSyncableService(webdata_backend, app_locale)));
 }
 
 // static
@@ -251,7 +252,8 @@
 AutofillWalletSyncableService::FromWebDataService(
     AutofillWebDataService* web_data_service) {
   return static_cast<AutofillWalletSyncableService*>(
-      web_data_service->GetDBUserData()->GetUserData(UserDataKey()));
+      web_data_service->GetDBUserData()->GetUserData(
+          AutofillWalletSyncableServiceUserDataKey()));
 }
 
 void AutofillWalletSyncableService::InjectStartSyncFlare(
diff --git a/components/browser_sync/profile_sync_components_factory_impl.cc b/components/browser_sync/profile_sync_components_factory_impl.cc
index fc0be1b..a98efcc 100644
--- a/components/browser_sync/profile_sync_components_factory_impl.cc
+++ b/components/browser_sync/profile_sync_components_factory_impl.cc
@@ -312,8 +312,25 @@
   }
 
   if (base::FeatureList::IsEnabled(switches::kSyncUserConsentSeparateType)) {
-    controllers.push_back(CreateModelTypeControllerForModelRunningOnUIThread(
-        syncer::USER_CONSENTS));
+    // Forward both on-disk and in-memory storage modes to the same delegate,
+    // since behavior for USER_CONSENTS does not differ (they are always
+    // persisted).
+    // TODO(crbug.com/867801): Replace the proxy delegates below with a simpler
+    // forwarding delegate that involves no posting of tasks.
+    controllers.push_back(std::make_unique<ModelTypeController>(
+        syncer::USER_CONSENTS,
+        /*delegate_on_disk=*/
+        std::make_unique<syncer::ProxyModelTypeControllerDelegate>(
+            ui_thread_,
+            base::BindRepeating(
+                &syncer::SyncClient::GetControllerDelegateForModelType,
+                base::Unretained(sync_client_), syncer::USER_CONSENTS)),
+        /*delegate_in_memory=*/
+        std::make_unique<syncer::ProxyModelTypeControllerDelegate>(
+            ui_thread_,
+            base::BindRepeating(
+                &syncer::SyncClient::GetControllerDelegateForModelType,
+                base::Unretained(sync_client_), syncer::USER_CONSENTS))));
   }
 
   return controllers;
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index 687b2dc9..b961ef2e 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -159,6 +159,10 @@
   return type_map;
 }
 
+bool IsStandaloneTransportEnabled() {
+  return base::FeatureList::IsEnabled(switches::kSyncStandaloneTransport);
+}
+
 }  // namespace
 
 ProfileSyncService::InitParams::InitParams() = default;
@@ -245,7 +249,7 @@
   startup_controller_ = std::make_unique<syncer::StartupController>(
       base::BindRepeating(&ProfileSyncService::GetPreferredDataTypes,
                           base::Unretained(this)),
-      base::BindRepeating(&ProfileSyncService::ShouldSyncStart,
+      base::BindRepeating(&ProfileSyncService::ShouldStartEngine,
                           base::Unretained(this)),
       base::BindRepeating(&ProfileSyncService::StartUpSlowEngineComponents,
                           base::Unretained(this)));
@@ -428,7 +432,7 @@
     DCHECK(!engine_);
   } else {
     DCHECK(!engine_);
-    startup_controller_->TryStart(IsSetupInProgress());
+    startup_controller_->TryStart(/*force_immediate=*/IsSetupInProgress());
   }
 }
 
@@ -448,10 +452,34 @@
   NotifyObservers();
 }
 
-bool ProfileSyncService::ShouldSyncStart(bool bypass_first_setup_check) {
-  if (!CanSyncStart()) {
+bool ProfileSyncService::IsEngineAllowedToStart() const {
+  int disable_reasons = GetDisableReasons();
+  if (IsStandaloneTransportEnabled()) {
+    // USER_CHOICE (i.e. the Sync feature toggle) and PLATFORM_OVERRIDE (i.e.
+    // Android's "MasterSync" toggle) do not prevent starting up the Sync
+    // transport.
+    const int kDisableReasonMask =
+        ~(DISABLE_REASON_USER_CHOICE | DISABLE_REASON_PLATFORM_OVERRIDE);
+    disable_reasons &= kDisableReasonMask;
+  }
+  return disable_reasons == DISABLE_REASON_NONE;
+}
+
+bool ProfileSyncService::ShouldStartEngine(
+    bool bypass_first_setup_check) const {
+  if (!IsEngineAllowedToStart()) {
     return false;
   }
+  // If standalone transport is enabled, we always start the engine as soon as
+  // we can.
+  if (IsStandaloneTransportEnabled()) {
+    return true;
+  }
+  // Without standalone transport, we generally wait for first-time setup to be
+  // complete before starting the engine (because if it isn't, we can't
+  // configure the DataTypeManager anyway). Note that if a setup is currently in
+  // progress (which requires the engine to be initialized), then
+  // |bypass_first_setup_check| will be set to true.
   return bypass_first_setup_check || IsFirstSetupComplete();
 }
 
@@ -535,7 +563,7 @@
 }
 
 void ProfileSyncService::StartUpSlowEngineComponents() {
-  DCHECK(CanSyncStart());
+  DCHECK(IsEngineAllowedToStart());
 
   engine_ = sync_client_->GetSyncApiComponentFactory()->CreateSyncEngine(
       debug_identifier_, sync_client_->GetInvalidationService(),
@@ -763,7 +791,7 @@
 syncer::SyncService::State ProfileSyncService::GetState() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (GetDisableReasons() != DISABLE_REASON_NONE) {
+  if (!IsEngineAllowedToStart()) {
     // We shouldn't have an engine while in a disabled state, with one
     // exception: When encountering an unrecoverable error, we post a task to
     // shut down instead of doing it immediately, so there's a brief timeframe
@@ -775,9 +803,6 @@
     return State::DISABLED;
   }
 
-  // Since there is no disable reason, Sync can start in principle.
-  DCHECK(CanSyncStart());
-
   // Typically, Sync won't start until the initial setup is at least in
   // progress. StartupController::TryStartImmediately bypasses the first setup
   // check though, so we first have to check whether the engine is initialized.
@@ -809,9 +834,10 @@
     return State::PENDING_DESIRED_CONFIGURATION;
   }
 
-  // The DataTypeManager shouldn't get configured (i.e. leave the STOPPED state)
-  // before the initial setup is complete.
-  DCHECK(IsFirstSetupComplete());
+  // Unless standalone transport is enabled, the DataTypeManager shouldn't get
+  // configured (i.e. leave the STOPPED state) before the initial setup is
+  // complete.
+  DCHECK(IsStandaloneTransportEnabled() || IsFirstSetupComplete());
 
   // Note that if a setup is started after the data types have been configured,
   // then they'll stay configured even though CanConfigureDataTypes will be
@@ -1296,7 +1322,13 @@
 }
 
 bool ProfileSyncService::CanConfigureDataTypes() const {
-  return data_type_manager_ && IsFirstSetupComplete() && !IsSetupInProgress();
+  // TODO(crbug.com/856179): Arguably, IsSetupInProgress() shouldn't prevent
+  // configuring data types in transport mode, but at least for now, it's
+  // easier to keep it like this. Changing this will likely require changes to
+  // the setup UI flow.
+  return data_type_manager_ &&
+         (IsFirstSetupComplete() || IsStandaloneTransportEnabled()) &&
+         !IsSetupInProgress();
 }
 
 std::unique_ptr<syncer::SyncSetupInProgressHandle>
@@ -1537,6 +1569,7 @@
   configure_context.authenticated_account_id =
       GetAuthenticatedAccountInfo().account_id;
   configure_context.cache_guid = local_device_->GetLocalSyncCacheGUID();
+  configure_context.storage_option = syncer::ConfigureContext::STORAGE_ON_DISK;
   configure_context.reason = reason;
 
   if (!migrator_) {
@@ -1561,7 +1594,15 @@
   DCHECK(!configure_context.cache_guid.empty());
   DCHECK_NE(configure_context.reason, syncer::CONFIGURE_REASON_UNKNOWN);
 
-  data_type_manager_->Configure(GetPreferredDataTypes(), configure_context);
+  syncer::ModelTypeSet types = GetPreferredDataTypes();
+  // If Sync-the-feature isn't fully enabled, then only a subset of data types
+  // is supported.
+  if (!IsSyncFeatureEnabled()) {
+    DCHECK(IsStandaloneTransportEnabled());
+    const syncer::ModelTypeSet allowed_types = {syncer::USER_CONSENTS};
+    types = Intersection(types, allowed_types);
+  }
+  data_type_manager_->Configure(types, configure_context);
 }
 
 syncer::UserShare* ProfileSyncService::GetUserShare() const {
@@ -1738,6 +1779,7 @@
     StopImpl(CLEAR_DATA);
   } else {
     // Sync is no longer disabled by policy. Try starting it up if appropriate.
+    DCHECK(!engine_);
     startup_controller_->TryStart(IsSetupInProgress());
   }
 }
@@ -1965,6 +2007,14 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   sync_prefs_.SetSyncRequested(false);
   StopImpl(data_fate);
+
+  // TODO(crbug.com/856179): Evaluate whether we can get away without a full
+  // restart (i.e. just reconfigure plus whatever cleanup is necessary).
+  // Especially in the CLEAR_DATA case, StopImpl does a lot of cleanup that
+  // might still be required.
+  if (IsStandaloneTransportEnabled()) {
+    startup_controller_->TryStart(/*force_immediate=*/false);
+  }
 }
 
 void ProfileSyncService::RequestStart() {
@@ -1979,7 +2029,12 @@
     sync_prefs_.SetSyncRequested(true);
     NotifyObservers();
   }
-  startup_controller_->TryStart(/*force_immediate=*/true);
+  // If Sync-the-transport was already running, just reconfigure.
+  if (IsStandaloneTransportEnabled() && engine_initialized_) {
+    ReconfigureDatatypeManager();
+  } else {
+    startup_controller_->TryStart(/*force_immediate=*/true);
+  }
 }
 
 void ProfileSyncService::ReconfigureDatatypeManager() {
diff --git a/components/browser_sync/profile_sync_service.h b/components/browser_sync/profile_sync_service.h
index f4a148f..06ed438 100644
--- a/components/browser_sync/profile_sync_service.h
+++ b/components/browser_sync/profile_sync_service.h
@@ -492,8 +492,10 @@
   void AccountStateChanged();
   void CredentialsChanged();
 
+  bool IsEngineAllowedToStart() const;
+
   // Callback for StartupController.
-  bool ShouldSyncStart(bool bypass_first_setup_check);
+  bool ShouldStartEngine(bool bypass_first_setup_check) const;
 
   // Destroys the |crypto_| object and creates a new one with fresh state.
   void ResetCryptoState();
diff --git a/components/browser_sync/profile_sync_service_startup_unittest.cc b/components/browser_sync/profile_sync_service_startup_unittest.cc
index 218253d..801ae25 100644
--- a/components/browser_sync/profile_sync_service_startup_unittest.cc
+++ b/components/browser_sync/profile_sync_service_startup_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/browser_sync/profile_sync_service.h"
 
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/browser_sync/profile_sync_test_util.h"
 #include "components/prefs/pref_service.h"
@@ -11,6 +12,7 @@
 #include "components/sync/driver/data_type_manager_mock.h"
 #include "components/sync/driver/fake_data_type_controller.h"
 #include "components/sync/driver/sync_api_component_factory_mock.h"
+#include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/engine/fake_sync_engine.h"
 #include "components/sync/engine/mock_sync_engine.h"
 #include "services/identity/public/cpp/identity_test_utils.h"
@@ -158,10 +160,37 @@
   std::unique_ptr<ProfileSyncService> sync_service_;
 };
 
+class ProfileSyncServiceWithStandaloneTransportStartupTest
+    : public ProfileSyncServiceStartupTest {
+ protected:
+  ProfileSyncServiceWithStandaloneTransportStartupTest() {
+    feature_list_.InitAndEnableFeature(switches::kSyncStandaloneTransport);
+  }
+
+  ~ProfileSyncServiceWithStandaloneTransportStartupTest() override {}
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class ProfileSyncServiceWithoutStandaloneTransportStartupTest
+    : public ProfileSyncServiceStartupTest {
+ protected:
+  ProfileSyncServiceWithoutStandaloneTransportStartupTest() {
+    feature_list_.InitAndDisableFeature(switches::kSyncStandaloneTransport);
+  }
+
+  ~ProfileSyncServiceWithoutStandaloneTransportStartupTest() override {}
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // ChromeOS does not support sign-in after startup (in particular,
 // IdentityManager::Observer::OnPrimaryAccountSet never gets called).
 #if !defined(OS_CHROMEOS)
-TEST_F(ProfileSyncServiceStartupTest, StartFirstTime) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportStartupTest,
+       StartFirstTime) {
   // We've never completed startup.
   pref_service()->ClearPref(syncer::prefs::kSyncFirstSetupComplete);
 
@@ -238,6 +267,95 @@
   // This should have fully enabled sync.
   EXPECT_FALSE(sync_service()->IsSyncConfirmationNeeded());
   EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
+
+  EXPECT_CALL(*data_type_manager, Stop(syncer::BROWSER_SHUTDOWN));
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportStartupTest, StartFirstTime) {
+  // We've never completed startup.
+  pref_service()->ClearPref(syncer::prefs::kSyncFirstSetupComplete);
+
+  CreateSyncService(ProfileSyncService::MANUAL_START);
+  SetUpFakeSyncEngine();
+  DataTypeManagerMock* data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _)).Times(0);
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::STOPPED));
+
+  // Should not actually start, rather just clean things up and wait
+  // to be enabled.
+  sync_service()->Initialize();
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN,
+            sync_service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, sync_service()->GetState());
+
+  // Preferences should be back to defaults.
+  EXPECT_EQ(0, pref_service()->GetInt64(syncer::prefs::kSyncLastSyncedTime));
+  EXPECT_FALSE(
+      pref_service()->GetBoolean(syncer::prefs::kSyncFirstSetupComplete));
+
+  // Confirmation isn't needed before sign in occurs.
+  EXPECT_FALSE(sync_service()->IsSyncConfirmationNeeded());
+
+  // This tells the ProfileSyncService that setup is now in progress, which
+  // causes it to try starting up the engine. We're not signed in yet though, so
+  // that won't work.
+  auto sync_blocker = sync_service()->GetSetupInProgressHandle();
+  EXPECT_FALSE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN,
+            sync_service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, sync_service()->GetState());
+
+  // Confirmation isn't needed before sign in occurs, or when setup is already
+  // in progress.
+  EXPECT_FALSE(sync_service()->IsSyncConfirmationNeeded());
+
+  SimulateTestUserSignin();
+
+  // Now we're signed in, so the engine can start. There's already a setup in
+  // progress, so we don't go into the WAITING_FOR_START_REQUEST state. Engine
+  // initialization is immediate in this test, so we also bypass the
+  // INITIALIZING state.
+  EXPECT_TRUE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            sync_service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION,
+            sync_service()->GetState());
+
+  // Setup is already in progress, so confirmation still isn't needed.
+  EXPECT_FALSE(sync_service()->IsSyncConfirmationNeeded());
+
+  // Simulate the UI telling sync it has finished setting up. Note that this is
+  // a two-step process: Releasing the SetupInProgressHandle, and marking first
+  // setup complete.
+  // Since standalone transport is enabled, completed first-time setup is not a
+  // requirement, so the service will start up as soon as the setup handle is
+  // released.
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  sync_blocker.reset();
+  ASSERT_FALSE(sync_service()->IsSetupInProgress());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  // Sync-the-feature is still not active, but rather pending confirmation.
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
+  EXPECT_TRUE(sync_service()->IsSyncConfirmationNeeded());
+
+  // Marking first setup complete will let ProfileSyncService reconfigure the
+  // DataTypeManager in full Sync-the-feature mode.
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  sync_service()->SetFirstSetupComplete();
+
+  // This should have fully enabled sync.
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
+  EXPECT_FALSE(sync_service()->IsSyncConfirmationNeeded());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
 
   EXPECT_CALL(*data_type_manager, Stop(syncer::BROWSER_SHUTDOWN));
 }
@@ -363,7 +481,7 @@
   EXPECT_CALL(*data_type_manager, Stop(syncer::BROWSER_SHUTDOWN));
 }
 
-TEST_F(ProfileSyncServiceStartupTest, StopSync) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportStartupTest, StopSync) {
   CreateSyncService(ProfileSyncService::MANUAL_START);
   SimulateTestUserSignin();
   sync_service()->SetFirstSetupComplete();
@@ -377,9 +495,37 @@
 
   EXPECT_CALL(*data_type_manager, Stop(syncer::STOP_SYNC));
   sync_service()->RequestStop(syncer::SyncService::KEEP_DATA);
+
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
 }
 
-TEST_F(ProfileSyncServiceStartupTest, DisableSync) {
+TEST_F(ProfileSyncServiceWithStandaloneTransportStartupTest, StopSync) {
+  CreateSyncService(ProfileSyncService::MANUAL_START);
+  SimulateTestUserSignin();
+  sync_service()->SetFirstSetupComplete();
+  SetUpFakeSyncEngine();
+  DataTypeManagerMock* data_type_manager = SetUpDataTypeManagerMock();
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  ON_CALL(*data_type_manager, IsNigoriEnabled()).WillByDefault(Return(true));
+
+  sync_service()->Initialize();
+
+  EXPECT_CALL(*data_type_manager, Stop(syncer::STOP_SYNC));
+  // On RequestStop(), the sync service will immediately start up again in
+  // transport mode.
+  SetUpFakeSyncEngine();
+  data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  sync_service()->RequestStop(syncer::SyncService::KEEP_DATA);
+
+  // Sync-the-feature is still considered off.
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
+}
+
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportStartupTest, DisableSync) {
   CreateSyncService(ProfileSyncService::MANUAL_START);
   SimulateTestUserSignin();
   sync_service()->SetFirstSetupComplete();
@@ -393,6 +539,33 @@
 
   EXPECT_CALL(*data_type_manager, Stop(syncer::DISABLE_SYNC));
   sync_service()->RequestStop(syncer::SyncService::CLEAR_DATA);
+
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportStartupTest, DisableSync) {
+  CreateSyncService(ProfileSyncService::MANUAL_START);
+  SimulateTestUserSignin();
+  sync_service()->SetFirstSetupComplete();
+  SetUpFakeSyncEngine();
+  DataTypeManagerMock* data_type_manager = SetUpDataTypeManagerMock();
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  ON_CALL(*data_type_manager, IsNigoriEnabled()).WillByDefault(Return(true));
+
+  sync_service()->Initialize();
+
+  // On RequestStop(), the sync service will immediately start up again in
+  // transport mode.
+  SetUpFakeSyncEngine();
+  data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  sync_service()->RequestStop(syncer::SyncService::CLEAR_DATA);
+
+  // Sync-the-feature is still considered off.
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
 }
 
 // Test that we can recover from a case where a bug in the code resulted in
@@ -457,14 +630,14 @@
   pref_service()->SetBoolean(syncer::prefs::kSyncManaged, true);
   ASSERT_EQ(syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY,
             sync_service()->GetDisableReasons());
+
   EXPECT_CALL(*component_factory(), CreateSyncEngine(_, _, _, _)).Times(0);
   EXPECT_CALL(*component_factory(), CreateDataTypeManager(_, _, _, _, _, _))
       .Times(0);
-
   sync_service()->Initialize();
 }
 
-TEST_F(ProfileSyncServiceStartupTest, SwitchManaged) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportStartupTest, SwitchManaged) {
   CreateSyncService(ProfileSyncService::MANUAL_START);
   SimulateTestUserSignin();
   sync_service()->SetFirstSetupComplete();
@@ -479,6 +652,8 @@
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             sync_service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
 
   // The service should stop when switching to managed mode.
   Mock::VerifyAndClearExpectations(data_type_manager);
@@ -490,6 +665,8 @@
             sync_service()->GetDisableReasons());
   EXPECT_FALSE(sync_service()->IsEngineInitialized());
   EXPECT_EQ(syncer::SyncService::State::DISABLED, sync_service()->GetState());
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
   // Note that PSS no longer references |data_type_manager| after stopping.
 
   // When switching back to unmanaged, the state should change but sync should
@@ -505,6 +682,63 @@
   EXPECT_FALSE(sync_service()->IsEngineInitialized());
   EXPECT_EQ(syncer::SyncService::State::WAITING_FOR_START_REQUEST,
             sync_service()->GetState());
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportStartupTest, SwitchManaged) {
+  CreateSyncService(ProfileSyncService::MANUAL_START);
+  SimulateTestUserSignin();
+  sync_service()->SetFirstSetupComplete();
+  SetUpFakeSyncEngine();
+  DataTypeManagerMock* data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  ON_CALL(*data_type_manager, IsNigoriEnabled()).WillByDefault(Return(true));
+  sync_service()->Initialize();
+  EXPECT_TRUE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            sync_service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
+
+  // The service should stop when switching to managed mode.
+  Mock::VerifyAndClearExpectations(data_type_manager);
+  EXPECT_CALL(*data_type_manager, state())
+      .WillOnce(Return(DataTypeManager::CONFIGURED));
+  EXPECT_CALL(*data_type_manager, Stop(syncer::DISABLE_SYNC));
+
+  pref_service()->SetBoolean(syncer::prefs::kSyncManaged, true);
+  ASSERT_EQ(syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY,
+            sync_service()->GetDisableReasons());
+  EXPECT_FALSE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, sync_service()->GetState());
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
+  // Note that PSS no longer references |data_type_manager| after stopping.
+
+  // When switching back to unmanaged, Sync-the-transport should start up
+  // automatically, which causes (re)creation of SyncEngine and
+  // DataTypeManager.
+  SetUpFakeSyncEngine();
+  Mock::VerifyAndClearExpectations(data_type_manager);
+  data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+
+  pref_service()->ClearPref(syncer::prefs::kSyncManaged);
+
+  ASSERT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            sync_service()->GetDisableReasons());
+
+  EXPECT_TRUE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  // Sync-the-feature is still considered off.
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(sync_service()->IsSyncActive());
 }
 
 TEST_F(ProfileSyncServiceStartupTest, StartFailure) {
@@ -549,7 +783,8 @@
 // ChromeOS does not support sign-in after startup (in particular,
 // IdentityManager::Observer::OnPrimaryAccountSet never gets called).
 #if !defined(OS_CHROMEOS)
-TEST_F(ProfileSyncServiceStartupTest, FullStartupSequenceFirstTime) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportStartupTest,
+       FullStartupSequenceFirstTime) {
   // We've never completed startup.
   pref_service()->ClearPref(syncer::prefs::kSyncFirstSetupComplete);
 
@@ -595,6 +830,7 @@
   ASSERT_TRUE(sync_service()->IsEngineInitialized());
   EXPECT_EQ(syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION,
             sync_service()->GetState());
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
 
   // Once the user finishes the initial setup, the service can actually start
   // configuring the data types. Just marking the initial setup as complete
@@ -603,6 +839,7 @@
   sync_service()->SetFirstSetupComplete();
   EXPECT_EQ(syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION,
             sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
 
   // Releasing the setup in progress handle lets the service actually configure
   // the DataTypeManager.
@@ -616,6 +853,7 @@
   // CONFIGURING.
   EXPECT_EQ(syncer::SyncService::State::CONFIGURING,
             sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
 
   // Finally, once the DataTypeManager says it's done with configuration, Sync
   // is actually fully up and running.
@@ -625,6 +863,90 @@
       .WillByDefault(Return(DataTypeManager::CONFIGURED));
   sync_service()->OnConfigureDone(configure_result);
   EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportStartupTest,
+       FullStartupSequenceFirstTime) {
+  // We've never completed startup.
+  pref_service()->ClearPref(syncer::prefs::kSyncFirstSetupComplete);
+
+  MockSyncEngine* sync_engine = SetUpMockSyncEngine();
+  EXPECT_CALL(*sync_engine, Initialize(_)).Times(0);
+
+  DataTypeManagerMock* data_type_manager = SetUpDataTypeManagerMock();
+  EXPECT_CALL(*data_type_manager, Configure(_, _)).Times(0);
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::STOPPED));
+
+  // Note: Deferred startup is only enabled if SESSIONS is among the preferred
+  // data types.
+  CreateSyncService(ProfileSyncService::MANUAL_START,
+                    syncer::ModelTypeSet(syncer::SESSIONS));
+  sync_service()->Initialize();
+
+  // There is no signed-in user, but nothing else prevents Sync from starting.
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN,
+            sync_service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, sync_service()->GetState());
+
+  // Sign in. Now Sync is ready to start, just waiting for a prod.
+  SimulateTestUserSignin();
+  EXPECT_EQ(syncer::SyncService::State::START_DEFERRED,
+            sync_service()->GetState());
+
+  // Once we give the service a prod by initiating Sync setup, it'll start and
+  // initialize the engine. Since this is the initial Sync start, this will not
+  // be deferred.
+  EXPECT_CALL(*sync_engine, Initialize(_));
+  auto setup_in_progress_handle = sync_service()->GetSetupInProgressHandle();
+  EXPECT_EQ(syncer::SyncService::State::INITIALIZING,
+            sync_service()->GetState());
+
+  // Once the engine calls back and says it's initialized, we're just waiting
+  // for the user to finish the initial configuration (choosing data types etc.)
+  // before actually syncing data.
+  sync_service()->OnEngineInitialized(
+      syncer::ModelTypeSet(), syncer::WeakHandle<syncer::JsBackend>(),
+      syncer::WeakHandle<syncer::DataTypeDebugInfoListener>(), "test-guid",
+      /*success=*/true);
+  ASSERT_TRUE(sync_service()->IsEngineInitialized());
+  EXPECT_EQ(syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION,
+            sync_service()->GetState());
+  EXPECT_FALSE(sync_service()->IsSyncFeatureEnabled());
+
+  // Once the user finishes the initial setup, the service can actually start
+  // configuring the data types. Just marking the initial setup as complete
+  // isn't enough though, because setup is still considered in progress (we
+  // haven't released the setup-in-progress handle).
+  sync_service()->SetFirstSetupComplete();
+  EXPECT_EQ(syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION,
+            sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+
+  // Releasing the setup in progress handle lets the service actually configure
+  // the DataTypeManager.
+  EXPECT_CALL(*data_type_manager, Configure(_, _))
+      .WillOnce(InvokeWithoutArgs(sync_service(),
+                                  &ProfileSyncService::OnConfigureStart));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURING));
+  setup_in_progress_handle.reset();
+  // While DataTypeManager configuration is ongoing, the overall state is still
+  // CONFIGURING.
+  EXPECT_EQ(syncer::SyncService::State::CONFIGURING,
+            sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
+
+  // Finally, once the DataTypeManager says it's done with configuration, Sync
+  // is actually fully up and running.
+  DataTypeManager::ConfigureResult configure_result(
+      DataTypeManager::OK, syncer::ModelTypeSet(syncer::SESSIONS));
+  ON_CALL(*data_type_manager, state())
+      .WillByDefault(Return(DataTypeManager::CONFIGURED));
+  sync_service()->OnConfigureDone(configure_result);
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, sync_service()->GetState());
+  EXPECT_TRUE(sync_service()->IsSyncActive());
 }
 #endif  // OS_CHROMEOS
 
diff --git a/components/browser_sync/profile_sync_service_unittest.cc b/components/browser_sync/profile_sync_service_unittest.cc
index 5d6ddf65..cfbe8e9 100644
--- a/components/browser_sync/profile_sync_service_unittest.cc
+++ b/components/browser_sync/profile_sync_service_unittest.cc
@@ -97,7 +97,7 @@
 };
 
 // A variant of the FakeSyncEngine that won't automatically call back when asked
-// to initialized. Allows us to test things that could happen while backend init
+// to initialize. Allows us to test things that could happen while backend init
 // is in progress.
 class FakeSyncEngineNoReturn : public syncer::FakeSyncEngine {
   void Initialize(InitParams params) override {}
@@ -152,6 +152,10 @@
   return std::make_unique<syncer::FakeSyncEngine>();
 }
 
+ACTION(ReturnNewFakeSyncEngineNoReturn) {
+  return std::make_unique<FakeSyncEngineNoReturn>();
+}
+
 void OnClearServerDataCalled(base::Closure* captured_callback,
                              const base::Closure& callback) {
   *captured_callback = callback;
@@ -324,6 +328,32 @@
   std::unique_ptr<ProfileSyncService> service_;
 };
 
+class ProfileSyncServiceWithStandaloneTransportTest
+    : public ProfileSyncServiceTest {
+ protected:
+  ProfileSyncServiceWithStandaloneTransportTest() {
+    feature_list_.InitAndEnableFeature(switches::kSyncStandaloneTransport);
+  }
+
+  ~ProfileSyncServiceWithStandaloneTransportTest() override {}
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class ProfileSyncServiceWithoutStandaloneTransportTest
+    : public ProfileSyncServiceTest {
+ protected:
+  ProfileSyncServiceWithoutStandaloneTransportTest() {
+    feature_list_.InitAndDisableFeature(switches::kSyncStandaloneTransport);
+  }
+
+  ~ProfileSyncServiceWithoutStandaloneTransportTest() override {}
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Verify that the server URLs are sane.
 TEST_F(ProfileSyncServiceTest, InitialState) {
   CreateService(ProfileSyncService::AUTO_START);
@@ -363,7 +393,7 @@
 
 // Verify that an initialization where first setup is not complete does not
 // start up the backend.
-TEST_F(ProfileSyncServiceTest, NeedsConfirmation) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportTest, NeedsConfirmation) {
   SignIn();
   CreateService(ProfileSyncService::MANUAL_START);
 
@@ -395,6 +425,31 @@
   EXPECT_EQ(now, sync_prefs.GetLastSyncedTime());
 }
 
+TEST_F(ProfileSyncServiceWithStandaloneTransportTest, NeedsConfirmation) {
+  SignIn();
+  CreateService(ProfileSyncService::MANUAL_START);
+
+  syncer::SyncPrefs sync_prefs(prefs());
+  base::Time now = base::Time::Now();
+  sync_prefs.SetLastSyncedTime(now);
+  sync_prefs.SetKeepEverythingSynced(true);
+  service()->Initialize();
+
+  EXPECT_TRUE(service()->IsSyncConfirmationNeeded());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            service()->GetDisableReasons());
+
+  // Sync should immediately start up in transport mode.
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncActive());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+
+  // The last sync time shouldn't be cleared.
+  // TODO(zea): figure out a way to check that the directory itself wasn't
+  // cleared.
+  EXPECT_EQ(now, sync_prefs.GetLastSyncedTime());
+}
+
 // Verify that the SetSetupInProgress function call updates state
 // and notifies observers.
 TEST_F(ProfileSyncServiceTest, SetupInProgress) {
@@ -433,7 +488,7 @@
 
   ASSERT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             service()->GetDisableReasons());
-  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  ASSERT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
 
   prefs()->SetManagedPref(syncer::prefs::kSyncManaged,
                           std::make_unique<base::Value>(true));
@@ -448,8 +503,7 @@
 TEST_F(ProfileSyncServiceTest, AbortedByShutdown) {
   CreateService(ProfileSyncService::AUTO_START);
   ON_CALL(*component_factory(), CreateSyncEngine(_, _, _, _))
-      .WillByDefault(
-          Return(ByMove(std::make_unique<FakeSyncEngineNoReturn>())));
+      .WillByDefault(ReturnNewFakeSyncEngineNoReturn());
 
   SignIn();
   InitializeForNthSync();
@@ -459,30 +513,68 @@
 }
 
 // Test RequestStop() before we've initialized the backend.
-TEST_F(ProfileSyncServiceTest, EarlyRequestStop) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportTest, EarlyRequestStop) {
   CreateService(ProfileSyncService::AUTO_START);
+  // Set up a fake sync engine that will not immediately finish initialization.
+  EXPECT_CALL(*component_factory(), CreateSyncEngine(_, _, _, _))
+      .WillOnce(ReturnNewFakeSyncEngineNoReturn());
   SignIn();
+  InitializeForNthSync();
 
+  ASSERT_EQ(syncer::SyncService::State::INITIALIZING, service()->GetState());
+
+  // Request stop. Sync should get disabled.
   service()->RequestStop(ProfileSyncService::KEEP_DATA);
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
             service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
 
-  // Because sync is not requested, this should fail.
-  InitializeForNthSync();
-  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
-            service()->GetDisableReasons());
-  EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
-
-  // Request start. This should be enough to allow init to happen.
+  // Request start again, this time with an engine that does get initialized.
+  // Sync should become active.
+  EXPECT_CALL(*component_factory(), CreateSyncEngine(_, _, _, _))
+      .WillOnce(ReturnNewFakeSyncEngine());
   service()->RequestStart();
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_TRUE(service()->IsSyncActive());
+  EXPECT_TRUE(service()->IsSyncFeatureEnabled());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportTest, EarlyRequestStop) {
+  CreateService(ProfileSyncService::AUTO_START);
+  // Set up a fake sync engine that will not immediately finish initialization.
+  EXPECT_CALL(*component_factory(), CreateSyncEngine(_, _, _, _))
+      .WillOnce(ReturnNewFakeSyncEngineNoReturn());
+  SignIn();
+  InitializeForNthSync();
+
+  ASSERT_EQ(syncer::SyncService::State::INITIALIZING, service()->GetState());
+
+  // Request stop. This should immediately restart the service in standalone
+  // transport mode.
+  EXPECT_CALL(*component_factory(), CreateSyncEngine(_, _, _, _))
+      .WillOnce(ReturnNewFakeSyncEngine());
+  service()->RequestStop(ProfileSyncService::KEEP_DATA);
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
+            service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncActive());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+
+  // Request start. Now Sync-the-feature should start again.
+  service()->RequestStart();
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_TRUE(service()->IsSyncActive());
+  EXPECT_TRUE(service()->IsSyncFeatureEnabled());
 }
 
 // Test RequestStop() after we've initialized the backend.
-TEST_F(ProfileSyncServiceTest, DisableAndEnableSyncTemporarily) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportTest,
+       DisableAndEnableSyncTemporarily) {
   CreateService(ProfileSyncService::AUTO_START);
   SignIn();
   InitializeForNthSync();
@@ -491,6 +583,8 @@
   ASSERT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             service()->GetDisableReasons());
   ASSERT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  ASSERT_TRUE(service()->IsSyncActive());
+  ASSERT_TRUE(service()->IsSyncFeatureEnabled());
 
   testing::Mock::VerifyAndClearExpectations(component_factory());
 
@@ -499,12 +593,48 @@
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
             service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncActive());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
 
   service()->RequestStart();
   EXPECT_FALSE(prefs()->GetBoolean(syncer::prefs::kSyncSuppressStart));
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_TRUE(service()->IsSyncActive());
+  EXPECT_TRUE(service()->IsSyncFeatureEnabled());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportTest,
+       DisableAndEnableSyncTemporarily) {
+  CreateService(ProfileSyncService::AUTO_START);
+  SignIn();
+  InitializeForNthSync();
+
+  ASSERT_FALSE(prefs()->GetBoolean(syncer::prefs::kSyncSuppressStart));
+  ASSERT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            service()->GetDisableReasons());
+  ASSERT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  ASSERT_TRUE(service()->IsSyncActive());
+  ASSERT_TRUE(service()->IsSyncFeatureEnabled());
+
+  testing::Mock::VerifyAndClearExpectations(component_factory());
+
+  service()->RequestStop(ProfileSyncService::KEEP_DATA);
+  EXPECT_TRUE(prefs()->GetBoolean(syncer::prefs::kSyncSuppressStart));
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
+            service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncActive());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+
+  service()->RequestStart();
+  EXPECT_FALSE(prefs()->GetBoolean(syncer::prefs::kSyncSuppressStart));
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
+            service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_TRUE(service()->IsSyncActive());
+  EXPECT_TRUE(service()->IsSyncFeatureEnabled());
 }
 
 // Certain ProfileSyncService tests don't apply to Chrome OS, for example
@@ -705,7 +835,7 @@
 #endif
 
 // Verify that LastSyncedTime and local DeviceInfo is cleared on sign out.
-TEST_F(ProfileSyncServiceTest, ClearDataOnSignOut) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportTest, ClearDataOnSignOut) {
   SignIn();
   CreateService(ProfileSyncService::AUTO_START);
   InitializeForNthSync();
@@ -716,12 +846,36 @@
 
   // Sign out.
   service()->RequestStop(ProfileSyncService::CLEAR_DATA);
-  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
 
   EXPECT_TRUE(service()->GetLastSyncedTime().is_null());
   EXPECT_FALSE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
 }
 
+TEST_F(ProfileSyncServiceWithStandaloneTransportTest, ClearDataOnSignOut) {
+  SignIn();
+  CreateService(ProfileSyncService::AUTO_START);
+  InitializeForNthSync();
+  ASSERT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  base::Time last_synced_time = service()->GetLastSyncedTime();
+  ASSERT_LT(base::Time::Now() - last_synced_time,
+            base::TimeDelta::FromMinutes(1));
+  ASSERT_TRUE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
+
+  // Sign out.
+  service()->RequestStop(ProfileSyncService::CLEAR_DATA);
+
+  // Even though Sync-the-feature is disabled, Sync-the-transport should still
+  // be running, and should have updated the last synced time.
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+
+  EXPECT_NE(service()->GetLastSyncedTime(), last_synced_time);
+  EXPECT_TRUE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
+}
+
 // Verify that credential errors get returned from GetAuthError().
 TEST_F(ProfileSyncServiceTest, CredentialErrorReturned) {
   // This test needs to manually send access tokens (or errors), so disable
@@ -1144,7 +1298,7 @@
 
 // Test that when ProfileSyncService receives actionable error
 // DISABLE_SYNC_ON_CLIENT it disables sync and signs out.
-TEST_F(ProfileSyncServiceTest, DisableSyncOnClient) {
+TEST_F(ProfileSyncServiceWithoutStandaloneTransportTest, DisableSyncOnClient) {
   SignIn();
   CreateService(ProfileSyncService::AUTO_START);
   InitializeForNthSync();
@@ -1158,21 +1312,60 @@
   client_cmd.action = syncer::DISABLE_SYNC_ON_CLIENT;
   service()->OnActionableError(client_cmd);
 
-// CrOS does not support signout.
-#if !defined(OS_CHROMEOS)
-  EXPECT_TRUE(signin_manager()->GetAuthenticatedAccountId().empty());
-  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE |
-                syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN,
-            service()->GetDisableReasons());
-#else
+#if defined(OS_CHROMEOS)
+  // ChromeOS does not support signout.
   EXPECT_FALSE(signin_manager()->GetAuthenticatedAccountId().empty());
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
             service()->GetDisableReasons());
-#endif
-
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
+#else
+  EXPECT_TRUE(signin_manager()->GetAuthenticatedAccountId().empty());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN |
+                syncer::SyncService::DISABLE_REASON_USER_CHOICE,
+            service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
   EXPECT_TRUE(service()->GetLastSyncedTime().is_null());
   EXPECT_FALSE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
+#endif
+
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(service()->IsSyncActive());
+}
+
+TEST_F(ProfileSyncServiceWithStandaloneTransportTest, DisableSyncOnClient) {
+  SignIn();
+  CreateService(ProfileSyncService::AUTO_START);
+  InitializeForNthSync();
+
+  ASSERT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+  ASSERT_LT(base::Time::Now() - service()->GetLastSyncedTime(),
+            base::TimeDelta::FromMinutes(1));
+  ASSERT_TRUE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
+
+  syncer::SyncProtocolError client_cmd;
+  client_cmd.action = syncer::DISABLE_SYNC_ON_CLIENT;
+  service()->OnActionableError(client_cmd);
+
+#if defined(OS_CHROMEOS)
+  // ChromeOS does not support signout.
+  EXPECT_FALSE(signin_manager()->GetAuthenticatedAccountId().empty());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_USER_CHOICE,
+            service()->GetDisableReasons());
+  // Since ChromeOS doesn't support signout and so the account is still there
+  // and available, Sync will restart in standalone transport mode.
+  EXPECT_EQ(syncer::SyncService::State::ACTIVE, service()->GetState());
+#else
+  EXPECT_TRUE(signin_manager()->GetAuthenticatedAccountId().empty());
+  EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN |
+                syncer::SyncService::DISABLE_REASON_USER_CHOICE,
+            service()->GetDisableReasons());
+  EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
+  EXPECT_TRUE(service()->GetLastSyncedTime().is_null());
+  EXPECT_FALSE(service()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo());
+#endif
+
+  EXPECT_FALSE(service()->IsSyncFeatureEnabled());
+  EXPECT_FALSE(service()->IsSyncActive());
 }
 
 // Verify a that local sync mode resumes after the policy is lifted.
@@ -1192,6 +1385,9 @@
             service()->GetDisableReasons());
   EXPECT_EQ(syncer::SyncService::State::DISABLED, service()->GetState());
 
+  // Note: If standalone transport is enabled, then setting kSyncManaged to
+  // false will immediately start up the engine. Otherwise, the RequestStart
+  // call below will trigger it.
   prefs()->SetManagedPref(syncer::prefs::kSyncManaged,
                           std::make_unique<base::Value>(false));
 
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
index b006ccc1..f399d801 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
@@ -138,6 +138,9 @@
     public static final String PARTNER_HOME_PAGE_BUTTON_PRESSED =
             "partner_home_page_button_pressed";
 
+    /** The user used a button in the bottom toolbar. */
+    public static final String CHROME_DUET_USED_BOTTOM_TOOLBAR = "chrome_duet_used_bottom_toolbar";
+
     /**
      * Do not instantiate.
      */
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
index 56ee24c3..9967165 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java
@@ -12,6 +12,7 @@
     public static final String DOWNLOAD_PAGE_FEATURE = "IPH_DownloadPage";
     public static final String DOWNLOAD_PAGE_SCREENSHOT_FEATURE = "IPH_DownloadPageScreenshot";
     public static final String DOWNLOAD_HOME_FEATURE = "IPH_DownloadHome";
+    public static final String CHROME_DUET_FEATURE = "IPH_ChromeDuet";
     public static final String CHROME_HOME_EXPAND_FEATURE = "IPH_ChromeHomeExpand";
     public static final String CHROME_HOME_PULL_TO_REFRESH_FEATURE = "IPH_ChromeHomePullToRefresh";
     public static final String CONTEXTUAL_SUGGESTIONS_FEATURE = "IPH_ContextualSuggestions";
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index d04bdcf1..86ac839a 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -25,6 +25,8 @@
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHDownloadPageScreenshotFeature{
     "IPH_DownloadPageScreenshot", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kIPHChromeDuetFeature{"IPH_ChromeDuet",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHChromeHomeExpandFeature{
     "IPH_ChromeHomeExpand", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kIPHChromeHomePullToRefreshFeature{
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index cd8c766..ce0ddd0 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -26,6 +26,7 @@
 extern const base::Feature kIPHDownloadHomeFeature;
 extern const base::Feature kIPHDownloadPageFeature;
 extern const base::Feature kIPHDownloadPageScreenshotFeature;
+extern const base::Feature kIPHChromeDuetFeature;
 extern const base::Feature kIPHChromeHomeExpandFeature;
 extern const base::Feature kIPHChromeHomePullToRefreshFeature;
 extern const base::Feature kIPHMediaDownloadFeature;
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index 3b59190e4..3a53e224 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -21,6 +21,7 @@
     &kIPHDownloadHomeFeature,
     &kIPHDownloadPageFeature,
     &kIPHDownloadPageScreenshotFeature,
+    &kIPHChromeDuetFeature,
     &kIPHChromeHomeExpandFeature,
     &kIPHChromeHomePullToRefreshFeature,
     &kIPHMediaDownloadFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index 69e73a2..7385596 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -52,6 +52,7 @@
 DEFINE_VARIATION_PARAM(kIPHDownloadPageFeature, "IPH_DownloadPage");
 DEFINE_VARIATION_PARAM(kIPHDownloadPageScreenshotFeature,
                        "IPH_DownloadPageScreenshot");
+DEFINE_VARIATION_PARAM(kIPHChromeDuetFeature, "IPH_ChromeDuet");
 DEFINE_VARIATION_PARAM(kIPHChromeHomeExpandFeature, "IPH_ChromeHomeExpand");
 DEFINE_VARIATION_PARAM(kIPHChromeHomePullToRefreshFeature,
                        "IPH_ChromeHomePullToRefresh");
@@ -101,6 +102,7 @@
         VARIATION_ENTRY(kIPHDownloadHomeFeature),
         VARIATION_ENTRY(kIPHDownloadPageFeature),
         VARIATION_ENTRY(kIPHDownloadPageScreenshotFeature),
+        VARIATION_ENTRY(kIPHChromeDuetFeature),
         VARIATION_ENTRY(kIPHChromeHomeExpandFeature),
         VARIATION_ENTRY(kIPHChromeHomePullToRefreshFeature),
         VARIATION_ENTRY(kIPHMediaDownloadFeature),
diff --git a/components/gcm_driver/BUILD.gn b/components/gcm_driver/BUILD.gn
index 0ddf0509..0c5bcb1e 100644
--- a/components/gcm_driver/BUILD.gn
+++ b/components/gcm_driver/BUILD.gn
@@ -203,9 +203,6 @@
     ]
   }
   deps += [
-    "//components/signin/core/browser",
-    "//components/signin/core/browser:test_support",
-    "//components/sync_preferences:test_support",
     "//services/identity/public/cpp",
     "//services/identity/public/cpp:test_support",
   ]
diff --git a/components/gcm_driver/DEPS b/components/gcm_driver/DEPS
index 952417e..4adaea8 100644
--- a/components/gcm_driver/DEPS
+++ b/components/gcm_driver/DEPS
@@ -5,14 +5,17 @@
   "+components/keyed_service",
   "+components/pref_registry",
   "+components/prefs",
-  "+components/signin",
   "+components/sync/driver",
   "+components/sync/protocol",
   "+components/sync_preferences",
   "+components/timers",  # Only used for Chrome OS builds.
   "+components/version_info",
   "+content/public/browser/browser_context.h",
-  "+google_apis/gaia",
+
+  # Whitelist specific headers from //google_apis/gaia. Contact
+  # blundell@chromium.org if looking to add more.
+  "+google_apis/gaia/gaia_oauth_client.h",
+  "+google_apis/gaia/google_service_auth_error.h",
   "+google_apis/gcm",
   "+jni",
   "+net",
diff --git a/components/gcm_driver/gcm_account_tracker_unittest.cc b/components/gcm_driver/gcm_account_tracker_unittest.cc
index a635925b..2abba16 100644
--- a/components/gcm_driver/gcm_account_tracker_unittest.cc
+++ b/components/gcm_driver/gcm_account_tracker_unittest.cc
@@ -12,31 +12,17 @@
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "components/gcm_driver/fake_gcm_driver.h"
-#include "components/signin/core/browser/account_tracker_service.h"
-#include "components/signin/core/browser/fake_gaia_cookie_manager_service.h"
-#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
-#include "components/signin/core/browser/fake_signin_manager.h"
-#include "components/signin/core/browser/test_signin_client.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "google_apis/gaia/fake_oauth2_token_service.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_status_code.h"
 #include "net/url_request/test_url_fetcher_factory.h"
 #include "net/url_request/url_request_test_util.h"
-#include "services/identity/public/cpp/identity_manager.h"
-#include "services/identity/public/cpp/identity_test_utils.h"
+#include "services/identity/public/cpp/identity_test_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace gcm {
 
 namespace {
 
-#if defined(OS_CHROMEOS)
-using SigninManagerForTest = FakeSigninManagerBase;
-#else
-using SigninManagerForTest = FakeSigninManager;
-#endif  // OS_CHROMEOS
-
 const char kEmail1[] = "account_1@me.com";
 const char kEmail2[] = "account_2@me.com";
 
@@ -213,48 +199,18 @@
 
   base::MessageLoop message_loop_;
   net::TestURLFetcherFactory test_fetcher_factory_;
-  sync_preferences::TestingPrefServiceSyncable pref_service_;
-  AccountTrackerService account_tracker_service_;
-  std::unique_ptr<TestSigninClient> test_signin_client_;
-  std::unique_ptr<SigninManagerForTest> fake_signin_manager_;
-  std::unique_ptr<FakeProfileOAuth2TokenService> fake_token_service_;
-  std::unique_ptr<FakeGaiaCookieManagerService>
-      fake_gaia_cookie_manager_service_;
-  std::unique_ptr<identity::IdentityManager> identity_manager_;
+  identity::IdentityTestEnvironment identity_test_env_;
   std::unique_ptr<GCMAccountTracker> tracker_;
 };
 
 GCMAccountTrackerTest::GCMAccountTrackerTest() {
-  fake_token_service_.reset(new FakeProfileOAuth2TokenService());
-
-  test_signin_client_.reset(new TestSigninClient(&pref_service_));
-#if defined(OS_CHROMEOS)
-  fake_signin_manager_.reset(new SigninManagerForTest(
-      test_signin_client_.get(), &account_tracker_service_));
-#else
-  fake_signin_manager_.reset(new SigninManagerForTest(
-      test_signin_client_.get(), fake_token_service_.get(),
-      &account_tracker_service_, nullptr));
-#endif
-
-  fake_gaia_cookie_manager_service_.reset(new FakeGaiaCookieManagerService(
-      fake_token_service_.get(), "gcm_account_tracker_unittest",
-      test_signin_client_.get()));
-  AccountTrackerService::RegisterPrefs(pref_service_.registry());
-  SigninManagerBase::RegisterProfilePrefs(pref_service_.registry());
-  SigninManagerBase::RegisterPrefs(pref_service_.registry());
-  account_tracker_service_.Initialize(test_signin_client_.get());
-
-  identity_manager_ = std::make_unique<identity::IdentityManager>(
-      fake_signin_manager_.get(), fake_token_service_.get(),
-      &account_tracker_service_, fake_gaia_cookie_manager_service_.get());
-
   std::unique_ptr<AccountTracker> gaia_account_tracker(new AccountTracker(
-      identity_manager_.get(),
+      identity_test_env_.identity_manager(),
       new net::TestURLRequestContextGetter(message_loop_.task_runner())));
 
   tracker_.reset(new GCMAccountTracker(std::move(gaia_account_tracker),
-                                       identity_manager_.get(), &driver_));
+                                       identity_test_env_.identity_manager(),
+                                       &driver_));
 }
 
 GCMAccountTrackerTest::~GCMAccountTrackerTest() {
@@ -264,10 +220,7 @@
 
 std::string GCMAccountTrackerTest::StartAccountAddition(
     const std::string& email) {
-  return identity::MakeAccountAvailable(&account_tracker_service_,
-                                        fake_token_service_.get(),
-                                        identity_manager_.get(), email)
-      .account_id;
+  return identity_test_env_.MakeAccountAvailable(email).account_id;
 }
 
 std::string GCMAccountTrackerTest::StartPrimaryAccountAddition(
@@ -279,10 +232,7 @@
 // setting of the primary account is done afterward to check that the flow
 // that ensues from the GoogleSigninSucceeded callback firing works as
 // expected.
-return identity::MakePrimaryAccountAvailable(fake_signin_manager_.get(),
-                                             fake_token_service_.get(),
-                                             identity_manager_.get(), email)
-    .account_id;
+return identity_test_env_.MakePrimaryAccountAvailable(email).account_id;
 }
 
 void GCMAccountTrackerTest::FinishAccountAddition(
@@ -310,23 +260,22 @@
 }
 
 void GCMAccountTrackerTest::RemoveAccount(const std::string& account_id) {
-  identity::RemoveRefreshTokenForAccount(fake_token_service_.get(),
-                                         identity_manager_.get(), account_id);
+  identity_test_env_.RemoveRefreshTokenForAccount(account_id);
 }
 
 void GCMAccountTrackerTest::IssueAccessToken(const std::string& account_id) {
-  fake_token_service_->IssueAllTokensForAccount(
+  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
       account_id, MakeAccessToken(account_id), base::Time::Max());
 }
 
 void GCMAccountTrackerTest::IssueExpiredAccessToken(
     const std::string& account_id) {
-  fake_token_service_->IssueAllTokensForAccount(
+  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
       account_id, MakeAccessToken(account_id), base::Time::Now());
 }
 
 void GCMAccountTrackerTest::IssueError(const std::string& account_id) {
-  fake_token_service_->IssueErrorForAllPendingRequestsForAccount(
+  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
       account_id,
       GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
 }
diff --git a/components/metrics/call_stack_profile_builder.h b/components/metrics/call_stack_profile_builder.h
index ec01592..fcd972cf 100644
--- a/components/metrics/call_stack_profile_builder.h
+++ b/components/metrics/call_stack_profile_builder.h
@@ -5,11 +5,10 @@
 #ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
 #define COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
 
-#include "base/profiler/stack_sampling_profiler.h"
-
 #include <map>
 
 #include "base/callback.h"
+#include "base/profiler/stack_sampling_profiler.h"
 
 namespace metrics {
 
diff --git a/components/ntp_tiles/most_visited_sites.cc b/components/ntp_tiles/most_visited_sites.cc
index 4bc981d6..3fca315 100644
--- a/components/ntp_tiles/most_visited_sites.cc
+++ b/components/ntp_tiles/most_visited_sites.cc
@@ -346,9 +346,10 @@
 
 void MostVisitedSites::OnMostVisitedURLsAvailable(
     const history::MostVisitedURLList& visited_list) {
-  // Ignore the event if tiles provided by the Suggestions Service, which take
-  // precedence.
-  if (mv_source_ == TileSource::SUGGESTIONS_SERVICE) {
+  // Ignore the event if tiles are provided by the Suggestions Service or custom
+  // links, which take precedence.
+  if ((custom_links_ && custom_links_->IsInitialized()) ||
+      mv_source_ == TileSource::SUGGESTIONS_SERVICE) {
     return;
   }
 
@@ -380,8 +381,11 @@
 
 void MostVisitedSites::OnSuggestionsProfileChanged(
     const SuggestionsProfile& suggestions_profile) {
-  if (suggestions_profile.suggestions_size() == 0 &&
-      mv_source_ != TileSource::SUGGESTIONS_SERVICE) {
+  // Ignore the event if tiles are provided by custom links, which take
+  // precedence.
+  if ((custom_links_ && custom_links_->IsInitialized()) ||
+      (suggestions_profile.suggestions_size() == 0 &&
+       mv_source_ != TileSource::SUGGESTIONS_SERVICE)) {
     return;
   }
 
diff --git a/components/ntp_tiles/most_visited_sites_unittest.cc b/components/ntp_tiles/most_visited_sites_unittest.cc
index 09e4ec0..6b4dc80 100644
--- a/components/ntp_tiles/most_visited_sites_unittest.cc
+++ b/components/ntp_tiles/most_visited_sites_unittest.cc
@@ -1090,7 +1090,8 @@
       .WillRepeatedly(InvokeCallbackArgument<0>(
           MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
   EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
-  EXPECT_CALL(*mock_custom_links_, IsInitialized()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_custom_links_, IsInitialized())
+      .WillRepeatedly(Return(false));
   EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
       .WillOnce(SaveArg<0>(&sections));
 
@@ -1123,7 +1124,8 @@
       .WillRepeatedly(InvokeCallbackArgument<0>(
           MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
   EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
-  EXPECT_CALL(*mock_custom_links_, IsInitialized()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_custom_links_, IsInitialized())
+      .WillRepeatedly(Return(false));
   EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
       .WillOnce(SaveArg<0>(&sections));
 
@@ -1134,6 +1136,118 @@
       sections.at(SectionType::PERSONALIZED),
       ElementsAre(MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES)));
 }
+
+TEST_P(MostVisitedSitesTest, ShouldFavorCustomLinksOverTopSites) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kNtpCustomLinks);
+  const char kTestUrl[] = "http://site1/";
+  const char kTestTitle[] = "Site 1";
+  std::vector<CustomLinksManager::Link> expected_links(
+      {CustomLinksManager::Link{GURL(kTestUrl),
+                                base::UTF8ToUTF16(kTestTitle)}});
+
+  std::map<SectionType, NTPTilesVector> sections;
+  EnableCustomLinks();
+  RecreateMostVisitedSites();
+  DisableRemoteSuggestions();
+
+  // Build tiles when custom links is not initialized. Tiles should be Top
+  // Sites.
+  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_, false))
+      .WillRepeatedly(InvokeCallbackArgument<0>(
+          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
+  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
+  EXPECT_CALL(*mock_custom_links_, IsInitialized())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
+      .WillOnce(SaveArg<0>(&sections));
+  most_visited_sites_->SetMostVisitedURLsObserver(&mock_observer_,
+                                                  /*num_sites=*/1);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_THAT(
+      sections.at(SectionType::PERSONALIZED),
+      ElementsAre(MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES)));
+
+  // Initialize custom links and rebuild tiles. Tiles should be custom links.
+  EXPECT_CALL(*mock_custom_links_, Initialize(_)).WillOnce(Return(true));
+  EXPECT_CALL(*mock_custom_links_, IsInitialized()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_custom_links_, GetLinks())
+      .WillOnce(ReturnRef(expected_links));
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
+      .WillOnce(SaveArg<0>(&sections));
+  most_visited_sites_->InitializeCustomLinks();
+  base::RunLoop().RunUntilIdle();
+  ASSERT_THAT(
+      sections.at(SectionType::PERSONALIZED),
+      ElementsAre(MatchesTile(kTestTitle, kTestUrl, TileSource::CUSTOM_LINKS)));
+
+  // Initiate notification for new Top Sites. This should be ignored.
+  EXPECT_CALL(*mock_custom_links_, IsInitialized()).WillOnce(Return(true));
+  // Notify with an empty SuggestionsProfile first to trigger a query for
+  // TopSites.
+  suggestions_service_callbacks_.Notify(SuggestionsProfile());
+  VerifyAndClearExpectations();
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_)).Times(0);
+  top_sites_callbacks_.ClearAndNotify(
+      {MakeMostVisitedURL("Site 2", "http://site2/")});
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_P(MostVisitedSitesTest, ShouldFavorCustomLinksOverSuggestions) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kNtpCustomLinks);
+  const char kTestUrl[] = "http://site1/";
+  const char kTestTitle[] = "Site 1";
+  std::vector<CustomLinksManager::Link> expected_links(
+      {CustomLinksManager::Link{GURL(kTestUrl),
+                                base::UTF8ToUTF16(kTestTitle)}});
+
+  std::map<SectionType, NTPTilesVector> sections;
+  EnableCustomLinks();
+  RecreateMostVisitedSites();
+
+  // Build tiles when custom links is not initialized. Tiles should be Top
+  // Sites.
+  EXPECT_CALL(mock_suggestions_service_, AddCallback(_))
+      .WillOnce(Invoke(&suggestions_service_callbacks_,
+                       &SuggestionsService::ResponseCallbackList::Add));
+  EXPECT_CALL(mock_suggestions_service_, GetSuggestionsDataFromCache())
+      .WillOnce(Return(MakeProfile({MakeSuggestion(kTestTitle, kTestUrl)})));
+  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
+  EXPECT_CALL(mock_suggestions_service_, FetchSuggestionsData())
+      .WillOnce(Return(true));
+  EXPECT_CALL(*mock_custom_links_, IsInitialized())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
+      .WillOnce(SaveArg<0>(&sections));
+  most_visited_sites_->SetMostVisitedURLsObserver(&mock_observer_,
+                                                  /*num_sites=*/1);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_THAT(sections.at(SectionType::PERSONALIZED),
+              ElementsAre(MatchesTile(kTestTitle, kTestUrl,
+                                      TileSource::SUGGESTIONS_SERVICE)));
+
+  // Initialize custom links and rebuild tiles. Tiles should be custom links.
+  EXPECT_CALL(*mock_custom_links_, Initialize(_)).WillOnce(Return(true));
+  EXPECT_CALL(*mock_custom_links_, IsInitialized()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_custom_links_, GetLinks())
+      .WillOnce(ReturnRef(expected_links));
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_))
+      .WillOnce(SaveArg<0>(&sections));
+  most_visited_sites_->InitializeCustomLinks();
+  base::RunLoop().RunUntilIdle();
+  ASSERT_THAT(
+      sections.at(SectionType::PERSONALIZED),
+      ElementsAre(MatchesTile(kTestTitle, kTestUrl, TileSource::CUSTOM_LINKS)));
+
+  // Initiate notification for new suggestions. This should be ignored.
+  EXPECT_CALL(*mock_custom_links_, IsInitialized())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(mock_observer_, OnURLsAvailable(_)).Times(0);
+  suggestions_service_callbacks_.Notify(
+      MakeProfile({MakeSuggestion("Site 2", "http://site2/")}));
+  base::RunLoop().RunUntilIdle();
+}
 #endif
 
 class MostVisitedSitesWithCacheHitTest : public MostVisitedSitesTest {
diff --git a/components/nux/show_promo_delegate.h b/components/nux/show_promo_delegate.h
index 6b935566..9c1e4fa 100644
--- a/components/nux/show_promo_delegate.h
+++ b/components/nux/show_promo_delegate.h
@@ -19,7 +19,8 @@
   virtual void ShowForNode(const bookmarks::BookmarkNode* node) = 0;
 
   // Return an instance of the promo delegate.
-  static std::unique_ptr<ShowPromoDelegate> CreatePromoDelegate();
+  static std::unique_ptr<ShowPromoDelegate> CreatePromoDelegate(
+      int string_specifier);
 };
 
 #endif  //  COMPONENTS_NUX_SHOW_PROMO_DELEGATE_H_
diff --git a/components/nux_google_apps/google_apps_handler.cc b/components/nux_google_apps/google_apps_handler.cc
index c9b2a624..85016c6d 100644
--- a/components/nux_google_apps/google_apps_handler.cc
+++ b/components/nux_google_apps/google_apps_handler.cc
@@ -103,8 +103,9 @@
   // TODO(hcarmona): Any advice here would be helpful.
 
   // Show bookmark bubble.
-  ShowPromoDelegate::CreatePromoDelegate()->ShowForNode(
-      bookmark_model_->bookmark_bar_node()->GetChild(0));
+  ShowPromoDelegate::CreatePromoDelegate(
+      IDS_NUX_GOOGLE_APPS_DESCRIPTION_PROMO_BUBBLE)
+      ->ShowForNode(bookmark_model_->bookmark_bar_node()->GetChild(0));
 
   UMA_HISTOGRAM_ENUMERATION(kGoogleAppsInteractionHistogram,
                             GoogleAppsInteraction::kGetStarted,
diff --git a/components/nux_google_apps_strings.grdp b/components/nux_google_apps_strings.grdp
index a3610ce..1dd1bf24 100644
--- a/components/nux_google_apps_strings.grdp
+++ b/components/nux_google_apps_strings.grdp
@@ -6,4 +6,7 @@
   <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION" desc="Description of what selecting apps and pressing 'Get started' will do.">
     Get quick access to your favorite Google Apps
   </message>
+  <message name="IDS_NUX_GOOGLE_APPS_DESCRIPTION_PROMO_BUBBLE" desc="Text shown when Google Apps have been added to highlight where bookmarks have been placed.">
+    Open apps easily with bookmarks
+  </message>
 </grit-part>
diff --git a/components/offline_pages/core/offline_pages_ukm_reporter.cc b/components/offline_pages/core/offline_pages_ukm_reporter.cc
index 355320d..74e488170 100644
--- a/components/offline_pages/core/offline_pages_ukm_reporter.cc
+++ b/components/offline_pages/core/offline_pages_ukm_reporter.cc
@@ -17,7 +17,7 @@
     return;
 
   // This unique ID represents this whole navigation.
-  int32_t source_id = ukm::UkmRecorder::GetNewSourceID();
+  ukm::SourceId source_id = ukm::UkmRecorder::GetNewSourceID();
 
   // Associate the URL with this navigation.
   // TODO(petewil): re-enable once crbug/792197 is addressed.
diff --git a/components/omnibox/browser/document_provider.cc b/components/omnibox/browser/document_provider.cc
index 010b62e1..d15d9123 100644
--- a/components/omnibox/browser/document_provider.cc
+++ b/components/omnibox/browser/document_provider.cc
@@ -50,6 +50,39 @@
                             DOCUMENT_MAX_REQUEST_HISTOGRAM_VALUE);
 }
 
+const char kErrorMessageAdminDisabled[] =
+    "Not eligible to query due to admin disabled Chrome search settings.";
+const char kErrorMessageRetryLater[] = "Not eligible to query, see retry info.";
+bool ResponseContainsBackoffSignal(const base::DictionaryValue* root_dict) {
+  const base::DictionaryValue* error_info;
+  if (!root_dict->GetDictionary("error", &error_info)) {
+    return false;
+  }
+  int code;
+  std::string status;
+  std::string message;
+  if (!error_info->GetInteger("code", &code) ||
+      !error_info->GetString("status", &status) ||
+      !error_info->GetString("message", &message)) {
+    return false;
+  }
+
+  // 403/PERMISSION_DENIED: Account is currently ineligible to receive results.
+  if (code == 403 && status == "PERMISSION_DENIED" &&
+      message == kErrorMessageAdminDisabled) {
+    return true;
+  }
+
+  // 503/UNAVAILABLE: Uninteresting set of results, or another server request to
+  // backoff.
+  if (code == 503 && status == "UNAVAILABLE" &&
+      message == kErrorMessageRetryLater) {
+    return true;
+  }
+
+  return false;
+}
+
 }  // namespace
 
 // static
@@ -86,6 +119,11 @@
   if (!is_authenticated)
     return false;
 
+  // We haven't received a server backoff signal.
+  if (backoff_for_session_) {
+    return false;
+  }
+
   // Google must be set as default search provider; we mix results which may
   // change placement.
   if (template_url_service == nullptr)
@@ -102,10 +140,8 @@
   TRACE_EVENT0("omnibox", "DocumentProvider::Start");
   matches_.clear();
 
-  // TODO(skare): get from identitymanager, to short-circuit sooner.
-  bool is_authenticated = true;
   if (!IsDocumentProviderAllowed(client_->GetPrefs(), client_->IsOffTheRecord(),
-                                 is_authenticated,
+                                 client_->IsAuthenticated(),
                                  client_->GetTemplateURLService())) {
     return;
   }
@@ -176,6 +212,7 @@
 DocumentProvider::DocumentProvider(AutocompleteProviderClient* client,
                                    AutocompleteProviderListener* listener)
     : AutocompleteProvider(AutocompleteProvider::TYPE_DOCUMENT),
+      backoff_for_session_(false),
       client_(client),
       listener_(listener),
       weak_ptr_factory_(this) {}
@@ -202,8 +239,8 @@
 }
 
 bool DocumentProvider::UpdateResults(const std::string& json_data) {
-  std::unique_ptr<base::DictionaryValue> response =
-      base::DictionaryValue::From(base::JSONReader::Read(json_data));
+  std::unique_ptr<base::DictionaryValue> response = base::DictionaryValue::From(
+      base::JSONReader::Read(json_data, base::JSON_ALLOW_TRAILING_COMMAS));
   if (!response)
     return false;
 
@@ -223,6 +260,16 @@
   if (!root_val.GetAsDictionary(&root_dict)) {
     return false;
   }
+
+  // The server may ask the client to back off, in which case we back off for
+  // the session.
+  // TODO(skare): Respect retryDelay if provided, ideally by calling via gRPC.
+  if (ResponseContainsBackoffSignal(root_dict)) {
+    backoff_for_session_ = true;
+    return false;
+  }
+
+  // Otherwise parse the results.
   if (!root_dict->GetList("results", &results_list)) {
     return false;
   }
diff --git a/components/omnibox/browser/document_provider.h b/components/omnibox/browser/document_provider.h
index cbfcfd1..4943ac2 100644
--- a/components/omnibox/browser/document_provider.h
+++ b/components/omnibox/browser/document_provider.h
@@ -62,7 +62,13 @@
                            CheckFeaturePrerequisiteClientSettingOff);
   FRIEND_TEST_ALL_PREFIXES(DocumentProviderTest,
                            CheckFeaturePrerequisiteDefaultSearch);
+  FRIEND_TEST_ALL_PREFIXES(DocumentProviderTest,
+                           CheckFeaturePrerequisiteServerBackoff);
   FRIEND_TEST_ALL_PREFIXES(DocumentProviderTest, ParseDocumentSearchResults);
+  FRIEND_TEST_ALL_PREFIXES(DocumentProviderTest,
+                           ParseDocumentSearchResultsWithBackoff);
+  FRIEND_TEST_ALL_PREFIXES(DocumentProviderTest,
+                           ParseDocumentSearchResultsWithIneligibleFlag);
   DocumentProvider(AutocompleteProviderClient* client,
                    AutocompleteProviderListener* listener);
 
@@ -90,9 +96,14 @@
       std::unique_ptr<network::SimpleURLLoader> loader);
 
   // Parses document search result JSON.
+  // Returns true if |matches| was populated with fresh suggestions.
   bool ParseDocumentSearchResults(const base::Value& root_val,
                                   ACMatches* matches);
 
+  // Whether the server has instructed us to backoff for this session (in
+  // cases where the corpus is uninteresting).
+  bool backoff_for_session_;
+
   // Client for accessing TemplateUrlService, prefs, etc.
   AutocompleteProviderClient* client_;
 
diff --git a/components/omnibox/browser/document_provider_unittest.cc b/components/omnibox/browser/document_provider_unittest.cc
index 0c6df69..de31af7 100644
--- a/components/omnibox/browser/document_provider_unittest.cc
+++ b/components/omnibox/browser/document_provider_unittest.cc
@@ -166,6 +166,25 @@
   template_url_service->Remove(new_default_provider);
 }
 
+TEST_F(DocumentProviderTest, CheckFeaturePrerequisiteServerBackoff) {
+  PrefService* fake_prefs = client_->GetPrefs();
+  TemplateURLService* template_url_service = client_->GetTemplateURLService();
+  bool is_incognito = false;
+  bool is_authenticated = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(omnibox::kDocumentProvider);
+
+  // Feature starts enabled.
+  EXPECT_TRUE(provider_->IsDocumentProviderAllowed(
+      fake_prefs, is_incognito, is_authenticated, template_url_service));
+
+  // Server setting backoff flag disables it.
+  provider_->backoff_for_session_ = true;
+  EXPECT_FALSE(provider_->IsDocumentProviderAllowed(
+      fake_prefs, is_incognito, is_authenticated, template_url_service));
+  provider_->backoff_for_session_ = false;
+}
+
 TEST_F(DocumentProviderTest, ParseDocumentSearchResults) {
   const char kGoodJSONResponse[] = R"({
       "results": [
@@ -193,4 +212,73 @@
   EXPECT_EQ(matches[1].contents, base::ASCIIToUTF16("Document 2"));
   EXPECT_EQ(matches[1].destination_url,
             GURL("https://documentprovider.tld/doc?id=2"));
+  ASSERT_FALSE(provider_->backoff_for_session_);
+}
+
+TEST_F(DocumentProviderTest, ParseDocumentSearchResultsWithBackoff) {
+  // Response where the server wishes to trigger backoff.
+  const char kBackoffJSONResponse[] = R"({
+      "error": {
+        "code": 503,
+        "message": "Not eligible to query, see retry info.",
+        "status": "UNAVAILABLE",
+        "details": [
+          {
+            "@type": "type.googleapis.com/google.rpc.RetryInfo",
+            "retryDelay": "100000s"
+          },
+        ]
+      }
+    })";
+
+  ASSERT_FALSE(provider_->backoff_for_session_);
+  std::unique_ptr<base::DictionaryValue> backoff_response =
+      base::DictionaryValue::From(base::JSONReader::Read(
+          kBackoffJSONResponse, base::JSON_ALLOW_TRAILING_COMMAS));
+  ASSERT_TRUE(backoff_response != nullptr);
+
+  ACMatches matches;
+  provider_->ParseDocumentSearchResults(*backoff_response, &matches);
+  ASSERT_TRUE(provider_->backoff_for_session_);
+}
+
+TEST_F(DocumentProviderTest, ParseDocumentSearchResultsWithIneligibleFlag) {
+  // Response where the server wishes to trigger backoff.
+  const char kIneligibleJSONResponse[] = R"({
+      "error": {
+        "code": 403,
+        "message": "Not eligible to query due to admin disabled Chrome search settings.",
+        "status": "PERMISSION_DENIED",
+      }
+    })";
+
+  // Same as above, but the message doesn't match. We should accept this
+  // response, but it isn't expected to trigger backoff.
+  const char kMismatchedMessageJSON[] = R"({
+      "error": {
+        "code": 403,
+        "message": "Some other thing went wrong.",
+        "status": "PERMISSION_DENIED",
+      }
+    })";
+
+  ACMatches matches;
+  ASSERT_FALSE(provider_->backoff_for_session_);
+
+  // First, parse an invalid response - shouldn't prohibit future requests
+  // from working but also shouldn't trigger backoff.
+  std::unique_ptr<base::DictionaryValue> bad_response =
+      base::DictionaryValue::From(base::JSONReader::Read(
+          kMismatchedMessageJSON, base::JSON_ALLOW_TRAILING_COMMAS));
+  ASSERT_TRUE(bad_response != nullptr);
+  provider_->ParseDocumentSearchResults(*bad_response, &matches);
+  ASSERT_FALSE(provider_->backoff_for_session_);
+
+  // Now parse a response that does trigger backoff.
+  std::unique_ptr<base::DictionaryValue> backoff_response =
+      base::DictionaryValue::From(base::JSONReader::Read(
+          kIneligibleJSONResponse, base::JSON_ALLOW_TRAILING_COMMAS));
+  ASSERT_TRUE(backoff_response != nullptr);
+  provider_->ParseDocumentSearchResults(*backoff_response, &matches);
+  ASSERT_TRUE(provider_->backoff_for_session_);
 }
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md
index d35d35d..32e4625 100644
--- a/components/onc/docs/onc_spec.md
+++ b/components/onc/docs/onc_spec.md
@@ -476,6 +476,13 @@
       provided by the system. If the network is not in range this field will
       be set to '0' or not present.
 
+* **TetheringState**
+    * (optional, read-only, defaults to "NotDetected") - **string**
+    * The tethering state of the WiFi connection. If the connection is
+      tethered the value is "Confirmed". If the connection is suspected to be
+      tethered the value is "Suspected". In all other cases it's
+      "NotDetected".
+
 ---
   * At least one of the fields **HexSSID** or **SSID** must be present.
   * If both **HexSSID** and **SSID** are set, the values must be consistent.
diff --git a/components/onc/onc_constants.cc b/components/onc/onc_constants.cc
index 382a9aa..432571c 100644
--- a/components/onc/onc_constants.cc
+++ b/components/onc/onc_constants.cc
@@ -233,6 +233,7 @@
 const char kSecurity[] = "Security";
 const char kSecurityNone[] = "None";
 const char kSignalStrength[] = "SignalStrength";
+const char kTetheringState[] = "TetheringState";
 const char kWEP_8021X[] = "WEP-8021X";
 const char kWEP_PSK[] = "WEP-PSK";
 const char kWPA_EAP[] = "WPA-EAP";
@@ -475,4 +476,10 @@
 const char kEnabled[] = "Enabled";
 }  // device_state
 
+namespace tethering_state {
+const char kTetheringConfirmedState[] = "Confirmed";
+const char kTetheringNotDetectedState[] = "NotDetected";
+const char kTetheringSuspectedState[] = "Suspected";
+}  // namespace tethering_state
+
 }  // namespace onc
diff --git a/components/onc/onc_constants.h b/components/onc/onc_constants.h
index ac878240..3babd622 100644
--- a/components/onc/onc_constants.h
+++ b/components/onc/onc_constants.h
@@ -246,6 +246,7 @@
 ONC_EXPORT extern const char kSecurity[];
 ONC_EXPORT extern const char kSecurityNone[];
 ONC_EXPORT extern const char kSignalStrength[];
+ONC_EXPORT extern const char kTetheringState[];
 ONC_EXPORT extern const char kWEP_PSK[];
 ONC_EXPORT extern const char kWEP_8021X[];
 ONC_EXPORT extern const char kWPA_PSK[];
@@ -479,6 +480,12 @@
 ONC_EXPORT extern const char kEnabled[];
 }  // device_state
 
+namespace tethering_state {
+ONC_EXPORT extern const char kTetheringConfirmedState[];
+ONC_EXPORT extern const char kTetheringNotDetectedState[];
+ONC_EXPORT extern const char kTetheringSuspectedState[];
+}  // namespace tethering_state
+
 }  // namespace onc
 
 #endif  // COMPONENTS_ONC_ONC_CONSTANTS_H_
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 759a7f86..200262e 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -81,7 +81,7 @@
 namespace {
 
 // Convenience enum for interacting with SQL queries that use all the columns.
-enum LoginTableColumns {
+enum LoginDatabaseTableColumns {
   COLUMN_ORIGIN_URL = 0,
   COLUMN_ACTION_URL,
   COLUMN_USERNAME_ELEMENT,
@@ -448,7 +448,8 @@
   DCHECK_EQ(19u, version);
 
   DCHECK_EQ(static_cast<size_t>(COLUMN_NUM), builder->NumberOfColumns())
-      << "Adjust LoginTableColumns if you change column definitions here.";
+      << "Adjust LoginDatabaseTableColumns if you change column definitions "
+         "here.";
 }
 
 // Call this after having called InitializeBuilder, to migrate the database from
diff --git a/components/password_manager/core/browser/new_password_form_manager.cc b/components/password_manager/core/browser/new_password_form_manager.cc
index 3ddaf1f..410250ff7 100644
--- a/components/password_manager/core/browser/new_password_form_manager.cc
+++ b/components/password_manager/core/browser/new_password_form_manager.cc
@@ -60,14 +60,6 @@
   return {form.new_password_value, form.new_password_element};
 }
 
-// Update |credential| to reflect usage.
-void UpdateMetadataForUsage(PasswordForm* credential) {
-  ++credential->times_used;
-
-  // Remove alternate usernames. At this point we assume that we have found
-  // the right username.
-  credential->other_possible_usernames.clear();
-}
 
 // Copies field properties masks from the form |from| to the form |to|.
 void CopyFieldPropertiesMasks(const FormData& from, FormData* to) {
@@ -363,7 +355,7 @@
       // If this isn't updated, then password generation uploads are off for
       // sites where PSL matching is required to fill the login form, as two
       // PASSWORD votes are uploaded per saved password instead of one.
-      UpdateMetadataForUsage(&pending_credentials_);
+      password_manager_util::UpdateMetadataForUsage(&pending_credentials_);
 
       // Update |pending_credentials_| in order to be able correctly save it.
       pending_credentials_.origin = submitted_form_.origin;
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index bb9643b..22512104 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -345,13 +345,12 @@
     metrics_util::LogContextOfShowAllSavedPasswordsAccepted(
         metrics_util::SHOW_ALL_SAVED_PASSWORDS_CONTEXT_PASSWORD);
 
-    if (password_client_) {
+    if (password_client_ && password_client_->GetMetricsRecorder()) {
       using UserAction =
           password_manager::PasswordManagerMetricsRecorder::PageLevelUserAction;
-
-      password_client_->GetMetricsRecorder().RecordPageLevelUserAction(
+      password_client_->GetMetricsRecorder()->RecordPageLevelUserAction(
           UserAction::kShowAllPasswordsWhileSomeAreSuggested);
-      }
+    }
   } else {
     bool success =
         FillSuggestion(form_data_key_, GetUsernameFromSuggestion(value));
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc
index 1f556f02..32bb274b2 100644
--- a/components/password_manager/core/browser/password_form_manager.cc
+++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -63,15 +63,6 @@
   return !s.empty() && DoesStringContainOnlyDigits(s) && s.size() < 3;
 }
 
-// Update |credential| to reflect usage.
-void UpdateMetadataForUsage(PasswordForm* credential) {
-  ++credential->times_used;
-
-  // Remove alternate usernames. At this point we assume that we have found
-  // the right username.
-  credential->other_possible_usernames.clear();
-}
-
 // Returns true iff |best_matches| contain a preferred credential with a
 // username other than |preferred_username|.
 bool DidPreferenceChange(
@@ -599,7 +590,7 @@
   DCHECK(!IsNewLogin() && pending_credentials_.preferred);
   DCHECK(!client_->IsIncognito());
 
-  UpdateMetadataForUsage(&pending_credentials_);
+  password_manager_util::UpdateMetadataForUsage(&pending_credentials_);
 
   base::RecordAction(
       base::UserMetricsAction("PasswordManager_LoginFollowingAutofill"));
@@ -643,7 +634,7 @@
       // If this isn't updated, then password generation uploads are off for
       // sites where PSL matching is required to fill the login form, as two
       // PASSWORD votes are uploaded per saved password instead of one.
-      UpdateMetadataForUsage(&pending_credentials_);
+      password_manager_util::UpdateMetadataForUsage(&pending_credentials_);
 
       // Update |pending_credentials_| in order to be able correctly save it.
       pending_credentials_.origin = submitted_form_->origin;
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index 47740b18..321be70 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -29,7 +29,6 @@
 #include "components/password_manager/core/browser/password_form_manager.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_driver.h"
-#include "components/password_manager/core/browser/password_manager_metrics_recorder.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/password_reuse_defines.h"
@@ -395,17 +394,16 @@
   }
 
   if (!client_->IsSavingAndFillingEnabledForCurrentPage()) {
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::SAVING_DISABLED, main_frame_url_,
-        form.origin, logger.get());
+    RecordProvisionalSaveFailure(
+        PasswordManagerMetricsRecorder::SAVING_DISABLED, form.origin,
+        logger.get());
     return;
   }
 
   // No password value to save? Then don't.
   if (PasswordFormManager::PasswordToSave(form).first.empty()) {
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::EMPTY_PASSWORD, main_frame_url_,
-        form.origin, logger.get());
+    RecordProvisionalSaveFailure(PasswordManagerMetricsRecorder::EMPTY_PASSWORD,
+                                 form.origin, logger.get());
     return;
   }
 
@@ -413,9 +411,9 @@
   metrics_util::LogShouldBlockPasswordForSameOriginButDifferentScheme(
       should_block);
   if (should_block) {
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS,
-        main_frame_url_, form.origin, logger.get());
+    RecordProvisionalSaveFailure(
+        PasswordManagerMetricsRecorder::SAVING_ON_HTTP_AFTER_HTTPS, form.origin,
+        logger.get());
     return;
   }
 
@@ -426,9 +424,9 @@
   // first loading the page containing the form. Don't offer to save
   // passwords in this case.
   if (!matched_manager) {
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::NO_MATCHING_FORM, main_frame_url_,
-        form.origin, logger.get());
+    RecordProvisionalSaveFailure(
+        PasswordManagerMetricsRecorder::NO_MATCHING_FORM, form.origin,
+        logger.get());
     return;
   }
   matched_manager->SaveSubmittedFormTypeForMetrics(form);
@@ -811,8 +809,8 @@
       FormFetcher::State::WAITING) {
     // We have a provisional save manager, but it didn't finish matching yet.
     // We just give up.
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::MATCHING_NOT_COMPLETE, main_frame_url_,
+    RecordProvisionalSaveFailure(
+        PasswordManagerMetricsRecorder::MATCHING_NOT_COMPLETE,
         provisional_save_manager_->GetOrigin(), logger.get());
     provisional_save_manager_.reset();
     return false;
@@ -945,8 +943,8 @@
   if (!client_->GetStoreResultFilter()->ShouldSave(
           *provisional_save_manager_->submitted_form())) {
     provisional_save_manager_->WipeStoreCopyIfOutdated();
-    client_->GetMetricsRecorder().RecordProvisionalSaveFailure(
-        PasswordManagerMetricsRecorder::SYNC_CREDENTIAL, main_frame_url_,
+    RecordProvisionalSaveFailure(
+        PasswordManagerMetricsRecorder::SYNC_CREDENTIAL,
         provisional_save_manager_->GetOrigin(), logger.get());
     provisional_save_manager_.reset();
     return;
@@ -1142,4 +1140,14 @@
   return matched_manager;
 }
 
+void PasswordManager::RecordProvisionalSaveFailure(
+    PasswordManagerMetricsRecorder::ProvisionalSaveFailure failure,
+    const GURL& form_origin,
+    BrowserSavePasswordProgressLogger* logger) {
+  if (client_ && client_->GetMetricsRecorder()) {
+    client_->GetMetricsRecorder()->RecordProvisionalSaveFailure(
+        failure, main_frame_url_, form_origin, logger);
+  }
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager.h b/components/password_manager/core/browser/password_manager.h
index f54a51c..dad50c33 100644
--- a/components/password_manager/core/browser/password_manager.h
+++ b/components/password_manager/core/browser/password_manager.h
@@ -21,6 +21,7 @@
 #include "components/password_manager/core/browser/form_submission_observer.h"
 #include "components/password_manager/core/browser/login_model.h"
 #include "components/password_manager/core/browser/password_form_manager.h"
+#include "components/password_manager/core/browser/password_manager_metrics_recorder.h"
 
 class PrefRegistrySimple;
 
@@ -244,6 +245,13 @@
   PasswordFormManager* GetMatchingPendingManager(
       const autofill::PasswordForm& form);
 
+  // Records provisional save failure using current |client_| and
+  // |main_frame_url_|.
+  void RecordProvisionalSaveFailure(
+      PasswordManagerMetricsRecorder::ProvisionalSaveFailure failure,
+      const GURL& form_origin,
+      BrowserSavePasswordProgressLogger* logger);
+
   // Note about how a PasswordFormManager can transition from
   // pending_login_managers_ to provisional_save_manager_ and the infobar.
   //
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index ca894aa..9804694 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -247,8 +247,9 @@
   // Gets a metrics recorder for the currently committed navigation.
   // As PasswordManagerMetricsRecorder submits metrics on destruction, a new
   // instance will be returned for each committed navigation. A caller must not
-  // hold on to the pointer.
-  virtual PasswordManagerMetricsRecorder& GetMetricsRecorder() = 0;
+  // hold on to the pointer. This method returns a nullptr if the client
+  // does not support metrics recording.
+  virtual PasswordManagerMetricsRecorder* GetMetricsRecorder() = 0;
 
   // Gets the PasswordRequirementsService associated with the client. It is
   // valid that this method returns a nullptr if the PasswordRequirementsService
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index 7a3ef7719..0413115c 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -40,7 +40,7 @@
   ~BlacklistedCredentialsCleaner() override = default;
 
   void OnGetPasswordStoreResults(
-      std::vector<std::unique_ptr<autofill::PasswordForm>> results) override {
+      std::vector<std::unique_ptr<PasswordForm>> results) override {
     bool need_to_clean = !prefs_->GetBoolean(
         password_manager::prefs::kBlacklistedCredentialsStripped);
     UMA_HISTOGRAM_BOOLEAN("PasswordManager.BlacklistedSites.NeedToBeCleaned",
@@ -55,7 +55,7 @@
   // TODO(https://crbug.com/817754): remove the code once majority of the users
   // executed it.
   void RemoveUsernameAndPassword(
-      const std::vector<std::unique_ptr<autofill::PasswordForm>>& results) {
+      const std::vector<std::unique_ptr<PasswordForm>>& results) {
     bool cleaned_something = false;
     for (const auto& form : results) {
       DCHECK(form->blacklisted_by_user);
@@ -77,7 +77,7 @@
   }
 
   void RemoveDuplicates(
-      const std::vector<std::unique_ptr<autofill::PasswordForm>>& results) {
+      const std::vector<std::unique_ptr<PasswordForm>>& results) {
     std::set<std::string> signon_realms;
     for (const auto& form : results) {
       DCHECK(form->blacklisted_by_user);
@@ -112,6 +112,15 @@
 
 }  // namespace
 
+// Update |credential| to reflect usage.
+void UpdateMetadataForUsage(PasswordForm* credential) {
+  ++credential->times_used;
+
+  // Remove alternate usernames. At this point we assume that we have found
+  // the right username.
+  credential->other_possible_usernames.clear();
+}
+
 password_manager::SyncState GetPasswordSyncState(
     const syncer::SyncService* sync_service) {
   if (sync_service && sync_service->IsFirstSetupComplete() &&
@@ -138,10 +147,9 @@
   return password_manager::NOT_SYNCING;
 }
 
-void FindDuplicates(
-    std::vector<std::unique_ptr<autofill::PasswordForm>>* forms,
-    std::vector<std::unique_ptr<autofill::PasswordForm>>* duplicates,
-    std::vector<std::vector<autofill::PasswordForm*>>* tag_groups) {
+void FindDuplicates(std::vector<std::unique_ptr<PasswordForm>>* forms,
+                    std::vector<std::unique_ptr<PasswordForm>>* duplicates,
+                    std::vector<std::vector<PasswordForm*>>* tag_groups) {
   if (forms->empty())
     return;
 
@@ -149,11 +157,11 @@
   // duplicates. Therefore, the caller should try to preserve it.
   std::stable_sort(forms->begin(), forms->end(), autofill::LessThanUniqueKey());
 
-  std::vector<std::unique_ptr<autofill::PasswordForm>> unique_forms;
+  std::vector<std::unique_ptr<PasswordForm>> unique_forms;
   unique_forms.push_back(std::move(forms->front()));
   if (tag_groups) {
     tag_groups->clear();
-    tag_groups->push_back(std::vector<autofill::PasswordForm*>());
+    tag_groups->push_back(std::vector<PasswordForm*>());
     tag_groups->front().push_back(unique_forms.front().get());
   }
   for (auto it = forms->begin() + 1; it != forms->end(); ++it) {
@@ -163,8 +171,7 @@
       duplicates->push_back(std::move(*it));
     } else {
       if (tag_groups)
-        tag_groups->push_back(
-            std::vector<autofill::PasswordForm*>(1, it->get()));
+        tag_groups->push_back(std::vector<PasswordForm*>(1, it->get()));
       unique_forms.push_back(std::move(*it));
     }
   }
@@ -172,22 +179,20 @@
 }
 
 void TrimUsernameOnlyCredentials(
-    std::vector<std::unique_ptr<autofill::PasswordForm>>* android_credentials) {
+    std::vector<std::unique_ptr<PasswordForm>>* android_credentials) {
   // Remove username-only credentials which are not federated.
   base::EraseIf(*android_credentials,
-                [](const std::unique_ptr<autofill::PasswordForm>& form) {
-                  return form->scheme ==
-                             autofill::PasswordForm::SCHEME_USERNAME_ONLY &&
+                [](const std::unique_ptr<PasswordForm>& form) {
+                  return form->scheme == PasswordForm::SCHEME_USERNAME_ONLY &&
                          form->federation_origin.unique();
                 });
 
   // Set "skip_zero_click" on federated credentials.
-  std::for_each(
-      android_credentials->begin(), android_credentials->end(),
-      [](const std::unique_ptr<autofill::PasswordForm>& form) {
-        if (form->scheme == autofill::PasswordForm::SCHEME_USERNAME_ONLY)
-          form->skip_zero_click = true;
-      });
+  std::for_each(android_credentials->begin(), android_credentials->end(),
+                [](const std::unique_ptr<PasswordForm>& form) {
+                  if (form->scheme == PasswordForm::SCHEME_USERNAME_ONLY)
+                    form->skip_zero_click = true;
+                });
 }
 
 bool IsLoggingActive(const password_manager::PasswordManagerClient* client) {
diff --git a/components/password_manager/core/browser/password_manager_util.h b/components/password_manager/core/browser/password_manager_util.h
index cfe81f4..9363a20 100644
--- a/components/password_manager/core/browser/password_manager_util.h
+++ b/components/password_manager/core/browser/password_manager_util.h
@@ -32,6 +32,9 @@
 
 namespace password_manager_util {
 
+// Update |credential| to reflect usage.
+void UpdateMetadataForUsage(autofill::PasswordForm* credential);
+
 // Reports whether and how passwords are currently synced. In particular, for a
 // null |sync_service| returns NOT_SYNCING.
 password_manager::SyncState GetPasswordSyncState(
diff --git a/components/password_manager/core/browser/stub_password_manager_client.cc b/components/password_manager/core/browser/stub_password_manager_client.cc
index c003ad4..ee3df8f 100644
--- a/components/password_manager/core/browser/stub_password_manager_client.cc
+++ b/components/password_manager/core/browser/stub_password_manager_client.cc
@@ -94,12 +94,12 @@
   return ukm_source_id_;
 }
 
-PasswordManagerMetricsRecorder&
+PasswordManagerMetricsRecorder*
 StubPasswordManagerClient::GetMetricsRecorder() {
   if (!metrics_recorder_) {
     metrics_recorder_.emplace(GetUkmSourceId(), GetMainFrameURL());
   }
-  return metrics_recorder_.value();
+  return base::OptionalOrNullptr(metrics_recorder_);
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/stub_password_manager_client.h b/components/password_manager/core/browser/stub_password_manager_client.h
index 58f2610..88c5ea4 100644
--- a/components/password_manager/core/browser/stub_password_manager_client.h
+++ b/components/password_manager/core/browser/stub_password_manager_client.h
@@ -63,7 +63,7 @@
   void LogPasswordReuseDetectedEvent() override;
 #endif
   ukm::SourceId GetUkmSourceId() override;
-  PasswordManagerMetricsRecorder& GetMetricsRecorder() override;
+  PasswordManagerMetricsRecorder* GetMetricsRecorder() override;
 
  private:
   const StubCredentialsFilter credentials_filter_;
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index e0b2d788..82b31483 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -6611,6 +6611,25 @@
       If this policy is set to False, an error message will be displayed instead of the network configuration prompt.'''
     },
     {
+      'name': 'DeviceLocalAccountManagedSessionEnabled',
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'supported_on': ['chrome_os:70-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': False,
+      },
+      'example_value': True,
+      'id': 463,
+      'caption': '''Allow managed session on device''',
+      'tags': [],
+      'desc': '''If this policy is set to false or left unset, managed guest session will behave as documented in https://support.google.com/chrome/a/answer/3017014 - the standard "Public Session".
+
+      If this policy is set to true, managed guest session will take on "Managed Session" behaviour which lifts many of the restrictions that are in place for regular "Public Sessions".
+
+      If this policy is set, the user cannot change or override it.''',
+    },
+    {
       'name': 'DeviceBlockDevmode',
       'type': 'main',
       'schema': { 'type': 'boolean' },
@@ -12102,8 +12121,16 @@
                       'type': 'object',
                       'id': 'Time',
                       'properties': {
-                        'hour': { 'type': 'integer' },
-                        'minute': { 'type': 'integer'}
+                        'hour': {
+                          'type': 'integer',
+                          'minimum': 0,
+                          'maximum': 23
+                        },
+                        'minute': {
+                          'type': 'integer',
+                          'minimum': 0,
+                          'maximum': 59
+                        },
                       }
                     },
                     'ends_at': { '$ref': 'Time' },
@@ -12120,7 +12147,10 @@
                 'type': 'object',
                 'id': 'TimeUsageLimitEntry',
                 'properties': {
-                  'usage_quota_mins': { 'type': 'integer' },
+                  'usage_quota_mins': {
+                    'type': 'integer',
+                    'minimum': 0
+                  },
                   'last_updated_millis': { 'type': 'string' }
                 }
               },
@@ -12149,7 +12179,10 @@
                 'action_specific_data': {
                   'type': 'object',
                   'properties': {
-                    'duration_mins': { 'type': 'integer' }
+                    'duration_mins': {
+                      'type': 'integer',
+                      'minimum': 0
+                    }
                   }
                 }
               }
@@ -12727,5 +12760,5 @@
   },
   'placeholders': [],
   'deleted_policy_ids': [412],
-  'highest_id_currently_used': 462
+  'highest_id_currently_used': 463
 }
diff --git a/components/policy/resources/policy_templates_ca.xtb b/components/policy/resources/policy_templates_ca.xtb
index 48a44501..b42a9d20 100644
--- a/components/policy/resources/policy_templates_ca.xtb
+++ b/components/policy/resources/policy_templates_ca.xtb
@@ -1125,15 +1125,15 @@
 
           Si aquesta política es deixa sense establir, s'utilitzarà el valor general predeterminat de la política "DefaultKeygenSetting" (si està configurada) per a tots els llocs web, o bé s'utilitzarà la configuració personal de l'usuari.</translation>
 <translation id="4239720644496144453">La memòria cau no s'utilitza a les aplicacions per a Android. Si diversos usuaris instal·len la mateixa aplicació per a Android, es baixarà de nou en cada cas.</translation>
-<translation id="4243336580717651045">Activa la recopilació de dades anònimes i amb claus d'URL a <ph name="PRODUCT_NAME" /> impedeix que els usuaris puguin canviar aquesta opció.
+<translation id="4243336580717651045">Activa la recopilació de dades anònimes amb URL a <ph name="PRODUCT_NAME" /> impedeix que els usuaris puguin canviar aquesta opció.
 
-      La recopilació de dades anònimes i amb claus d'URL envia a Google els URL de pàgines que l'usuari visita per millorar les cerques i la navegació.
+      La recopilació de dades anònimes amb URL envia a Google els URL de pàgines que l'usuari visita per millorar les cerques i la navegació.
 
-      Si actives aquesta política, la recopilació de dades anònimes i amb claus d'URL estarà sempre activa.
+      Si actives aquesta política, la recopilació de dades anònimes amb URL estarà sempre activa.
 
-      Si desactives aquesta política, la recopilació de dades anònimes i amb claus d'URL no estarà mai activa.
+      Si desactives aquesta política, la recopilació de dades anònimes amb URL no estarà mai activa.
 
-      Si aquesta política es deixa sense definir, la recopilació de dades anònimes i amb claus d'URL s'activarà però l'usuari podrà canviar aquesta opció.</translation>
+      Si aquesta política es deixa sense definir, la recopilació de dades anònimes amb URL s'activarà però l'usuari podrà canviar aquesta opció.</translation>
 <translation id="4250680216510889253">No</translation>
 <translation id="4261820385751181068">Configuració regional de la pantalla d'inici de sessió del dispositiu</translation>
 <translation id="427220754384423013">Especifica les impressores que un usuari pot fer servir.
@@ -1754,7 +1754,7 @@
           Per bloquejar la pantalla en mode inactiu, us recomanem que activeu el bloqueig de pantalla en mode de suspensió i que configureu <ph name="PRODUCT_OS_NAME" /> perquè se suspengui després del temps d'inactivitat. Aquesta política només s'ha d'utilitzar quan el bloqueig de pantalla s'hagi de produir molt abans que la suspensió o quan no vulgueu emprar la suspensió en mode inactiu.
 
           El valor de la política s'ha d'especificar en mil·lisegons. Els valors es fixen perquè siguin inferiors al temps d'inactivitat.</translation>
-<translation id="6097601282776163274">Activa la recopilació de dades anònimes i amb claus d'URL</translation>
+<translation id="6097601282776163274">Activa la recopilació de dades anònimes amb URL</translation>
 <translation id="6111936128861357925">Permet el joc sorpresa del dinosaure</translation>
 <translation id="6114416803310251055">obsolet</translation>
 <translation id="6133088669883929098">Permet que tots els llocs utilitzin la generació de claus</translation>
diff --git a/components/policy/resources/policy_templates_cs.xtb b/components/policy/resources/policy_templates_cs.xtb
index e8dc29c..f1bc67c 100644
--- a/components/policy/resources/policy_templates_cs.xtb
+++ b/components/policy/resources/policy_templates_cs.xtb
@@ -284,6 +284,11 @@
       Pokud některá možnost nebude nakonfigurována, použije se výchozí hodnota.
 
       Pokud tato zásada není nastavena, budou pro všechna nastavení použity výchozí hodnoty.</translation>
+<translation id="1958138414749279167">Aktivuje v prohlížeči <ph name="PRODUCT_NAME" /> funkci Automatické vyplňování a umožňuje uživatelům automaticky ve webových formulářích vyplňovat informace o adresách pomocí dříve uložených údajů.
+
+      Pokud je toto nastavení vypnuto, automatické vyplňování nebude navrhovat ani vyplňovat informace o adresách a nebude ani ukládat další informace o adresách, které uživatel při prohlížení webu odešle.
+
+      Pokud je toto nastavení zapnuto nebo nemá žádnou hodnotu, uživatel bude moci automatické vyplňování adres ovládat v uživatelském rozhraní.</translation>
 <translation id="1960840544413786116">Udává, zda mají být povoleny certifikáty vydané místními kotvami vztahu důvěryhodnosti bez rozšíření subjectAlternativeName.</translation>
 <translation id="1964634611280150550">Anonymní režim zakázán</translation>
 <translation id="1964802606569741174">Tato zásada nemá vliv na aplikaci YouTube pro Android. Chcete-li na YouTube vynutit bezpečný režim, měli byste instalaci aplikace YouTube pro Android zakázat.</translation>
@@ -488,6 +493,11 @@
 Pokud toto nastavení ponecháte nenastavené, uživatelé se budou moci rozhodnout, zda tuto funkci chtějí používat.
 
 Z prohlížeče <ph name="PRODUCT_NAME" /> verze 29 a novějších verzí bylo toto nastavení odebráno.</translation>
+<translation id="2433412232489478893">Tato zásada určuje, zda má uživatel v prohlížeči <ph name="PRODUCT_NAME" /> povolenou funkci sdílených síťových složek.
+
+      Pokud tato zásada není nakonfigurována nebo je nastavena na hodnotu True, uživatelé sdílené síťové složky budou moci používat.
+
+      Pokud je tato zásada nastavena na hodnotu False, uživatelé sdílené síťové složky nebudou moci používat.</translation>
 <translation id="2438609638493026652">Umožňuje hlášení zásadních událostí během instalace aplikací pro Android do Googlu. Události se zaznamenávají pouze pro aplikace, jejichž instalace byla spuštěna prostřednictvím zásady.
 
       Pokud je tato zásada nastavena na hodnotu true, budou se události protokolovat.
@@ -569,6 +579,7 @@
 <translation id="2598508021807251719">Konfiguruje jazyky, ve kterých se <ph name="PRODUCT_OS_NAME" /> může zobrazovat.
 
       Pokud je tato zásada nastavena, může uživatel nakonfigurovat zobrazení systému <ph name="PRODUCT_OS_NAME" /> jen v jednom z jazyků zadaných v této zásadě. Pokud tato zásada není nastavena nebo je nastavena na prázdný seznam, je možné systém <ph name="PRODUCT_OS_NAME" /> zobrazovat ve všech podporovaných jazycích uživatelského prostředí. Pokud je tato zásada nastavena na seznam s neplatnými hodnotami, všechny neplatné hodnoty budou ignorovány. Pokud uživatel v minulosti systém <ph name="PRODUCT_OS_NAME" /> nastavil na zobrazování v jazyce, který tato zásada nepovoluje, přepne se zobrazovaný jazyk při příštím přihlášení uživatele na povolený jazyk uživatelského rozhraní. Pokud uživatel nakonfiguroval preferované jazyky a tato zásada některý z nich povoluje, přejde <ph name="PRODUCT_OS_NAME" /> na daný jazyk. Jinak <ph name="PRODUCT_OS_NAME" /> přejde na první platnou hodnotu zadanou v této zásadě nebo na záložní jazyk (v současné době en-US), pokud tato zásada obsahuje jen neplatné položky.</translation>
+<translation id="2604182581880595781">Konfigurace zásad souvisejících se sdílenými síťovými složkami.</translation>
 <translation id="2623014935069176671">Počkat na první aktivitu uživatele</translation>
 <translation id="262740370354162807">Povolí odesílání dokumentů do služby <ph name="CLOUD_PRINT_NAME" /></translation>
 <translation id="2627554163382448569">Poskytuje konfigurace pro podnikové tiskárny.
@@ -653,6 +664,7 @@
 <translation id="2823870601012066791">Umístění v registru systému Windows pro klienty <ph name="PRODUCT_OS_NAME" />:</translation>
 <translation id="2824715612115726353">Povolit anonymní režim</translation>
 <translation id="2838830882081735096">Zakázat migraci dat a rozšíření ARC</translation>
+<translation id="2839294585867804686">Nastavení sdílených síťových složek</translation>
 <translation id="2840269525054388612">Určuje, které tiskárny uživatel může používat.
 
 Tato zásada se použije, jen pokud je v zásadě <ph name="PRINTERS_WHITELIST" /> vybrána možnost <ph name="DEVICE_PRINTERS_ACCESS_MODE" />.
@@ -1140,6 +1152,7 @@
       Pokud tuto zásadu ponecháte nenastavenou, bude shromažďování anonymizovaných dat zapnuto, ale uživatel to bude moci změnit.</translation>
 <translation id="4250680216510889253">Ne</translation>
 <translation id="4261820385751181068">Jazyk přihlašovací obrazovky zařízení</translation>
+<translation id="4264607809747169568">Ovládá dostupnost sdílených síťových složek pro Chrome OS</translation>
 <translation id="427220754384423013">Určuje, které tiskárny uživatel může používat.
 
 Tato zásada se použije, jen pokud je v zásadě <ph name="PRINTERS_WHITELIST" /> vybrána možnost <ph name="BULK_PRINTERS_ACCESS_MODE" />.
@@ -1568,6 +1581,15 @@
       Hodnota zásady musí být zadána v milisekundách.</translation>
 <translation id="5511702823008968136">Aktivovat panel záložek</translation>
 <translation id="5512418063782665071">Adresa URL domovské stránky</translation>
+<translation id="551639594034811656">Tato zásada určuje seznam procentuálních podílů zařízení se systémem <ph name="PRODUCT_OS_NAME" /> v organizační jednotce, která se aktualizují každý den ode dne prvního objevení aktualizace. Čas objevení je pozdější než čas publikování aktualizace, protože zařízení může dostupnost aktualizací zkontrolovat až nějakou dobu po publikování aktualizace.
+
+Každý pár (den, procento) udává, jaké procento všech zařízení musí být aktualizováno do daného počtu dní od prvního objevení aktualizace. Páry [(4, 40), (10, 70), (15, 100)] například znamenají, že 40 % zařízení má být aktualizováno do 4 dnů po objevení aktualizace, 70 % má být aktualizováno do 10 dnů atd.
+
+Pokud je v této zásadě definovaná nějaká hodnota, budou aktualizace ignorovat zásadu <ph name="DEVICE_UPDATE_SCATTER_FACTOR_POLICY_NAME" /> a budou se namísto ní řídit touto zásadou.
+
+Je-li seznam prázdný, aktualizace se nebudou zavádět na etapy, ale podle jiných zásad zařízení.
+
+Tato zásada se nevztahuje na přepínání mezi kanály.</translation>
 <translation id="5523812257194833591">Veřejná relace, do které se zařízení po prodlevě automaticky přihlásí.
 
       Pokud je zásada nastavena, zařízení se po uplynutí určité doby nečinnosti uživatele na přihlašovací obrazovce automaticky přihlásí do určené relace. Veřejná relace již musí být nakonfigurována (viz |DeviceLocalAccounts|).
@@ -1880,6 +1902,7 @@
 <translation id="6440051664870270040">Povolit webům zároveň provádět navigaci a otevírat vyskakovací okna</translation>
 <translation id="6447948611083700881">Zálohování a obnovení zakázáno</translation>
 <translation id="645425387487868471">Aktivovat vynucené přihlášení do prohlížeče <ph name="PRODUCT_NAME" /></translation>
+<translation id="6464074037294098618">Povolit automatické vyplňování pro adresy</translation>
 <translation id="6473623140202114570">Konfiguruje seznam domén, na kterých Bezpečné prohlížení nebude zobrazovat upozornění.</translation>
 <translation id="6491139795995924304">Povolit na zařízení Bluetooth</translation>
 <translation id="6520802717075138474">Importování vyhledávačů z výchozího prohlížeče při prvním spuštění</translation>
@@ -2485,6 +2508,9 @@
       Pokud je tato zásada zakázána, žádná explicitní izolace webů se provádět nebude a zkušební verze zásad IsolateOrigins a SitePerProcess budou zakázány. Uživatelé budou zásadu SitePerProcess moci povolit ručně.
       Pokud tuto zásadu nenakonfigurujete, uživatel toto nastavení bude moci změnit.
       </translation>
+<translation id="7902255855035461275">Vzory v tomto seznamu budou porovnány s žádající adresou URL. Pokud bude nalezena shoda, přístup k zařízením pro záznam videa bude udělen bez zobrazení výzvy.
+
+      POZNÁMKA: Až do verze 45 byla tato zásada podporována pouze v režimu veřejného terminálu.</translation>
 <translation id="7912255076272890813">Konfigurovat povolené typy aplikací nebo rozšíření</translation>
 <translation id="7915236031252389808">Zde můžete zadat adresu URL souboru PAC proxy serveru.
 
diff --git a/components/policy/resources/policy_templates_el.xtb b/components/policy/resources/policy_templates_el.xtb
index 161326f..fcffefb 100644
--- a/components/policy/resources/policy_templates_el.xtb
+++ b/components/policy/resources/policy_templates_el.xtb
@@ -296,6 +296,11 @@
       Αν δεν οριστεί αυτή η ρύθμιση, θα χρησιμοποιηθεί μια προεπιλεγμένη τιμή.
 
       Αν δεν ρυθμιστεί αυτή η πολιτική, θα εφαρμοστούν οι προεπιλογές για όλες τις ρυθμίσεις.</translation>
+<translation id="1958138414749279167">Ενεργοποιεί τη λειτουργία Αυτόματης Συμπλήρωσης του <ph name="PRODUCT_NAME" /> και επιτρέπει στους χρήστες την αυτόματη συμπλήρωση πληροφοριών διεύθυνσης σε φόρμες ιστού που χρησιμοποιούν πληροφορίες που αποθηκεύτηκαν στο παρελθόν.
+
+      Εάν αυτή η ρύθμιση απενεργοποιηθεί, η Αυτόματη Συμπλήρωση δεν θα προτείνει ποτέ ή δεν θα συμπληρώνει τις πληροφορίες διεύθυνσης, ούτε θα αποθηκεύει επιπλέον πληροφορίες διεύθυνσης που μπορεί να υποβάλει ο χρήστης κατά την περιήγησή του στον ιστό.
+
+      Εάν αυτή η ρύθμιση ενεργοποιηθεί ή δεν έχει τιμή, ο χρήστης θα έχει τη δυνατότητα να ελέγξει την Αυτόματη Συμπλήρωση για διευθύνσεις στη διεπαφή χρήστη.</translation>
 <translation id="1960840544413786116">Εάν θα επιτρέπονται τα πιστοποιητικά που εκδίδονται από τοπικά σημεία αγκύρωσης εμπιστοσύνης από τα οποία λείπει η επέκταση subjectAlternativeName</translation>
 <translation id="1964634611280150550">Η κατάσταση ανώνυμης περιήγησης απενεργοποιήθηκε</translation>
 <translation id="1964802606569741174">Αυτή η πολιτική δεν επηρεάζει την εφαρμογή Android YouTube. Εάν πρέπει να επιβληθεί η Ασφαλής λειτουργία στο YouTube, η εγκατάσταση της εφαρμογής Android YouTube δεν θα πρέπει να επιτρέπεται.</translation>
@@ -1914,6 +1919,7 @@
 <translation id="6440051664870270040">Επιτρέπει στους ιστοτόπους την ταυτόχρονη περιήγηση και το άνοιγμα αναδυόμενων παραθύρων</translation>
 <translation id="6447948611083700881">Η λειτουργία δημιουργίας αντιγράφων ασφαλείας και επαναφοράς απενεργοποιήθηκε</translation>
 <translation id="645425387487868471">Ενεργοποιεί την υποχρεωτική σύνδεση για το <ph name="PRODUCT_NAME" /></translation>
+<translation id="6464074037294098618">Ενεργοποίηση της Αυτόματης Συμπλήρωσης για διευθύνσεις</translation>
 <translation id="6473623140202114570">Διαμορφώστε τη λίστα τομέων στους οποίους η Ασφαλής περιήγηση δεν θα ενεργοποιεί προειδοποιήσεις.</translation>
 <translation id="6491139795995924304">Να επιτρέπεται η χρήση του bluetooth στη συσκευή</translation>
 <translation id="6520802717075138474">Εισαγωγή μηχανών αναζήτησης από το προεπιλεγμένο πρόγραμμα περιήγησης στην πρώτη εκτέλεση</translation>
diff --git a/components/policy/resources/policy_templates_en-GB.xtb b/components/policy/resources/policy_templates_en-GB.xtb
index 874d773..e7a490bf 100644
--- a/components/policy/resources/policy_templates_en-GB.xtb
+++ b/components/policy/resources/policy_templates_en-GB.xtb
@@ -525,6 +525,11 @@
       If this setting is left unset, the user can decide to use this function or not.
 
       This setting has been removed from <ph name="PRODUCT_NAME" /> 29 and higher versions.</translation>
+<translation id="2433412232489478893">This policy controls whether the Network File Shares feature for <ph name="PRODUCT_NAME" /> is allowed for a user.
+
+      When this policy is not configured or set to True, users will be able to use Network File Shares.
+
+      When this policy is set to False, users will be unable to use Network File Shares.</translation>
 <translation id="2438609638493026652">Enables reporting of key events during Android app installation to Google. Events are captured only for apps whose installation was triggered via policy.
 
       If the policy is set to true, events will be logged.
@@ -606,6 +611,7 @@
 <translation id="2598508021807251719">Configures the locales <ph name="PRODUCT_OS_NAME" /> may be displayed in.
 
       If this policy is set, the user can only configure <ph name="PRODUCT_OS_NAME" /> to be displayed in one of the locales specified by this policy. If this policy is not set or set to an empty list, <ph name="PRODUCT_OS_NAME" /> can be displayed in all supported UI locales. If this policy is set to a list with invalid values, all invalid values will be ignored. If a user previously configured <ph name="PRODUCT_OS_NAME" /> to be displayed in a locale that is not allowed by this policy, the display locale will be switched to an allowed UI locale the next time the user signs in. If the user had configured preferred locales and one of the preferred locales is allowed by this policy, <ph name="PRODUCT_OS_NAME" /> will switch to this locale. Otherwise, <ph name="PRODUCT_OS_NAME" /> will switch to the first valid value specified by this policy, or to a fallback locale (currently en-US), if this policy only contains invalid entries.</translation>
+<translation id="2604182581880595781">Configure Network File Shares-related policies.</translation>
 <translation id="2623014935069176671">Wait for initial user activity</translation>
 <translation id="262740370354162807">Enable submission of documents to <ph name="CLOUD_PRINT_NAME" /></translation>
 <translation id="2627554163382448569">Provides configurations for enterprise printers.
@@ -691,6 +697,7 @@
 <translation id="2823870601012066791">Windows registry location for <ph name="PRODUCT_OS_NAME" /> clients:</translation>
 <translation id="2824715612115726353">Enable Incognito mode</translation>
 <translation id="2838830882081735096">Disallow data migration and ARC</translation>
+<translation id="2839294585867804686">Network File Shares settings</translation>
 <translation id="2840269525054388612">Specifies the printers which a user can use.
 
       This policy is only used if <ph name="PRINTERS_WHITELIST" /> is chosen for <ph name="DEVICE_PRINTERS_ACCESS_MODE" />
@@ -1188,6 +1195,7 @@
       If this policy is left not set, URL-keyed anonymised data collection will be enabled but the user will be able to change it.</translation>
 <translation id="4250680216510889253">No</translation>
 <translation id="4261820385751181068">Device sign-in screen locale</translation>
+<translation id="4264607809747169568">Contorls Network File Shares for ChromeOS availibility</translation>
 <translation id="427220754384423013">Specifies the printers which a user can use.
 
       This policy is only used if <ph name="PRINTERS_WHITELIST" /> is chosen for <ph name="BULK_PRINTERS_ACCESS_MODE" />.
@@ -1622,6 +1630,15 @@
       The policy value should be specified in milliseconds.</translation>
 <translation id="5511702823008968136">Enable Bookmark Bar</translation>
 <translation id="5512418063782665071">Home page URL</translation>
+<translation id="551639594034811656">This policy defines a list of percentages that will define the fraction of <ph name="PRODUCT_OS_NAME" /> devices in the OU to update per day starting from the day that the update is first discovered. The discovery time is later than the update published time, since it could be a while after the update publishing until the device checks for updates.
+
+      Each (day, percentage) pair contains which percentage of the fleet has to be updated by the given number of days since the update has been discovered. For example, if we have the pairs [(4, 40), (10, 70), (15, 100)], then 40% of the fleet should have been updated 4 days after seeing the update. 70% should be updated after 10 days, and so on.
+
+      If there is a value defined for this policy, updates will ignore the <ph name="DEVICE_UPDATE_SCATTER_FACTOR_POLICY_NAME" /> policy and follow this policy instead.
+
+      If this list is empty, there will be no staging, and updates will be applied according to other device policies.
+
+      This policy does not apply for channel switches.</translation>
 <translation id="5523812257194833591">A public session to auto-login after a delay.
 
       If this policy is set, the specified session will be automatically logged in after a period of time has elapsed at the login screen without user interaction. The public session must already be configured (see |DeviceLocalAccounts|).
@@ -2550,6 +2567,11 @@
       If the policy is disabled, no explicit Site Isolation will happen and field trials of IsolateOrigins and SitePerProcess will be disabled. However, users will still be able to facilitate SitePerProcess manually.
       If the policy is not configured, the user will be able to change this setting.
       </translation>
+<translation id="7902255855035461275">Patterns in this list will be matched against the security
+      origin of the requesting URL. If a match is found, access to video
+      capture devices will be granted without prompt.
+
+      NOTE: Until version 45, this policy was only supported in Kiosk mode.</translation>
 <translation id="7912255076272890813">Configure allowed app/extension types</translation>
 <translation id="7915236031252389808">You can specify a URL to a proxy .pac file here.
 
diff --git a/components/policy/resources/policy_templates_fa.xtb b/components/policy/resources/policy_templates_fa.xtb
index 3ac3c1b..59bc7154 100644
--- a/components/policy/resources/policy_templates_fa.xtb
+++ b/components/policy/resources/policy_templates_fa.xtb
@@ -290,6 +290,11 @@
       اگر تنظیمی تعیین نشود، مقدار پیش‌فرض استفاده می‌شود.
 
       اگر این خط‌مشی تنظیم نشود، مقادیر پیش‌فرض برای همه تنظیمات استفاده می‌شود.</translation>
+<translation id="1958138414749279167">ویژگی «تکمیل خودکار» <ph name="PRODUCT_NAME" /> را فعال می‌کند و به کاربران اجازه می‌دهد با استفاده از اطلاعات ذخیره‌شده قبلی، اطلاعات نشانی را به‌طور خودکار در فرم‌های وب تکمیل کنند.
+
+      اگر این تنظیم غیرفعال شود، «تکمیل خودکار» هیچ‌گاه اطلاعات نشانی را پیشنهاد یا تکمیل نمی‌کند و یا اطلاعات تکمیلی نشانی را که ممکن است کاربر درحین مرور وب ارسال کند ذخیره نمی‌کند.
+
+      اگر این تنظیم فعال شود یا مقداری نداشته باشد، کاربر می‌تواند ویژگی «تکمیل خودکار» را در رابط کاربری کنترل کند.</translation>
 <translation id="1960840544413786116">‏مشخص می‌کند گواهینامه‌های صادرشده توسط اتصالات مورد اعتماد محلی فاقد افزونه subjectAlternativeName مجاز باشند یا نه</translation>
 <translation id="1964634611280150550">حالت ناشناس غیرفعال شد</translation>
 <translation id="1964802606569741174">‏این خط‌مشی بر برنامه Android YouTube تأثیری ندارد. اگر حالت ایمن در YouTube باید اجرا شود، نباید به نصب برنامه Android YouTube اجازه داده شود.</translation>
@@ -1826,6 +1831,7 @@
 <translation id="6440051664870270040">مجاز کردن سایت‌ها برای پیمایش و باز کردن کادرهای بازشو به‌طور هم‌زمان</translation>
 <translation id="6447948611083700881">پشتیبان‌گیری و بازیابی غیرفعال باشد</translation>
 <translation id="645425387487868471">فعال کردن ورود به سیستم اجباری برای <ph name="PRODUCT_NAME" /></translation>
+<translation id="6464074037294098618">فعال کردن تکمیل خودکار برای نشانی‌ها</translation>
 <translation id="6473623140202114570">فهرست دامنه‌هایی را پیکربندی کنید که مرور امن هشدارها را در آن‌ها راه‌اندازی نمی‌کند.</translation>
 <translation id="6491139795995924304">به بلوتوث در دستگاه اجازه دهید</translation>
 <translation id="6520802717075138474">موتورهای جستجو در اولین اجرا از مرورگر پیش‌فرض وارد شود</translation>
diff --git a/components/policy/resources/policy_templates_fil.xtb b/components/policy/resources/policy_templates_fil.xtb
index 6f3c2ab..72e8362 100644
--- a/components/policy/resources/policy_templates_fil.xtb
+++ b/components/policy/resources/policy_templates_fil.xtb
@@ -300,6 +300,11 @@
       Kapag iniwang hindi tinukoy ang isang setting, ang default na value ang gagamitin.
 
       Kapag hindi nakatakda ang patakarang ito, gagamitin ang mga default para sa lahat ng setting.</translation>
+<translation id="1958138414749279167">Ine-enable ang feature na AutoFill ng <ph name="PRODUCT_NAME" /> at binibigyang-daan ang mga user na awtomatikong kumpletuhin ang impormasyon ng address sa mga web form gamit ang dati nang na-store na impormasyon.
+
+      Kung naka-disable ang setting na ito, hindi kailanman magmumungkahi o magkukumpleto ang AutoFill ng impormasyon ng address, o hindi nito ise-save ang karagdagang impormasyon ng address na maaaring isumite ng user habang nagba-browse siya sa web.
+
+      Kung naka-enable o walang value ang setting na ito, makokontrol ng user ang AutoFill para sa mga address sa UI.</translation>
 <translation id="1960840544413786116">Kung papayagan ba ang mga certificate na naisyu ng mga lokal na trust anchor na walang subjectAlternativeName na extension</translation>
 <translation id="1964634611280150550">Hindi pinagana ang mode na incognito</translation>
 <translation id="1964802606569741174">Walang epekto ang patakarang ito sa YouTube app sa Android. Kung dapat ipatupad ang Safety Mode sa YouTube, hindi dapat payagan ang pag-install ng YouTube app sa Android.</translation>
@@ -1914,6 +1919,7 @@
 <translation id="6440051664870270040">Payagan ang mga site na sabay na mag-navigate at magbukas ng mga pop-up</translation>
 <translation id="6447948611083700881">Na-disable ang pag-backup at pag-restore</translation>
 <translation id="645425387487868471">I-enable ang pwersahang pag-sign in para sa <ph name="PRODUCT_NAME" /></translation>
+<translation id="6464074037294098618">I-enable ang AutoFill para sa mga address</translation>
 <translation id="6473623140202114570">I-configure ang listahan ng mga domain kung saan hindi magti-trigger ng mga babala ang Ligtas na Pag-browse.</translation>
 <translation id="6491139795995924304">Payagan ang bluetooth sa device</translation>
 <translation id="6520802717075138474">Mag-import ng mga search engine mula sa default na browser sa unang pagtakbo</translation>
diff --git a/components/policy/resources/policy_templates_id.xtb b/components/policy/resources/policy_templates_id.xtb
index ea8f16e..8524b25 100644
--- a/components/policy/resources/policy_templates_id.xtb
+++ b/components/policy/resources/policy_templates_id.xtb
@@ -1132,15 +1132,15 @@
 
           Jika kebijakan ini tidak disetel, nilai default global akan digunakan untuk semua situs dari kebijakan 'DefaultKeygenSetting' jika disetel, atau dari konfigurasi pribadi pengguna.</translation>
 <translation id="4239720644496144453">Cache tidak digunakan untuk aplikasi Android. Jika beberapa pengguna memasang aplikasi Android yang sama, aplikasi akan didownload lagi untuk setiap pengguna.</translation>
-<translation id="4243336580717651045">Mengaktifkan pengumpulan data anonim yang dimasukkan dengan URL di <ph name="PRODUCT_NAME" /> dan mencegah pengguna mengubah setelan ini.
+<translation id="4243336580717651045">Mengaktifkan pengumpulan data anonim yang menyertakan URL di <ph name="PRODUCT_NAME" /> dan mencegah pengguna mengubah setelan ini.
 
-      Pengumpulan data anonim yang dimasukkan dengan URL mengirimkan URL halaman yang dibuka oleh pengguna ke Google untuk membuat penelusuran dan browsing lebih baik.
+      Pengumpulan data anonim yang menyertakan URL mengirimkan URL halaman yang dibuka oleh pengguna ke Google untuk membuat penelusuran dan browsing lebih baik.
 
-      Jika Anda mengaktifkan kebijakan ini, pengumpulan data anonim yang dimasukkan dengan URL selalu aktif.
+      Jika Anda mengaktifkan kebijakan ini, pengumpulan data anonim yang menyertakan URL selalu aktif.
 
-      Jika Anda menonaktifkan kebijakan ini, pengumpulan data anonim yang dimasukkan dengan URL tidak pernah aktif.
+      Jika Anda menonaktifkan kebijakan ini, pengumpulan data anonim yang menyertakan URL tidak pernah aktif.
 
-      Jika kebijakan ini dibiarkan tidak disetel, pengumpulan data anonim yang dimasukkan dengan URL akan diaktifkan, namun pengguna dapat mengubahnya.</translation>
+      Jika kebijakan ini dibiarkan tidak disetel, pengumpulan data anonim yang menyertakan URL akan diaktifkan, namun pengguna dapat mengubahnya.</translation>
 <translation id="4250680216510889253">Tidak</translation>
 <translation id="4261820385751181068">Lokal layar login perangkat</translation>
 <translation id="427220754384423013">Menentukan printer yang dapat digunakan pengguna.
@@ -1765,7 +1765,7 @@
           Cara yang disarankan untuk mengunci layar saat menganggur adalah dengan mengaktifkan penangguhan penguncian layar dan meminta <ph name="PRODUCT_OS_NAME" /> menangguhkan setelah penundaan waktu menganggur. Sebaiknya hanya gunakan kebijakan ini saat penguncian layar perlu terjadi jauh lebih awal dibandingkan penangguhan atau saat penangguhan ketika menganggur tidak diinginkan sama sekali.
 
           Nilai kebijakan harus ditetapkan dalam milidetik. Nilai dijepit agar kurang dari penundaan waktu menganggur.</translation>
-<translation id="6097601282776163274">Mengaktifkan pengumpulan data anonim yang dimasukkan dengan URL</translation>
+<translation id="6097601282776163274">Mengaktifkan pengumpulan data anonim yang menyertakan URL</translation>
 <translation id="6111936128861357925">Izinkan Game Dinosaur Easter Egg</translation>
 <translation id="6114416803310251055">tak lagi digunakan</translation>
 <translation id="6133088669883929098">Izinkan semua situs menggunakan pembuatan kunci</translation>
diff --git a/components/policy/resources/policy_templates_it.xtb b/components/policy/resources/policy_templates_it.xtb
index 91423d9..1b1fa20e 100644
--- a/components/policy/resources/policy_templates_it.xtb
+++ b/components/policy/resources/policy_templates_it.xtb
@@ -1115,7 +1115,7 @@
 
       Se questa norma viene disattivata, la raccolta di dati anonimizzati con chiave URL non è mai attiva.
 
-      Se questa norma non viene impostata, a raccolta di dati anonimizzati con chiave URL verrà attivata, ma l'utente potrà modificare l'impostazione.</translation>
+      Se questa norma non viene impostata, la raccolta di dati anonimizzati con chiave URL verrà attivata, ma l'utente potrà modificare l'impostazione.</translation>
 <translation id="4250680216510889253">No</translation>
 <translation id="4261820385751181068">Lingua della schermata di accesso del dispositivo</translation>
 <translation id="427220754384423013">Consente di specificare le stampanti utilizzabili dagli utenti.
diff --git a/components/policy/resources/policy_templates_lt.xtb b/components/policy/resources/policy_templates_lt.xtb
index 193bba61..5f24507 100644
--- a/components/policy/resources/policy_templates_lt.xtb
+++ b/components/policy/resources/policy_templates_lt.xtb
@@ -520,6 +520,11 @@
       Jei šis nustatymas nenustatomas, naudotojas galės nuspręsti, ar naudoti šią funkciją, ar ne.
 
       Šis nustatymas pašalintas iš 29 ir naujesnių „<ph name="PRODUCT_NAME" />“ versijų.</translation>
+<translation id="2433412232489478893">Ši politika valdo, ar „<ph name="PRODUCT_NAME" />“ tinklo failų bendrinimo įrenginių funkcija leidžiama naudotojui.
+
+      Kai ši politika nesukonfigūruota arba nustatyta kaip „Tiesa“, naudotojai gali naudoti tinklo failų bendrinimo įrenginius.
+
+      Kai ši politika nustatyta kaip „Netiesa“, naudotojai negali naudoti tinklo failų bendrinimo įrenginių.</translation>
 <translation id="2438609638493026652">Įgalinamas pagrindinių įvykių ataskaitų teikimas sistemoje „Google“ diegiant „Android“ programą. Užfiksuojami tik programų, kurių diegimas suaktyvintas naudojant šią politiką, įvykiai.
 
       Nustačius politiką į „true“, įvykiai bus registruojami.
@@ -601,6 +606,7 @@
 <translation id="2598508021807251719">Konfigūruojamos lokalės, kuriose galima pateikti „<ph name="PRODUCT_OS_NAME" />“.
 
       Jei ši politika nustatyta, naudotojas gali konfigūruoti, kad „<ph name="PRODUCT_OS_NAME" />“ būtų pateikta tik vienoje iš lokalių, nurodytų pagal šią politiką. Jei ši politika nenustatyta arba nustatyta kaip tuščias sąrašas, „<ph name="PRODUCT_OS_NAME" />“ galima pateikti visose palaikomose NS lokalėse. Jei ši politika nustatyta kaip sąrašas su netinkamomis vertėmis, visų netinkamų verčių nepaisoma. Jei naudotojas anksčiau sukonfigūravo, kad „<ph name="PRODUCT_OS_NAME" />“ būtų pateikta pagal šią politiką neleidžiamoje lokalėje, pateikimo lokalė bus perjungta į leidžiamą NS lokalę kitą kartą naudotojui prisijungus. Jei naudotojas sukonfigūravo pageidaujamas lokales ir viena iš pageidaujamų lokalių leidžiama pagal šią politiką, „<ph name="PRODUCT_OS_NAME" />“ bus perjungta į šią lokalę. Priešingu atveju „<ph name="PRODUCT_OS_NAME" />“ bus taikoma pagal šią politiką nustatyta pirmoji tinkama vertė arba bus perjungta į atsarginę lokalę (šiuo metu „en-US“), jei šioje politikoje yra tik netinkami įrašai.</translation>
+<translation id="2604182581880595781">Konfigūruojama su tinklo failų bendrinimo įrenginiu susijusi politika.</translation>
 <translation id="2623014935069176671">Laukti pradinės naudotojo veiklos</translation>
 <translation id="262740370354162807">Įgalinti dokumentų pateikimą „<ph name="CLOUD_PRINT_NAME" />“</translation>
 <translation id="2627554163382448569">Nurodoma įmonės spausdintuvų konfigūracija.
@@ -686,6 +692,7 @@
 <translation id="2823870601012066791">„<ph name="PRODUCT_OS_NAME" />“ klientų „Windows“ registro vieta:</translation>
 <translation id="2824715612115726353">Įgalinti inkognito režimą</translation>
 <translation id="2838830882081735096">Neleisti perkelti duomenų ir ARC</translation>
+<translation id="2839294585867804686">Tinklo failų bendrinimo įrenginių nustatymai</translation>
 <translation id="2840269525054388612">Nurodomi spausdintuvai, kuriuos gali naudoti naudotojas.
 
       Ši politika naudojama, tik jei pasirinktas politikos „<ph name="DEVICE_PRINTERS_ACCESS_MODE" />“ nustatymas „<ph name="PRINTERS_WHITELIST" />“
@@ -1181,6 +1188,7 @@
       Jei ši politika bus palikta nenustatyta, URL pagrįsto anonimizuotų duomenų rinkimo funkcija bus įgalinta, bet naudotojas galės ją pakeisti.</translation>
 <translation id="4250680216510889253">Ne</translation>
 <translation id="4261820385751181068">Įrenginio prisijungimo ekrano lokalė</translation>
+<translation id="4264607809747169568">Valdomas „Chrome“ OS skirtų tinklo failų bendrinimo įrenginių pasiekiamumas</translation>
 <translation id="427220754384423013">Nurodomi spausdintuvai, kuriuos gali naudoti naudotojas.
 
       Ši politika naudojama, tik jei pasirinktas politikos „<ph name="BULK_PRINTERS_ACCESS_MODE" />“ nustatymas „<ph name="PRINTERS_WHITELIST" />“.
@@ -1614,6 +1622,15 @@
       Politikos vertę reikia nurodyti milisekundėmis.</translation>
 <translation id="5511702823008968136">Įgalinti žymių juostą</translation>
 <translation id="5512418063782665071">Pagrindinio puslapio URL</translation>
+<translation id="551639594034811656">Ši politika apibrėžia procentinių verčių sąrašą, kur nurodoma OV įrenginių dalis („<ph name="PRODUCT_OS_NAME" />“), kurie bus atnaujinti per savaitę nuo pirmos naujinio aptikimo dienos. Aptikimo laikas yra vėlesnis nei naujinio paskelbimo laikas, nes paskelbus naujinį įrenginys gali tikrinti, ar yra naujinių, tik po tam tikro laiko
+
+      Kiekviena pora (diena, procentinė vertė) nurodo, kokia srauto procentinė dalis turi būti atnaujinta praėjus nurodytam skaičiui dienų nuo naujinio aptikimo dienos. Pvz., jei turime poras [(4, 40), (10, 70), (15, 100)], reiškia, 40 proc. srauto turi būti atnaujinta praėjus 4 dienoms nuo naujinio peržiūros dienos, 70 proc. turėtų būti atnaujinta praėjus 10 dienų ir t. t.
+
+      Jei šios politikos vertė nustatyta, naujiniai nepaisys politikos „<ph name="DEVICE_UPDATE_SCATTER_FACTOR_POLICY_NAME" />“ ir vietoje to atsižvelgs į šią politiką.
+
+      Jei šis sąrašas tuščias, etapinis diegimas nebus naudojamas, o naujiniai bus pritaikyti atsižvelgiant į kitas įrenginių politikos nuostatas.
+
+      Ši politika netaikoma keičiant kanalus.</translation>
 <translation id="5523812257194833591">Vieša sesija, skirta automatiškai prijungti po delsos.
 
       Jei ši politika nenustatyta, prie nurodytos sesijos bus automatiškai prisijungta prisijungimo ekrane praėjus tam tikram laikui be naudotojo sąveikos. Vieša sesija jau turi būti sukonfigūruota (žr. |DeviceLocalAccounts|).
@@ -2543,6 +2560,11 @@
       Išjungus politiką nebus taikomas svetainių atskyrimas, o „IsolateOrigins“ ir „SitePerProcess“ bandomosios versijos bus išjungtos. Naudotojai vis tiek galės neautomatiškai įgalinti „SitePerProcess“.
       Nesukonfigūravus politikos naudotojas galės pakeisti šį nustatymą.
       </translation>
+<translation id="7902255855035461275">Šiame sąraše pateikti šablonai bus palyginti su užklausą pateikusio
+      URL saugos šaltiniu. Jei bus rasta atitiktis, galimybė pasiekti vaizdo įrašą
+      fiksuojančius įrenginius bus suteikta be raginimo.
+
+      PASTABA: iki 45 versijos programos ši politika buvo palaikoma tik įjungus viešojo terminalo režimą.</translation>
 <translation id="7912255076272890813">Konfigūruoti leidžiamus programų / plėtinių tipus</translation>
 <translation id="7915236031252389808">Čia galite nurodyti tarpinio serverio .pac failo URL.
 
diff --git a/components/policy/resources/policy_templates_mr.xtb b/components/policy/resources/policy_templates_mr.xtb
index c11e5bd..4b30f37 100644
--- a/components/policy/resources/policy_templates_mr.xtb
+++ b/components/policy/resources/policy_templates_mr.xtb
@@ -572,6 +572,11 @@
       हे धोरण असत्य वर सेट केले असल्यास, <ph name="PRODUCT_OS_NAME" /> लॉगइन स्क्रीनवर असलेले वर्तमान वापरकर्ते दाखवणार नाही. सार्वजनिक सत्र कॉन्फिगर होईपर्यंत सामान्य साइन इन स्क्रीन (वापरकर्त्याचा ईमेल, पासवर्ड किंवा फोन नंबरसाठी सूचित करणारी) किंवा SAML इंटरस्टिटल स्क्रीन (<ph name="LOGIN_AUTHENTICATION_BEHAVIOR_POLICY_NAME" /> धोरणाद्वारे चालू केली असल्यास) दाखवल्या जातील. सार्वजनिक सत्र कॉन्फिगर झाल्यानंतर केवळ सार्वजनिक क्षेत्रातील खाती दाखवली जातील, ज्यापैकी एक निवडण्याची अनुमती दिली जाईल.
 
       लक्षात ठेवा डीव्हाइसवर स्थानिक वापरकर्ता डेटा ठेवला जातो की नाही यास हे धोरण प्रभावित करत नाही.</translation>
+<translation id="2433412232489478893">हे धोरण वापरकर्त्याला <ph name="PRODUCT_NAME" /> साठी नेटवर्क फाइल शेअर वैशिष्ट्याला अनुमती आहे किंवा नाही हे नियंत्रित करते.
+
+      हे धोरण कॉन्फिगर केलेले नसेल किंवा सत्य वर सेट केलेले असताना, वापरकर्ते नेटवर्क फाइल शेअरचा वापर करू शकतात.
+
+      हे धोरण असत्य वर सेट केलेले असल्यास, वापरकर्ते नेटवर्क फाइल शेअरचा वापर करू शकणार नाहीत.</translation>
 <translation id="2438609638493026652">Google वर Android अ‍ॅप इंस्टॉलेशन दरम्यान मुख्य इव्हेंटवर अहवाल देणे सुरू करते. फक्त इंस्टॉलेशन धोरणाद्वारे ट्रिगर केलेल्या अ‍ॅप्ससाठी इव्हेंट कॅप्चर केले आहेत.
 
       धोरण सत्यवर सेट केले असल्यास, इव्हेंट लॉग केले जातील.
@@ -657,6 +662,7 @@
 <translation id="2598508021807251719">ज्यामध्ये <ph name="PRODUCT_OS_NAME" /> दाखवलेले असू शकते अशी लोकॅल कॉन्फिगर करते.
 
       हे धोरण सेट केलेले असल्यास, वापरकर्त्याला या धोरणाने निर्दिष्ट केलेल्या एका लोकॅलमध्ये दाखवले जाण्यासाठी फक्त <ph name="PRODUCT_OS_NAME" /> कॉन्फिगर करता येते. हे धोरण सेट केलेले नसल्यास किंवा रिकाम्या सूचीवर सेट केलेले असल्यास, <ph name="PRODUCT_OS_NAME" /> सपोर्ट असलेल्या सर्व UI लोकॅलवर दाखवले जाऊ शकते. हे धोरण अवैध मूल्ये असलेल्या सूचीवर सेट केलेले असल्यास, सर्व अवैध मूल्यांकडे दुर्लक्ष केले जाईल. वापरकर्त्याने याआधी या धोरणाने अनुमती न दिलेल्या लोकॅलमध्ये दाखवले जाण्यासाठी <ph name="PRODUCT_OS_NAME" /> कॉन्फिगर केलेले असल्यास, वापरकर्त्याने पुढील वेळी साइन इन केल्यावर डिस्प्ले लोकॅल अनुमती असलेल्या UI लोकॅलवर स्विच केले जाईल. वापरकर्त्याने प्राधान्य दिलेली लोकॅल कॉन्फिगर केलेली असल्यास आणि प्राधान्य दिलेल्या एका लोकॅलला या धोरणाने अनुमती दिलेली असल्यास, <ph name="PRODUCT_OS_NAME" /> या लोकॅलवर स्विच होईल. अन्यथा, <ph name="PRODUCT_OS_NAME" /> या धोरणाने निर्दिष्ट केलेल्या पहिल्या वैध मूल्यावर किंवा, या धोरणामध्ये फक्त अवैध एंट्री असल्यास, फॉलबॅक लोकॅलवर (सध्या en-US) वर स्विच होईल.</translation>
+<translation id="2604182581880595781">नेटवर्क फाइल शेअर संबंधित धोरणे कॉन्फिगर करा.</translation>
 <translation id="2623014935069176671">प्रारंभिक वापरकर्ता क्रियाकलापासाठी प्रतीक्षा करा</translation>
 <translation id="262740370354162807"><ph name="CLOUD_PRINT_NAME" /> मध्‍ये दस्तऐवजांचे सबमिशन सक्षम करा</translation>
 <translation id="2627554163382448569">एंटरप्राइझ प्रिंटरसाठी कॉन्फिगरेशन पुरवते.
@@ -744,6 +750,7 @@
 <translation id="283695852388224413">धोरण सेट केले असल्यास, कॉन्फिगर केलेली कमाल पिन लांबी मंजूर केली जाते. 0 किंवा त्यापेक्षा कमी मूल्य म्हणजे कमाल लांबी नाही; या बाबतीत वापरकर्ता त्यांना पाहिजे तितका लांब पिन सेट करू शकतात. ही सेटिंग <ph name="PIN_UNLOCK_MINIMUM_LENGTH_POLICY_NAME" />पेक्षा लहान परंतु 0 पेक्षा मोठी असल्यास, कमाल लांबी ही किमान लांबीच्या समान असते.
           हे धोरण सेट केले नसल्यास, कोणतीही कमाल लांबी मंजूर केली जात नाही.</translation>
 <translation id="2838830882081735096">डेटा स्‍थलांतर आणि ARC करण्‍याची अनुमती रद्द करा</translation>
+<translation id="2839294585867804686">नेटवर्क फाइल शेअर सेटिंग्ज</translation>
 <translation id="2840269525054388612">वापरकर्ता कोणते प्रिंटर वापरू शकतो हे निर्दिष्ट करतो.
 
       केवळ <ph name="DEVICE_PRINTERS_ACCESS_MODE" /> साठी <ph name="PRINTERS_WHITELIST" /> निवडलेली असल्यासच हे धोरण वापरले जाते
@@ -1323,6 +1330,7 @@
       हे धोरण सेट न केल्यास, URL-keyed अॅनोनिमाइझ केलेल्या डेटा संकलन सुरू केले जाईल परंतु वापरकर्ता ते बदलू शकेल.</translation>
 <translation id="4250680216510889253">नाही</translation>
 <translation id="4261820385751181068">डिव्हाइस साइन-इन स्क्रीन लोकॅल</translation>
+<translation id="4264607809747169568">ChromeOS उपलब्धतेसाठी नेटवर्क फाइल शेअर नियंत्रित करते</translation>
 <translation id="427220754384423013">वापरकर्ता कोणते प्रिंटर वापरू शकतो हे निर्दिष्ट करतो.
 
       केवळ <ph name="BULK_PRINTERS_ACCESS_MODE" /> साठी <ph name="PRINTERS_WHITELIST" /> निवडलेली असल्यासच हे धोरण वापरले जाते.
@@ -1815,6 +1823,15 @@
       धोरणाचे मूल्य मिलिसेकंदांमध्ये निर्दिष्ट केले जावे.</translation>
 <translation id="5511702823008968136">बुकमार्क बार सक्षम करा</translation>
 <translation id="5512418063782665071">होमपेज URL</translation>
+<translation id="551639594034811656">हे धोरण टक्केवारींची एक सूची परिभाषित करते जे पहिल्यांदा आढळलेल्या दिवसापासून दररोज अपडेट करण्यासाठी OU मधील <ph name="PRODUCT_OS_NAME" /> डिव्हाइसचा एक छोटासा अंश परिभाषित करेल. शोध लागण्याची वेळ अपडेट झालेल्या नंतरची आहे, कारण अपडेट प्रकाशित करण्याअगोदर डिव्हाइस अपडेटसाठी तपासणी करेपर्यंत थोडा वेळ लागू शकतो.
+
+      अपडेट आढळल्यापासून प्रत्येक (दिवस, टक्केवारी) पेअरमध्ये दिलेल्या दिवसांच्या संख्येनुसार फ्लिटची कोणती टक्केवारी अपडेट करावी लागते याचा समावेश असतो. उदाहरणार्थ, आमच्याकडे [(४, ४०), (१०, ७०), (१५, १००)] या पेअर असल्यास, ४०% फ्लिट अपडेट पाहिल्याच्या ४ दिवसानंतर अपडेट केले गेले पाहिजे. ७०% नंतर १० दिवसांनी आणि नंतर अडपेट केले पाहिजे
+
+      या धोरणासाठी निर्धारित मूल्य असल्यास, अपडेट <ph name="DEVICE_UPDATE_SCATTER_FACTOR_POLICY_NAME" /> धोरणाकडे दुर्लक्ष करेल आणि त्याऐवजी या धोरणाचे अनुसरण करेल.
+
+      ही सूची रिकामी असल्यास, कोणतीही स्टेजिंग होणार नाही आणि इतर डिव्हाइस धोरणांनुसार अपडेट लागू केले जातील.
+
+      हे धोरण चॅनेल स्विचसाठी लागू होत नाही.</translation>
 <translation id="5523812257194833591">विलंबानंतर स्वयं-लॉग इनसाठी सार्वजनिक सत्र.
 
       हे धोरण सेट केलेले असल्यास, वापरकर्ता संवादाशिवाय लॉग इन स्क्रीनवर गेलेल्या कालावधीनंतर निर्दिष्ट सत्र स्वयंचलितपणे लॉग इन केले जाईल. सार्वजनिक सत्र आधीपासून कॉन्फिगर केले जाणे आवश्यक आहे (|DeviceLocalAccounts| पहा).
@@ -2782,6 +2799,11 @@
       धोरण बंद केल्यास, कोणतेही स्पष्ट साइट आयसोलेशन होणार नाही आणि IsolateOrigins आणि SitePerProcess च्या फील्ड चाचण्या बंद केल्या जातील. वापरकर्त्यांना तरीही SitePerProcess मॅन्युअली सुरू करता येईल.
       धोरण कॉन्फिगर केलेले नसल्यास, वापरकर्त्याला हे सेटिंग बदलता येईल.
       </translation>
+<translation id="7902255855035461275">या सूचीमधील नमुने विनंती करणार्‍या URL च्या मूळच्या सुरक्षितता संबंधात जुळविले जातील.
+
+  जुळणी आढळल्यास, ऑडिओ कॅप्चर डिव्‍हाइसना सूचनेशिवाय मंजूरी दिली जाईल.
+
+      टीप: ४५ आवृत्तीपर्यंत, या धोरणाला फक्त कियोस्क मोडमध्‍ये सपोर्ट आहे.</translation>
 <translation id="7912255076272890813">अनुमत अ‍ॅप/विस्तार प्रकार कॉन्फिगर करा</translation>
 <translation id="7915236031252389808">आपण प्रॉक्सी .pac फाईलची URL येथे निर्दिष्‍ट करु शकता.
 
diff --git a/components/policy/resources/policy_templates_no.xtb b/components/policy/resources/policy_templates_no.xtb
index 56b0e14..a8907f389 100644
--- a/components/policy/resources/policy_templates_no.xtb
+++ b/components/policy/resources/policy_templates_no.xtb
@@ -301,6 +301,11 @@
       Hvis en innstilling ikke angis, brukes en standardverdi.
 
       Hvis denne planen ikke angis, brukes standardverdiene for alle innstillingene.</translation>
+<translation id="1958138414749279167">Slår på autofyllfunksjonen i <ph name="PRODUCT_NAME" />og gjør det mulig for brukere å fylle ut adresseinformasjon i nettskjemaer automatisk ved hjelp av tidligere lagret informasjon.
+
+      Hvis denne innstillingen er av, gir ikke autofyllfunksjonen forslag, og den fyller ikke inn informasjon automatisk. Den lagrer heller ikke lenger informasjon som brukeren sender inn på nettet.
+
+      Hvis denne innstillingen er på eller ikke har noen verdi, kan brukeren kontrollere all funksjonalitet for autofyllfunksjonen i brukergrensesnittet.</translation>
 <translation id="1960840544413786116">Hvorvidt du vil tillate sertifikater som utstedes av lokale klareringsankre som mangler subjectAlternativeName-utvidelsen</translation>
 <translation id="1964634611280150550">Inkognitomodus er deaktivert</translation>
 <translation id="1964802606569741174">Denne regelen har ingen innvirkning på YouTube-appen for Android. Hvis du vil tvinge bruk av sikkerhetsmodus for YouTube, bør du ikke tillate installasjon av YouTube-appen for Android.</translation>
@@ -1901,6 +1906,7 @@
 <translation id="6440051664870270040">Tillat at nettsteder navigerer og åpner forgrunnsvinduer samtidig</translation>
 <translation id="6447948611083700881">Sikkerhetskopiering og gjenoppretting er deaktivert</translation>
 <translation id="645425387487868471">Slå på tvunget pålogging for <ph name="PRODUCT_NAME" /></translation>
+<translation id="6464074037294098618">Slå på autofyll for adresser</translation>
 <translation id="6473623140202114570">Konfigurer listen over domener hvor Safe Browsing ikke utløser advarsler.</translation>
 <translation id="6491139795995924304">Tillat Bluetooth på enheten</translation>
 <translation id="6520802717075138474">Importér søkemotorer fra standard nettleser ved første kjøring</translation>
diff --git a/components/policy/resources/policy_templates_sk.xtb b/components/policy/resources/policy_templates_sk.xtb
index f1fca9fc..107f0e3f 100644
--- a/components/policy/resources/policy_templates_sk.xtb
+++ b/components/policy/resources/policy_templates_sk.xtb
@@ -505,6 +505,11 @@
       Ak toto pravidlo ponecháte nenastavené, používatelia si budú môcť vybrať, či chcú funkciu používať alebo nie.
 
       Toto nastavenie sa v prehliadači <ph name="PRODUCT_NAME" /> verzie 29 a vyšších už nenachádza.</translation>
+<translation id="2433412232489478893">Toto pravidlo riadi, či je funkcia zdieľania súborov v sieti pre <ph name="PRODUCT_NAME" /> povolená pre používateľa.
+
+      Keď ho nenakonfigurujete alebo nastavíte na hodnotu True, používatelia budú môcť používať zdieľanie súborov v sieti.
+
+      Keď ho nastavíte na hodnotu False, používatelia nebudú môcť používať zdieľanie súborov v sieti.</translation>
 <translation id="2438609638493026652">Povolí nahlasovanie kľúčových udalostí počas inštalácie aplikácie pre Android do Googlu. Udalosti sa zaznamenávajú iba v prípade aplikácií, ktorých inštalácia bola spustená prostredníctvom pravidla.
 
       Ak toto pravidlo nastavíte na hodnotu true, udalosti sa budú zaznamenávať.
@@ -586,6 +591,7 @@
 <translation id="2598508021807251719">Konfiguruje miestne nastavenia, v ktorých sa <ph name="PRODUCT_OS_NAME" /> môže zobrazovať.
 
       Ak toto pravidlo nastavíte, používateľ môže nakonfigurovať iba to, aby sa <ph name="PRODUCT_OS_NAME" /> zobrazoval v jednom z miestnych nastavení špecifikovaných týmto pravidlom. Ak toto pravidlo nenastavíte alebo ho nastavíte na prázdny zoznam, <ph name="PRODUCT_OS_NAME" /> sa môže zobrazovať vo všetkých podporovaných miestnych nastaveniach používateľského rozhrania. Ak pravidlo nastavíte na zoznam s neplatnými hodnotami, všetky neplatné hodnoty sa budú ignorovať. Ak používateľ predtým nakonfiguroval, aby sa <ph name="PRODUCT_OS_NAME" /> zobrazoval v jazyku, ktorý toto pravidlo nepovoľuje, pri najbližšom prihlásení používateľa sa zobrazené miestne nastavenie prepne na povolené miestne nastavenie používateľského rozhrania. Ak používateľ nakonfiguroval preferované miestne nastavenia a jedno z týchto nastavení je povolené týmto pravidlom, <ph name="PRODUCT_OS_NAME" /> sa prepne na toto miestne nastavenie. V opačnom prípade sa <ph name="PRODUCT_OS_NAME" /> prepne na prvú platnú hodnotu špecifikovanú týmto pravidlom alebo na záložné miestne nastavenie (aktuálne en-US), ak pravidlo obsahuje iba neplatné záznamy.</translation>
+<translation id="2604182581880595781">Konfigurácia pravidiel súvisiacich so zdieľaním súborov v sieti</translation>
 <translation id="2623014935069176671">Počkať na aktivitu používateľa</translation>
 <translation id="262740370354162807">Povolí odoslanie dokumentov do služby <ph name="CLOUD_PRINT_NAME" /></translation>
 <translation id="2627554163382448569">Umožňuje poskytnúť konfigurácie pre podnikové tlačiarne.
@@ -671,6 +677,7 @@
 <translation id="2823870601012066791">Umiestnenie v registri systému Windows pre klienty <ph name="PRODUCT_OS_NAME" />:</translation>
 <translation id="2824715612115726353">Povoliť režim inkognito</translation>
 <translation id="2838830882081735096">Zakázať migráciu dát a ARC</translation>
+<translation id="2839294585867804686">Nastavenia zdieľania súborov v sieti</translation>
 <translation id="2840269525054388612">Umožňuje určiť tlačiarne, ktoré má používateľ k dispozícii.
 
       Toto pravidlo sa používa iba vtedy, keď je pre <ph name="DEVICE_PRINTERS_ACCESS_MODE" /> vybraná možnosť <ph name="PRINTERS_WHITELIST" />
@@ -1161,6 +1168,7 @@
       Ak toto pravidlo ponecháte nenastavené, anonymizované zhromažďovanie dát prostredníctvom kódovaných webových adries sa povolí, no používateľ ho bude môcť zmeniť.</translation>
 <translation id="4250680216510889253">Nie</translation>
 <translation id="4261820385751181068">Jazyk prihlasovacej obrazovky zariadenia</translation>
+<translation id="4264607809747169568">Riadi dostupnosť zdieľania súborov v sieti pre Chrome OS</translation>
 <translation id="427220754384423013">Umožňuje určiť tlačiarne, ktoré má používateľ k dispozícii.
 
       Toto pravidlo sa používa iba vtedy, keď je pre <ph name="BULK_PRINTERS_ACCESS_MODE" /> vybraná možnosť <ph name="PRINTERS_WHITELIST" />.
@@ -1592,6 +1600,15 @@
       Hodnota pravidla musí byť zadaná v milisekundách.</translation>
 <translation id="5511702823008968136">Povoliť panel so záložkami</translation>
 <translation id="5512418063782665071">Webová adresa domovskej stránky</translation>
+<translation id="551639594034811656">Toto pravidlo definuje zoznam percentuálnych hodnôt určujúcich časť zariadení so systémom <ph name="PRODUCT_OS_NAME" /> v organizačnej jednotke, ktorá má byť každý týždeň aktualizovaná odo dňa prvého zistenia dostupnosti aktualizácie. Čas zistenia nasleduje po čase zverejnenia aktualizácie, pretože zariadenie môže skontrolovať dostupnosť aktualizácií až s určitým časovým odstupom po ich zverejnení.
+
+      Jednotlivé páry (počet dní a percento) uvádzajú percento zariadení, ktoré sa má aktualizovať za určený počet dní odo dňa zistenia dostupnosti aktualizácie. Ak máte napríklad páry [(4, 40), (10, 70), (15, 100)], potom 40 % zariadení by sa malo aktualizovať do 4 dní od zistenia dostupnosti danej aktualizácie. 70 % by sa malo aktualizovať po 10 dňoch atď.
+
+      Ak pre toto pravidlo definujete hodnotu, aktualizácie budú ignorovať pravidlo <ph name="DEVICE_UPDATE_SCATTER_FACTOR_POLICY_NAME" /> a nasledovať namiesto neho toto pravidlo.
+
+      Ak tento zoznam ponecháte prázdny, nedôjde k postupnému zavádzaniu po etapách a aktualizácie sa budú uplatňovať v súlade s ostatnými pravidlami zariadenia.
+
+      Toto pravidlo sa netýka zmien kanálov.</translation>
 <translation id="5523812257194833591">Verejná relácia, do ktorej sa zariadenie po oneskorení automaticky prihlási.
 
       Ak je pravidlo nastavené, zariadenie sa po uplynutí určitého časového obdobia nečinnosti používateľa na prihlasovacej obrazovke automaticky prihlási do určenej relácie. Verejná relácia už musí byť nakonfigurovaná (prečítajte si časť |DeviceLocalAccounts|).
@@ -2524,6 +2541,11 @@
       Ak toto pravidlo zakážete, nenastane žiadna explicitná izoláciu webu a skúšobné testy pravidiel IsolateOrigins a SitePerProcess budú zakázané. Používatelia budú môcť ale pravidlo SitePerProcess ručne povoliť.
       Ak toto pravidlo nenakonfigurujete, používateľ bude môcť dané nastavenie zmeniť.
       </translation>
+<translation id="7902255855035461275">Vzory v tomto zozname sa porovnajú s pôvodom
+      zabezpečenia webovej adresy, ktorá odoslala žiadosť. Ak sa nájde zhoda, prístup k zariadeniam
+      na zaznamenávanie zvuku sa udelí bez výzvy.
+
+      POZNÁMKA: Do verzie 45 bolo toto pravidlo podporované iba v režime verejného terminálu.</translation>
 <translation id="7912255076272890813">Nakonfigurovať povolené typy aplikácií/rozšírení</translation>
 <translation id="7915236031252389808">Tu môžete zadať webovú adresu súboru .pac proxy servera.
 
diff --git a/components/policy/resources/policy_templates_ta.xtb b/components/policy/resources/policy_templates_ta.xtb
index e9d2d31a..bdb81d4 100644
--- a/components/policy/resources/policy_templates_ta.xtb
+++ b/components/policy/resources/policy_templates_ta.xtb
@@ -283,6 +283,11 @@
       அமைப்பானது எதுவும் குறிக்கப்படாமல் இருந்தால், இயல்புநிலை மதிப்பு பயன்படுத்தப்படும்.
 
       இந்தக் கொள்கை அமைக்கப்படாமல் இருந்தால், எல்லா அமைப்புகளுக்கும் இயல்புநிலைகள் பயன்படுத்தப்படும்.</translation>
+<translation id="1958138414749279167"><ph name="PRODUCT_NAME" /> இன் தன்னிரப்பி அம்சத்தை இயக்கும். மேலும் ஏற்கனவே சேமிக்கப்பட்ட தகவல்களைப் பயன்படுத்தி, இணையதளப் படிவங்களில் பயனர்கள் தங்களுடைய முகவரித் தகவலைத் தானாக நிரப்ப அனுமதிக்கும்.
+
+      இந்த அமைப்பை முடக்கினால், தன்னிரப்பி அம்சமானது முகவரித் தகவலைப் பரிந்துரைக்காது அல்லது நிரப்பாது. மேலும், இணையத்தில் உலாவும்போது பயனர் சமர்ப்பிக்கக்கூடிய கூடுதல் முகவரித் தகவலையும் சேமிக்காது.
+
+      இந்த அமைப்பை இயக்கினால் அல்லது அதற்கென எந்தவொரு மதிப்பையும் அமைக்கவில்லை என்றால், பயனர் இடைமுகத்தில் முகவரிகளுக்கான தன்னிரப்பி அம்சத்தைப் பயனர் கட்டுப்படுத்த முடியும்.</translation>
 <translation id="1960840544413786116">subjectAlternativeName நீட்டிப்பு இல்லாத லோக்கல் ட்ரஸ்ட் ஆங்கர்கள் வழங்கிய சான்றிதழ்களை அனுமதிக்கவா?</translation>
 <translation id="1964634611280150550">மறைநிலைப் பயன்முறை முடக்கப்பட்டது</translation>
 <translation id="1964802606569741174">Android YouTube பயன்பாட்டில் இந்தக் கொள்கை எந்த மாற்றத்தையும் ஏற்படுத்தாது. YouTube இல் பாதுகாப்புப் பயன்முறையைச் செயல்படுத்த வேண்டும் எனில், Android YouTube பயன்பாட்டை நிறுவுவது அனுமதிக்கப்படாது.</translation>
@@ -1144,15 +1149,15 @@
 
           இந்தக் கொள்கை அமைக்கப்படாமல் விடப்பட்டால், எல்லா தளங்களுக்குமான ஒட்டுமொத்த இயல்புநிலை மதிப்பானது 'DefaultKeygenSetting' கொள்கை அமைக்கப்பட்டிருந்தால் அதிலிருந்து பயன்படுத்தப்படும் அல்லது பயனரின் தனிப்பட்ட உள்ளமைவின்படி பயன்படுத்தப்படும்.</translation>
 <translation id="4239720644496144453">Android பயன்பாடுகளுக்குத் தற்காலிகச் சேமிப்பு பயன்படுத்தப்படாது. பல பயனர்கள் ஒரே Android பயன்பாட்டை நிறுவியிருந்தால், ஒவ்வொரு பயனருக்கும் அது புதியதாகப் பதிவிறக்கப்படும்.</translation>
-<translation id="4243336580717651045"><ph name="PRODUCT_NAME" /> இல், URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பை இயக்கும் மற்றும் இந்த அமைப்பைப் பயனர்கள் மாற்றுவதைத் தடுக்கும்.
+<translation id="4243336580717651045"><ph name="PRODUCT_NAME" /> இல், URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பை இயக்கும். மேலும் இந்த அமைப்பைப் பயனர்கள் மாற்றுவதைத் தடுக்கும்.
 
-      தேடல்களையும் பயனரின் உலாவல் அனுபவத்தையும் மேம்படுத்துவதற்காக, URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பானது பயனர் பார்வையிடும் பக்கங்களின் URLகளை Googleளுக்கு அனுப்பும்.
+      தேடல்களையும் பயனரின் உலாவல் அனுபவத்தையும் மேம்படுத்துவதற்காக, URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பானது பயனர் பார்வையிடும் பக்கங்களின் URLகளை Googleளுக்கு அனுப்பும்.
 
-      இந்தக் கொள்கையை இயக்கினால், URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு எப்போதும் செயலில் இருக்கும்.
+      இந்தக் கொள்கையை இயக்கினால், URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு எப்போதும் செயலில் இருக்கும்.
 
-      இந்தக் கொள்கையை முடக்கினால், URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு ஒருபோதும் செயலில் இருக்காது.
+      இந்தக் கொள்கையை முடக்கினால், URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு ஒருபோதும் செயலில் இருக்காது.
 
-      இந்தக் கொள்கையை அமைக்காவிட்டால், URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு இயக்கப்படும், எனினும் பயனரால் அதை மாற்ற முடியும்.</translation>
+      இந்தக் கொள்கையை அமைக்காவிட்டால், URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பு இயக்கப்படும், எனினும் பயனரால் அதை மாற்ற முடியும்.</translation>
 <translation id="4250680216510889253">இல்லை</translation>
 <translation id="4261820385751181068">சாதன உள்நுழைவுத் திரையின் மொழி</translation>
 <translation id="427220754384423013">பயனர் பயன்படுத்தக்கூடிய பிரிண்டர்களைக் குறிப்பிடும்.
@@ -1783,7 +1788,7 @@
           செயல்படாமல் இருக்கும்போது திரையைப் பூட்டுவதற்குப் பரிந்துரைக்கப்படும் வழியானது, தற்காலிகமாக நிறுத்தும்போது திரையைப் பூட்டுவதை இயக்குவதும் மற்றும் செயல்படாமல் இருந்த தாமதத்திற்குப் பிறகு <ph name="PRODUCT_OS_NAME" /> ஐத் தற்காலிகமாக நிறுத்த வைப்பதும் ஆகும். திரையைப் பூட்டுதல், தற்காலிக நிறுத்தத்தைவிட குறிப்பிட்ட நேரம் முன்பாக ஏற்பட வேண்டும் எனும்போது மட்டுமே இந்தக் கொள்கை பயன்படுத்தப்பட வேண்டும் அல்லது செயல்படாமல் இருக்கும்போது நிறுத்துவது விரும்பத்தக்கதல்ல.
 
           கொள்கை மதிப்பை மில்லிவினாடிகளில் குறிப்பிட வேண்டும். மதிப்புகளானவை, செயல்படாமல் இருந்த தாமத்தைவிட குறைவாகவே இருக்க வேண்டும்.</translation>
-<translation id="6097601282776163274">URLலில் இருந்து சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பை இயக்கும்</translation>
+<translation id="6097601282776163274">URLகளோடு சேகரிக்கப்படும் அடையாளமற்ற தரவுத் தொகுப்பை இயக்கும்</translation>
 <translation id="6111936128861357925">டைனோசர் ஈஸ்டர் எக் கேமை அனுமதிக்கும்</translation>
 <translation id="6114416803310251055">மறுக்கப்பட்டது</translation>
 <translation id="6133088669883929098">விசை உருவாக்கத்தை எல்லா தளங்களும் பயன்படுத்த அனுமதி</translation>
@@ -1858,6 +1863,7 @@
 <translation id="6440051664870270040">தளங்கள் ஒரே நேரத்தில் செல்லவும் பாப் அப்களைத் திறக்கவும் அனுமதிக்கும்</translation>
 <translation id="6447948611083700881">காப்புப் பிரதி எடுப்பது மற்றும் மீட்டெடுப்பது முடக்கப்பட்டது</translation>
 <translation id="645425387487868471"><ph name="PRODUCT_NAME" />க்குக் கட்டாய உள்நுழைவை இயக்கும்</translation>
+<translation id="6464074037294098618">முகவரிகளுக்கு, தன்னிரப்பியை இயக்கும்</translation>
 <translation id="6473623140202114570">பாதுகாப்பான உலாவல் எச்சரிக்கைகளைத் தூண்டாத டொமைன்களின் பட்டியலை உள்ளமைக்கவும்.</translation>
 <translation id="6491139795995924304">சாதனத்தில் புளூடூத்தை அனுமதி</translation>
 <translation id="6520802717075138474">முதல் இயக்கத்தின்போதே இயல்புநிலை உலாவலிருந்து தேடு பொறிகளை இறக்குமதி செய்</translation>
diff --git a/components/policy/resources/policy_templates_te.xtb b/components/policy/resources/policy_templates_te.xtb
index 30a92622..a9f7a63 100644
--- a/components/policy/resources/policy_templates_te.xtb
+++ b/components/policy/resources/policy_templates_te.xtb
@@ -422,6 +422,13 @@
       ఈ విధానం సెట్ చేయబడకపోతే, డిఫాల్ట్ విలువ 0 డిగ్రీలుగా ఉంటుంది మరియు వినియోగదారు
       దీన్ని స్వేచ్ఛగా మార్చవచ్చు. ఈ సందర్భంలో, డిఫాల్ట్ విలువ పునఃప్రారంభ సమయంలో
       మళ్లీ వర్తింపజేయబడదు.</translation>
+<translation id="2138449619211358657">ఈ విధానం <ph name="PRODUCT_OS_NAME" />ని క్యాప్టివ్ పోర్టల్ ప్రమాణీకరణ కోసం ఏదైనా ప్రాక్సీని బైపాస్ చేయడానికి అనుమతిస్తుంది.
+
+      ఈ విధానం ప్రాక్సీ కాన్ఫిగర్ అయినప్పుడు మాత్రమే ప్రభావం చూపుతుంది (ఉదాహరణకు విధానం ద్వారా, వినియోగదారు ద్వారా chrome://settingsలో లేదా ఎక్స్‌టెన్షన్‌ల ద్వారా).
+
+      మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే, ప్రస్తుత వినియోగదారు కోసం అన్ని విధానం సెట్టింగ్‌లను మరియు నియంత్రణలను విస్మరించి వేరే విండోలో ఏవైనా క్యాప్టివ్ పోర్టల్ ప్రమాణీకరణ పేజీలను ప్రదర్శిస్తాయి (అంటే <ph name="PRODUCT_NAME" /> విజయవంతమైన ఇంటర్నెట్ కనెక్షన్‌ని గుర్తించే వరకు క్యాప్టివ్ పోర్టల్ సైన్ ఇన్ పేజీతో ప్రారంభమయ్యే అన్ని వెబ్ పేజీలు).
+
+      మీరు ఈ సెట్టింగ్‌ను నిలిపివేస్తే లేదా సెట్ చేయకుండా వదిలివేస్తే, ఏవైనా క్యాప్టివ్ పోర్టల్ ప్రమాణీకరణ పేజీలు ప్రస్తుత వినియోగదారు ప్రాక్సీ సెట్టింగ్‌లను ఉపయోగించి (సాధారణ) కొత్త బ్రౌజర్ ట్యాబ్‌లో చూపబడతాయి.</translation>
 <translation id="21394354835637379">పొడిగింపులను, అనువర్తనాలను మరియు థీమ్‌లను ఇన్‌స్టాల్ చేయడానికి అనుమతించవలసిన URLలను పేర్కొనడానికి మిమ్మల్ని అనుమతిస్తుంది.
 
 <ph name="PRODUCT_NAME" /> 21,ప్రారంభంలో, Chrome వెబ్ స్టోర్ వెలుపల నుండి పొడిగింపులను, అనువర్తనాలను మరియు వినియోగదారు స్క్రిప్ట్‌లను ఇన్‌స్టాల్ చేయడం చాలా కష్టం. మునుపు, వినియోగదారులు *.crx ఫైల్‌కు లింక్‌పై క్లిక్ చేస్తే, <ph name="PRODUCT_NAME" /> కొన్ని హెచ్చరికల తర్వాత ఫైల్‌ను ఇన్‌స్టాల్ చేసే అవకాశాన్ని అందిస్తుంది. <ph name="PRODUCT_NAME" /> 21 తర్వాత, ఇటువంటి ఫైల్‌లు తప్పనిసరిగా డౌన్‌లోడ్ చేయబడతాయి ఆపై <ph name="PRODUCT_NAME" /> సెట్టింగ్‌ల పేజీకి లాగబడతాయి. ఈ సెట్టింగ్ నిర్దిష్ట URLలను పాత, సులభమైన ఇన్‌స్టాలేషన్ విధానాన్ని కలిగి ఉండటానికి అనుమతిస్తాయి.
@@ -695,6 +702,17 @@
           'DefaultSearchProviderEnabled' విధానాన్ని ప్రారంభించినప్పుడు, ఈ ఎంపికను తప్పనిసరిగా సెట్ చేయాలి, ఇది ఈ సందర్భంలో మాత్రమే పరిగణించబడుతుంది.</translation>
 <translation id="2660846099862559570">ఇప్పటి వరకు ప్రాక్సీని ఉపయోగించలేదా</translation>
 <translation id="267596348720209223">శోధన ప్రొవైడర్ ద్వారా మద్దతు గల అక్షర ఎన్‌కోడింగ్‌లను పేర్కొంటుంది. ఎన్‌కోడింగ్‌లు అంటే UTF-8 GB2312 మరియు ISO-8859-1 వంటి కోడ్ పేజీ పేర్లు. అవి అందించబడిన క్రమంలో ప్రయత్నించబడతాయి. ఈ విధానం ఐచ్ఛికం. సెట్ చేయకపోతే, UTF-8 డిఫాల్ట్ ఉపయోగించబడుతుంది. ఈ విధానం కేవలం 'DefaultSearchProviderEnabled' విధానం ప్రారంభించబడితే పరిగణించబడుతుంది.</translation>
+<translation id="2682697254900811431"><ph name="PRODUCT_NAME" /> గురించి వినియోగం యొక్క అజ్ఞాత నివేదికను మరియు క్రాష్-సంబంధిత డేటాను Googleకి పంపడాన్ని ప్రారంభిస్తుంది మరియు ఈ సెట్టింగ్‌ను మార్చకుండా వినియోగదారులను నిరోధిస్తుంది.
+
+      ఈ సెట్టింగ్ ప్రారంభించబడితే, వినియోగం యొక్క అజ్ఞాత నివేదికను మరియు క్రాష్-సంబంధిత
+      డేటా Googleకి పంపబడుతుంది.  ఇది నిలిపివేయబడితే, ఈ సమాచారం Googleకి
+      పంపబడదు.  రెండు సందర్భాలలో, వినియోగదారులు సెట్టింగ్‌లను మార్చలేరు లేదా భర్తీ చేయలేరు.
+      ఈ విధానం సెట్ చేయబడకపోతే, సెట్టింగ్ వినియోగదారు ఇన్‌స్టాలేషన్ / మొదటిసారి అమలు చేస్తున్నప్పుడు
+      ఏది ఎంచుకుంటారో అదే ఉంటుంది.
+
+      ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు
+      అందుబాటులో ఉండదు.  (Chrome OS కోసం,
+      DeviceMetricsReportingEnabledని చూడండి.)</translation>
 <translation id="268577405881275241">డేటా కుదింపు ప్రాక్సీ లక్షణాన్ని ప్రారంభించండి</translation>
 <translation id="2693108589792503178">పాస్‌వర్డ్‌‌ని మార్చే URLని కాన్ఫిగర్ చేయండి.</translation>
 <translation id="2710534340210290498">ఈ విధానాన్ని తప్పుకు సెట్ చేస్తే, వినియోగదారులు స్క్రీన్‌ను లాక్ చేయలేరు (వినియోగదారు సెషన్ నుండి సైన్ అవుట్ చేయడం మాత్రమే సాధ్యమవుతుంది). ఈ సెట్టింగ్‌ను ఒప్పుకు సెట్ చేస్తే లేదా సెట్ చేయకుండా వదిలివేస్తే, పాస్‌వర్డ్ కలిగి ఉండే వినియోగదారులు స్క్రీన్‌ను లాక్ చేయగలరు.</translation>
@@ -1183,6 +1201,20 @@
       ఈ విధానాన్ని ఒప్పుకి సెట్ చేస్తే, పర్యవేక్షిత నెట్‌వర్క్ ప్యాకెట్‌లు (<ph name="HEARTBEATS_TERM" />గా పిలిచేవి) పంపబడతాయి.
       తప్పుకి సెట్ చేస్తే లేదా సెట్ చేయకుండా ఉంటే, ప్యాకెట్‌లు ఏవీ పంపబడవు.</translation>
 <translation id="3950239119790560549">సమయ పరిమితులను అప్‌డేట్ చేయండి</translation>
+<translation id="3957134519352019843">డిఫాల్ట్ శోధన ప్రదాత వినియోగాన్ని ప్రారంభిస్తుంది.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే, URL కాని ఓమ్నిబాక్స్‌లో వినియోగదారు వచనాన్ని టైప్ చేస్తున్నప్పుడు ఒక డిఫాల్ట్ శోధన అమలవుతుంది.
+
+          మిగిలిన డిఫాల్ట్ శోధన విధానాలను సెట్ చేయడం ద్వారా మీరు ఉపయోగించాల్సిన డిఫాల్ట్ శోధన ప్రదాతను పేర్కొనవచ్చు. వీటిని ఖాళీగా వదిలివేస్తే, వినియోగదారు డిఫాల్ట్ ప్రదాతను ఎంచుకోగలరు.
+
+          మీరు ఈ సెట్టింగ్‌ను నిలిపివేస్తే, వినియోగదారు ఓమ్నిబాక్స్‌లో URL-యేతర వచనాన్ని నమోదు చేస్తున్నప్పుడు శోధన ఏదీ అమలు కాదు.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభించినా లేదా నిలిపివేసినా, వినియోగదారులు <ph name="PRODUCT_NAME" />లో ఈ సెట్టింగ్‌ను మార్చలేరు లేదా భర్తీ చేయలేరు.
+
+          ఈ విధానాన్ని సెట్ చేయకుండా వదిలివేస్తే, డిఫాల్ట్ శోధన ప్రదాత ప్రారంభించబడుతుంది మరియు వినియోగదారు శోధన ప్రదాత జాబితాను సెట్ చేయగలరు.
+
+          ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు
+          అందుబాటులో ఉండదు.</translation>
 <translation id="3958586912393694012">Smart Lockను ఉపయోగించేలా అనుమతించండి</translation>
 <translation id="3963602271515417124">ఒప్పు అయితే, పరికరం కోసం రిమోట్ ధృవీకరణ అనుమతించబడుతుంది మరియు ప్రమాణపత్రం స్వయంచాలకంగా రూపొందించబడుతుంది మరియు పరికర నిర్వహణ సర్వర్‌కు అప్‌లోడ్ చేయబడుతుంది.
 
@@ -1535,6 +1567,22 @@
       వినియోగదారు ఈ నమూనాకు సరిపోలని వినియోగదారు పేరుతో లాగిన్ చేయడానికి ప్రయత్నిస్తే తగిన లోపం ప్రదర్శించబడుతుంది.
 
       ఈ విధానం సెట్ చేయకుండా వదిలివేయబడితే లేదా ఖాళీగా ఉంటే, ఏ వినియోగదారు అయినా <ph name="PRODUCT_NAME" />కు సైన్ ఇన్ చేయగలరు.</translation>
+<translation id="4852080537521553509">ప్రారంభ ప్రవర్తనను పేర్కొనడానికి మిమ్మల్ని అనుమతిస్తుంది.
+
+          మీరు 'కొత్త ట్యాబ్ పేజీని తెరవండి' ఎంచుకుంటే కొత్త ట్యాబ్ పేజీ మీరు <ph name="PRODUCT_NAME" />ని ప్రారంభించినప్పుడు ఎల్లప్పుడూ తెరవబడుతుంది.
+
+          మీరు 'చివరి సెషన్‌ని పునరుద్ధరించు' ఎంచుకుంటే, చివరిసారి <ph name="PRODUCT_NAME" />ని మూసివేసినప్పుడు తెరిచి ఉన్న URLలు మళ్లీ తెరవబడతాయి మరియు బ్రౌజింగ్ సెషన్ నుండి నిష్క్రమించబడినందున అది పునరుద్ధరించబడుతుంది.
+          
+ఈ ఎంపికను ఎంచుకోవడం వలన సెషన్‌లపై ఆధారపడే లేదా నిష్క్రమించినప్పుడు చర్యలు (నిష్క్రమించినప్పుడు బ్రౌజింగ్ డేటాను లేదా సెషన్-మాత్రమే కుక్కీలను తీసివేయడం) అమలు చేసే కొన్ని సెట్టింగ్‌లను నిలిపివేస్తాయి.
+
+          'URLల జాబితాను తెరవండి' ఎంచుకుంటే, 'ప్రారంభంలో తెరవాల్సిన URLల' జాబితా వినియోగదారు <ph name="PRODUCT_NAME" />ని ప్రారంభించినప్పుడు తెరవబడుతుంది.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే, వినియోగదారులు దాన్ని <ph name="PRODUCT_NAME" />లో మార్చలేరు లేదా భర్తీ చేయలేరు.
+
+          ఈ సెట్టింగ్‌ని నిలిపివేయడం కాన్ఫిగర్ చేయకుండా వదిలివేయడానికి సమానం. వినియోగదారు ఇప్పటికీ దీన్ని <ph name="PRODUCT_NAME" />లో మార్చగలరు.
+
+          ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు
+          అందుబాటులో ఉండదు.</translation>
 <translation id="4858735034935305895">పూర్తిస్క్రీన్ మోడ్‌ను అనుమతించండి</translation>
 <translation id="4861767323695239729">వినియోగదారు సెషన్‌లో అనుమతించబడిన ఇన్‌పుట్ పద్ధతులను కాన్ఫిగర్ చేయండి</translation>
 <translation id="4869787217450099946">స్క్రీన్‌ను సాధారణ స్థితికి తీసుకువచ్చే లాక్‌లు అనుమతించబడాలో లేదో పేర్కొంటుంది. స్క్రీన్‌ను సాధారణ స్థితికి తీసుకువచ్చే లాక్‌లను శక్తి నిర్వహణ పొడిగింపు API ద్వారా పొడిగింపులతో అభ్యర్థించవచ్చు.
@@ -1598,6 +1646,15 @@
       విధానాన్ని 0కి సెట్ చేస్తే, మెషీన్ ఖాతా పాస్‌వర్డ్‌‌ మార్పు నిలిపివేయబడుతుంది.
 
       క్లయింట్ చాలా ఎక్కువ రోజుల వరకు ఆఫ్‌లైన్‌లో ఉంటే పాస్‌వర్డ్‌‌‌లు, పేర్కొన్న రోజులు కన్నా ముందే పాతవి అవ్వచ్చు అని గమనించండి.</translation>
+<translation id="5102187683953991824">విశ్వసనీయ మూలం నుండి సురక్షిత బ్రౌజింగ్ తనిఖీలు లేకుండా డౌన్‌లోడ్ చేయడానికి <ph name="PRODUCT_NAME" /> అనుమతిస్తుందో లేదో గుర్తించండి.
+
+      తప్పు అయినప్పుడు, విశ్వసనీయ మూలం నుండి డౌన్‌లోడ్ చేయబడిన ఫైల్‌లు సురక్షిత బ్రౌజింగ్ ద్వారా విశ్లేషించబడటానికి పంపబడవు.
+
+      సెట్ చేయనప్పుడు (లేదా ఒప్పుకి సెట్ చేసినప్పుడు), విశ్వసనీయ మూలం నుండి డౌన్‌లోడ్ చేయబడిన ఫైల్‌లు అయినప్పటికీ సురక్షిత బ్రౌజింగ్ ద్వారా విశ్లేషించబడటానికి పంపబడతాయి.
+
+      ఈ నియంత్రణలు వెబ్ పేజీ కంటెంట్ నుండి ప్రారంభించిన డౌన్‌లోడ్‌లకు అలాగే 'డౌన్‌లోడ్ లింక్...' సందర్భ మెను ఎంపికకు వర్తిస్తాయని గమనించండి. ఈ నియంత్రణలు ప్రస్తుతం ప్రదర్శించబడే పేజీని సేవ్ చేయికి / డౌన్‌లోడ్ చేయికి వర్తించవు, అలాగే ప్రింట్ ఎంపికలలో  PDFగా సేవ్ చేస్తోందికి వర్తించవు.
+
+      ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు అందుబాటులో ఉండదు.</translation>
 <translation id="5105313908130842249">బ్యాటరీ శక్తితో అమలవుతున్నప్పుడు స్క్రీన్ లాక్ ఆలస్యం</translation>
 <translation id="5108031557082757679">ఎంటర్‌ప్రైజ్ పరికర ప్రింటర్‌లు నిలిపివేయబడ్డాయి</translation>
 <translation id="5130288486815037971">TLSలో RC4 సైఫర్ సూట్‌లు ప్రారంభించబడ్డాయి</translation>
@@ -1638,6 +1695,18 @@
 <translation id="5208240613060747912">ప్రకటనలను ప్రదర్శించడానికి అనుమతించబడని సైట్‌లను పేర్కొనే url నమూనాల జాబితాను సెట్ చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఈ విధానం సెట్ చేయకుండా వదిలి పెట్టినది అయితే అన్ని సైట్‌లకు సార్వజనీన డిఫాల్ట్ విలువ ఇది సెట్ చేయబడి ఉంటే 'DefaultNotificationsSetting' విధానం నుండి లేదా చేయబడకపోతే వినియోగదారు వ్యక్తిగత కాన్ఫిగరేషన్ నుండి ఉపయోగించబడుతుంది.</translation>
 <translation id="5219844027738217407">Android అనువర్తనాల కోసం, ఈ విధానం మైక్రోఫోన్‌పై మాత్రమే ప్రభావం చూపుతుంది. ఈ విధానాన్ని ఒప్పుకి సెట్ చేసినప్పుడు, మినహాయింపులు లేకుండా అన్ని Android అనువర్తనాల కోసం మైక్రోఫోన్ మ్యూట్ చేయబడుతుంది.</translation>
 <translation id="523505283826916779">ప్రాప్యత సెట్టింగ్‌లు</translation>
+<translation id="5235958368503433463"><ph name="PRODUCT_NAME" />లో డిఫాల్ట్ హోమ్ పేజీ యొక్క రకాన్ని కాన్ఫిగర్ చేస్తుంది మరియు హోమ్ పేజీ ప్రాధాన్యతలను మార్చకుండా వినియోగదారులను నిరోధిస్తుంది. హోమ్ పేజీ మీరు పేర్కొనే URLకి సెట్ చేయబడవచ్చు లేదా కొత్త ట్యాబ్ పేజీకి సెట్ చేయబడవచ్చు.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే, కొత్త ట్యాబ్ పేజీ ఎల్లప్పుడూ హోమ్ పేజీ కోసం ఉపయోగించబడుతుంది మరియు హోమ్ పేజీ URL స్థానం విస్మరించబడుతుంది.
+
+          మీరు ఈ సెట్టింగ్‌ను నిలిపివేస్తే, వినియోగదారు హోమ్ పేజీ URLని 'chrome://newtab'కి సెట్ చేస్తే మినహా ఎన్నటికీ అది కొత్త ట్యాబ్ పేజీ కాదు.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే లేదా నిలిపివేస్తే, వినియోగదారులు వారి హోమ్ పేజీ రకాన్ని <ph name="PRODUCT_NAME" />లో మార్చలేరు.
+
+         ఈ విధానాన్ని సెట్ చేయకుండా వదిలివేస్తే కొత్త ట్యాబ్ పేజీ వినియోగదారు స్వంతంగా నిర్ణయించుకున్న హోమ్ పేజీ అవునో కాదో ఎంచుకోవడానికి అనుమతిస్తుంది.
+
+          ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు
+          అందుబాటులో ఉండదు.</translation>
 <translation id="5236882091572996759">ఈ విధానాన్ని ఒప్పుకు సెట్ చేసినప్పుడు లేదా సెట్ చేయకుండా వదిలేసినప్పుడు, ఆడియో ప్లే అవుతుంటే వినియోగదారు నిష్క్రియంగా ఉన్నట్లు పరిగణించబడరు. ఇది నిష్క్రియ సమయ ముగింపు గడువు ఏర్పడకుండా మరియు నిష్క్రియ చర్య తీసుకోబడకుండా నిరోధిస్తుంది. అయితే, ఆడియో కార్యాచరణతో సంబంధం లేకుండా కాన్ఫిగర్ చేసిన సమయ ముగింపు గడువుల తర్వాత స్క్రీన్ కాంతివిహీనత, స్క్రీన్ ఆపివేత మరియు స్క్రీన్ లాక్ కావడం వంటివి అమలవుతాయి.
 
 ఈ విధానాన్ని తప్పుకు సెట్ చేసినప్పుడు, వినియోగదారును నిష్క్రియంగా పరిగణించకుండా ఆడియో కార్యాచరణ నిరోధించదు.</translation>
@@ -1826,6 +1895,13 @@
 ఈ విధానాన్ని సెట్ చేయనప్పుడు, డిఫాల్ట్ చర్య అయిన తాత్కాలిక తొలగింపు తీసుకోబడుతుంది.
 
 చర్య తాత్కాలిక తొలగింపు అయితే, తాత్కాలిక తొలగింపుకు పూర్వం స్క్రీన్ లాక్ కావాలని లేదా లాక్ కాకూడదని <ph name="PRODUCT_OS_NAME" /> వేరుగా కాన్ఫిగర్ చేయబడవచ్చు.</translation>
+<translation id="5618398258385745432">పాస్‌వర్డ్‌ల వీక్షణ పరిచయం పునఃప్రమాణీకరణకు ముందు అనుబంధించిన సెట్టింగ్ ఉపయోగించబడుతుంది. అప్పటి నుండి, సెట్టింగ్ మరియు ఈ విధానం Chrome ప్రవర్తనపై ఎలాంటి ప్రభావాన్ని కలిగి ఉండవు. Chrome ప్రస్తుత ప్రవర్తన అనేది పాస్‌వర్డ్ సెట్టింగ్‌ల పేజీలో స్పష్టమైన వచనంలా పాస్‌వర్డ్‌లను చూపడాన్ని నిలిపివేతకు సెట్ చేసినప్పటి ప్రవర్తన లాగే ఉంటుంది. సెట్టింగ్‌ల పేజీ ప్లేస్‌హోల్డర్‌ను మాత్రమే కలిగి ఉంటుంది మరియు వినియోగదారు "చూపు" (మరియు అవసరమైతే, పునఃప్రమాణీకరణ) క్లిక్ చేసినప్పుడు మాత్రమే Chrome పాస్‌వర్డ్‌ని చూపుతుంది. విధానం అసలైన వివరణ దిగువ ఉంది.
+
+          పాస్‌వర్డ్ మేనేజర్‌లో స్పష్టమైన వచనంలా వినియోగదారు పాస్‌వర్డ్‌లను చూపవచ్చో లేదో అనేదాన్ని నియంత్రిస్తుంది.
+
+          మీరు ఈ సెట్టింగ్‌ను నిలిపివేస్తే, పాస్‌వర్డ్ మేనేజర్ నిల్వ చేసిన పాస్‌వర్డ్‌లను స్పష్టమైన వచనంలా పాస్‌వర్డ్ మేనేజర్ విండోలో చూపడాన్ని అనుమతించదు.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే లేదా విధానాన్ని సెట్ చేయకపోతే, వినియోగదారులు పాస్‌వర్డ్ మేనేజర్‌లో వారి పాస్‌వర్డ్‌లను స్పష్టమైన వచనంలా వీక్షించగలరు.</translation>
 <translation id="5620392548325769024">OS అప్‌గ్రేడ్ చేసిన అనంతరం మొదటిసారి బ్రౌజర్‌ను ప్రారంభించినప్పుడు స్వాగత పేజీని చూపడం ప్రారంభిస్తుంది</translation>
 <translation id="5630352020869108293">చివరి సెషన్‌ని పునరుద్ధరించు</translation>
 <translation id="5645779841392247734">ఈ సైట్‌లలో కుక్కీలని అనుమతించు</translation>
@@ -1996,6 +2072,18 @@
 <translation id="602728333950205286">డిఫాల్ట్ శోధన అందింపుదారు తక్షణ URL</translation>
 <translation id="603410445099326293">POSTని ఉపయోగించే సూచన URL కోసం పరామితులు</translation>
 <translation id="6036523166753287175">రిమోట్ ప్రాప్యత హోస్ట్ నుండి ఫైర్‌వాల్ ట్రావెర్సల్‌ను ప్రారంభించండి</translation>
+<translation id="6041625286664587019">డిఫాల్ట్ హోమ్ పేజీ URLని <ph name="PRODUCT_NAME" />లో కాన్ఫిగర్ చేస్తుంది మరియు దాన్ని మార్చకుండా వినియోగదారులను నిరోధిస్తుంది.
+
+          హోమ్ పేజీ అనేది హోమ్ బటన్ ద్వారా తెరవబడే పేజీ. ప్రారంభంలో తెరవబడే పేజీలు RestoreOnStartup విధానాల ద్వారా నియంత్రించబడతాయి.
+
+          హోమ్ పేజీ రకం మీరు ఇక్కడ పేర్కొనే URLకి సెట్ చేయబడుతుంది లేదా కొత్త ట్యాబ్ పేజీకి సెట్ చేయబడుతుంది. మీరు కొత్త ట్యాబ్ పేజీని ఎంచుకుంటే, ఆపై ఈ విధానం ప్రభావం చూపదు.
+
+          మీరు ఈ సెట్టింగ్‌ను ప్రారంభిస్తే, వినియోగదారులు <ph name="PRODUCT_NAME" />లో వారి హోమ్ పేజీ URLను మార్చలేరు, కానీ వారు కొత్త ట్యాబ్ పేజీని వారి హోమ్ పేజీగా ఇప్పటికీ ఎంచుకోగలరు.
+
+          ఈ విధానాన్ని సెట్ చేయకుండా వదిలివేయడం వలన HomepageIsNewTabPageని కూడా సెట్ చేయకపోతే వినియోగదారు వారి హోమ్ పేజీని వారి స్వంతంగా ఎంచుకోవడానికి అనుమతినిస్తుంది.
+
+          ఈ విధానం <ph name="MS_AD_NAME" /> డొమైన్‌లో చేరని Windows సందర్భాలకు
+          అందుబాటులో ఉండదు.</translation>
 <translation id="6070667616071269965">పరికర సైన్-ఇన్ స్క్రీన్ కీబోర్డ్ లేఅవుట్‌లు</translation>
 <translation id="6074963268421707432">డెస్క్‌టాప్ నోటిఫికేషన్‌లను చూపించడానికి ఏ సైట్‌ను అనుమతించవద్దు</translation>
 <translation id="6074964551275531965">అప్‌డేట్ నోటిఫికేషన్‌ల కోసం వ్యవధిని సెట్ చేయండి</translation>
@@ -2557,6 +2645,12 @@
       ఈ విధానం SHA-1 సంతకాలను అనుమతించే ఆపరేటింగ్ సిస్టమ్ ప్రమాణపత్ర ధృవీకరణ స్టాక్‌పై ఆధారపడి ఉంటుందని గమనించండి. OS నవీకరణ SHA-1 ప్రమాణపత్రాల OS నిర్వహణను మారిస్తే, ఈ విధానం ఆపై ప్రభావవంతంగా ఉండకపోవచ్చు.  ఇంకా, ఈ విధానం సంస్థలు భవిష్యత్తులో SHA-1 వినియోగాన్ని నిలిపివేసే సందర్భాల్లో మరికొంత సమయాన్ని పొందడం కోసం తాత్కాలిక పరిష్కారంగా ఉపయోగించడానికి ఉద్దేశించినది.  ఈ విధానం ఇంచుమించుగా 1 జనవరి 2019 నాటికి తీసివేయబడుతుంది.
 
       ఈ విధానాన్ని సెట్ చేయకపోయినా లేదా తప్పుకి సెట్ చేసినా, <ph name="PRODUCT_NAME" /> పబ్లిక్‌గా ప్రకటించిన SHA-1 నిలిపివేత షెడ్యూల్‌ని అనుసరిస్తుంది.</translation>
+<translation id="7591049650304818898">ప్రారంభ చర్యగా 'URLల జాబితాను తెరువు' ఎంచుకుంటే, ఇది తెరవాల్సిన URLల జాబితాను పేర్కొనడానికి మిమ్మల్ని అనుమతిస్తుంది. సెట్ చేయకుండా వదిలివేస్తే, ప్రారంభంలో URL ఏదీ తెరవబడదు.
+
+          ఈ విధానం 'RestoreOnStartup' విధానాన్ని 'RestoreOnStartupIsURLs'కి సెట్ చేసినప్పుడు మాత్రమే పని చేస్తుంది.
+
+          ఈ విధానం Windowsని <ph name="MS_AD_NAME" /> డొమైన్‌కు చేరని సందర్భాలలో
+          అందుబాటులో ఉండదు.</translation>
 <translation id="7593523670408385997">డిస్క్‌లో కాష్ చేసిన ఫైల్‌లను నిల్వ చేయడానికి <ph name="PRODUCT_NAME" /> ఉపయోగించే కాష్ పరిమాణాన్ని కాన్ఫిగర్ చేస్తుంది.
 
      మీరు ఈ విధానాన్ని సెట్ చేస్తే, <ph name="PRODUCT_NAME" /> వినియోగదారు '--disk-cache-size' ఫ్లాగ్‌ని పేర్కొన్నారో లేదో అనే దానితో సంబంధం లేకుండా అందించిన కాష్ పరిమాణాన్ని ఉపయోగిస్తుంది. ఈ విధానంలో పేర్కొనబడిన విలువ ఖచ్చితమైన సరిహద్దు కాదు, కానీ కాషింగ్ సిస్టమ్‌కు ఒక సూచన, కొన్ని మెగాబైట్‌ల దిగువ ఉన్న ఏ విలువ అయినా చాలా చిన్నదిగా పరిగణించబడుతుంది మరియు స్థిరమైన కనిష్టానికి పూరించబడుతుంది.
@@ -2591,6 +2685,8 @@
 
       సురక్షిత బ్రౌజింగ్ గురించి మరింత సమాచారం కావాలంటే https://developers.google.com/safe-browsingని చూడండి.</translation>
 <translation id="7643883929273267746"><ph name="PRODUCT_NAME" />లో కనిపించే ఖాతాలను నియంత్రించండి</translation>
+<translation id="7644825865811580663">ఈ విధానాన్ని ఒప్పుకి సెట్ చేస్తే, <ph name="PRODUCT_NAME" /> మొదటి అమలులో చూపబడే మొదటి విండోను బేషరతుగా విస్తరిస్తుంది.
+      ఈ విధానాన్ని తప్పుకి సెట్ చేస్తే లేదా కాన్ఫిగర్ చేయకుంటే, చూపబడే మొదటి విండోను విస్తరించాలా వద్దా అనే నిర్ణయం స్క్రీన్ పరిమాణం ఆధారంగా ఉంటుంది.</translation>
 <translation id="7651739109954974365">ఈ పరికరం కోసం డేటా రోమింగ్ ప్రారంభించబడాలో లేదో అనే దాన్ని నిశ్చయిస్తుంది. ఒప్పుకు సెట్ చేయబడితే, డేటా రోమింగ్ ప్రారంభించబడుతుంది. కాన్ఫిగర్ చేయకుండా ఉంటే లేదా తప్పుకు సెట్ చేస్తే, డేటా రోమింగ్ అందుబాటులో ఉండదు.</translation>
 <translation id="7673194325208122247">వ్యవధి (మిల్లీసెకన్లు)</translation>
 <translation id="7676708657861783864">ఈ URL నమూనాలను సరిపోలే పేజీల ద్వారా సెట్ చేయబడిన కుక్కీలు ప్రస్తుత సెషన్‌కి పరిమితం చేయబడతాయి, అంటే బ్రౌజర్ నుండి నిష్క్రమిస్తున్నప్పుడు అవి తొలగించబడతాయి.
@@ -2616,6 +2712,15 @@
 
       గమనిక: ప్రస్తుతం, స్వయంచాలక రీబూట్‌లు లాగిన్ స్క్రీన్ చూపబడుతున్నప్పుడు లేదా కియోస్క్ అనువర్తన సెషన్ పురోగమనంలో ఉన్నప్పుడు మాత్రమే ప్రారంభించబడతాయి. ఇది భవిష్యత్తులో మారుతుంది మరియు విధానం ఎల్లప్పుడూ వర్తింపజేయబడుతుంది, ఏదైనా నిర్దిష్ట సెషన్ రకం పురోగమనంలో ఉందా లేదా అన్న దానిపై ఆధాపర పడి ఉండదు.</translation>
 <translation id="7701341006446125684">అనువర్తనాలు మరియు పొడిగింపుల కాష్ పరిమాణాన్ని (బైట్‌ల్లో) సెట్ చేస్తుంది</translation>
+<translation id="7703364815046569387">డిఫాల్ట్ కొత్త ట్యాబ్ పేజీ URLని కాన్ఫిగర్ చేస్తుంది మరియు వినియోగదారులు దాన్ని మార్చకుండా నిరోధిస్తుంది.
+
+          కొత్త ట్యాబ్ పేజీ అనేది కొత్త ట్యాబ్‌లను సృష్టించినప్పుడు తెరవబడే పేజీ (కొత్త విండోలలో తెరిచిన దానితో సహా).
+
+          ఈ విధానం ప్రారంభంలో ఏ పేజీలను తెరవాలో నిర్ణయించదు. అవి <ph name="RESTORE_ON_STARTUP_POLICY_NAME" /> విధానాల ద్వారా నియంత్రించబడతాయి. అయినప్పటికీ ఈ విధానం కొత్త ట్యాబ్ పేజీని తెరవడానికి సెట్ చేయబడితే హోమ్ పేజీపై, అలాగే అది కొత్త ట్యాబ్ పేజీకి సెట్ చేయబడితే ప్రారంభ పేజీపై ప్రభావాన్ని చూపదు.
+
+          విధానాన్ని సెట్ చేయకపోతే లేదా ఖాళీగా వదిలివేస్తే డిఫాల్ట్ కొత్త ట్యాబ్ పేజీ ఉపయోగించబడుతుంది.
+
+          ఈ విధానం Windowsని <ph name="MS_AD_NAME" /> డొమైన్‌కు చేర్చని సందర్భాల్లో అందుబాటులో ఉండదు.</translation>
 <translation id="7709537117200051035">హోస్ట్‌కు ప్రాప్యత అనుమతించాలో (ఒప్పు) లేదా బ్లాక్ చేయాలో (తప్పు) పేర్కొనే బులియన్ ఫ్లాగ్‌కు హోస్ట్‌పేర్లను మ్యాప్ చేసే నిఘంటువు.
 
           ఈ విధానం <ph name="PRODUCT_NAME" /> యొక్క అంతర్గత ఉపయోగానికి మాత్రమే.</translation>
diff --git a/components/policy/resources/policy_templates_th.xtb b/components/policy/resources/policy_templates_th.xtb
index 474942a..ad137e7e 100644
--- a/components/policy/resources/policy_templates_th.xtb
+++ b/components/policy/resources/policy_templates_th.xtb
@@ -1118,15 +1118,15 @@
 
           หากไม่ได้ตั้งค่านโยบายนี้ไว้ ระบบจะใช้ค่าเริ่มต้นทั่วไปสำหรับเว็บไซต์ทั้งหมด ทั้งจากนโยบาย "DefaultKeygenSetting" หากมีการตั้งค่าไว้ หรือจากการกำหนดค่าส่วนตัวของผู้ใช้เอง</translation>
 <translation id="4239720644496144453">ไม่มีการใช้แคชสำหรับแอป Android หากมีผู้ใช้หลายคนติดตั้งแอป Android เดียวกัน จะมีการดาวน์โหลดแอปใหม่สำหรับผู้ใช้แต่ละราย</translation>
-<translation id="4243336580717651045">เปิดใช้การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์ใน <ph name="PRODUCT_NAME" /> และป้องกันไม่ให้ผู้ใช้เปลี่ยนการตั้งค่านี้
+<translation id="4243336580717651045">เปิดใช้การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL ใน <ph name="PRODUCT_NAME" /> และป้องกันไม่ให้ผู้ใช้เปลี่ยนการตั้งค่านี้
 
-      การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์จะส่ง URL ของหน้าที่ผู้ใช้เข้าชมไปให้ Google เพื่อปรับปรุงการค้นหาและการท่องเว็บให้ดีขึ้น
+      การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL จะส่ง URL ของหน้าที่ผู้ใช้เข้าชมไปให้ Google เพื่อปรับปรุงการค้นหาและการท่องเว็บให้ดีขึ้น
 
-      หากเปิดใช้นโยบายนี้ จะมีการใช้การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์อยู่เสมอ
+      หากเปิดใช้นโยบายนี้ การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL จะทำงานอยู่เสมอ
 
-      หากปิดใช้นโยบายนี้ จะไม่มีการใช้การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์เลย
+      หากปิดใช้นโยบายนี้ การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL จะไม่ทำงานเลย
 
-      หากไม่ได้ตั้งค่านโยบายนี้ จะมีการเปิดใช้การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์ แต่ผู้ใช้จะเปลี่ยนการตั้งค่าได้</translation>
+      หากไม่ได้ตั้งค่านโยบายนี้ จะมีการเปิดใช้การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL แต่ผู้ใช้จะเปลี่ยนการตั้งค่าได้</translation>
 <translation id="4250680216510889253">ไม่มี</translation>
 <translation id="4261820385751181068">ภาษาในหน้าจอการลงชื่อเข้าใช้อุปกรณ์</translation>
 <translation id="427220754384423013">ระบุเครื่องพิมพ์ที่ผู้ใช้ใช้งานได้
@@ -1751,7 +1751,7 @@
           วิธีที่แนะนำในการล็อกหน้าจอเมื่อไม่มีการใช้งานคือการเปิดใช้การล็อกหน้าจอเมื่อระงับการใช้งานและให้ <ph name="PRODUCT_OS_NAME" /> ระงับการใช้งานหลังจากการหน่วงเวลาเมื่อไม่มีการใช้งาน นโยบายนี้ควรนำมาใช้เฉพาะเมื่อการล็อกหน้าจอเกิดขึ้นนานแล้วก่อนที่จะมีการระงับการใช้งานหรือเมื่อไม่ต้องการให้ระงับการใช้งานเลยเมื่อไม่มีการใช้งาน
 
           ควรระบุค่าของนโยบายโดยมีหน่วยเป็นมิลลิวินาที ค่าจะถูกบีบให้น้อยกว่าการหน่วงเวลาเมื่อไม่มีการใช้งาน</translation>
-<translation id="6097601282776163274">เปิดใช้การรวบรวมข้อมูลที่ลบข้อมูลระบุตัวตนของ URL ที่มีคีย์</translation>
+<translation id="6097601282776163274">เปิดใช้การรวบรวมข้อมูลที่ไม่ระบุตัวบุคคลซึ่งผูกกับ URL</translation>
 <translation id="6111936128861357925">อนุญาตให้เล่นเกมไดโนเสาร์ที่ซ่อนไว้ได้</translation>
 <translation id="6114416803310251055">ถูกกำหนดให้เลิกใช้</translation>
 <translation id="6133088669883929098">อนุญาตให้เว็บไซต์ทั้งหมดใช้การสร้างคีย์</translation>
diff --git a/components/policy/resources/policy_templates_tr.xtb b/components/policy/resources/policy_templates_tr.xtb
index bd08eb56..4f2b0c71 100644
--- a/components/policy/resources/policy_templates_tr.xtb
+++ b/components/policy/resources/policy_templates_tr.xtb
@@ -1156,15 +1156,15 @@
 
           Bu politika ayarlanmadan bırakıldığında, ayarlandıysa "DefaultKeygenSetting" politikası değeri, ayarlanmadıysa kullanıcının kişisel yapılandırmasındaki değer tüm siteler için genel varsayılan değer olarak kullanılır.</translation>
 <translation id="4239720644496144453">Önbellek, Android uygulamaları için kullanılmaz. Birden fazla kullanıcı aynı Android uygulamasını yüklerse bu uygulama her kullanıcı için yeniden indirilir.</translation>
-<translation id="4243336580717651045"><ph name="PRODUCT_NAME" /> ürününde URL anahtarlı anonim veri toplamayı etkinleştirir ve kullanıcıların bu ayarı değiştirmesini önler.
+<translation id="4243336580717651045"><ph name="PRODUCT_NAME" /> ürününde URL içeren veya URL'lerle ilişkili anonim veri toplamayı etkinleştirir ve kullanıcıların bu ayarı değiştirmesini önler.
 
-      URL anahtarlı anonim veri toplama özelliği, arama ve göz atmayı iyileştirmek için, kullanıcının ziyaret ettiği sayfaların URL'lerini Google'a gönderir.
+      URL içeren veya URL'lerle ilişkili anonim veri toplama özelliği, arama ve göz atmayı iyileştirmek için, kullanıcının ziyaret ettiği sayfaların URL'lerini Google'a gönderir.
 
-      Bu politikayı etkinleştirirseniz URL anahtarlı anonim veri toplama özelliği her zaman etkin olur.
+      Bu politikayı etkinleştirirseniz URL içeren veya URL'lerle ilişkili anonim veri toplama özelliği her zaman etkin olur.
 
-      Bu politikayı devre dışı bırakırsanız URL anahtarlı anonim veri toplama özelliği hiçbir zaman etkin olmaz.
+      Bu politikayı devre dışı bırakırsanız URL içeren veya URL'lerle ilişkili anonim veri toplama özelliği hiçbir zaman etkin olmaz.
 
-      Bu politika ayarlanmadan bırakılırsa URL anahtarlı anonim veri toplama özelliği etkinleştirilir ancak kullanıcı tarafından kapatılabilir.</translation>
+      Bu politika ayarlanmadan bırakılırsa URL içeren veya URL'lerle ilişkili anonim veri toplama özelliği etkinleştirilir ancak kullanıcı tarafından kapatılabilir.</translation>
 <translation id="4250680216510889253">Hayır</translation>
 <translation id="4261820385751181068">Cihaz oturum açma ekranı yerel ayarı</translation>
 <translation id="427220754384423013">Kullanıcının kullanabileceği yazıcıları belirtir.
@@ -1822,7 +1822,7 @@
           Boşta kalma durumunda ekranı kilitlemek için önerilen yöntem, askıya alındığında ekranın kilitlenmesini etkinleştirmek ve boşta kalma süresi beklendikten sonra <ph name="PRODUCT_OS_NAME" /> tarafından askıya alma işlemi yapılmasını sağlamaktır.
 
           Politikanın değeri milisaniye olarak belirtilmelidir. Değerler boşta kalma bekleme süresinden az olacak şekilde ayarlanır.</translation>
-<translation id="6097601282776163274">URL anahtarlı anonim veri toplamayı etkinleştir</translation>
+<translation id="6097601282776163274">URL içeren veya URL'lerle ilişkili anonim veri toplamayı etkinleştir</translation>
 <translation id="6111936128861357925">Dinozor Paskalya Yumurtası Oyununa İzin Verme</translation>
 <translation id="6114416803310251055">onaylanmadı</translation>
 <translation id="6133088669883929098">Tüm sitelerin anahtar oluşturma işlevini kullanmasına izin ver</translation>
diff --git a/components/policy/resources/policy_templates_zh-CN.xtb b/components/policy/resources/policy_templates_zh-CN.xtb
index 43d14aa..180b3a3 100644
--- a/components/policy/resources/policy_templates_zh-CN.xtb
+++ b/components/policy/resources/policy_templates_zh-CN.xtb
@@ -1090,15 +1090,15 @@
 
           如果此政策未设置,系统将根据“DefaultKeygenSetting”政策(如果此政策已设置)或用户的个人配置为所有网站使用全局默认值。</translation>
 <translation id="4239720644496144453">缓存不适用于 Android 应用。如果多个用户都想安装同一款 Android 应用,系统便会分别为每个用户重新下载相应的 Android 应用。</translation>
-<translation id="4243336580717651045">启用 <ph name="PRODUCT_NAME" /> 的已加密网址的匿名化数据收集功能,并阻止用户更改此设置。
+<translation id="4243336580717651045">对 <ph name="PRODUCT_NAME" /> 启用包含网址的匿名化数据收集功能,并阻止用户更改此设置。
 
-      已加密网址匿名化数据收集功能会将用户访问的网页网址发送给 Google,用于改善搜索和浏览体验。
+      包含网址的匿名化数据收集功能会将用户访问的网页网址发送给 Google,用于改善搜索和浏览体验。
 
-      如果启用此政策,系统会始终启用已加密网址匿名化数据收集功能。
+      如果启用此政策,系统会始终启用包含网址的匿名化数据收集功能。
 
-      如果停用此政策,则系统始终不会启用已加密网址匿名化数据收集功能。
+      如果停用此政策,则系统始终不会启用包含网址的匿名化数据收集功能。
 
-      如果未设置此政策,系统将启用已加密网址匿名化数据收集功能,但用户将能够更改此设置。</translation>
+      如果未设置此政策,系统将启用包含网址的匿名化数据收集功能,但用户将能够更改此设置。</translation>
 <translation id="4250680216510889253">否</translation>
 <translation id="4261820385751181068">设备登录屏幕语言区域</translation>
 <translation id="427220754384423013">指定用户可以使用的打印机。
@@ -1718,7 +1718,7 @@
           要在设备闲置时锁定屏幕,建议您采用以下方式:启用在系统处于暂停状态时锁定屏幕,并指示<ph name="PRODUCT_OS_NAME" />在闲置延迟时间过后进入暂停状态。只有在以下情况下才应使用此政策:系统进入暂停状态之前的很长一段时间内便应锁定屏幕,或者完全不需要启用在设备闲置时暂停系统。
 
           此政策的值应以毫秒为单位,且必须小于闲置延迟时间。</translation>
-<translation id="6097601282776163274">启用已加密网址匿名化数据收集功能</translation>
+<translation id="6097601282776163274">启用包含网址的匿名化数据收集功能</translation>
 <translation id="6111936128861357925">允许用户玩恐龙复活节彩蛋游戏</translation>
 <translation id="6114416803310251055">已弃用</translation>
 <translation id="6133088669883929098">允许所有网站使用密钥生成功能</translation>
diff --git a/components/previews/OWNERS b/components/previews/OWNERS
index 9a86a9f4..75baba8 100644
--- a/components/previews/OWNERS
+++ b/components/previews/OWNERS
@@ -1,3 +1,3 @@
 file://components/data_reduction_proxy/OWNERS
 
-# COMPONENT: UI>Browser>Previews
\ No newline at end of file
+# COMPONENT: Blink>Previews
\ No newline at end of file
diff --git a/components/previews/content/hint_cache.cc b/components/previews/content/hint_cache.cc
index 5c09edf..3180dc3 100644
--- a/components/previews/content/hint_cache.cc
+++ b/components/previews/content/hint_cache.cc
@@ -22,8 +22,13 @@
   return !DetermineHostSuffix(host).empty();
 }
 
-void HintCache::LoadHint(const std::string& host) {
+void HintCache::LoadHint(const std::string& host, HintLoadedCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const optimization_guide::proto::Hint* hint = GetHint(host);
+  if (hint) {
+    // Hint already loaded in memory.
+    std::move(callback).Run(*hint);
+  }
   // TODO(dougarnett): Add backing store support to load from.
 }
 
diff --git a/components/previews/content/hint_cache.h b/components/previews/content/hint_cache.h
index bf156922..f9233123 100644
--- a/components/previews/content/hint_cache.h
+++ b/components/previews/content/hint_cache.h
@@ -10,12 +10,16 @@
 #include <unordered_set>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/sequence_checker.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 
 namespace previews {
 
+using HintLoadedCallback =
+    base::OnceCallback<void(const optimization_guide::proto::Hint&)>;
+
 // Holds a set of optimization hints received from the Cacao service.
 // This may include hints received from the ComponentUpdater and hints
 // fetched from a Cacao Optimization Guide Service API. It will hold
@@ -31,8 +35,10 @@
   // in memory or persisted on disk).
   bool HasHint(const std::string& host) const;
 
-  // Requests that hint data for |host| be loaded from disk.
-  void LoadHint(const std::string& host);
+  // Requests that hint data for |host| be loaded asynchronously and passed to
+  // |callback| if/when loaded. |callback| will not be called if no hint data
+  // is found for |host|.
+  void LoadHint(const std::string& host, HintLoadedCallback callback);
 
   // Returns whether there is hint data available for |host| in memory.
   bool IsHintLoaded(const std::string& host);
diff --git a/components/previews/content/hint_cache_unittest.cc b/components/previews/content/hint_cache_unittest.cc
index fa15faaf..ba942326 100644
--- a/components/previews/content/hint_cache_unittest.cc
+++ b/components/previews/content/hint_cache_unittest.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <vector>
 
+#include "base/bind.h"
 #include "base/macros.h"
 #include "components/previews/core/previews_experiments.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -22,6 +23,17 @@
 
   ~HintCacheTest() override {}
 
+  void LoadCallback(const optimization_guide::proto::Hint& hint) {
+    loaded_hint_ = std::make_unique<optimization_guide::proto::Hint>(hint);
+  }
+
+  const optimization_guide::proto::Hint* GetLoadedHint() const {
+    return loaded_hint_.get();
+  }
+
+ private:
+  std::unique_ptr<optimization_guide::proto::Hint> loaded_hint_;
+
   DISALLOW_COPY_AND_ASSIGN(HintCacheTest);
 };
 
@@ -65,6 +77,23 @@
             hint_cache.GetHint("host.subdomain.domain.org")->key());
 }
 
+TEST_F(HintCacheTest, TestMemoryCacheLoadCallback) {
+  HintCache hint_cache;
+
+  optimization_guide::proto::Hint hint1;
+  hint1.set_key("subdomain.domain.org");
+  hint1.set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+  hint_cache.AddHint(hint1);
+
+  EXPECT_TRUE(hint_cache.IsHintLoaded("host.subdomain.domain.org"));
+  hint_cache.LoadHint(
+      "host.subdomain.domain.org",
+      base::BindOnce(&HintCacheTest::LoadCallback, base::Unretained(this)));
+
+  EXPECT_TRUE(GetLoadedHint());
+  EXPECT_EQ(hint1.key(), GetLoadedHint()->key());
+}
+
 }  // namespace
 
 }  // namespace previews
diff --git a/components/previews/content/previews_content_util.cc b/components/previews/content/previews_content_util.cc
index 6872231..08075b90 100644
--- a/components/previews/content/previews_content_util.cc
+++ b/components/previews/content/previews_content_util.cc
@@ -18,7 +18,7 @@
 
 content::PreviewsState DetermineEnabledClientPreviewsState(
     const net::URLRequest& url_request,
-    const previews::PreviewsDecider* previews_decider) {
+    previews::PreviewsDecider* previews_decider) {
   content::PreviewsState previews_state = content::PREVIEWS_UNSPECIFIED;
 
   if (!previews::params::ArePreviewsAllowed()) {
@@ -32,6 +32,8 @@
   if (previews_decider->ShouldAllowPreview(
           url_request, previews::PreviewsType::RESOURCE_LOADING_HINTS)) {
     previews_state |= content::RESOURCE_LOADING_HINTS_ON;
+    // Initiate load of any applicable hint details.
+    previews_decider->LoadResourceHints(url_request);
   }
 
   if (previews_decider->ShouldAllowPreview(url_request,
@@ -100,7 +102,7 @@
   // decided at Commit time.
   if (previews_state & content::RESOURCE_LOADING_HINTS_ON) {
     // Resource loading hints was chosen for the original URL but only continue
-    //  with it if the committed URL has HTTPS scheme and is allowed by decider.
+    // with it if the committed URL has HTTPS scheme and is allowed by decider.
     if (is_https && previews_decider &&
         previews_decider->IsURLAllowedForPreview(
             url_request, previews::PreviewsType::RESOURCE_LOADING_HINTS)) {
diff --git a/components/previews/content/previews_content_util.h b/components/previews/content/previews_content_util.h
index df779c3..c814f09 100644
--- a/components/previews/content/previews_content_util.h
+++ b/components/previews/content/previews_content_util.h
@@ -19,7 +19,7 @@
 // definitions for content::PreviewsState.
 content::PreviewsState DetermineEnabledClientPreviewsState(
     const net::URLRequest& url_request,
-    const previews::PreviewsDecider* previews_decider);
+    previews::PreviewsDecider* previews_decider);
 
 // Returns an updated PreviewsState given |previews_state| that has already
 // been updated wrt server previews. This should be called at Navigation Commit
diff --git a/components/previews/content/previews_content_util_unittest.cc b/components/previews/content/previews_content_util_unittest.cc
index 5f23270..a4aa11a 100644
--- a/components/previews/content/previews_content_util_unittest.cc
+++ b/components/previews/content/previews_content_util_unittest.cc
@@ -50,6 +50,8 @@
     return IsEnabled(type);
   }
 
+  void LoadResourceHints(const net::URLRequest& request) override {}
+
  private:
   bool IsEnabled(PreviewsType type) const {
     switch (type) {
diff --git a/components/previews/content/previews_decider_impl.cc b/components/previews/content/previews_decider_impl.cc
index 5d517b48..15fa635 100644
--- a/components/previews/content/previews_decider_impl.cc
+++ b/components/previews/content/previews_decider_impl.cc
@@ -26,7 +26,6 @@
 #include "net/nqe/network_quality_estimator.h"
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
-#include "url/gurl.h"
 
 namespace previews {
 
@@ -162,6 +161,17 @@
                      weak_factory_.GetWeakPtr()));
 }
 
+void PreviewsDeciderImpl::OnResourceLoadingHints(
+    const GURL& document_gurl,
+    const std::vector<std::string>& patterns_to_block) {
+  DCHECK(io_task_runner_->BelongsToCurrentThread());
+  ui_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &PreviewsUIService::SetResourceLoadingHintsResourcePatternsToBlock,
+          previews_ui_service_, document_gurl, patterns_to_block));
+}
+
 void PreviewsDeciderImpl::SetPreviewsBlacklistForTesting(
     std::unique_ptr<PreviewsBlackList> previews_back_list) {
   previews_black_list_ = std::move(previews_back_list);
@@ -385,6 +395,13 @@
   return true;
 }
 
+void PreviewsDeciderImpl::LoadResourceHints(const net::URLRequest& request) {
+  DCHECK(io_task_runner_->BelongsToCurrentThread());
+  previews_opt_guide_->MaybeLoadOptimizationHints(
+      request, base::BindOnce(&PreviewsDeciderImpl::OnResourceLoadingHints,
+                              weak_factory_.GetWeakPtr()));
+}
+
 bool PreviewsDeciderImpl::IsURLAllowedForPreview(const net::URLRequest& request,
                                                  PreviewsType type) const {
   DCHECK(PreviewsType::NOSCRIPT == type ||
diff --git a/components/previews/content/previews_decider_impl.h b/components/previews/content/previews_decider_impl.h
index 13757d4..0b1b8b26 100644
--- a/components/previews/content/previews_decider_impl.h
+++ b/components/previews/content/previews_decider_impl.h
@@ -25,8 +25,7 @@
 #include "components/previews/core/previews_experiments.h"
 #include "components/previews/core/previews_logger.h"
 #include "net/nqe/effective_connection_type.h"
-
-class GURL;
+#include "url/gurl.h"
 
 namespace base {
 class Clock;
@@ -122,6 +121,8 @@
   bool IsURLAllowedForPreview(const net::URLRequest& request,
                               PreviewsType type) const override;
 
+  void LoadResourceHints(const net::URLRequest& request) override;
+
   // Generates a page ID that is guaranteed to be unique from any other page ID
   // generated in this browser session. Also, guaranteed to be non-zero.
   uint64_t GeneratePageId();
@@ -133,6 +134,11 @@
       std::unique_ptr<blacklist::OptOutStore> previews_opt_out_store,
       blacklist::BlacklistData::AllowedTypesAndVersions allowed_previews);
 
+  // Posts a task to deliver the resource patterns to the PreviewsUIService.
+  void OnResourceLoadingHints(
+      const GURL& document_gurl,
+      const std::vector<std::string>& patterns_to_block);
+
   // Sets a blacklist for testing.
   void SetPreviewsBlacklistForTesting(
       std::unique_ptr<PreviewsBlackList> previews_back_list);
diff --git a/components/previews/content/previews_hints.cc b/components/previews/content/previews_hints.cc
index 139c46c0..3177929 100644
--- a/components/previews/content/previews_hints.cc
+++ b/components/previews/content/previews_hints.cc
@@ -276,13 +276,15 @@
   return false;
 }
 
-bool PreviewsHints::MaybeLoadOptimizationHints(const GURL& url) const {
+bool PreviewsHints::MaybeLoadOptimizationHints(
+    const GURL& url,
+    HintLoadedCallback callback) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!hint_cache_ || !url.has_host())
     return false;
 
-  // TODO(dougarnett): Request loading of hints if not cached in memory.
+  hint_cache_->LoadHint(url.host(), std::move(callback));
   return hint_cache_->HasHint(url.host());
 }
 
diff --git a/components/previews/content/previews_hints.h b/components/previews/content/previews_hints.h
index 54cdf2b4..4aa4fca08 100644
--- a/components/previews/content/previews_hints.h
+++ b/components/previews/content/previews_hints.h
@@ -46,8 +46,10 @@
                      int* out_inflation_percent);
 
   // Returns whether |url| may have PageHints and triggers asynchronous load
-  // of such hints are not currently available synchronously.
-  bool MaybeLoadOptimizationHints(const GURL& url) const;
+  // of such hints are not currently available synchronously. |callback| is
+  // called if any applicable hint data is loaded and available for |url|.
+  bool MaybeLoadOptimizationHints(const GURL& url,
+                                  HintLoadedCallback callback) const;
 
  private:
   PreviewsHints();
diff --git a/components/previews/content/previews_optimization_guide.cc b/components/previews/content/previews_optimization_guide.cc
index 3275c7d..8c98176 100644
--- a/components/previews/content/previews_optimization_guide.cc
+++ b/components/previews/content/previews_optimization_guide.cc
@@ -50,13 +50,54 @@
   return true;
 }
 
+void PreviewsOptimizationGuide::OnLoadedHint(
+    ResourceLoadingHintsCallback callback,
+    const GURL& document_url,
+    const optimization_guide::proto::Hint& loaded_hint) const {
+  DCHECK(io_task_runner_->BelongsToCurrentThread());
+  std::string url = document_url.spec();
+  std::vector<std::string> resource_patterns_to_block;
+  for (const auto& page_hint : loaded_hint.page_hints()) {
+    // TODO(dougarnett): Support wildcards. crbug.com/870039
+    if (!page_hint.page_pattern().empty() &&
+        url.find(page_hint.page_pattern()) != std::string::npos) {
+      // Matched the page pattern so now check for resource loading
+      // optimization.
+      for (const auto& optimization : page_hint.whitelisted_optimizations()) {
+        if (optimization.optimization_type() ==
+                optimization_guide::proto::RESOURCE_LOADING) {
+          for (const auto& resource_loading_hint :
+               optimization.resource_loading_hints()) {
+            if (!resource_loading_hint.resource_pattern().empty() &&
+                resource_loading_hint.loading_optimization_type() ==
+                    optimization_guide::proto::LOADING_BLOCK_RESOURCE) {
+              resource_patterns_to_block.push_back(
+                  resource_loading_hint.resource_pattern());
+            }
+          }
+        }
+      }
+      // Only use this first matching page hint.
+      break;
+    }
+  }
+  if (!resource_patterns_to_block.empty()) {
+    std::move(callback).Run(document_url, resource_patterns_to_block);
+  }
+}
+
 bool PreviewsOptimizationGuide::MaybeLoadOptimizationHints(
-    const net::URLRequest& request) const {
+    const net::URLRequest& request,
+    ResourceLoadingHintsCallback callback) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
 
   if (!hints_)
     return false;
-  return hints_->MaybeLoadOptimizationHints(request.url());
+
+  return hints_->MaybeLoadOptimizationHints(
+      request.url(), base::BindOnce(&PreviewsOptimizationGuide::OnLoadedHint,
+                                    io_weak_ptr_factory_.GetWeakPtr(),
+                                    std::move(callback), request.url()));
 }
 
 void PreviewsOptimizationGuide::OnHintsProcessed(
diff --git a/components/previews/content/previews_optimization_guide.h b/components/previews/content/previews_optimization_guide.h
index 5b0952f..c456578 100644
--- a/components/previews/content/previews_optimization_guide.h
+++ b/components/previews/content/previews_optimization_guide.h
@@ -5,6 +5,9 @@
 #ifndef COMPONENTS_PREVIEWS_CONTENT_PREVIEWS_OPTIMIZATION_GUIDE_H_
 #define COMPONENTS_PREVIEWS_CONTENT_PREVIEWS_OPTIMIZATION_GUIDE_H_
 
+#include <string>
+#include <vector>
+
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -14,6 +17,7 @@
 #include "components/optimization_guide/optimization_guide_service_observer.h"
 #include "components/previews/content/previews_optimization_guide.h"
 #include "components/previews/core/previews_experiments.h"
+#include "url/gurl.h"
 
 namespace net {
 class URLRequest;
@@ -29,6 +33,10 @@
 
 class PreviewsHints;
 
+using ResourceLoadingHintsCallback = base::OnceCallback<void(
+    const GURL& document_gurl,
+    const std::vector<std::string>& resource_patterns_to_block)>;
+
 // A Previews optimization guide that makes decisions guided by hints received
 // from the OptimizationGuideService.
 class PreviewsOptimizationGuide
@@ -50,7 +58,8 @@
   // (specifically, PageHints). If so, but the hints are not available
   // synchronously, this method will request that they be loaded (from disk or
   // network).
-  bool MaybeLoadOptimizationHints(const net::URLRequest& request) const;
+  bool MaybeLoadOptimizationHints(const net::URLRequest& request,
+                                  ResourceLoadingHintsCallback callback);
 
   // optimization_guide::OptimizationGuideServiceObserver implementation:
   void OnHintsProcessed(
@@ -61,6 +70,14 @@
   // Updates the hints to the latest hints sent by the Component Updater.
   void UpdateHints(std::unique_ptr<PreviewsHints> hints);
 
+  // Handles a loaded hint. It checks if the |loaded_hint| has any page hint
+  // that apply to |doucment_url|. If so, it looks for any applicable resource
+  // loading hints and will call |callback| with the applicable resource loading
+  // details if found.
+  void OnLoadedHint(ResourceLoadingHintsCallback callback,
+                    const GURL& document_url,
+                    const optimization_guide::proto::Hint& loaded_hint) const;
+
   // The OptimizationGuideService that this guide is listening to. Not owned.
   optimization_guide::OptimizationGuideService* optimization_guide_service_;
 
diff --git a/components/previews/content/previews_optimization_guide_unittest.cc b/components/previews/content/previews_optimization_guide_unittest.cc
index db580d55..d4ae7856 100644
--- a/components/previews/content/previews_optimization_guide_unittest.cc
+++ b/components/previews/content/previews_optimization_guide_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -86,6 +87,13 @@
                                   TRAFFIC_ANNOTATION_FOR_TESTS);
   }
 
+  void MaybeLoadOptimizationHintsCallback(
+      const GURL& document_gurl,
+      const std::vector<std::string>& resource_patterns) {
+    loaded_hints_document_gurl_ = document_gurl;
+    loaded_hints_resource_patterns_ = resource_patterns;
+  }
+
   void ResetGuide() {
     guide_.reset();
     RunUntilIdle();
@@ -93,6 +101,13 @@
 
   base::FilePath temp_dir() const { return temp_dir_.GetPath(); }
 
+  const GURL& loaded_hints_document_gurl() const {
+    return loaded_hints_document_gurl_;
+  }
+  const std::vector<std::string>& loaded_hints_resource_patterns() const {
+    return loaded_hints_resource_patterns_;
+  }
+
  protected:
   void RunUntilIdle() {
     scoped_task_environment_.RunUntilIdle();
@@ -113,6 +128,9 @@
 
   net::TestURLRequestContext context_;
 
+  GURL loaded_hints_document_gurl_;
+  std::vector<std::string> loaded_hints_resource_patterns_;
+
   DISALLOW_COPY_AND_ASSIGN(PreviewsOptimizationGuideTest);
 };
 
@@ -586,12 +604,32 @@
   optimization_guide::proto::Hint* hint1 = config.add_hints();
   hint1->set_key("somedomain.org");
   hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
+
+  // Page hint for "/news/"
   optimization_guide::proto::PageHint* page_hint1 = hint1->add_page_hints();
   page_hint1->set_page_pattern("/news/");
   optimization_guide::proto::Optimization* optimization1 =
-      hint1->add_whitelisted_optimizations();
+      page_hint1->add_whitelisted_optimizations();
   optimization1->set_optimization_type(
       optimization_guide::proto::RESOURCE_LOADING);
+  optimization_guide::proto::ResourceLoadingHint* resource_loading_hint1 =
+      optimization1->add_resource_loading_hints();
+  resource_loading_hint1->set_loading_optimization_type(
+      optimization_guide::proto::LOADING_BLOCK_RESOURCE);
+  resource_loading_hint1->set_resource_pattern("news_cruft.js");
+
+  // Page hint for "football"
+  optimization_guide::proto::PageHint* page_hint2 = hint1->add_page_hints();
+  page_hint2->set_page_pattern("football");
+  optimization_guide::proto::Optimization* optimization2 =
+      page_hint2->add_whitelisted_optimizations();
+  optimization2->set_optimization_type(
+      optimization_guide::proto::RESOURCE_LOADING);
+  optimization_guide::proto::ResourceLoadingHint* resource_loading_hint2 =
+      optimization2->add_resource_loading_hints();
+  resource_loading_hint2->set_loading_optimization_type(
+      optimization_guide::proto::LOADING_BLOCK_RESOURCE);
+  resource_loading_hint2->set_resource_pattern("football_cruft.js");
   ProcessHints(config, "2.0.0");
 
   RunUntilIdle();
@@ -604,11 +642,24 @@
   InitializeResourceLoadingHints();
 
   EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
-      *CreateRequestWithURL(GURL("https://somedomain.org/"))));
+      *CreateRequestWithURL(GURL("https://somedomain.org/")),
+      base::DoNothing()));
   EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
-      *CreateRequestWithURL(GURL("https://www.somedomain.org"))));
+      *CreateRequestWithURL(GURL("https://www.somedomain.org/news/football")),
+      base::BindOnce(
+          &PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
+          base::Unretained(this))));
   EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
-      *CreateRequestWithURL(GURL("https://www.unknown.com"))));
+      *CreateRequestWithURL(GURL("https://www.unknown.com")),
+      base::DoNothing()));
+
+  RunUntilIdle();
+
+  // Verify loaded hint data for www.somedomain.org
+  EXPECT_EQ(GURL("https://www.somedomain.org/news/football"),
+            loaded_hints_document_gurl());
+  EXPECT_EQ(1ul, loaded_hints_resource_patterns().size());
+  EXPECT_EQ("news_cruft.js", loaded_hints_resource_patterns().front());
 }
 
 TEST_F(PreviewsOptimizationGuideTest,
@@ -621,7 +672,8 @@
   InitializeResourceLoadingHints();
 
   EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
-      *CreateRequestWithURL(GURL("https://www.somedomain.org"))));
+      *CreateRequestWithURL(GURL("https://www.somedomain.org")),
+      base::DoNothing()));
 }
 
 TEST_F(PreviewsOptimizationGuideTest, RemoveObserverCalledAtDestruction) {
diff --git a/components/previews/core/previews_decider.h b/components/previews/core/previews_decider.h
index 6f8c0f79..bae0b34 100644
--- a/components/previews/core/previews_decider.h
+++ b/components/previews/core/previews_decider.h
@@ -45,6 +45,9 @@
   virtual bool IsURLAllowedForPreview(const net::URLRequest& request,
                                       PreviewsType type) const = 0;
 
+  // Requests that any applicable detailed resource hints be loaded.
+  virtual void LoadResourceHints(const net::URLRequest& request) = 0;
+
  protected:
   PreviewsDecider() {}
   virtual ~PreviewsDecider() {}
diff --git a/components/previews/core/test_previews_decider.cc b/components/previews/core/test_previews_decider.cc
index 801dbc6..a90d491 100644
--- a/components/previews/core/test_previews_decider.cc
+++ b/components/previews/core/test_previews_decider.cc
@@ -31,4 +31,6 @@
   return allow_previews_;
 }
 
+void TestPreviewsDecider::LoadResourceHints(const net::URLRequest& request) {}
+
 }  // namespace previews
diff --git a/components/previews/core/test_previews_decider.h b/components/previews/core/test_previews_decider.h
index 8241381..70d00cb0c 100644
--- a/components/previews/core/test_previews_decider.h
+++ b/components/previews/core/test_previews_decider.h
@@ -26,6 +26,7 @@
                           previews::PreviewsType type) const override;
   bool IsURLAllowedForPreview(const net::URLRequest& request,
                               PreviewsType type) const override;
+  void LoadResourceHints(const net::URLRequest& request) override;
 
  private:
   bool allow_previews_;
diff --git a/components/safe_browsing/password_protection/password_protection_service.cc b/components/safe_browsing/password_protection/password_protection_service.cc
index 614e7614..1171f66 100644
--- a/components/safe_browsing/password_protection/password_protection_service.cc
+++ b/components/safe_browsing/password_protection/password_protection_service.cc
@@ -157,7 +157,7 @@
   DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
          trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
 
-  if (!url.is_valid())
+  if (!url.is_valid() || !CanGetReputationOfURL(url))
     return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
 
   GURL hostname = GetHostNameWithHTTPScheme(url);
@@ -231,6 +231,10 @@
   DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
          trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
 
+  if (!CanGetReputationOfURL(url)) {
+    return;
+  }
+
   GURL hostname = GetHostNameWithHTTPScheme(url);
   int* stored_verdict_count =
       trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE
diff --git a/components/safe_browsing/password_protection/password_protection_service_unittest.cc b/components/safe_browsing/password_protection/password_protection_service_unittest.cc
index d762040..c26c25a 100644
--- a/components/safe_browsing/password_protection/password_protection_service_unittest.cc
+++ b/components/safe_browsing/password_protection/password_protection_service_unittest.cc
@@ -614,6 +614,22 @@
                     LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE));
 }
 
+TEST_P(PasswordProtectionServiceTest, TestDoesNotCacheAboutBlank) {
+  ASSERT_EQ(0U, GetStoredVerdictCount(
+                    LoginReputationClientRequest::PASSWORD_REUSE_EVENT));
+
+  // Should not actually cache, since about:blank is not valid for reputation
+  // computing.
+  CacheVerdict(GURL("about:blank"),
+               LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE,
+               PasswordReuseEvent::REUSED_PASSWORD_TYPE_UNKNOWN,
+               LoginReputationClientResponse::SAFE, 10 * kMinute, "about:blank",
+               base::Time::Now());
+
+  EXPECT_EQ(0U, GetStoredVerdictCount(
+                    LoginReputationClientRequest::PASSWORD_REUSE_EVENT));
+}
+
 TEST_P(PasswordProtectionServiceTest, VerifyCanGetReputationOfURL) {
   // Invalid main frame URL.
   EXPECT_FALSE(PasswordProtectionService::CanGetReputationOfURL(GURL()));
diff --git a/components/signin/core/browser/fake_signin_manager.cc b/components/signin/core/browser/fake_signin_manager.cc
index d73df4f..7e32a1d 100644
--- a/components/signin/core/browser/fake_signin_manager.cc
+++ b/components/signin/core/browser/fake_signin_manager.cc
@@ -30,14 +30,40 @@
     SigninClient* client,
     ProfileOAuth2TokenService* token_service,
     AccountTrackerService* account_tracker_service,
+    GaiaCookieManagerService* cookie_manager_service)
+    : FakeSigninManager(client,
+                        token_service,
+                        account_tracker_service,
+                        cookie_manager_service,
+                        nullptr,
+                        signin::AccountConsistencyMethod::kDisabled) {}
+
+FakeSigninManager::FakeSigninManager(
+    SigninClient* client,
+    ProfileOAuth2TokenService* token_service,
+    AccountTrackerService* account_tracker_service,
     GaiaCookieManagerService* cookie_manager_service,
     SigninErrorController* signin_error_controller)
+    : FakeSigninManager(client,
+                        token_service,
+                        account_tracker_service,
+                        cookie_manager_service,
+                        signin_error_controller,
+                        signin::AccountConsistencyMethod::kDisabled) {}
+
+FakeSigninManager::FakeSigninManager(
+    SigninClient* client,
+    ProfileOAuth2TokenService* token_service,
+    AccountTrackerService* account_tracker_service,
+    GaiaCookieManagerService* cookie_manager_service,
+    SigninErrorController* signin_error_controller,
+    signin::AccountConsistencyMethod account_consistency)
     : SigninManager(client,
                     token_service,
                     account_tracker_service,
                     cookie_manager_service,
                     signin_error_controller,
-                    signin::AccountConsistencyMethod::kDisabled),
+                    account_consistency),
       token_service_(token_service) {}
 
 FakeSigninManager::~FakeSigninManager() {}
@@ -89,6 +115,22 @@
     signin_metrics::ProfileSignout signout_source_metric,
     signin_metrics::SignoutDelete signout_delete_metric,
     RemoveAccountsOption remove_option) {
+  if (!IsAuthenticated()) {
+    if (AuthInProgress()) {
+      // If the user is in the process of signing in, then treat a call to
+      // SignOut as a cancellation request.
+      GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
+      HandleAuthError(error);
+    } else {
+      // Clean up our transient data and exit if we aren't signed in.
+      // This avoids a perf regression from clearing out the TokenDB if
+      // SignOut() is invoked on startup to clean up any incomplete previous
+      // signin attempts.
+      ClearTransientSigninData();
+    }
+    return;
+  }
+
   if (IsSignoutProhibited())
     return;
   set_auth_in_progress(std::string());
diff --git a/components/signin/core/browser/fake_signin_manager.h b/components/signin/core/browser/fake_signin_manager.h
index 3f3e9ce..41dc74c 100644
--- a/components/signin/core/browser/fake_signin_manager.h
+++ b/components/signin/core/browser/fake_signin_manager.h
@@ -35,8 +35,20 @@
   FakeSigninManager(SigninClient* client,
                     ProfileOAuth2TokenService* token_service,
                     AccountTrackerService* account_tracker_service,
+                    GaiaCookieManagerService* cookie_manager_service);
+
+  FakeSigninManager(SigninClient* client,
+                    ProfileOAuth2TokenService* token_service,
+                    AccountTrackerService* account_tracker_service,
                     GaiaCookieManagerService* cookie_manager_service,
-                    SigninErrorController* signin_error_controller = nullptr);
+                    SigninErrorController* signin_error_controller);
+
+  FakeSigninManager(SigninClient* client,
+                    ProfileOAuth2TokenService* token_service,
+                    AccountTrackerService* account_tracker_service,
+                    GaiaCookieManagerService* cookie_manager_service,
+                    SigninErrorController* signin_error_controller,
+                    signin::AccountConsistencyMethod account_consistency);
 
   ~FakeSigninManager() override;
 
diff --git a/components/signin/ios/browser/account_consistency_service_unittest.mm b/components/signin/ios/browser/account_consistency_service_unittest.mm
index 82dcd9a6..2d37318 100644
--- a/components/signin/ios/browser/account_consistency_service_unittest.mm
+++ b/components/signin/ios/browser/account_consistency_service_unittest.mm
@@ -149,9 +149,10 @@
     web_view_load_expection_count_ = 0;
     gaia_cookie_manager_service_.reset(new MockGaiaCookieManagerService());
     signin_client_.reset(new TestSigninClient(&prefs_));
+    account_tracker_service_.Initialize(signin_client_.get());
     signin_manager_.reset(new FakeSigninManager(
         signin_client_.get(), nullptr, &account_tracker_service_, nullptr));
-    account_tracker_service_.Initialize(signin_client_.get());
+    signin_manager_->Initialize(nullptr);
     settings_map_ = new HostContentSettingsMap(
         &prefs_, false /* incognito_profile */, false /* guest_profile */,
         false /* store_last_modified */);
diff --git a/components/strings/components_strings_cs.xtb b/components/strings/components_strings_cs.xtb
index f9713d42..5105b1a7 100644
--- a/components/strings/components_strings_cs.xtb
+++ b/components/strings/components_strings_cs.xtb
@@ -484,6 +484,7 @@
 <translation id="4277028893293644418">Resetovat heslo</translation>
 <translation id="4280429058323657511">s platností do <ph name="EXPIRATION_DATE_ABBR" /></translation>
 <translation id="4305817255990598646">Přepínač</translation>
+<translation id="4308131620840579419">Chcete všechny platební karty uložit na jedno místo?</translation>
 <translation id="4312866146174492540">Blokovat (výchozí)</translation>
 <translation id="4325863107915753736">Článek nebyl nalezen</translation>
 <translation id="4326324639298822553">Zkontrolujte datum vypršení platnosti a zkuste to znovu.</translation>
@@ -594,6 +595,7 @@
 <translation id="5190835502935405962">Lišta záložek</translation>
 <translation id="5201306358585911203">Stránka vložená na této stránce říká</translation>
 <translation id="5205222826937269299">Je nutné zadat jméno</translation>
+<translation id="5215116848420601511">Platební metody a adresy pomocí služby Google Pay</translation>
 <translation id="5222812217790122047">Je nutné zadat e-mail</translation>
 <translation id="5230733896359313003">Dodací adresa</translation>
 <translation id="5250209940322997802">Připojit k síti</translation>
@@ -670,6 +672,7 @@
 <translation id="5689199277474810259">Exportovat do formátu JSON</translation>
 <translation id="5689516760719285838">Poloha</translation>
 <translation id="570530837424789914">Spravovat...</translation>
+<translation id="5705882733397021510">Zpět</translation>
 <translation id="57094364128775171">Navrhnout silné heslo…</translation>
 <translation id="5710435578057952990">Identita těchto webových stránek nebyla ověřena.</translation>
 <translation id="5719499550583120431">Obchodník přijímá předplacené karty.</translation>
@@ -687,6 +690,7 @@
 <translation id="5810442152076338065">Vaše připojení k doméně <ph name="DOMAIN" /> je šifrováno za použití zastaralé šifrovací sady.</translation>
 <translation id="5813119285467412249">&amp;Opakovat přidání</translation>
 <translation id="5838278095973806738">Na tento web byste neměli zadávat citlivé údaje (například hesla nebo čísla platebních karet), protože by je mohli odcizit útočníci.</translation>
+<translation id="5863847714970149516">Následující stránka se vám může pokusit naúčtovat poplatky</translation>
 <translation id="5866257070973731571">Přidání telefonního čísla</translation>
 <translation id="5869405914158311789">Tento web není dostupný</translation>
 <translation id="5869522115854928033">Uložená hesla</translation>
@@ -816,6 +820,7 @@
 <translation id="6973656660372572881">Určeny jsou pevně dané servery proxy i adresa URL skriptu PAC.</translation>
 <translation id="6989763994942163495">Zobrazit rozšířená nastavení...</translation>
 <translation id="7012363358306927923">China UnionPay</translation>
+<translation id="7016992613359344582">Může se jednat o jednorázové nebo opakované poplatky, které nemusejí být jasně patrné.</translation>
 <translation id="7029809446516969842">Hesla</translation>
 <translation id="7050187094878475250">Pokusili jste se připojit k doméně <ph name="DOMAIN" />, ale server předložil certifikát, který má příliš dlouhé období platnosti a je proto nedůvěryhodný.</translation>
 <translation id="7053983685419859001">Blokovat</translation>
@@ -1081,6 +1086,7 @@
 <translation id="8957210676456822347">Autorizace captive portálu</translation>
 <translation id="8971063699422889582">Platnost certifikátu serveru vypršela.</translation>
 <translation id="8978053250194585037">Služba Bezpečné prohlížení Google nedávno na webu <ph name="SITE" /> <ph name="BEGIN_LINK" />zjistila phishing<ph name="END_LINK" />. Phishingové weby se vás snaží oklamat tím, že se vydávají za jiné weby.</translation>
+<translation id="8983003182662520383">Platební metody a adresy pomocí služby Google Pay</translation>
 <translation id="8987927404178983737">Měsíc</translation>
 <translation id="8989148748219918422"><ph name="ORGANIZATION" /> [<ph name="COUNTRY" />]</translation>
 <translation id="8996941253935762404">Web, na který se chystáte přejít, obsahuje škodlivé programy</translation>
diff --git a/components/strings/components_strings_en-GB.xtb b/components/strings/components_strings_en-GB.xtb
index 95eb881..33fd66b 100644
--- a/components/strings/components_strings_en-GB.xtb
+++ b/components/strings/components_strings_en-GB.xtb
@@ -488,6 +488,7 @@
 <translation id="4277028893293644418">Reset password</translation>
 <translation id="4280429058323657511">, exp <ph name="EXPIRATION_DATE_ABBR" /></translation>
 <translation id="4305817255990598646">Switch</translation>
+<translation id="4308131620840579419">Save all your cards in one place?</translation>
 <translation id="4312866146174492540">Block (default)</translation>
 <translation id="4325863107915753736">Failed to find article</translation>
 <translation id="4326324639298822553">Check your expiry date and try again</translation>
@@ -598,6 +599,7 @@
 <translation id="5190835502935405962">Bookmarks Bar</translation>
 <translation id="5201306358585911203">An embedded page on this page says</translation>
 <translation id="5205222826937269299">Name required</translation>
+<translation id="5215116848420601511">Payment methods and addresses using Google Pay</translation>
 <translation id="5222812217790122047">Email (required)</translation>
 <translation id="5230733896359313003">Delivery Address</translation>
 <translation id="5250209940322997802">'Connect to network'</translation>
@@ -674,6 +676,7 @@
 <translation id="5689199277474810259">Export to JSON</translation>
 <translation id="5689516760719285838">Location</translation>
 <translation id="570530837424789914">Manage...</translation>
+<translation id="5705882733397021510">Go back</translation>
 <translation id="57094364128775171">Suggest strong password…</translation>
 <translation id="5710435578057952990">The identity of this website has not been verified.</translation>
 <translation id="5719499550583120431">Prepaid cards are accepted.</translation>
@@ -691,6 +694,7 @@
 <translation id="5810442152076338065">Your connection to <ph name="DOMAIN" /> is encrypted using an obsolete cipher suite.</translation>
 <translation id="5813119285467412249">&amp;Redo Add</translation>
 <translation id="5838278095973806738">You should not enter any sensitive information on this site (for example, passwords or credit cards), because it could be stolen by attackers.</translation>
+<translation id="5863847714970149516">The page ahead may try to charge you money</translation>
 <translation id="5866257070973731571">Add Phone Number</translation>
 <translation id="5869405914158311789">This site can’t be reached</translation>
 <translation id="5869522115854928033">Saved passwords</translation>
@@ -821,6 +825,7 @@
 <translation id="6973656660372572881">Both fixed proxy servers and a .pac script URL are specified.</translation>
 <translation id="6989763994942163495">+ Show advanced settings</translation>
 <translation id="7012363358306927923">China UnionPay</translation>
+<translation id="7016992613359344582">These charges could be one-off or recurring and may not be obvious.</translation>
 <translation id="7029809446516969842">Passwords</translation>
 <translation id="7050187094878475250">You attempted to reach <ph name="DOMAIN" />, but the server presented a certificate whose validity period is too long to be trustworthy.</translation>
 <translation id="7053983685419859001">Block</translation>
@@ -1085,6 +1090,7 @@
 <translation id="8957210676456822347">Captive Portal Authorisation</translation>
 <translation id="8971063699422889582">Server's certificate has expired.</translation>
 <translation id="8978053250194585037">Google Safe Browsing recently <ph name="BEGIN_LINK" />detected phishing<ph name="END_LINK" /> on <ph name="SITE" />. Phishing sites pretend to be other websites to trick you.</translation>
+<translation id="8983003182662520383">Payment methods and addresses using Google Pay</translation>
 <translation id="8987927404178983737">Month</translation>
 <translation id="8989148748219918422"><ph name="ORGANIZATION" /> [<ph name="COUNTRY" />]</translation>
 <translation id="8996941253935762404">The site ahead contains harmful programs</translation>
diff --git a/components/strings/components_strings_lt.xtb b/components/strings/components_strings_lt.xtb
index b7a6b9b7..99d5f623a 100644
--- a/components/strings/components_strings_lt.xtb
+++ b/components/strings/components_strings_lt.xtb
@@ -489,6 +489,7 @@
 <translation id="4277028893293644418">Iš naujo nustatyti slaptažodį</translation>
 <translation id="4280429058323657511">, gal. pab. <ph name="EXPIRATION_DATE_ABBR" /></translation>
 <translation id="4305817255990598646">Perjungti</translation>
+<translation id="4308131620840579419">Visos jūsų kortelės yra vienoje vietoje?</translation>
 <translation id="4312866146174492540">Užblokuoti (numatytoji parinktis)</translation>
 <translation id="4325863107915753736">Nepavyko rasti straipsnio</translation>
 <translation id="4326324639298822553">Patikrinkite galiojimo pabaigos datą ir bandykite dar kartą</translation>
@@ -599,6 +600,7 @@
 <translation id="5190835502935405962">Žymių juosta</translation>
 <translation id="5201306358585911203">Šiame puslapyje įterptame puslapyje nurodyta:</translation>
 <translation id="5205222826937269299">Būtina nurodyti pavadinimą</translation>
+<translation id="5215116848420601511">„Google Pay“ naudojami mokėjimo metodai ir adresai</translation>
 <translation id="5222812217790122047">Būtina nurodyti el. paštą</translation>
 <translation id="5230733896359313003">Pristatymo adresas</translation>
 <translation id="5250209940322997802">„Prisijungimas prie tinklo“</translation>
@@ -675,6 +677,7 @@
 <translation id="5689199277474810259">Eksportuoti kaip JSON</translation>
 <translation id="5689516760719285838">Vieta</translation>
 <translation id="570530837424789914">Tvarkyti...</translation>
+<translation id="5705882733397021510">Atgal</translation>
 <translation id="57094364128775171">Siūlyti sudėtingą slaptažodį…</translation>
 <translation id="5710435578057952990">Šio tinklalapio tapatybė nenustatyta.</translation>
 <translation id="5719499550583120431">Išankstinio mokėjimo kortelės tinkamos.</translation>
@@ -692,6 +695,7 @@
 <translation id="5810442152076338065">Ryšys su <ph name="DOMAIN" /> užšifruotas naudojant pasenusį šifravimo paketą.</translation>
 <translation id="5813119285467412249">&amp;Pridėti dar kartą</translation>
 <translation id="5838278095973806738">Šioje svetainėje neturėtumėte pateikti neskelbtinos informacijos (pvz., slaptažodžių ar kredito kortelių numerių), nes ją gali pavogti užpuolikai.</translation>
+<translation id="5863847714970149516">Toliau pateiktame puslapyje gali būti bandoma jus apmokestinti</translation>
 <translation id="5866257070973731571">Telefono numerio pridėjimas</translation>
 <translation id="5869405914158311789">Nepavyksta pasiekti šios svetainės</translation>
 <translation id="5869522115854928033">Išsaugoti slaptažodžiai</translation>
@@ -822,6 +826,7 @@
 <translation id="6973656660372572881">Nurodyti fiksuoti įgaliotieji serveriai ir .pac scenarijaus URL.</translation>
 <translation id="6989763994942163495">Rodyti išplėstinius nustatymus...</translation>
 <translation id="7012363358306927923">China UnionPay</translation>
+<translation id="7016992613359344582">Šie mokesčiai gali būti vienkartiniai arba pasikartojantys ir gali būti neaiškūs.</translation>
 <translation id="7029809446516969842">Slaptažodžiai</translation>
 <translation id="7050187094878475250">Bandėte pasiekti <ph name="DOMAIN" />, bet serveris pateikė sertifikatą, kurio galiojimo laikotarpis per ilgas, kad būtų patikimas.</translation>
 <translation id="7053983685419859001">Blokuoti</translation>
@@ -1087,6 +1092,7 @@
 <translation id="8957210676456822347">Fiksuotojo portalo autorizavimas</translation>
 <translation id="8971063699422889582">Baigėsi serverio sertifikato galiojimo laikas.</translation>
 <translation id="8978053250194585037">„Google“ saugaus naršymo sistema neseniai <ph name="BEGIN_LINK" />aptiko, kad svetainėje <ph name="SITE" /> sukčiaujama<ph name="END_LINK" />. Sukčiavimo svetainės apsimeta kitomis svetainėmis, kad jus apgautų.</translation>
+<translation id="8983003182662520383">„Google Pay“ naudojami mokėjimo metodai ir adresai</translation>
 <translation id="8987927404178983737">Mėnuo</translation>
 <translation id="8989148748219918422"><ph name="ORGANIZATION" /> [<ph name="COUNTRY" />]</translation>
 <translation id="8996941253935762404">Pateiktoje svetainėje yra kenkėjiškų programų</translation>
diff --git a/components/strings/components_strings_mr.xtb b/components/strings/components_strings_mr.xtb
index de4bf51..614da3c 100644
--- a/components/strings/components_strings_mr.xtb
+++ b/components/strings/components_strings_mr.xtb
@@ -488,6 +488,7 @@
 <translation id="4277028893293644418">पासवर्ड रीसेट करा</translation>
 <translation id="4280429058323657511">, कालबाह्यता <ph name="EXPIRATION_DATE_ABBR" /></translation>
 <translation id="4305817255990598646">स्विच</translation>
+<translation id="4308131620840579419">तुमची सर्व कार्ड एकाच ठिकाणी सेव्ह करायची का?</translation>
 <translation id="4312866146174492540">अवरोधित करा (डीफॉल्ट)</translation>
 <translation id="4325863107915753736">लेख शोधण्यात अयशस्वी</translation>
 <translation id="4326324639298822553">तुमची कालबाह्यता तारीख तपासा आणि पुन्हा प्रयत्न करा</translation>
@@ -598,6 +599,7 @@
 <translation id="5190835502935405962">बुकमार्क बार</translation>
 <translation id="5201306358585911203">या पेजवरील एंबेड केलेल्‍या पेजचे म्हणणे हे आहे की</translation>
 <translation id="5205222826937269299">नाव आवश्यक आहे</translation>
+<translation id="5215116848420601511">Google Pay वापरून पेमेंट पद्धती आणि पत्ते</translation>
 <translation id="5222812217790122047">ईमेल आवश्यक आहे</translation>
 <translation id="5230733896359313003">पाठविण्याचा पत्ता</translation>
 <translation id="5250209940322997802">"नेटवर्कशी कनेक्ट करा"</translation>
@@ -674,6 +676,7 @@
 <translation id="5689199277474810259">JSON वर निर्यात करा</translation>
 <translation id="5689516760719285838">स्थान</translation>
 <translation id="570530837424789914">व्यवस्थापित करा...</translation>
+<translation id="5705882733397021510">मागे जा</translation>
 <translation id="57094364128775171">क्लिष्ट पासवर्ड सुचवा…</translation>
 <translation id="5710435578057952990">या वेबसाइटची ओळख सत्यापित केली गेली नाही.</translation>
 <translation id="5719499550583120431">प्रीपेड कार्डे स्वीकारली जातात.</translation>
@@ -691,6 +694,7 @@
 <translation id="5810442152076338065">आपले <ph name="DOMAIN" /> वरील कनेक्शन अप्रचलित सायफर सूट वापरून कूटबद्ध केलेले आहे.</translation>
 <translation id="5813119285467412249">&amp;जोडा पुन्हा करा</translation>
 <translation id="5838278095973806738">या साइटवर कोणतीही संवेदनशील माहिती (उदाहरणार्थ, पासवर्ड किंवा क्रेडिट कार्ड) एंटर करू नका, कारण आक्रमणकर्ते ती चोरू शकतात.</translation>
+<translation id="5863847714970149516">पुढील पेजवर तुमच्याकडून शुल्क आकारले जाऊ शकते</translation>
 <translation id="5866257070973731571">फोन नंबर जोडा</translation>
 <translation id="5869405914158311789">या साइटवर पोहचणे शक्य नाही</translation>
 <translation id="5869522115854928033">सेव्ह केलेले पासवर्ड</translation>
@@ -822,6 +826,7 @@
 <translation id="6973656660372572881">निश्चित प्रॉक्सी सर्व्हर आणि .pac स्क्रिप्ट URL निर्दिष्‍ट करण्‍यात आले आहेत.</translation>
 <translation id="6989763994942163495">प्रगत सेटिंग्ज दर्शवा...</translation>
 <translation id="7012363358306927923">China UnionPay</translation>
+<translation id="7016992613359344582">हे शुल्क एकाच वेळी द्यायचे किंवा आवर्ती असू शकतात आणि स्पष्ट नसू शकतात.</translation>
 <translation id="7029809446516969842">पासवर्ड</translation>
 <translation id="7050187094878475250">आपण <ph name="DOMAIN" /> वर पोहोचण्याचा प्रयत्न केला, परंतु सर्व्हरने एक प्रमाणपत्र सादर केले आहे ज्याचा वैधता कालावधी हा विश्वासार्हतेसाठी खूप मोठा आहे.</translation>
 <translation id="7053983685419859001">अवरोधित करा</translation>
@@ -1086,6 +1091,7 @@
 <translation id="8957210676456822347">बंद पोर्टल प्राधिकृतता</translation>
 <translation id="8971063699422889582">सर्व्हरचे प्रमाणपत्र कालबाह्य झाले आहे.</translation>
 <translation id="8978053250194585037">Google सुरक्षित ब्राउझिंगला <ph name="SITE" /> वर अलीकडे <ph name="BEGIN_LINK" />फिशिंग आढळले<ph name="END_LINK" />. तुम्हाला फसवण्यासाठी फिशिंग साइट दुसर्‍याच कुठल्यातरी वेबसाइट असल्याचे भासवतात.</translation>
+<translation id="8983003182662520383">Google Pay वापरून पेमेंट पद्धती आणि पत्ते</translation>
 <translation id="8987927404178983737">महिना</translation>
 <translation id="8989148748219918422"><ph name="ORGANIZATION" /> [<ph name="COUNTRY" />]</translation>
 <translation id="8996941253935762404">पुढे असणार्‍या साइटमध्ये हानिकारक प्रोग्राम आहेत</translation>
diff --git a/components/strings/components_strings_sk.xtb b/components/strings/components_strings_sk.xtb
index da392e7..d09da233 100644
--- a/components/strings/components_strings_sk.xtb
+++ b/components/strings/components_strings_sk.xtb
@@ -484,6 +484,7 @@
 <translation id="4277028893293644418">Obnoviť heslo</translation>
 <translation id="4280429058323657511">, dátum vypršania platnosti: <ph name="EXPIRATION_DATE_ABBR" /></translation>
 <translation id="4305817255990598646">Prepnúť</translation>
+<translation id="4308131620840579419">Chcete uložiť všetky karty na jednom mieste?</translation>
 <translation id="4312866146174492540">Blokovať (predvolené)</translation>
 <translation id="4325863107915753736">Článok sa nepodarilo nájsť</translation>
 <translation id="4326324639298822553">Skontrolujte dátum vypršania platnosti a skúste to znova</translation>
@@ -594,6 +595,7 @@
 <translation id="5190835502935405962">Panel so záložkami</translation>
 <translation id="5201306358585911203">Vložená stránka na tejto stránke hovorí</translation>
 <translation id="5205222826937269299">Meno je povinný údaj</translation>
+<translation id="5215116848420601511">Spôsoby platby a adresy pomocou Google Pay</translation>
 <translation id="5222812217790122047">E-mailová adresa je povinný údaj</translation>
 <translation id="5230733896359313003">Dodacia adresa</translation>
 <translation id="5250209940322997802">„Pripojte sa k sieti“</translation>
@@ -670,6 +672,7 @@
 <translation id="5689199277474810259">Exportovať vo formáte JSON</translation>
 <translation id="5689516760719285838">Poloha</translation>
 <translation id="570530837424789914">Spravovať...</translation>
+<translation id="5705882733397021510">Prejsť späť</translation>
 <translation id="57094364128775171">Navrhnúť silné heslo…</translation>
 <translation id="5710435578057952990">Identita tejto webovej stránky nebola overená.</translation>
 <translation id="5719499550583120431">Predplatené karty sú akceptované.</translation>
@@ -687,6 +690,7 @@
 <translation id="5810442152076338065">Vaše pripojenie k doméne <ph name="DOMAIN" /> je šifrované pomocou zastaranej šifrovacej súpravy.</translation>
 <translation id="5813119285467412249">&amp;Znova pridať</translation>
 <translation id="5838278095973806738">Na tomto webe by ste nemali zadávať citlivé informácie (napríklad heslá alebo kreditné karty), pretože by ich mohli ukradnúť útočníci.</translation>
+<translation id="5863847714970149516">Stránka, na ktorú sa chystáte prejsť, vám môže účtovať poplatky</translation>
 <translation id="5866257070973731571">Pridanie telefónneho čísla</translation>
 <translation id="5869405914158311789">K tomuto webu sa nedá pripojiť</translation>
 <translation id="5869522115854928033">Uložené heslá</translation>
@@ -816,6 +820,7 @@
 <translation id="6973656660372572881">Určené sú pevne dané servery proxy aj skript PAC webovej adresy.</translation>
 <translation id="6989763994942163495">Zobraziť rozšírené nastavenia...</translation>
 <translation id="7012363358306927923">China UnionPay</translation>
+<translation id="7016992613359344582">Tieto poplatky môžu byť jednorazové alebo opakované a nemusia byť predvídateľné.</translation>
 <translation id="7029809446516969842">Heslá</translation>
 <translation id="7050187094878475250">Pokúsili ste sa prejsť do domény <ph name="DOMAIN" />, ale server udelil certifikát, ktorého obdobie platnosti je príliš dlhé, a preto nie je dôveryhodný</translation>
 <translation id="7053983685419859001">Blokovať</translation>
@@ -1081,6 +1086,7 @@
 <translation id="8957210676456822347">Autorizácia portálu na prihlásenie do siete</translation>
 <translation id="8971063699422889582">Platnosť certifikátu servera vypršala.</translation>
 <translation id="8978053250194585037">Funkcia Bezpečné prehliadanie Google nedávno <ph name="BEGIN_LINK" />zistila phishing<ph name="END_LINK" /> na webe <ph name="SITE" />. Phishingové stránky sa vydávajú za iné weby, aby vás oklamali.</translation>
+<translation id="8983003182662520383">Spôsoby platby a adresy pomocou Google Pay</translation>
 <translation id="8987927404178983737">Mesiac</translation>
 <translation id="8989148748219918422"><ph name="ORGANIZATION" /> [<ph name="COUNTRY" />]</translation>
 <translation id="8996941253935762404">Webové stránky, ktoré sa chystáte navštíviť, obsahujú škodlivé programy</translation>
diff --git a/components/strings/components_strings_ta.xtb b/components/strings/components_strings_ta.xtb
index 4a1c7347..985551c5 100644
--- a/components/strings/components_strings_ta.xtb
+++ b/components/strings/components_strings_ta.xtb
@@ -805,7 +805,7 @@
 <translation id="6897140037006041989">பயனர் முகவர்</translation>
 <translation id="6903319715792422884">Googleளுக்குச் சில <ph name="BEGIN_WHITEPAPER_LINK" />சாதனத் தகவல்களையும் பக்க உள்ளடக்கத்தையும்<ph name="END_WHITEPAPER_LINK" /> அனுப்புவதன் மூலம் பாதுகாப்பான உலாவலை மேம்படுத்த உதவுங்கள். <ph name="PRIVACY_PAGE_LINK" /></translation>
 <translation id="6915804003454593391">பயனர்:</translation>
-<translation id="6944692733090228304"><ph name="BEGIN_BOLD" /><ph name="ORG_NAME" /><ph name="END_BOLD" /> ஆல் நிர்வகிக்கப்படாத ஒரு தளத்தில் உங்கள் கடவுச்சொல்லை உள்ளிட்டுள்ளீர்கள். உங்கள் கணக்கைப் பாதுகாக்க, பிற ஆப்ஸ் மற்றும் தளங்களில் உங்கள் கடவுச்சொல்லை மீண்டும் பயன்படுத்த வேண்டாம்.</translation>
+<translation id="6944692733090228304"><ph name="BEGIN_BOLD" /><ph name="ORG_NAME" /><ph name="END_BOLD" /> நிர்வாகிக்காத ஒரு தளத்தில் உங்கள் கடவுச்சொல்லை உள்ளிட்டுள்ளீர்கள். உங்கள் கணக்கைப் பாதுகாக்க, பிற ஆப்ஸிலும் தளங்களிலும் உங்கள் கடவுச்சொல்லை மீண்டும் பயன்படுத்த வேண்டாம்.</translation>
 <translation id="6945221475159498467">தேர்ந்தெடு</translation>
 <translation id="6948701128805548767">பிக்அப் முறைகளையும் தேவைகளையும் பார்க்க, முகவரியைத் தேர்ந்தெடுக்கவும்</translation>
 <translation id="6949872517221025916">கடவுச்சொல்லை மீட்டமைக்கவும்</translation>
diff --git a/components/strings/components_strings_te.xtb b/components/strings/components_strings_te.xtb
index 659bbad..883ef0e 100644
--- a/components/strings/components_strings_te.xtb
+++ b/components/strings/components_strings_te.xtb
@@ -801,6 +801,7 @@
 <translation id="6831043979455480757">అనువదించు</translation>
 <translation id="6839929833149231406">ప్రాంతం</translation>
 <translation id="6852204201400771460">యాప్‌ను మళ్లీ లోడ్ చేయాలా?</translation>
+<translation id="6865412394715372076">ప్రస్తుతం ఈ కార్డ్‌ని ధృవీకరించడం సాధ్యపడదు</translation>
 <translation id="6874604403660855544">&amp;జోడించడాన్ని పునరావృతం చేయి</translation>
 <translation id="6886577214605505410"><ph name="LOCATION_TITLE" /> <ph name="SHORT_URL" /></translation>
 <translation id="6891596781022320156">విధానం స్థాయికి మద్దతు లేదు.</translation>
@@ -1050,6 +1051,7 @@
 <translation id="8680536109547170164"><ph name="QUERY" />, సమాధానం, <ph name="ANSWER" /></translation>
 <translation id="8703575177326907206"><ph name="DOMAIN" />కు మీ కనెక్షన్ గుప్తీకరించబడలేదు.</translation>
 <translation id="8718314106902482036">చెల్లింపు పూర్తి కాలేదు</translation>
+<translation id="8719263113926255150"><ph name="ENTITY" />, <ph name="DESCRIPTION" />, శోధన సూచన</translation>
 <translation id="8725066075913043281">మళ్ళీ ప్రయత్నించండి</translation>
 <translation id="8728672262656704056">మీరు ఇప్పుడు అజ్ఞాత మోడ్‌లో ఉన్నారు</translation>
 <translation id="8730621377337864115">పూర్తయింది</translation>
diff --git a/components/sync/BUILD.gn b/components/sync/BUILD.gn
index f887cab7..e3585ea 100644
--- a/components/sync/BUILD.gn
+++ b/components/sync/BUILD.gn
@@ -436,6 +436,8 @@
     "model_impl/blocking_model_type_store_impl.h",
     "model_impl/client_tag_based_model_type_processor.cc",
     "model_impl/client_tag_based_model_type_processor.h",
+    "model_impl/forwarding_model_type_controller_delegate.cc",
+    "model_impl/forwarding_model_type_controller_delegate.h",
     "model_impl/in_memory_metadata_change_list.cc",
     "model_impl/in_memory_metadata_change_list.h",
     "model_impl/model_type_store_backend.cc",
diff --git a/components/sync/driver/async_directory_type_controller.cc b/components/sync/driver/async_directory_type_controller.cc
index db3bad1..f02509b 100644
--- a/components/sync/driver/async_directory_type_controller.cc
+++ b/components/sync/driver/async_directory_type_controller.cc
@@ -11,6 +11,7 @@
 #include "components/sync/base/bind_to_task_runner.h"
 #include "components/sync/base/data_type_histogram.h"
 #include "components/sync/base/model_type.h"
+#include "components/sync/driver/configure_context.h"
 #include "components/sync/driver/generic_change_processor_factory.h"
 #include "components/sync/driver/sync_api_component_factory.h"
 #include "components/sync/driver/sync_client.h"
@@ -46,7 +47,11 @@
     const ConfigureContext& configure_context,
     const ModelLoadCallback& model_load_callback) {
   DCHECK(CalledOnValidThread());
+  DCHECK_EQ(configure_context.storage_option,
+            ConfigureContext::STORAGE_ON_DISK);
+
   model_load_callback_ = model_load_callback;
+
   if (state() != NOT_RUNNING) {
     model_load_callback.Run(type(),
                             SyncError(FROM_HERE, SyncError::DATATYPE_ERROR,
diff --git a/components/sync/driver/configure_context.h b/components/sync/driver/configure_context.h
index 16e403a..46e6bfb 100644
--- a/components/sync/driver/configure_context.h
+++ b/components/sync/driver/configure_context.h
@@ -18,8 +18,14 @@
 // controllers, which for USS datatypes propagate analogous information to the
 // processor/bridge via DataTypeActivationRequest.
 struct ConfigureContext {
+  enum StorageOption {
+    STORAGE_ON_DISK,
+    STORAGE_IN_MEMORY,
+  };
+
   std::string authenticated_account_id;
   std::string cache_guid;
+  StorageOption storage_option = STORAGE_ON_DISK;
   ConfigureReason reason = CONFIGURE_REASON_UNKNOWN;
   // TODO(mastiz): Consider adding |requested_types| here, but currently there
   // are subtle differences across layers (e.g. where control types are
diff --git a/components/sync/driver/frontend_data_type_controller.cc b/components/sync/driver/frontend_data_type_controller.cc
index e8682cd9..21d7eb2 100644
--- a/components/sync/driver/frontend_data_type_controller.cc
+++ b/components/sync/driver/frontend_data_type_controller.cc
@@ -10,6 +10,7 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "components/sync/base/data_type_histogram.h"
 #include "components/sync/base/model_type.h"
+#include "components/sync/driver/configure_context.h"
 #include "components/sync/driver/model_associator.h"
 #include "components/sync/driver/sync_client.h"
 #include "components/sync/driver/sync_service.h"
@@ -34,6 +35,9 @@
     const ConfigureContext& configure_context,
     const ModelLoadCallback& model_load_callback) {
   DCHECK(CalledOnValidThread());
+  DCHECK_EQ(configure_context.storage_option,
+            ConfigureContext::STORAGE_ON_DISK);
+
   model_load_callback_ = model_load_callback;
 
   if (state_ != NOT_RUNNING) {
diff --git a/components/sync/driver/model_type_controller.cc b/components/sync/driver/model_type_controller.cc
index 75cd08ab..68327a8 100644
--- a/components/sync/driver/model_type_controller.cc
+++ b/components/sync/driver/model_type_controller.cc
@@ -49,10 +49,20 @@
 
 ModelTypeController::ModelTypeController(
     ModelType type,
-    std::unique_ptr<ModelTypeControllerDelegate> delegate)
-    : DataTypeController(type),
-      delegate_(std::move(delegate)),
-      state_(NOT_RUNNING) {}
+    std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk)
+    : DataTypeController(type), state_(NOT_RUNNING) {
+  delegate_map_.emplace(ConfigureContext::STORAGE_ON_DISK,
+                        std::move(delegate_on_disk));
+}
+
+ModelTypeController::ModelTypeController(
+    ModelType type,
+    std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk,
+    std::unique_ptr<ModelTypeControllerDelegate> delegate_in_memory)
+    : ModelTypeController(type, std::move(delegate_on_disk)) {
+  delegate_map_.emplace(ConfigureContext::STORAGE_IN_MEMORY,
+                        std::move(delegate_in_memory));
+}
 
 ModelTypeController::~ModelTypeController() {}
 
@@ -69,10 +79,13 @@
   DCHECK(!model_load_callback.is_null());
   DCHECK_EQ(NOT_RUNNING, state_);
 
-  model_load_callback_ = model_load_callback;
+  auto it = delegate_map_.find(configure_context.storage_option);
+  DCHECK(it != delegate_map_.end());
+  delegate_ = it->second.get();
 
   DVLOG(1) << "Sync starting for " << ModelTypeToString(type());
   state_ = MODEL_STARTING;
+  model_load_callback_ = model_load_callback;
 
   DataTypeActivationRequest request;
   request.error_handler = base::BindRepeating(
@@ -114,6 +127,7 @@
     DCHECK(model_stop_callbacks_.empty());
 
     delegate_->OnSyncStopping(model_stop_metadata_fate_);
+    delegate_ = nullptr;
 
     for (StopCallback& stop_callback : model_stop_callbacks) {
       std::move(stop_callback).Run();
@@ -238,6 +252,7 @@
       DVLOG(1) << "Stopping sync for " << ModelTypeToString(type());
       state_ = NOT_RUNNING;
       delegate_->OnSyncStopping(metadata_fate);
+      delegate_ = nullptr;
       std::move(callback).Run();
       break;
   }
@@ -248,15 +263,18 @@
 }
 
 void ModelTypeController::GetAllNodes(const AllNodesCallback& callback) {
+  DCHECK(delegate_);
   delegate_->GetAllNodesForDebugging(callback);
 }
 
 void ModelTypeController::GetStatusCounters(
     const StatusCountersCallback& callback) {
+  DCHECK(delegate_);
   delegate_->GetStatusCountersForDebugging(callback);
 }
 
 void ModelTypeController::RecordMemoryUsageAndCountsHistograms() {
+  DCHECK(delegate_);
   delegate_->RecordMemoryUsageAndCountsHistograms();
 }
 
diff --git a/components/sync/driver/model_type_controller.h b/components/sync/driver/model_type_controller.h
index 31174b6..1370159b 100644
--- a/components/sync/driver/model_type_controller.h
+++ b/components/sync/driver/model_type_controller.h
@@ -10,9 +10,11 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "components/sync/base/model_type.h"
+#include "components/sync/driver/configure_context.h"
 #include "components/sync/driver/data_type_controller.h"
 #include "components/sync/model/model_error.h"
 #include "components/sync/model/model_type_controller_delegate.h"
@@ -25,8 +27,14 @@
 // DataTypeController implementation for Unified Sync and Storage model types.
 class ModelTypeController : public DataTypeController {
  public:
-  ModelTypeController(ModelType type,
-                      std::unique_ptr<ModelTypeControllerDelegate> delegate);
+  ModelTypeController(
+      ModelType type,
+      std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk);
+  // For datatypes that have support for STORAGE_IN_MEMORY.
+  ModelTypeController(
+      ModelType type,
+      std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk,
+      std::unique_ptr<ModelTypeControllerDelegate> delegate_in_memory);
   ~ModelTypeController() override;
 
   // DataTypeController implementation.
@@ -63,11 +71,16 @@
   void OnProcessorStarted(
       std::unique_ptr<DataTypeActivationResponse> activation_response);
 
-  const std::unique_ptr<ModelTypeControllerDelegate> delegate_;
+  base::flat_map<ConfigureContext::StorageOption,
+                 std::unique_ptr<ModelTypeControllerDelegate>>
+      delegate_map_;
 
   // State of this datatype controller.
   State state_;
 
+  // Owned by |delegate_map_|. Null while NOT_RUNNING.
+  ModelTypeControllerDelegate* delegate_;
+
   // Callback for use when starting the datatype (usually MODEL_STARTING, but
   // STOPPING if abort requested while starting).
   ModelLoadCallback model_load_callback_;
diff --git a/components/sync/driver/model_type_controller_unittest.cc b/components/sync/driver/model_type_controller_unittest.cc
index 3c6796a..7bb0a81 100644
--- a/components/sync/driver/model_type_controller_unittest.cc
+++ b/components/sync/driver/model_type_controller_unittest.cc
@@ -10,19 +10,19 @@
 #include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/logging.h"
-#include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
-#include "base/test/test_simple_task_runner.h"
+#include "base/test/mock_callback.h"
 #include "base/threading/sequenced_task_runner_handle.h"
-#include "base/threading/thread.h"
 #include "components/sync/driver/configure_context.h"
 #include "components/sync/engine/commit_queue.h"
 #include "components/sync/engine/data_type_activation_response.h"
 #include "components/sync/engine/fake_model_type_processor.h"
 #include "components/sync/engine/model_type_configurer.h"
 #include "components/sync/engine/model_type_processor_proxy.h"
-#include "components/sync/model_impl/proxy_model_type_controller_delegate.h"
+#include "components/sync/model/data_type_activation_request.h"
+#include "components/sync/model/sync_merge_result.h"
+#include "components/sync/model_impl/forwarding_model_type_controller_delegate.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -30,16 +30,24 @@
 
 namespace {
 
+using testing::NiceMock;
 using testing::_;
 
 const ModelType kTestModelType = AUTOFILL;
 const char kCacheGuid[] = "SomeCacheGuid";
 const char kAccountId[] = "SomeAccountId";
 
-void SetBool(bool* called, bool* out, bool in) {
-  *called = true;
-  *out = in;
-}
+class MockDelegate : public ModelTypeControllerDelegate {
+ public:
+  MOCK_METHOD2(OnSyncStarting,
+               void(const DataTypeActivationRequest& request,
+                    StartCallback callback));
+  MOCK_METHOD1(OnSyncStopping, void(SyncStopMetadataFate metadata_fate));
+  MOCK_METHOD1(GetAllNodesForDebugging, void(AllNodesCallback callback));
+  MOCK_METHOD1(GetStatusCountersForDebugging,
+               void(StatusCountersCallback callback));
+  MOCK_METHOD0(RecordMemoryUsageAndCountsHistograms, void());
+};
 
 // A simple processor that trackes connected state.
 class TestModelTypeProcessor
@@ -63,56 +71,6 @@
   DISALLOW_COPY_AND_ASSIGN(TestModelTypeProcessor);
 };
 
-// A delegate for testing that connects using a thread-jumping proxy, tracks
-// connected state, and counts DisableSync calls.
-class TestDelegate : public ModelTypeControllerDelegate,
-                     public base::SupportsWeakPtr<TestDelegate> {
- public:
-  TestDelegate() {}
-
-  void set_initial_sync_done(bool initial_sync_done) {
-    initial_sync_done_ = initial_sync_done;
-  }
-
-  bool is_processor_connected() const { return processor_.is_connected(); }
-
-  int cleared_metadata_count() const { return cleared_metadata_count_; }
-
-  // ModelTypeControllerDelegate overrides.
-  void OnSyncStarting(const DataTypeActivationRequest& request,
-                      StartCallback callback) override {
-    std::unique_ptr<DataTypeActivationResponse> activation_response =
-        std::make_unique<DataTypeActivationResponse>();
-    activation_response->model_type_state.set_initial_sync_done(
-        initial_sync_done_);
-    activation_response->type_processor =
-        std::make_unique<ModelTypeProcessorProxy>(
-            base::AsWeakPtr(&processor_),
-            base::SequencedTaskRunnerHandle::Get());
-    std::move(callback).Run(std::move(activation_response));
-  }
-
-  void OnSyncStopping(SyncStopMetadataFate metadata_fate) override {
-    if (metadata_fate == CLEAR_METADATA) {
-      cleared_metadata_count_++;
-    }
-  }
-
-  void GetAllNodesForDebugging(AllNodesCallback callback) override {}
-
-  void GetStatusCountersForDebugging(StatusCountersCallback callback) override {
-  }
-
-  void RecordMemoryUsageAndCountsHistograms() override {}
-
- private:
-  int cleared_metadata_count_ = 0;
-  bool initial_sync_done_ = false;
-  TestModelTypeProcessor processor_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestDelegate);
-};
-
 // A ModelTypeConfigurer that just connects USS types.
 class TestModelTypeConfigurer : public ModelTypeConfigurer {
  public:
@@ -162,122 +120,90 @@
   std::unique_ptr<ModelTypeProcessor> processor_;
 };
 
+ConfigureContext MakeConfigureContext() {
+  ConfigureContext context;
+  context.authenticated_account_id = kAccountId;
+  context.cache_guid = kCacheGuid;
+  return context;
+}
+
 }  // namespace
 
 class ModelTypeControllerTest : public testing::Test {
  public:
-  ModelTypeControllerTest() : model_thread_("modelthread") {
-    model_thread_.Start();
+  ModelTypeControllerTest()
+      : controller_(kTestModelType,
+                    std::make_unique<ForwardingModelTypeControllerDelegate>(
+                        &mock_delegate_)) {}
 
-    // TODO(crbug.com/855010): Remove the proxy and simplify these tests.
-    controller_ = std::make_unique<ModelTypeController>(
-        kTestModelType,
-        std::make_unique<ProxyModelTypeControllerDelegate>(
-            model_thread_.task_runner(),
-            base::BindRepeating(
-                [](base::WeakPtr<ModelTypeControllerDelegate> delegate) {
-                  return delegate;
-                },
-                base::AsWeakPtr(&delegate_))));
+  ~ModelTypeControllerTest() {
+    // Since we use ModelTypeProcessorProxy, which posts tasks, make sure we
+    // don't have anything pending on teardown that would make a test fail or
+    // crash.
+    base::RunLoop().RunUntilIdle();
   }
 
-  ~ModelTypeControllerTest() override {
-    PumpModelThread();
-    PumpUIThread();
-  }
+  bool LoadModels(bool initial_sync_done = false) {
+    base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
 
-  void LoadModels() {
-    ConfigureContext context;
-    context.authenticated_account_id = kAccountId;
-    context.cache_guid = kCacheGuid;
-    controller_->LoadModels(context,
-                            base::Bind(&ModelTypeControllerTest::LoadModelsDone,
-                                       base::Unretained(this)));
+    ModelTypeControllerDelegate::StartCallback start_callback;
+    EXPECT_CALL(mock_delegate_, OnSyncStarting(_, _))
+        .WillOnce([&](const DataTypeActivationRequest& request,
+                      ModelTypeControllerDelegate::StartCallback callback) {
+          start_callback = std::move(callback);
+        });
+
+    controller_.LoadModels(MakeConfigureContext(), load_models_done.Get());
+    if (!start_callback) {
+      return false;
+    }
+
+    // Prepare an activation response, which is the outcome of OnSyncStarting().
+    auto activation_response = std::make_unique<DataTypeActivationResponse>();
+    activation_response->model_type_state.set_initial_sync_done(
+        initial_sync_done);
+    activation_response->type_processor =
+        std::make_unique<ModelTypeProcessorProxy>(
+            base::AsWeakPtr(&processor_),
+            base::SequencedTaskRunnerHandle::Get());
+
+    // Mimic completion for OnSyncStarting().
+    EXPECT_CALL(load_models_done, Run(_, _));
+    std::move(start_callback).Run(std::move(activation_response));
+    return true;
   }
 
   void RegisterWithBackend(bool expect_downloaded) {
-    bool called = false;
-    bool downloaded;
-    controller_->RegisterWithBackend(base::Bind(&SetBool, &called, &downloaded),
-                                     &configurer_);
-    EXPECT_TRUE(called);
-    EXPECT_EQ(expect_downloaded, downloaded);
+    base::MockCallback<base::RepeatingCallback<void(bool)>> callback;
+    EXPECT_CALL(callback, Run(expect_downloaded));
+    controller_.RegisterWithBackend(callback.Get(), &configurer_);
+    // ModelTypeProcessorProxy does posting of tasks.
+    base::RunLoop().RunUntilIdle();
   }
 
   void StartAssociating() {
-    controller_->StartAssociating(base::Bind(
-        &ModelTypeControllerTest::AssociationDone, base::Unretained(this)));
-    // The callback is expected to be promptly called.
-    EXPECT_TRUE(association_callback_called_);
+    base::MockCallback<DataTypeController::StartCallback> callback;
+    EXPECT_CALL(callback, Run(DataTypeController::OK, _, _));
+    controller_.StartAssociating(callback.Get());
   }
 
   void DeactivateDataTypeAndStop(SyncStopMetadataFate metadata_fate) {
-    controller_->DeactivateDataType(&configurer_);
-    controller_->Stop(metadata_fate, base::DoNothing());
+    controller_.DeactivateDataType(&configurer_);
+    controller_.Stop(metadata_fate, base::DoNothing());
+    // ModelTypeProcessorProxy does posting of tasks.
+    base::RunLoop().RunUntilIdle();
   }
 
-  // These threads can ping-pong for a bit so we run the model thread twice.
-  void RunAllTasks() {
-    PumpModelThread();
-    PumpUIThread();
-    PumpModelThread();
-  }
-
-  // Runs any tasks posted on the model thread.
-  void PumpModelThread() {
-    base::RunLoop run_loop;
-    model_thread_.task_runner()->PostTaskAndReply(FROM_HERE, base::DoNothing(),
-                                                  run_loop.QuitClosure());
-    run_loop.Run();
-  }
-
-  void ExpectProcessorConnected(bool is_connected) {
-    if (model_thread_.task_runner()->BelongsToCurrentThread()) {
-      EXPECT_EQ(is_connected, delegate_.is_processor_connected());
-    } else {
-      model_thread_.task_runner()->PostTask(
-          FROM_HERE,
-          base::BindOnce(&ModelTypeControllerTest::ExpectProcessorConnected,
-                         base::Unretained(this), is_connected));
-      PumpModelThread();
-    }
-  }
-
-  void SetInitialSyncDone(bool initial_sync_done) {
-    delegate_.set_initial_sync_done(initial_sync_done);
-  }
-
-  DataTypeController* controller() { return controller_.get(); }
-  int load_models_done_count() { return load_models_done_count_; }
-  int cleared_metadata_count() { return delegate_.cleared_metadata_count(); }
-  SyncError load_models_last_error() { return load_models_last_error_; }
+  MockDelegate* delegate() { return &mock_delegate_; }
+  TestModelTypeProcessor* processor() { return &processor_; }
+  DataTypeController* controller() { return &controller_; }
 
  private:
-  // Runs any tasks posted on the UI thread.
-  void PumpUIThread() { base::RunLoop().RunUntilIdle(); }
-
-  void LoadModelsDone(ModelType type, const SyncError& error) {
-    load_models_done_count_++;
-    load_models_last_error_ = error;
-  }
-
-  void AssociationDone(DataTypeController::ConfigureResult result,
-                       const SyncMergeResult& local_merge_result,
-                       const SyncMergeResult& syncer_merge_result) {
-    EXPECT_FALSE(association_callback_called_);
-    EXPECT_EQ(DataTypeController::OK, result);
-    association_callback_called_ = true;
-  }
-
-  int load_models_done_count_ = 0;
-  bool association_callback_called_ = false;
-  SyncError load_models_last_error_;
-
   base::MessageLoop message_loop_;
-  base::Thread model_thread_;
-  TestDelegate delegate_;
+  NiceMock<MockDelegate> mock_delegate_;
   TestModelTypeConfigurer configurer_;
-  std::unique_ptr<ModelTypeController> controller_;
+  TestModelTypeProcessor processor_;
+  ModelTypeController controller_;
 };
 
 TEST_F(ModelTypeControllerTest, InitialState) {
@@ -286,40 +212,46 @@
 }
 
 TEST_F(ModelTypeControllerTest, LoadModelsOnBackendThread) {
-  LoadModels();
+  base::MockCallback<DataTypeController::ModelLoadCallback> load_models_done;
+
+  ModelTypeControllerDelegate::StartCallback start_callback;
+  EXPECT_CALL(*delegate(), OnSyncStarting(_, _))
+      .WillOnce([&](const DataTypeActivationRequest& request,
+                    ModelTypeControllerDelegate::StartCallback callback) {
+        start_callback = std::move(callback);
+      });
+
+  controller()->LoadModels(MakeConfigureContext(), load_models_done.Get());
   EXPECT_EQ(DataTypeController::MODEL_STARTING, controller()->state());
-  RunAllTasks();
+  ASSERT_TRUE(start_callback);
+
+  // Mimic completion for OnSyncStarting().
+  EXPECT_CALL(load_models_done, Run(kTestModelType, _));
+  std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
   EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
-  EXPECT_EQ(1, load_models_done_count());
-  EXPECT_FALSE(load_models_last_error().IsSet());
-  ExpectProcessorConnected(false);
 }
 
 TEST_F(ModelTypeControllerTest, Activate) {
-  LoadModels();
-  RunAllTasks();
+  ASSERT_TRUE(LoadModels());
   EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
-  RegisterWithBackend(false);
-  ExpectProcessorConnected(true);
+  RegisterWithBackend(/*expect_downloaded=*/false);
+  EXPECT_TRUE(processor()->is_connected());
 
   StartAssociating();
   EXPECT_EQ(DataTypeController::RUNNING, controller()->state());
 }
 
 TEST_F(ModelTypeControllerTest, ActivateWithInitialSyncDone) {
-  SetInitialSyncDone(true);
-  LoadModels();
-  RunAllTasks();
+  ASSERT_TRUE(LoadModels(/*initial_sync_done=*/true));
   EXPECT_EQ(DataTypeController::MODEL_LOADED, controller()->state());
-  RegisterWithBackend(true);
-  ExpectProcessorConnected(true);
+  RegisterWithBackend(/*expect_downloaded=*/true);
+  EXPECT_TRUE(processor()->is_connected());
 }
 
 TEST_F(ModelTypeControllerTest, Stop) {
-  LoadModels();
-  RunAllTasks();
-  RegisterWithBackend(false);
-  ExpectProcessorConnected(true);
+  ASSERT_TRUE(LoadModels());
+  RegisterWithBackend(/*expect_downloaded=*/false);
+  EXPECT_TRUE(processor()->is_connected());
 
   StartAssociating();
 
@@ -329,43 +261,105 @@
 
 // Test emulates normal browser shutdown. Ensures that metadata was not cleared.
 TEST_F(ModelTypeControllerTest, StopWhenDatatypeEnabled) {
-  LoadModels();
-  RunAllTasks();
+  ASSERT_TRUE(LoadModels());
   StartAssociating();
 
-  DeactivateDataTypeAndStop(KEEP_METADATA);
-  RunAllTasks();
-  EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
   // Ensures that metadata was not cleared.
-  EXPECT_EQ(0, cleared_metadata_count());
-  ExpectProcessorConnected(false);
+  EXPECT_CALL(*delegate(), OnSyncStopping(KEEP_METADATA));
+  DeactivateDataTypeAndStop(KEEP_METADATA);
+  EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
+  EXPECT_FALSE(processor()->is_connected());
 }
 
 // Test emulates scenario when user disables datatype. Metadata should be
 // cleared.
 TEST_F(ModelTypeControllerTest, StopWhenDatatypeDisabled) {
-  LoadModels();
-  RunAllTasks();
+  ASSERT_TRUE(LoadModels());
   StartAssociating();
 
+  EXPECT_CALL(*delegate(), OnSyncStopping(CLEAR_METADATA));
   DeactivateDataTypeAndStop(CLEAR_METADATA);
-  RunAllTasks();
   EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
-  // Ensures that metadata was cleared.
-  EXPECT_EQ(1, cleared_metadata_count());
-  ExpectProcessorConnected(false);
+  EXPECT_FALSE(processor()->is_connected());
 }
 
 // Test emulates disabling sync when datatype is not loaded yet. Metadata should
 // not be cleared as the delegate is potentially not ready to handle it.
 TEST_F(ModelTypeControllerTest, StopBeforeLoadModels) {
-  EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
+  EXPECT_CALL(*delegate(), OnSyncStopping(CLEAR_METADATA)).Times(0);
+
+  ASSERT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
 
   controller()->Stop(CLEAR_METADATA, base::DoNothing());
 
   EXPECT_EQ(DataTypeController::NOT_RUNNING, controller()->state());
-  // Ensure that DisableSync is not called.
-  EXPECT_EQ(0, cleared_metadata_count());
+}
+
+// Tests that StorageOption is honored when the controller has been constructed
+// with two delegates.
+TEST(ModelTypeControllerWithMultiDelegateTest, ToggleStorageOption) {
+  base::MessageLoop message_loop;
+  NiceMock<MockDelegate> delegate_on_disk;
+  NiceMock<MockDelegate> delegate_in_memory;
+
+  ModelTypeController controller(
+      kTestModelType,
+      std::make_unique<ForwardingModelTypeControllerDelegate>(
+          &delegate_on_disk),
+      std::make_unique<ForwardingModelTypeControllerDelegate>(
+          &delegate_in_memory));
+
+  ConfigureContext context;
+  context.authenticated_account_id = kAccountId;
+  context.cache_guid = kCacheGuid;
+
+  ModelTypeControllerDelegate::StartCallback start_callback;
+
+  // Start sync with STORAGE_IN_MEMORY.
+  EXPECT_CALL(delegate_on_disk, OnSyncStarting(_, _)).Times(0);
+  EXPECT_CALL(delegate_in_memory, OnSyncStarting(_, _))
+      .WillOnce([&](const DataTypeActivationRequest& request,
+                    ModelTypeControllerDelegate::StartCallback callback) {
+        start_callback = std::move(callback);
+      });
+  context.storage_option = ConfigureContext::STORAGE_IN_MEMORY;
+  controller.LoadModels(context, base::DoNothing());
+
+  ASSERT_EQ(DataTypeController::MODEL_STARTING, controller.state());
+  ASSERT_TRUE(start_callback);
+
+  // Mimic completion for OnSyncStarting().
+  std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
+  ASSERT_EQ(DataTypeController::MODEL_LOADED, controller.state());
+
+  // Stop sync.
+  EXPECT_CALL(delegate_on_disk, OnSyncStopping(_)).Times(0);
+  EXPECT_CALL(delegate_in_memory, OnSyncStopping(_));
+  controller.Stop(CLEAR_METADATA, base::DoNothing());
+  ASSERT_EQ(DataTypeController::NOT_RUNNING, controller.state());
+
+  // Start sync with STORAGE_ON_DISK.
+  EXPECT_CALL(delegate_in_memory, OnSyncStarting(_, _)).Times(0);
+  EXPECT_CALL(delegate_on_disk, OnSyncStarting(_, _))
+      .WillOnce([&](const DataTypeActivationRequest& request,
+                    ModelTypeControllerDelegate::StartCallback callback) {
+        start_callback = std::move(callback);
+      });
+  context.storage_option = ConfigureContext::STORAGE_ON_DISK;
+  controller.LoadModels(context, base::DoNothing());
+
+  ASSERT_EQ(DataTypeController::MODEL_STARTING, controller.state());
+  ASSERT_TRUE(start_callback);
+
+  // Mimic completion for OnSyncStarting().
+  std::move(start_callback).Run(std::make_unique<DataTypeActivationResponse>());
+  ASSERT_EQ(DataTypeController::MODEL_LOADED, controller.state());
+
+  // Stop sync.
+  EXPECT_CALL(delegate_in_memory, OnSyncStopping(_)).Times(0);
+  EXPECT_CALL(delegate_on_disk, OnSyncStopping(_));
+  controller.Stop(CLEAR_METADATA, base::DoNothing());
+  ASSERT_EQ(DataTypeController::NOT_RUNNING, controller.state());
 }
 
 }  // namespace syncer
diff --git a/components/sync/driver/proxy_data_type_controller.cc b/components/sync/driver/proxy_data_type_controller.cc
index 07868aa..400a804 100644
--- a/components/sync/driver/proxy_data_type_controller.cc
+++ b/components/sync/driver/proxy_data_type_controller.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/values.h"
+#include "components/sync/driver/configure_context.h"
 #include "components/sync/engine/model_safe_worker.h"
 #include "components/sync/engine/model_type_configurer.h"
 #include "components/sync/model/sync_merge_result.h"
@@ -38,6 +39,8 @@
     const ConfigureContext& configure_context,
     const ModelLoadCallback& model_load_callback) {
   DCHECK(CalledOnValidThread());
+  DCHECK_EQ(configure_context.storage_option,
+            ConfigureContext::STORAGE_ON_DISK);
   state_ = MODEL_LOADED;
   model_load_callback.Run(type(), SyncError());
 }
diff --git a/components/sync/driver/sync_driver_switches.cc b/components/sync/driver/sync_driver_switches.cc
index 3e7e59f..e21fa2a 100644
--- a/components/sync/driver/sync_driver_switches.cc
+++ b/components/sync/driver/sync_driver_switches.cc
@@ -36,6 +36,11 @@
 const base::Feature kSyncClearDataOnPassphraseEncryption{
     "ClearSyncDataOnPassphraseEncryption", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// If enabled, allows the Sync machinery ("transport layer") to start
+// independently of Sync-the-feature.
+const base::Feature kSyncStandaloneTransport{"SyncStandaloneTransport",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Gates registration and construction of user events machinery. Enabled by
 // default as each use case should have their own gating feature as well.
 const base::Feature kSyncUserEvents{"SyncUserEvents",
diff --git a/components/sync/driver/sync_driver_switches.h b/components/sync/driver/sync_driver_switches.h
index 93a6d36..ef0d7f9 100644
--- a/components/sync/driver/sync_driver_switches.h
+++ b/components/sync/driver/sync_driver_switches.h
@@ -20,6 +20,7 @@
 extern const char kSyncShortNudgeDelayForTest[];
 
 extern const base::Feature kSyncClearDataOnPassphraseEncryption;
+extern const base::Feature kSyncStandaloneTransport;
 extern const base::Feature kSyncUserEvents;
 extern const base::Feature kSyncUserFieldTrialEvents;
 extern const base::Feature kSyncUserConsentEvents;
diff --git a/components/sync/driver/sync_service.cc b/components/sync/driver/sync_service.cc
index e04efc7..aa3e6846 100644
--- a/components/sync/driver/sync_service.cc
+++ b/components/sync/driver/sync_service.cc
@@ -13,6 +13,10 @@
   on_destroy_.Run();
 }
 
+bool SyncService::IsSyncFeatureEnabled() const {
+  return GetDisableReasons() == DISABLE_REASON_NONE && IsFirstSetupComplete();
+}
+
 bool SyncService::CanSyncStart() const {
   return GetDisableReasons() == DISABLE_REASON_NONE;
 }
@@ -34,6 +38,9 @@
 }
 
 bool SyncService::IsSyncActive() const {
+  if (!IsSyncFeatureEnabled()) {
+    return false;
+  }
   switch (GetState()) {
     case State::DISABLED:
     case State::WAITING_FOR_START_REQUEST:
diff --git a/components/sync/driver/sync_service.h b/components/sync/driver/sync_service.h
index 8e33e05f..7f4e1cda 100644
--- a/components/sync/driver/sync_service.h
+++ b/components/sync/driver/sync_service.h
@@ -85,9 +85,8 @@
 
   // The overall state of the SyncService, in ascending order of "activeness".
   enum class State {
-    // Sync is disabled, e.g. due to enterprise policy, or because the user has
-    // disabled it, or simply because there is no authenticated user. Call
-    // GetDisableReasons to figure out which of these it is.
+    // Sync is inactive, e.g. due to enterprise policy, or simply because there
+    // is no authenticated user.
     DISABLED,
     // Sync can start in principle, but nothing has prodded it to actually do it
     // yet. Note that during subsequent browser startups, Sync starts
@@ -144,6 +143,8 @@
 
   // Returns the set of reasons that are keeping Sync disabled, as a bitmask of
   // DisableReason enum entries.
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be running
+  // even in the presence of disable reasons.
   virtual int GetDisableReasons() const = 0;
   // Helper that returns whether GetDisableReasons() contains the given |reason|
   // (possibly among others).
@@ -153,6 +154,8 @@
 
   // Returns the overall state of the SyncService. See the enum definition for
   // what the individual states mean.
+  // Note: This refers to Sync-the-transport, which may be active even if
+  // Sync-the-feature is disabled by the user, by enterprise policy, etc.
   // Note: If your question is "Are we actually sending this data to Google?" or
   // "Am I allowed to send this type of data to Google?", you probably want
   // syncer::GetUploadToGoogleState instead of this.
@@ -177,6 +180,13 @@
   // DERIVED STATE ACCESS
   //////////////////////////////////////////////////////////////////////////////
 
+  // Returns whether all conditions are satisfied for Sync-the-feature to start.
+  // This means that there are no disable reasons, and first-time Sync setup has
+  // been completed by the user.
+  // Note: This does not imply that Sync is actually running. Check GetState to
+  // get the current state of Sync-the-transport.
+  bool IsSyncFeatureEnabled() const;
+
   // DEPRECATED! Use GetDisableReasons/HasDisableReason instead.
   // Equivalent to "HasDisableReason(DISABLE_REASON_UNRECOVERABLE_ERROR)".
   bool HasUnrecoverableError() const;
@@ -184,16 +194,23 @@
   // DEPRECATED! Use GetState instead. Equivalent to
   // "GetState() == State::PENDING_DESIRED_CONFIGURATION ||
   // GetState() == State::CONFIGURING || GetState() == State::ACTIVE".
+  // Note: This refers to Sync-the-transport, which may be active even if
+  // Sync-the-feature is disabled by the user, by enterprise policy, etc.
   bool IsEngineInitialized() const;
 
   // DEPRECATED! Use GetDisableReasons/HasDisableReason instead.
   // Equivalent to having no disable reasons, i.e.
   // "GetDisableReasons() == DISABLE_REASON_NONE".
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be running
+  // even if this is false.
   bool CanSyncStart() const;
 
-  // DEPRECATED! Use GetState instead. Equivalent to
-  // "GetState() == State::CONFIGURING || GetState() == State::ACTIVE".
+  // Returns whether Sync-the-feature is active, which means GetState() is
+  // either State::CONFIGURING or State::ACTIVE and IsSyncFeatureEnabled() is
+  // true.
   // To see which datatypes are actually syncing, see GetActiveDataTypes().
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be active
+  // even if this is false.
   bool IsSyncActive() const;
 
   //////////////////////////////////////////////////////////////////////////////
@@ -204,15 +221,21 @@
   // if the user is customizing sync after already completing setup once).
   // SyncService uses this to determine if it's OK to start syncing, or if the
   // user is still setting up the initial sync configuration.
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be active
+  // independent of first-setup state.
   bool IsFirstSetupInProgress() const;
 
   // Whether the user has completed the initial Sync setup. This does not mean
   // that sync is currently running (due to delayed startup, unrecoverable
   // errors, or shutdown). If you want to know whether Sync is actually running,
   // use GetState instead.
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be active
+  // independent of first-setup state.
   virtual bool IsFirstSetupComplete() const = 0;
 
   // Called when Sync has been setup by the user and can be started.
+  // Note: This refers to Sync-the-feature. Sync-the-transport may be active
+  // independent of first-setup state.
   virtual void SetFirstSetupComplete() = 0;
 
   //////////////////////////////////////////////////////////////////////////////
@@ -262,6 +285,8 @@
   // engine should clear its data directory when it shuts down. Generally
   // KEEP_DATA is used when the user just stops sync, and CLEAR_DATA is used
   // when they sign out of the profile entirely.
+  // Note: This refers to Sync-the-feature. Sync-the-transport may remain active
+  // after calling this.
   virtual void RequestStop(SyncStopDataFate data_fate) = 0;
 
   // Called when a datatype (SyncableService) has a need for sync to start
diff --git a/components/sync/model_impl/forwarding_model_type_controller_delegate.cc b/components/sync/model_impl/forwarding_model_type_controller_delegate.cc
new file mode 100644
index 0000000..9da506b
--- /dev/null
+++ b/components/sync/model_impl/forwarding_model_type_controller_delegate.cc
@@ -0,0 +1,46 @@
+// Copyright 2018 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/sync/model_impl/forwarding_model_type_controller_delegate.h"
+
+#include <utility>
+
+namespace syncer {
+
+ForwardingModelTypeControllerDelegate::ForwardingModelTypeControllerDelegate(
+    ModelTypeControllerDelegate* other)
+    : other_(other) {
+  DCHECK(other_);
+}
+
+ForwardingModelTypeControllerDelegate::
+    ~ForwardingModelTypeControllerDelegate() {}
+
+void ForwardingModelTypeControllerDelegate::OnSyncStarting(
+    const DataTypeActivationRequest& request,
+    StartCallback callback) {
+  other_->OnSyncStarting(request, std::move(callback));
+}
+
+void ForwardingModelTypeControllerDelegate::OnSyncStopping(
+    SyncStopMetadataFate metadata_fate) {
+  other_->OnSyncStopping(metadata_fate);
+}
+
+void ForwardingModelTypeControllerDelegate::GetAllNodesForDebugging(
+    AllNodesCallback callback) {
+  other_->GetAllNodesForDebugging(std::move(callback));
+}
+
+void ForwardingModelTypeControllerDelegate::GetStatusCountersForDebugging(
+    StatusCountersCallback callback) {
+  other_->GetStatusCountersForDebugging(std::move(callback));
+}
+
+void ForwardingModelTypeControllerDelegate::
+    RecordMemoryUsageAndCountsHistograms() {
+  other_->RecordMemoryUsageAndCountsHistograms();
+}
+
+}  // namespace syncer
diff --git a/components/sync/model_impl/forwarding_model_type_controller_delegate.h b/components/sync/model_impl/forwarding_model_type_controller_delegate.h
new file mode 100644
index 0000000..4e6c9a6
--- /dev/null
+++ b/components/sync/model_impl/forwarding_model_type_controller_delegate.h
@@ -0,0 +1,42 @@
+// Copyright 2018 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_SYNC_MODEL_IMPL_FORWARDING_MODEL_TYPE_CONTROLLER_DELEGATE_H_
+#define COMPONENTS_SYNC_MODEL_IMPL_FORWARDING_MODEL_TYPE_CONTROLLER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "components/sync/model/model_type_controller_delegate.h"
+
+namespace syncer {
+
+// Trivial implementation of ModelTypeControllerDelegate that simply forwards
+// call to another delegate (no task posting involved). This is useful when an
+// API requires transferring ownership, but the calling site also wants to keep
+// ownership of the actual implementation, and can guarantee the lifetime
+// constraints.
+class ForwardingModelTypeControllerDelegate
+    : public ModelTypeControllerDelegate {
+ public:
+  // |other| must not be null and must outlive this object.
+  explicit ForwardingModelTypeControllerDelegate(
+      ModelTypeControllerDelegate* other);
+  ~ForwardingModelTypeControllerDelegate() override;
+
+  // ModelTypeControllerDelegate implementation.
+  void OnSyncStarting(const DataTypeActivationRequest& request,
+                      StartCallback callback) override;
+  void OnSyncStopping(SyncStopMetadataFate metadata_fate) override;
+  void GetAllNodesForDebugging(AllNodesCallback callback) override;
+  void GetStatusCountersForDebugging(StatusCountersCallback callback) override;
+  void RecordMemoryUsageAndCountsHistograms() override;
+
+ private:
+  ModelTypeControllerDelegate* const other_;
+
+  DISALLOW_COPY_AND_ASSIGN(ForwardingModelTypeControllerDelegate);
+};
+
+}  // namespace syncer
+
+#endif  // COMPONENTS_SYNC_MODEL_IMPL_FORWARDING_MODEL_TYPE_CONTROLLER_DELEGATE_H_
diff --git a/components/sync_bookmarks/BUILD.gn b/components/sync_bookmarks/BUILD.gn
index 8e3935dc..02920cff 100644
--- a/components/sync_bookmarks/BUILD.gn
+++ b/components/sync_bookmarks/BUILD.gn
@@ -14,12 +14,16 @@
     "bookmark_local_changes_builder.h",
     "bookmark_model_associator.cc",
     "bookmark_model_associator.h",
+    "bookmark_model_merger.cc",
+    "bookmark_model_merger.h",
     "bookmark_model_observer_impl.cc",
     "bookmark_model_observer_impl.h",
     "bookmark_model_type_processor.cc",
     "bookmark_model_type_processor.h",
     "bookmark_remote_updates_handler.cc",
     "bookmark_remote_updates_handler.h",
+    "bookmark_specifics_conversions.cc",
+    "bookmark_specifics_conversions.h",
     "bookmark_sync_service.cc",
     "bookmark_sync_service.h",
     "synced_bookmark_tracker.cc",
@@ -43,9 +47,11 @@
 
   sources = [
     "bookmark_data_type_controller_unittest.cc",
+    "bookmark_model_merger_unittest.cc",
     "bookmark_model_observer_impl_unittest.cc",
     "bookmark_model_type_processor_unittest.cc",
     "bookmark_remote_updates_handler_unittest.cc",
+    "bookmark_specifics_conversions_unittest.cc",
     "synced_bookmark_tracker_unittest.cc",
   ]
 
diff --git a/components/sync_bookmarks/bookmark_model_merger.cc b/components/sync_bookmarks/bookmark_model_merger.cc
new file mode 100644
index 0000000..a27f8ea
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_model_merger.cc
@@ -0,0 +1,360 @@
+// Copyright 2018 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/sync_bookmarks/bookmark_model_merger.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/guid.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/sync/base/hash_util.h"
+#include "components/sync/base/unique_position.h"
+#include "components/sync_bookmarks/bookmark_specifics_conversions.h"
+#include "components/sync_bookmarks/synced_bookmark_tracker.h"
+
+using syncer::EntityData;
+using syncer::UpdateResponseData;
+using syncer::UpdateResponseDataList;
+
+namespace sync_bookmarks {
+
+namespace {
+
+static const size_t kInvalidIndex = -1;
+
+// The sync protocol identifies top-level entities by means of well-known tags,
+// (aka server defined tags) which should not be confused with titles or client
+// tags that aren't supported by bookmarks (at the time of writing). Each tag
+// corresponds to a singleton instance of a particular top-level node in a
+// user's share; the tags are consistent across users. The tags allow us to
+// locate the specific folders whose contents we care about synchronizing,
+// without having to do a lookup by name or path.  The tags should not be made
+// user-visible. For example, the tag "bookmark_bar" represents the permanent
+// node for bookmarks bar in Chrome. The tag "other_bookmarks" represents the
+// permanent folder Other Bookmarks in Chrome.
+//
+// It is the responsibility of something upstream (at time of writing, the sync
+// server) to create these tagged nodes when initializing sync for the first
+// time for a user.  Thus, once the backend finishes initializing, the
+// ProfileSyncService can rely on the presence of tagged nodes.
+const char kBookmarkBarTag[] = "bookmark_bar";
+const char kMobileBookmarksTag[] = "synced_bookmarks";
+const char kOtherBookmarksTag[] = "other_bookmarks";
+
+const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
+
+// Heuristic to consider two nodes (local and remote) a match for the purpose of
+// merging. Two folders match if they have the same title, two bookmarks match
+// if they have the same title and url. A folder and a bookmark never match.
+bool NodesMatch(const bookmarks::BookmarkNode* local_node,
+                const EntityData& remote_node) {
+  if (local_node->is_folder() != remote_node.is_folder) {
+    return false;
+  }
+  const sync_pb::BookmarkSpecifics& specifics =
+      remote_node.specifics.bookmark();
+  if (local_node->GetTitle() != base::UTF8ToUTF16(specifics.title())) {
+    return false;
+  }
+  if (remote_node.is_folder) {
+    return true;
+  }
+  return local_node->url() == GURL(specifics.url());
+}
+
+// Tries to find a child local node under |local_parent| that matches
+// |remote_node| and returns the index of that node. Matching is decided using
+// NodesMatch(). It starts searching in the children list starting from position
+// |search_starting_child_index|. In case of no match is found, it returns
+// |kInvalidIndex|.
+size_t FindMatchingChildFor(const UpdateResponseData* remote_node,
+                            const bookmarks::BookmarkNode* local_parent,
+                            size_t search_starting_child_index) {
+  const EntityData& remote_node_update_entity = remote_node->entity.value();
+  for (int i = search_starting_child_index; i < local_parent->child_count();
+       ++i) {
+    const bookmarks::BookmarkNode* local_child = local_parent->GetChild(i);
+    if (NodesMatch(local_child, remote_node_update_entity)) {
+      return i;
+    }
+  }
+  return kInvalidIndex;
+}
+
+bool UniquePositionLessThan(const UpdateResponseData* a,
+                            const UpdateResponseData* b) {
+  const syncer::UniquePosition a_pos =
+      syncer::UniquePosition::FromProto(a->entity.value().unique_position);
+  const syncer::UniquePosition b_pos =
+      syncer::UniquePosition::FromProto(b->entity.value().unique_position);
+  return a_pos.LessThan(b_pos);
+}
+
+// Builds a map from node update to a vector of its children updates. The vector
+// is sorted according to the unique position information in each update. A
+// entry is only added for an update if it has children updates with the
+// exception of permanent folders. Updates of permanent folders always exist in
+// the returned folder. If they don't have children, each is asoociated with an
+// empty vector of updates. Returned map contains pointers to the elements in
+// |updates|.
+std::unordered_map<const UpdateResponseData*,
+                   std::vector<const UpdateResponseData*>>
+BuildUpdatesTreeWithoutTombstonesWithSortedChildren(
+    const UpdateResponseDataList* updates) {
+  std::unordered_map<const UpdateResponseData*,
+                     std::vector<const UpdateResponseData*>>
+      updates_tree;
+  std::unordered_map<base::StringPiece, const UpdateResponseData*,
+                     base::StringPieceHash>
+      id_to_updates;
+  // Tombstones carry only the sync id and cannot be merged with the local
+  // model. Hence, we ignore tombstones.
+  for (const UpdateResponseData& update : *updates) {
+    const EntityData& update_entity = update.entity.value();
+    if (update_entity.is_deleted()) {
+      continue;
+    }
+    id_to_updates[update_entity.id] = &update;
+  }
+
+  for (const UpdateResponseData& update : *updates) {
+    const EntityData& update_entity = update.entity.value();
+    if (update_entity.is_deleted()) {
+      continue;
+    }
+    // Permanent nodes should be handled differently. They must be added even
+    // if they don't have children associated to them because they are the roots
+    // from which the merge starts.
+    if (update_entity.parent_id == kBookmarksRootId ||
+        update_entity.parent_id == "0") {
+      // Make sure permanent node is added if it doesn't exist already.
+      updates_tree.emplace(&update, std::vector<const UpdateResponseData*>());
+      // No need to associate it with its parent (the root node). We start
+      // merging from permanent nodes.
+      continue;
+    }
+    if (!syncer::UniquePosition::FromProto(update_entity.unique_position)
+             .IsValid()) {
+      // Ignore updates with invalid positions.
+      DLOG(ERROR) << "Remote update with invalid position: "
+                  << update_entity.specifics.bookmark().title();
+      continue;
+    }
+    if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
+                                  update_entity.is_folder)) {
+      // Ignore updates with invalid specifics.
+      DLOG(ERROR) << "Remote update with invalid specifics";
+      continue;
+    }
+    const UpdateResponseData* parent_update =
+        id_to_updates[update_entity.parent_id];
+    updates_tree[parent_update].push_back(&update);
+  }
+
+  // Sort all child updates.
+  for (std::pair<const UpdateResponseData* const,
+                 std::vector<const UpdateResponseData*>>& pair : updates_tree) {
+    std::sort(pair.second.begin(), pair.second.end(), UniquePositionLessThan);
+  }
+  return updates_tree;
+}
+
+}  // namespace
+
+BookmarkModelMerger::BookmarkModelMerger(
+    const UpdateResponseDataList* updates,
+    bookmarks::BookmarkModel* bookmark_model,
+    SyncedBookmarkTracker* bookmark_tracker)
+    : updates_(updates),
+      bookmark_model_(bookmark_model),
+      bookmark_tracker_(bookmark_tracker),
+      updates_tree_(
+          BuildUpdatesTreeWithoutTombstonesWithSortedChildren(updates_)) {
+  DCHECK(bookmark_tracker_->IsEmpty());
+}
+
+BookmarkModelMerger::~BookmarkModelMerger() {}
+
+void BookmarkModelMerger::Merge() {
+  // Algorithm description:
+  // Match up the roots and recursively do the following:
+  // * For each sync node for the current sync parent node, find the best
+  //   matching bookmark node under the corresponding bookmark parent node.
+  //   If no matching node is found, create a new bookmark node in the same
+  //   position as the corresponding sync node.
+  //   If a matching node is found, update the properties of it from the
+  //   corresponding sync node.
+  // * When all children sync nodes are done, add the extra children bookmark
+  //   nodes to the sync parent node.
+  //
+  // The best match algorithm uses folder title or bookmark title/url to
+  // perform the primary match. If there are multiple match candidates it
+  // selects the first one.
+  // Associate permanent folders.
+  for (const UpdateResponseData& update : *updates_) {
+    const EntityData& update_entity = update.entity.value();
+    const bookmarks::BookmarkNode* permanent_folder =
+        GetPermanentFolder(update_entity);
+    if (!permanent_folder) {
+      continue;
+    }
+    MergeSubtree(permanent_folder, &update);
+  }
+  // TODO(crbug.com/516866): Check that both models match now.
+
+  // TODO(crbug.com/516866): What if no permanent nodes updates are sent from
+  // the server, check if this is a legit scenario.
+}
+
+void BookmarkModelMerger::MergeSubtree(
+    const bookmarks::BookmarkNode* local_node,
+    const UpdateResponseData* remote_update) {
+  const EntityData& remote_update_entity = remote_update->entity.value();
+  bookmark_tracker_->Add(
+      remote_update_entity.id, local_node, remote_update->response_version,
+      remote_update_entity.creation_time, remote_update_entity.unique_position,
+      remote_update_entity.specifics);
+  // If there are remote child updates, try to match them.
+  if (updates_tree_.count(remote_update) != 0) {
+    const std::vector<const UpdateResponseData*> remote_children =
+        updates_tree_.at(remote_update);
+    for (size_t remote_index = 0; remote_index < remote_children.size();
+         ++remote_index) {
+      // All local nodes up to |remote_index-1| have processed already. Look for
+      // a matching local node starting with the local node at postion
+      // |remote_index|.
+      const size_t local_index = FindMatchingChildFor(
+          /*remote_node=*/remote_children[remote_index],
+          /*local_parent=*/local_node,
+          /*search_starting_index=*/remote_index);
+      if (local_index == kInvalidIndex) {
+        // If no match found, create a corresponding local node.
+        ProcessRemoteCreation(remote_children[remote_index], local_node,
+                              remote_index);
+        continue;
+      }
+      const bookmarks::BookmarkNode* local_child =
+          local_node->GetChild(local_index);
+      // If local node isn't in the correct position, move it.
+      if (remote_index != local_index) {
+        bookmark_model_->Move(local_child, local_node, remote_index);
+      }
+      // Since nodes are matching, their subtrees should be merged as well.
+      MergeSubtree(local_child, remote_children[remote_index]);
+    }
+  }
+  // At this point all the children nodes of the parent sync node have
+  // corresponding children in the parent bookmark node and they are all in the
+  // right positions: from 0 to |updates_tree_.at(remote_update).size() - 1|. So
+  // the children starting from index |updates_tree_.at(remote_update).size()|
+  // in the parent bookmark node are the ones that are not present in the parent
+  // sync node and tracked yet. So create all of the remaining local nodes.
+  const int index_of_new_local_nodes =
+      updates_tree_.count(remote_update) > 0
+          ? updates_tree_.at(remote_update).size()
+          : 0;
+  for (int i = index_of_new_local_nodes; i < local_node->child_count(); ++i) {
+    ProcessLocalCreation(local_node, i);
+  }
+}
+
+void BookmarkModelMerger::ProcessRemoteCreation(
+    const UpdateResponseData* remote_update,
+    const bookmarks::BookmarkNode* local_parent,
+    int index) {
+  const EntityData& remote_update_entity = remote_update->entity.value();
+  const bookmarks::BookmarkNode* bookmark_node =
+      CreateBookmarkNodeFromSpecifics(
+          remote_update_entity.specifics.bookmark(), local_parent, index,
+          remote_update_entity.is_folder, bookmark_model_);
+  if (!bookmark_node) {
+    // We ignore bookmarks we can't add.
+    DLOG(ERROR) << "Failed to create bookmark node with title "
+                << remote_update_entity.specifics.bookmark().title()
+                << " and url "
+                << remote_update_entity.specifics.bookmark().url();
+    return;
+  }
+  bookmark_tracker_->Add(
+      remote_update_entity.id, bookmark_node, remote_update->response_version,
+      remote_update_entity.creation_time, remote_update_entity.unique_position,
+      remote_update_entity.specifics);
+  // If no child update, we are done.
+  if (updates_tree_.count(remote_update) == 0) {
+    return;
+  }
+  // Recusively, create local node for all child remote nodes.
+  int i = 0;
+  for (const UpdateResponseData* remote_child_update :
+       updates_tree_.at(remote_update)) {
+    ProcessRemoteCreation(remote_child_update, bookmark_node, i++);
+  }
+}
+
+void BookmarkModelMerger::ProcessLocalCreation(
+    const bookmarks::BookmarkNode* parent,
+    int index) {
+  DCHECK_GT(index, -1);
+  const SyncedBookmarkTracker::Entity* parent_entity =
+      bookmark_tracker_->GetEntityForBookmarkNode(parent);
+  // Since we are merging top down, parent entity must be tracked.
+  DCHECK(parent_entity);
+
+  // Similar to the diectory implementation here:
+  // https://cs.chromium.org/chromium/src/components/sync/syncable/mutable_entry.cc?l=237&gsn=CreateEntryKernel
+  // Assign a temp server id for the entity. Will be overriden by the actual
+  // server id upon receiving commit response.
+  const std::string sync_id = base::GenerateGUID();
+  const int64_t server_version = syncer::kUncommittedVersion;
+  const base::Time creation_time = base::Time::Now();
+  const std::string& suffix = syncer::GenerateSyncableBookmarkHash(
+      bookmark_tracker_->model_type_state().cache_guid(), sync_id);
+  syncer::UniquePosition pos;
+  // Locally created nodes aren't tracked and hence don't have a unique position
+  // yet so we need to produce new ones.
+  if (index == 0) {
+    pos = syncer::UniquePosition::InitialPosition(suffix);
+  } else {
+    const SyncedBookmarkTracker::Entity* predecessor_entity =
+        bookmark_tracker_->GetEntityForBookmarkNode(
+            parent->GetChild(index - 1));
+    pos = syncer::UniquePosition::After(
+        syncer::UniquePosition::FromProto(
+            predecessor_entity->metadata()->unique_position()),
+        suffix);
+  }
+
+  const bookmarks::BookmarkNode* node = parent->GetChild(index);
+  const sync_pb::EntitySpecifics specifics =
+      CreateSpecificsFromBookmarkNode(node);
+  bookmark_tracker_->Add(sync_id, node, server_version, creation_time,
+                         pos.ToProto(), specifics);
+  // Mark the entity that it needs to be committed.
+  bookmark_tracker_->IncrementSequenceNumber(sync_id);
+  for (int i = 0; i < node->child_count(); ++i) {
+    // If a local node hasn't matched with any remote entity, its descendants
+    // will neither.
+    ProcessLocalCreation(node, i);
+  }
+}
+
+const bookmarks::BookmarkNode* BookmarkModelMerger::GetPermanentFolder(
+    const EntityData& update_entity) const {
+  const bookmarks::BookmarkNode* permanent_folder = nullptr;
+  if (update_entity.server_defined_unique_tag == kBookmarkBarTag) {
+    permanent_folder = bookmark_model_->bookmark_bar_node();
+  } else if (update_entity.server_defined_unique_tag == kOtherBookmarksTag) {
+    permanent_folder = bookmark_model_->other_node();
+  } else if (update_entity.server_defined_unique_tag == kMobileBookmarksTag) {
+    permanent_folder = bookmark_model_->mobile_node();
+  }
+  return permanent_folder;
+}
+
+}  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_model_merger.h b/components/sync_bookmarks/bookmark_model_merger.h
new file mode 100644
index 0000000..580c44a
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_model_merger.h
@@ -0,0 +1,86 @@
+// Copyright 2018 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_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_
+#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/sync/engine/non_blocking_sync_common.h"
+
+namespace bookmarks {
+class BookmarkModel;
+class BookmarkNode;
+}  // namespace bookmarks
+
+namespace sync_bookmarks {
+
+class SyncedBookmarkTracker;
+
+// Responsible for merging local and remote bookmark models when bookmark sync
+// is enabled for the first time by the user (i.e. no sync metadata exists
+// locally), so we need a best-effort merge based on similarity. It implements
+// similar logic to that in BookmarkModelAssociator::AssociateModels() to be
+// used by the BookmarkModelTypeProcessor().
+class BookmarkModelMerger {
+ public:
+  // |updates|, |bookmark_model| and |bookmark_tracker| must not be null and
+  // must outlive this object.
+  BookmarkModelMerger(const syncer::UpdateResponseDataList* updates,
+                      bookmarks::BookmarkModel* bookmark_model,
+                      SyncedBookmarkTracker* bookmark_tracker);
+
+  ~BookmarkModelMerger();
+
+  // Merges the remote bookmark model represented as the |updates| received from
+  // the sync server and local bookmark model |bookmark_model|, and updates the
+  // model and |bookmark_tracker| (all of which are injected in the constructor)
+  // accordingly. On return, there will be a 1:1 mapping between bookmark nodes
+  // and metadata entities in the injected tracker.
+  void Merge();
+
+ private:
+  // Merges a local and a remote subtrees. The input nodes are two equivalent
+  // local and remote nodes. This method tries to recursively match their
+  // children. It updates the |bookmark_tracker_| accordingly.
+  void MergeSubtree(const bookmarks::BookmarkNode* local_node,
+                    const syncer::UpdateResponseData* remote_update);
+
+  // Creates a local bookmark node for a |remote_update|. The local node is
+  // created under |local_parent| at position |index|. If they the remote node
+  // has children, this method recursively creates them as well. It updates the
+  // |bookmark_tracker_| accordingly.
+  void ProcessRemoteCreation(const syncer::UpdateResponseData* remote_update,
+                             const bookmarks::BookmarkNode* local_parent,
+                             int index);
+
+  // Creates a server counter-part for the local node at position |index|
+  // under |parent|. If the local node has children, corresponding server nodes
+  // are created recursively. It updates the |bookmark_tracker_| accordingly and
+  // new nodes are marked to be committed.
+  void ProcessLocalCreation(const bookmarks::BookmarkNode* parent, int index);
+
+  // Gets the bookmark node corresponding to a permanent folder.
+  // |update_entity| must contain server_defined_unique_tag that is used to
+  // determine the corresponding permanent node.
+  const bookmarks::BookmarkNode* GetPermanentFolder(
+      const syncer::EntityData& update_entity) const;
+
+  const syncer::UpdateResponseDataList* const updates_;
+  bookmarks::BookmarkModel* const bookmark_model_;
+  SyncedBookmarkTracker* const bookmark_tracker_;
+  // Stores the tree of |updates_| as a map from a remote node to a
+  // vector of remote children. It's constructed in the c'tor.
+  const std::unordered_map<const syncer::UpdateResponseData*,
+                           std::vector<const syncer::UpdateResponseData*>>
+      updates_tree_;
+
+  DISALLOW_COPY_AND_ASSIGN(BookmarkModelMerger);
+};
+
+}  // namespace sync_bookmarks
+
+#endif  // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_MODEL_MERGER_H_
diff --git a/components/sync_bookmarks/bookmark_model_merger_unittest.cc b/components/sync_bookmarks/bookmark_model_merger_unittest.cc
new file mode 100644
index 0000000..363af8d
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_model_merger_unittest.cc
@@ -0,0 +1,393 @@
+// Copyright 2018 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/sync_bookmarks/bookmark_model_merger.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/sync/base/unique_position.h"
+#include "components/sync_bookmarks/synced_bookmark_tracker.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Eq;
+using testing::UnorderedElementsAre;
+
+namespace sync_bookmarks {
+
+namespace {
+
+const char kBookmarkBarId[] = "bookmark_bar_id";
+const char kBookmarkBarTag[] = "bookmark_bar";
+const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
+
+syncer::UpdateResponseData CreateUpdateResponseData(
+    const std::string& server_id,
+    const std::string& parent_id,
+    const std::string& title,
+    const std::string& url,
+    bool is_folder,
+    const syncer::UniquePosition& unique_position) {
+  syncer::EntityData data;
+  data.id = server_id;
+  data.parent_id = parent_id;
+  data.unique_position = unique_position.ToProto();
+
+  sync_pb::BookmarkSpecifics* bookmark_specifics =
+      data.specifics.mutable_bookmark();
+  bookmark_specifics->set_title(title);
+  bookmark_specifics->set_url(url);
+
+  data.is_folder = is_folder;
+  syncer::UpdateResponseData response_data;
+  response_data.entity = data.PassToPtr();
+  // Similar to what's done in the loopback_server.
+  response_data.response_version = 0;
+  return response_data;
+}
+
+syncer::UpdateResponseData CreateBookmarkBarNodeUpdateData() {
+  syncer::EntityData data;
+  data.id = kBookmarkBarId;
+  data.parent_id = kBookmarksRootId;
+  data.server_defined_unique_tag = kBookmarkBarTag;
+
+  data.specifics.mutable_bookmark();
+
+  syncer::UpdateResponseData response_data;
+  response_data.entity = data.PassToPtr();
+  // Similar to what's done in the loopback_server.
+  response_data.response_version = 0;
+  return response_data;
+}
+
+syncer::UniquePosition PositionOf(const bookmarks::BookmarkNode* node,
+                                  const SyncedBookmarkTracker& tracker) {
+  const SyncedBookmarkTracker::Entity* entity =
+      tracker.GetEntityForBookmarkNode(node);
+  return syncer::UniquePosition::FromProto(
+      entity->metadata()->unique_position());
+}
+
+bool PositionsInTrackerMatchModel(const bookmarks::BookmarkNode* node,
+                                  const SyncedBookmarkTracker& tracker) {
+  if (node->child_count() == 0) {
+    return true;
+  }
+  syncer::UniquePosition pos = PositionOf(node->GetChild(0), tracker);
+  for (int i = 1; i < node->child_count(); ++i) {
+    if (PositionOf(node->GetChild(i), tracker).LessThan(pos)) {
+      DLOG(ERROR) << "Position of " << node->GetChild(i)->GetTitle()
+                  << " is less than position of "
+                  << node->GetChild(i - 1)->GetTitle();
+      return false;
+    }
+    pos = PositionOf(node->GetChild(i), tracker);
+  }
+  for (int i = 0; i < node->child_count(); ++i) {
+    if (!PositionsInTrackerMatchModel(node->GetChild(i), tracker)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+TEST(BookmarkModelMergerTest, ShouldMergeLocalAndRemoteModels) {
+  const size_t kMaxEntries = 1000;
+
+  const std::string kFolder1Title = "folder1";
+  const std::string kFolder2Title = "folder2";
+  const std::string kFolder3Title = "folder3";
+
+  const std::string kUrl1Title = "url1";
+  const std::string kUrl2Title = "url2";
+  const std::string kUrl3Title = "url3";
+  const std::string kUrl4Title = "url4";
+
+  const std::string kUrl1 = "http://www.url1.com";
+  const std::string kUrl2 = "http://www.url2.com";
+  const std::string kUrl3 = "http://www.url3.com";
+  const std::string kUrl4 = "http://www.url4.com";
+  const std::string kAnotherUrl2 = "http://www.another-url2.com";
+
+  const std::string kFolder1Id = "Folder1Id";
+  const std::string kFolder3Id = "Folder3Id";
+  const std::string kUrl1Id = "Url1Id";
+  const std::string kUrl2Id = "Url2Id";
+  const std::string kUrl3Id = "Url3Id";
+  const std::string kUrl4Id = "Url4Id";
+
+  // -------- The local model --------
+  // bookmark_bar
+  //  |- folder 1
+  //    |- url1(http://www.url1.com)
+  //    |- url2(http://www.url2.com)
+  //  |- folder 2
+  //    |- url3(http://www.url3.com)
+  //    |- url4(http://www.url4.com)
+
+  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node =
+      bookmark_model->bookmark_bar_node();
+  const bookmarks::BookmarkNode* folder1 = bookmark_model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/0,
+      base::UTF8ToUTF16(kFolder1Title));
+
+  const bookmarks::BookmarkNode* folder2 = bookmark_model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/1,
+      base::UTF8ToUTF16(kFolder2Title));
+
+  bookmark_model->AddURL(
+      /*parent=*/folder1, /*index=*/0, base::UTF8ToUTF16(kUrl1Title),
+      GURL(kUrl1));
+  bookmark_model->AddURL(
+      /*parent=*/folder1, /*index=*/1, base::UTF8ToUTF16(kUrl2Title),
+      GURL(kUrl2));
+  bookmark_model->AddURL(
+      /*parent=*/folder2, /*index=*/0, base::UTF8ToUTF16(kUrl3Title),
+      GURL(kUrl3));
+  bookmark_model->AddURL(
+      /*parent=*/folder2, /*index=*/1, base::UTF8ToUTF16(kUrl4Title),
+      GURL(kUrl4));
+
+  // -------- The remote model --------
+  // bookmark_bar
+  //  |- folder 1
+  //    |- url1(http://www.url1.com)
+  //    |- url2(http://www.another-url2.com)
+  //  |- folder 3
+  //    |- url3(http://www.url3.com)
+  //    |- url4(http://www.url4.com)
+
+  const std::string suffix = syncer::UniquePosition::RandomSuffix();
+  syncer::UniquePosition posFolder1 =
+      syncer::UniquePosition::InitialPosition(suffix);
+  syncer::UniquePosition posFolder3 =
+      syncer::UniquePosition::After(posFolder1, suffix);
+
+  syncer::UniquePosition posUrl1 =
+      syncer::UniquePosition::InitialPosition(suffix);
+  syncer::UniquePosition posUrl2 =
+      syncer::UniquePosition::After(posUrl1, suffix);
+
+  syncer::UniquePosition posUrl3 =
+      syncer::UniquePosition::InitialPosition(suffix);
+  syncer::UniquePosition posUrl4 =
+      syncer::UniquePosition::After(posUrl3, suffix);
+
+  syncer::UpdateResponseDataList updates;
+  updates.push_back(CreateBookmarkBarNodeUpdateData());
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
+      /*url=*/std::string(),
+      /*is_folder=*/true, /*unique_position=*/posFolder1));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kUrl1Id, /*parent_id=*/kFolder1Id, kUrl1Title, kUrl1,
+      /*is_folder=*/false, /*unique_position=*/posUrl1));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kUrl2Id, /*parent_id=*/kFolder1Id, kUrl2Title, kAnotherUrl2,
+      /*is_folder=*/false, /*unique_position=*/posUrl2));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
+      /*url=*/std::string(),
+      /*is_folder=*/true, /*unique_position=*/posFolder3));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kUrl3Id, /*parent_id=*/kFolder3Id, kUrl3Title, kUrl3,
+      /*is_folder=*/false, /*unique_position=*/posUrl3));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kUrl4Id, /*parent_id=*/kFolder3Id, kUrl4Title, kUrl4,
+      /*is_folder=*/false, /*unique_position=*/posUrl4));
+
+  // -------- The expected merge outcome --------
+  // bookmark_bar
+  //  |- folder 1
+  //    |- url1(http://www.url1.com)
+  //    |- url2(http://www.another-url2.com)
+  //    |- url2(http://www.url2.com)
+  //  |- folder 3
+  //    |- url3(http://www.url3.com)
+  //    |- url4(http://www.url4.com)
+  //  |- folder 2
+  //    |- url3(http://www.url3.com)
+  //    |- url4(http://www.url4.com)
+
+  SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
+                                std::make_unique<sync_pb::ModelTypeState>());
+  BookmarkModelMerger(&updates, bookmark_model.get(), &tracker).Merge();
+  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(3));
+
+  // Verify Folder 1.
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder1Title)));
+  ASSERT_THAT(bookmark_bar_node->GetChild(0)->child_count(), Eq(3));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(0)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl1Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(0)->url(),
+              Eq(GURL(kUrl1)));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(1)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl2Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(1)->url(),
+              Eq(GURL(kAnotherUrl2)));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(2)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl2Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetChild(2)->url(),
+              Eq(GURL(kUrl2)));
+
+  // Verify Folder 3.
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder3Title)));
+  ASSERT_THAT(bookmark_bar_node->GetChild(1)->child_count(), Eq(2));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetChild(0)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl3Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetChild(0)->url(),
+              Eq(GURL(kUrl3)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetChild(1)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl4Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetChild(1)->url(),
+              Eq(GURL(kUrl4)));
+
+  // Verify Folder 2.
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder2Title)));
+  ASSERT_THAT(bookmark_bar_node->GetChild(2)->child_count(), Eq(2));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetChild(0)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl3Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetChild(0)->url(),
+              Eq(GURL(kUrl3)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetChild(1)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kUrl4Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetChild(1)->url(),
+              Eq(GURL(kUrl4)));
+
+  // Verify the tracker contents.
+  EXPECT_THAT(tracker.TrackedEntitiesCountForTest(), Eq(11U));
+  std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
+      tracker.GetEntitiesWithLocalChanges(kMaxEntries);
+
+  EXPECT_THAT(local_changes.size(), Eq(4U));
+  std::vector<const bookmarks::BookmarkNode*> nodes_with_local_changes;
+  for (const SyncedBookmarkTracker::Entity* local_change : local_changes) {
+    nodes_with_local_changes.push_back(local_change->bookmark_node());
+  }
+  // Verify that url2(http://www.url2.com), Folder 2 and children have
+  // corresponding update.
+  EXPECT_THAT(
+      nodes_with_local_changes,
+      UnorderedElementsAre(bookmark_bar_node->GetChild(0)->GetChild(2),
+                           bookmark_bar_node->GetChild(2),
+                           bookmark_bar_node->GetChild(2)->GetChild(0),
+                           bookmark_bar_node->GetChild(2)->GetChild(1)));
+
+  // Verify positions in tracker.
+  EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, tracker));
+}
+
+TEST(BookmarkModelMergerTest, ShouldMergeRemoteReorderToLocalModel) {
+  const size_t kMaxEntries = 1000;
+
+  const std::string kFolder1Title = "folder1";
+  const std::string kFolder2Title = "folder2";
+  const std::string kFolder3Title = "folder3";
+
+  const std::string kFolder1Id = "Folder1Id";
+  const std::string kFolder2Id = "Folder2Id";
+  const std::string kFolder3Id = "Folder3Id";
+
+  // -------- The local model --------
+  // bookmark_bar
+  //  |- folder 1
+  //  |- folder 2
+  //  |- folder 3
+
+  std::unique_ptr<bookmarks::BookmarkModel> bookmark_model =
+      bookmarks::TestBookmarkClient::CreateModel();
+
+  const bookmarks::BookmarkNode* bookmark_bar_node =
+      bookmark_model->bookmark_bar_node();
+  bookmark_model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/0,
+      base::UTF8ToUTF16(kFolder1Title));
+
+  bookmark_model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/1,
+      base::UTF8ToUTF16(kFolder2Title));
+
+  bookmark_model->AddFolder(
+      /*parent=*/bookmark_bar_node, /*index=*/2,
+      base::UTF8ToUTF16(kFolder3Title));
+
+  // -------- The remote model --------
+  // bookmark_bar
+  //  |- folder 1
+  //  |- folder 3
+  //  |- folder 2
+
+  const std::string suffix = syncer::UniquePosition::RandomSuffix();
+  syncer::UniquePosition posFolder1 =
+      syncer::UniquePosition::InitialPosition(suffix);
+  syncer::UniquePosition posFolder3 =
+      syncer::UniquePosition::After(posFolder1, suffix);
+  syncer::UniquePosition posFolder2 =
+      syncer::UniquePosition::After(posFolder3, suffix);
+
+  syncer::UpdateResponseDataList updates;
+  updates.push_back(CreateBookmarkBarNodeUpdateData());
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kFolder1Id, /*parent_id=*/kBookmarkBarId, kFolder1Title,
+      /*url=*/std::string(),
+      /*is_folder=*/true, /*unique_position=*/posFolder1));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kFolder2Id, /*parent_id=*/kBookmarkBarId, kFolder2Title,
+      /*url=*/std::string(),
+      /*is_folder=*/true, /*unique_position=*/posFolder2));
+  updates.push_back(CreateUpdateResponseData(
+      /*server_id=*/kFolder3Id, /*parent_id=*/kBookmarkBarId, kFolder3Title,
+      /*url=*/std::string(),
+      /*is_folder=*/true, /*unique_position=*/posFolder3));
+
+  // -------- The expected merge outcome --------
+  // bookmark_bar
+  //  |- folder 1
+  //  |- folder 3
+  //  |- folder 2
+
+  SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
+                                std::make_unique<sync_pb::ModelTypeState>());
+  BookmarkModelMerger(&updates, bookmark_model.get(), &tracker).Merge();
+  ASSERT_THAT(bookmark_bar_node->child_count(), Eq(3));
+
+  EXPECT_THAT(bookmark_bar_node->GetChild(0)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder1Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(1)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder3Title)));
+  EXPECT_THAT(bookmark_bar_node->GetChild(2)->GetTitle(),
+              Eq(base::ASCIIToUTF16(kFolder2Title)));
+
+  // Verify the tracker contents.
+  EXPECT_THAT(tracker.TrackedEntitiesCountForTest(), Eq(4U));
+
+  // There should be no local changes.
+  std::vector<const SyncedBookmarkTracker::Entity*> local_changes =
+      tracker.GetEntitiesWithLocalChanges(kMaxEntries);
+  EXPECT_THAT(local_changes.size(), Eq(0U));
+
+  // Verify positions in tracker.
+  EXPECT_TRUE(PositionsInTrackerMatchModel(bookmark_bar_node, tracker));
+}
+
+}  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_model_observer_impl.cc b/components/sync_bookmarks/bookmark_model_observer_impl.cc
index 91d40c7b..073fd7a 100644
--- a/components/sync_bookmarks/bookmark_model_observer_impl.cc
+++ b/components/sync_bookmarks/bookmark_model_observer_impl.cc
@@ -13,45 +13,11 @@
 #include "components/sync/base/hash_util.h"
 #include "components/sync/base/unique_position.h"
 #include "components/sync/engine/non_blocking_sync_common.h"
+#include "components/sync_bookmarks/bookmark_specifics_conversions.h"
 #include "components/sync_bookmarks/synced_bookmark_tracker.h"
 
 namespace sync_bookmarks {
 
-namespace {
-
-void UpdateBookmarkSpecificsMetaInfo(
-    const bookmarks::BookmarkNode::MetaInfoMap* metainfo_map,
-    sync_pb::BookmarkSpecifics* bm_specifics) {
-  // TODO(crbug.com/516866): update the implementation to be similar to the
-  // directory implementation
-  // https://cs.chromium.org/chromium/src/components/sync_bookmarks/bookmark_change_processor.cc?l=882&rcl=f38001d936d8b2abb5743e85cbc88c72746ae3d2
-  for (const std::pair<std::string, std::string>& pair : *metainfo_map) {
-    sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
-    meta_info->set_key(pair.first);
-    meta_info->set_value(pair.second);
-  }
-}
-
-sync_pb::EntitySpecifics CreateSpecificsFromBookmarkNode(
-    const bookmarks::BookmarkNode* node) {
-  sync_pb::EntitySpecifics specifics;
-  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
-  bm_specifics->set_url(node->url().spec());
-  // TODO(crbug.com/516866): Set the favicon.
-  bm_specifics->set_title(base::UTF16ToUTF8(node->GetTitle()));
-  bm_specifics->set_creation_time_us(
-      node->date_added().ToDeltaSinceWindowsEpoch().InMicroseconds());
-
-  bm_specifics->set_icon_url(node->icon_url() ? node->icon_url()->spec()
-                                              : std::string());
-  if (node->GetMetaInfoMap()) {
-    UpdateBookmarkSpecificsMetaInfo(node->GetMetaInfoMap(), bm_specifics);
-  }
-  return specifics;
-}
-
-}  // namespace
-
 BookmarkModelObserverImpl::BookmarkModelObserverImpl(
     const base::RepeatingClosure& nudge_for_commit_closure,
     SyncedBookmarkTracker* bookmark_tracker)
diff --git a/components/sync_bookmarks/bookmark_model_type_processor.cc b/components/sync_bookmarks/bookmark_model_type_processor.cc
index 2068a1e..6170d3c 100644
--- a/components/sync_bookmarks/bookmark_model_type_processor.cc
+++ b/components/sync_bookmarks/bookmark_model_type_processor.cc
@@ -17,6 +17,7 @@
 #include "components/sync/model/data_type_activation_request.h"
 #include "components/sync/protocol/bookmark_model_metadata.pb.h"
 #include "components/sync_bookmarks/bookmark_local_changes_builder.h"
+#include "components/sync_bookmarks/bookmark_model_merger.h"
 #include "components/sync_bookmarks/bookmark_model_observer_impl.h"
 #include "components/sync_bookmarks/bookmark_remote_updates_handler.h"
 #include "components/undo/bookmark_undo_utils.h"
@@ -93,7 +94,9 @@
 
 void BookmarkModelTypeProcessor::DisconnectSync() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  NOTIMPLEMENTED();
+  DCHECK(worker_);
+  DVLOG(1) << "Disconnecting sync for Bookmarks";
+  worker_.reset();
 }
 
 void BookmarkModelTypeProcessor::GetLocalChanges(
@@ -135,10 +138,20 @@
   DCHECK(model_type_state.initial_sync_done());
 
   if (!bookmark_tracker_) {
-    // TODO(crbug.com/516866): Implement the merge logic.
     StartTrackingMetadata(
         std::vector<NodeMetadataPair>(),
         std::make_unique<sync_pb::ModelTypeState>(model_type_state));
+    {
+      ScopedRemoteUpdateBookmarks update_bookmarks(
+          bookmark_model_, bookmark_undo_service_,
+          bookmark_model_observer_.get());
+
+      BookmarkModelMerger(&updates, bookmark_model_, bookmark_tracker_.get())
+          .Merge();
+    }
+    schedule_save_closure_.Run();
+    NudgeForCommitIfNeeded();
+    return;
   }
   // TODO(crbug.com/516866): Set the model type state.
 
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.cc b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
index 67ba6ef1..3c8bd6d4 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.cc
@@ -10,107 +10,21 @@
 #include <utility>
 
 #include "base/strings/string_piece.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node.h"
 #include "components/sync/base/unique_position.h"
 #include "components/sync/protocol/unique_position.pb.h"
+#include "components/sync_bookmarks/bookmark_specifics_conversions.h"
 
 namespace sync_bookmarks {
 
 namespace {
 
-// The sync protocol identifies top-level entities by means of well-known tags,
-// (aka server defined tags) which should not be confused with titles or client
-// tags that aren't supported by bookmarks (at the time of writing). Each tag
-// corresponds to a singleton instance of a particular top-level node in a
-// user's share; the tags are consistent across users. The tags allow us to
-// locate the specific folders whose contents we care about synchronizing,
-// without having to do a lookup by name or path.  The tags should not be made
-// user-visible. For example, the tag "bookmark_bar" represents the permanent
-// node for bookmarks bar in Chrome. The tag "other_bookmarks" represents the
-// permanent folder Other Bookmarks in Chrome.
-//
-// It is the responsibility of something upstream (at time of writing, the sync
-// server) to create these tagged nodes when initializing sync for the first
-// time for a user.  Thus, once the backend finishes initializing, the
-// ProfileSyncService can rely on the presence of tagged nodes.
-const char kBookmarkBarTag[] = "bookmark_bar";
-const char kMobileBookmarksTag[] = "synced_bookmarks";
-const char kOtherBookmarksTag[] = "other_bookmarks";
-
 // Id is created by concatenating the specifics field number and the server tag
 // similar to LookbackServerEntity::CreateId() that uses
 // GetSpecificsFieldNumberFromModelType() to compute the field number.
 const char kBookmarksRootId[] = "32904_google_chrome_bookmarks";
-
-// |sync_entity| must contain a bookmark specifics.
-// Metainfo entries must have unique keys.
-bookmarks::BookmarkNode::MetaInfoMap GetBookmarkMetaInfo(
-    const syncer::EntityData& sync_entity) {
-  const sync_pb::BookmarkSpecifics& specifics =
-      sync_entity.specifics.bookmark();
-  bookmarks::BookmarkNode::MetaInfoMap meta_info_map;
-  for (const sync_pb::MetaInfo& meta_info : specifics.meta_info()) {
-    meta_info_map[meta_info.key()] = meta_info.value();
-  }
-  DCHECK_EQ(static_cast<size_t>(specifics.meta_info_size()),
-            meta_info_map.size());
-  return meta_info_map;
-}
-
-// Creates a bookmark node under the given parent node from the given sync node.
-// Returns the newly created node. |sync_entity| must contain a bookmark
-// specifics with Metainfo entries having unique keys.
-const bookmarks::BookmarkNode* CreateBookmarkNode(
-    const syncer::EntityData& sync_entity,
-    const bookmarks::BookmarkNode* parent,
-    bookmarks::BookmarkModel* model,
-    int index) {
-  DCHECK(parent);
-  DCHECK(model);
-
-  const sync_pb::BookmarkSpecifics& specifics =
-      sync_entity.specifics.bookmark();
-  bookmarks::BookmarkNode::MetaInfoMap metainfo =
-      GetBookmarkMetaInfo(sync_entity);
-  if (sync_entity.is_folder) {
-    return model->AddFolderWithMetaInfo(
-        parent, index, base::UTF8ToUTF16(specifics.title()), &metainfo);
-  }
-  // 'creation_time_us' was added in M24. Assume a time of 0 means now.
-  const int64_t create_time_us = specifics.creation_time_us();
-  base::Time create_time =
-      (create_time_us == 0)
-          ? base::Time::Now()
-          : base::Time::FromDeltaSinceWindowsEpoch(
-                // Use FromDeltaSinceWindowsEpoch because create_time_us has
-                // always used the Windows epoch.
-                base::TimeDelta::FromMicroseconds(create_time_us));
-  return model->AddURLWithCreationTimeAndMetaInfo(
-      parent, index, base::UTF8ToUTF16(specifics.title()),
-      GURL(specifics.url()), create_time, &metainfo);
-  // TODO(crbug.com/516866): Add the favicon related code.
-}
-
-// Check whether an incoming specifics represent a valid bookmark or not.
-// |is_folder| is whether this specifics is for a folder or not.
-// Folders and tomstones entail different validation conditions.
-bool IsValidBookmark(const sync_pb::BookmarkSpecifics& specifics,
-                     bool is_folder) {
-  if (specifics.ByteSize() == 0) {
-    DLOG(ERROR) << "Invalid bookmark: empty specifics.";
-    return false;
-  }
-  if (is_folder) {
-    return true;
-  }
-  if (!GURL(specifics.url()).is_valid()) {
-    DLOG(ERROR) << "Invalid bookmark: invalid url in the specifics.";
-    return false;
-  }
-  return true;
-}
+const char kMobileBookmarksTag[] = "synced_bookmarks";
 
 // Recursive method to traverse a forest created by ReorderUpdates() to to
 // emit updates in top-down order. |ordered_updates| must not be null because
@@ -282,23 +196,24 @@
 
 void BookmarkRemoteUpdatesHandler::ProcessRemoteCreate(
     const syncer::UpdateResponseData& update) {
-  // Because the Synced Bookmarks node can be created server side, it's possible
-  // it'll arrive at the client as an update. In that case it won't have been
-  // associated at startup, the GetChromeNodeFromSyncId call above will return
-  // null, and we won't detect it as a permanent node, resulting in us trying to
-  // create it here (which will fail). Therefore, we add special logic here just
-  // to detect the Synced Bookmarks folder.
   const syncer::EntityData& update_entity = update.entity.value();
   DCHECK(!update_entity.is_deleted());
-  if (update_entity.parent_id == kBookmarksRootId) {
-    // Associate permanent folders.
-    // TODO(crbug.com/516866): Method documentation says this method should be
-    // used in initial sync only. Make sure this is the case.
-    AssociatePermanentFolder(update);
+  // Because the Synced Bookmarks node can be created server side, it's possible
+  // it'll arrive at the client as an update.
+  if (update_entity.server_defined_unique_tag == kMobileBookmarksTag) {
+    bookmark_tracker_->Add(update_entity.id, bookmark_model_->mobile_node(),
+                           update.response_version, update_entity.creation_time,
+                           update_entity.unique_position,
+                           update_entity.specifics);
     return;
   }
-  if (!IsValidBookmark(update_entity.specifics.bookmark(),
-                       update_entity.is_folder)) {
+  if (update_entity.parent_id == kBookmarksRootId) {
+    DLOG(ERROR) << "Permanent nodes other than the Synced Bookmarks node "
+                   "should have been merged during intial sync.";
+    return;
+  }
+  if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
+                                update_entity.is_folder)) {
     // Ignore creations with invalid specifics.
     DLOG(ERROR) << "Couldn't add bookmark with an invalid specifics.";
     return;
@@ -311,10 +226,12 @@
                 << ", parent id = " << update_entity.parent_id;
     return;
   }
-  const bookmarks::BookmarkNode* bookmark_node = CreateBookmarkNode(
-      update_entity, parent_node, bookmark_model_,
-      ComputeChildNodeIndex(parent_node, update_entity.unique_position,
-                            bookmark_tracker_));
+  const bookmarks::BookmarkNode* bookmark_node =
+      CreateBookmarkNodeFromSpecifics(
+          update_entity.specifics.bookmark(), parent_node,
+          ComputeChildNodeIndex(parent_node, update_entity.unique_position,
+                                bookmark_tracker_),
+          update_entity.is_folder, bookmark_model_);
   if (!bookmark_node) {
     // We ignore bookmarks we can't add.
     DLOG(ERROR) << "Failed to create bookmark node with title "
@@ -338,8 +255,8 @@
             bookmark_tracker_->GetEntityForSyncId(update_entity.id));
   // Must not be a deletion.
   DCHECK(!update_entity.is_deleted());
-  if (!IsValidBookmark(update_entity.specifics.bookmark(),
-                       update_entity.is_folder)) {
+  if (!IsValidBookmarkSpecifics(update_entity.specifics.bookmark(),
+                                update_entity.is_folder)) {
     // Ignore updates with invalid specifics.
     DLOG(ERROR) << "Couldn't update bookmark with an invalid specifics.";
     return;
@@ -385,15 +302,8 @@
                 << (node->is_folder() ? "folder" : "bookmark");
     return;
   }
-  const sync_pb::BookmarkSpecifics& specifics =
-      update_entity.specifics.bookmark();
-  if (!update_entity.is_folder) {
-    bookmark_model_->SetURL(node, GURL(specifics.url()));
-  }
-
-  bookmark_model_->SetTitle(node, base::UTF8ToUTF16(specifics.title()));
-  // TODO(crbug.com/516866): Add the favicon related code.
-  bookmark_model_->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(update_entity));
+  UpdateBookmarkNodeFromSpecifics(update_entity.specifics.bookmark(), node,
+                                  bookmark_model_);
   // Compute index information before updating the |bookmark_tracker_|.
   const int old_index = old_parent->GetIndexOf(node);
   const int new_index = ComputeChildNodeIndex(
@@ -466,26 +376,4 @@
   return parent_entity->bookmark_node();
 }
 
-void BookmarkRemoteUpdatesHandler::AssociatePermanentFolder(
-    const syncer::UpdateResponseData& update) {
-  const syncer::EntityData& update_entity = update.entity.value();
-  DCHECK_EQ(update_entity.parent_id, kBookmarksRootId);
-
-  const bookmarks::BookmarkNode* permanent_node = nullptr;
-  if (update_entity.server_defined_unique_tag == kBookmarkBarTag) {
-    permanent_node = bookmark_model_->bookmark_bar_node();
-  } else if (update_entity.server_defined_unique_tag == kOtherBookmarksTag) {
-    permanent_node = bookmark_model_->other_node();
-  } else if (update_entity.server_defined_unique_tag == kMobileBookmarksTag) {
-    permanent_node = bookmark_model_->mobile_node();
-  }
-
-  if (permanent_node != nullptr) {
-    bookmark_tracker_->Add(update_entity.id, permanent_node,
-                           update.response_version, update_entity.creation_time,
-                           update_entity.unique_position,
-                           update_entity.specifics);
-  }
-}
-
 }  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler.h b/components/sync_bookmarks/bookmark_remote_updates_handler.h
index 9890a41..c39f8d4 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler.h
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler.h
@@ -23,7 +23,7 @@
 // server.
 class BookmarkRemoteUpdatesHandler {
  public:
-  // |bookmark_model| and |bookmark_tracker| must not be null and most outlive
+  // |bookmark_model| and |bookmark_tracker| must not be null and must outlive
   // this object.
   BookmarkRemoteUpdatesHandler(bookmarks::BookmarkModel* bookmark_model,
                                SyncedBookmarkTracker* bookmark_tracker);
@@ -77,14 +77,6 @@
   // from |bookmark_tracker_|.
   void RemoveEntityAndChildrenFromTracker(const bookmarks::BookmarkNode* node);
 
-  // Associates the permanent bookmark folders with the corresponding server
-  // side ids and registers the association in |bookmark_tracker_|.
-  // |update_entity| must contain server_defined_unique_tag that is used to
-  // determine the corresponding permanent node. All permanent nodes are assumed
-  // to be directly children nodes of |kBookmarksRootId|. This method is used in
-  // the initial sync cycle only.
-  void AssociatePermanentFolder(const syncer::UpdateResponseData& update);
-
   bookmarks::BookmarkModel* const bookmark_model_;
   SyncedBookmarkTracker* const bookmark_tracker_;
 
diff --git a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
index 5fa31bc..2f71d44 100644
--- a/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
+++ b/components/sync_bookmarks/bookmark_remote_updates_handler_unittest.cc
@@ -16,6 +16,7 @@
 #include "components/sync/base/model_type.h"
 #include "components/sync/base/unique_position.h"
 #include "components/sync/protocol/unique_position.pb.h"
+#include "components/sync_bookmarks/bookmark_model_merger.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -198,6 +199,12 @@
       bookmarks::TestBookmarkClient::CreateModel();
   SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
                                 std::make_unique<sync_pb::ModelTypeState>());
+  const syncer::UpdateResponseDataList bookmark_bar_updates = {
+      CreateBookmarkBarNodeUpdateData()};
+  // TODO(crbug.com/516866): Create a test fixture that would encapsulate
+  // the merge functionality for all relevant tests.
+  BookmarkModelMerger(&bookmark_bar_updates, bookmark_model.get(), &tracker)
+      .Merge();
 
   const std::string kId0 = "id0";
   const std::string kId1 = "id1";
@@ -205,7 +212,6 @@
 
   // Constuct the updates list to have creations randomly ordered.
   syncer::UpdateResponseDataList updates;
-  updates.push_back(CreateBookmarkBarNodeUpdateData());
   updates.push_back(CreateUpdateResponseData(/*server_id=*/kId2,
                                              /*parent_id=*/kId1,
                                              /*is_deletion=*/false));
@@ -307,6 +313,10 @@
       bookmarks::TestBookmarkClient::CreateModel();
   SyncedBookmarkTracker tracker(std::vector<NodeMetadataPair>(),
                                 std::make_unique<sync_pb::ModelTypeState>());
+  const syncer::UpdateResponseDataList bookmark_bar_updates = {
+      CreateBookmarkBarNodeUpdateData()};
+  BookmarkModelMerger(&bookmark_bar_updates, bookmark_model.get(), &tracker)
+      .Merge();
 
   const std::string kId0 = "id0";
   const std::string kId1 = "id1";
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.cc b/components/sync_bookmarks/bookmark_specifics_conversions.cc
new file mode 100644
index 0000000..9030829
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.cc
@@ -0,0 +1,127 @@
+// Copyright 2018 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/sync_bookmarks/bookmark_specifics_conversions.h"
+
+#include <string>
+#include <unordered_set>
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/sync/protocol/sync.pb.h"
+#include "url/gurl.h"
+
+namespace sync_bookmarks {
+
+namespace {
+
+void UpdateBookmarkSpecificsMetaInfo(
+    const bookmarks::BookmarkNode::MetaInfoMap* metainfo_map,
+    sync_pb::BookmarkSpecifics* bm_specifics) {
+  // TODO(crbug.com/516866): update the implementation to be similar to the
+  // directory implementation
+  // https://cs.chromium.org/chromium/src/components/sync_bookmarks/bookmark_change_processor.cc?l=882&rcl=f38001d936d8b2abb5743e85cbc88c72746ae3d2
+  for (const std::pair<std::string, std::string>& pair : *metainfo_map) {
+    sync_pb::MetaInfo* meta_info = bm_specifics->add_meta_info();
+    meta_info->set_key(pair.first);
+    meta_info->set_value(pair.second);
+  }
+}
+
+// Metainfo entries in |specifics| must have unique keys.
+bookmarks::BookmarkNode::MetaInfoMap GetBookmarkMetaInfo(
+    const sync_pb::BookmarkSpecifics& specifics) {
+  bookmarks::BookmarkNode::MetaInfoMap meta_info_map;
+  for (const sync_pb::MetaInfo& meta_info : specifics.meta_info()) {
+    meta_info_map[meta_info.key()] = meta_info.value();
+  }
+  DCHECK_EQ(static_cast<size_t>(specifics.meta_info_size()),
+            meta_info_map.size());
+  return meta_info_map;
+}
+
+}  // namespace
+
+sync_pb::EntitySpecifics CreateSpecificsFromBookmarkNode(
+    const bookmarks::BookmarkNode* node) {
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
+  bm_specifics->set_url(node->url().spec());
+  // TODO(crbug.com/516866): Set the favicon.
+  bm_specifics->set_title(base::UTF16ToUTF8(node->GetTitle()));
+  bm_specifics->set_creation_time_us(
+      node->date_added().ToDeltaSinceWindowsEpoch().InMicroseconds());
+
+  bm_specifics->set_icon_url(node->icon_url() ? node->icon_url()->spec()
+                                              : std::string());
+  if (node->GetMetaInfoMap()) {
+    UpdateBookmarkSpecificsMetaInfo(node->GetMetaInfoMap(), bm_specifics);
+  }
+  return specifics;
+}
+
+const bookmarks::BookmarkNode* CreateBookmarkNodeFromSpecifics(
+    const sync_pb::BookmarkSpecifics& specifics,
+    const bookmarks::BookmarkNode* parent,
+
+    int index,
+    bool is_folder,
+    bookmarks::BookmarkModel* model) {
+  DCHECK(parent);
+  DCHECK(model);
+
+  bookmarks::BookmarkNode::MetaInfoMap metainfo =
+      GetBookmarkMetaInfo(specifics);
+  if (is_folder) {
+    return model->AddFolderWithMetaInfo(
+        parent, index, base::UTF8ToUTF16(specifics.title()), &metainfo);
+  }
+  const int64_t create_time_us = specifics.creation_time_us();
+  base::Time create_time = base::Time::FromDeltaSinceWindowsEpoch(
+      // Use FromDeltaSinceWindowsEpoch because create_time_us has
+      // always used the Windows epoch.
+      base::TimeDelta::FromMicroseconds(create_time_us));
+  return model->AddURLWithCreationTimeAndMetaInfo(
+      parent, index, base::UTF8ToUTF16(specifics.title()),
+      GURL(specifics.url()), create_time, &metainfo);
+  // TODO(crbug.com/516866): Add the favicon related code.
+}
+
+void UpdateBookmarkNodeFromSpecifics(
+    const sync_pb::BookmarkSpecifics& specifics,
+    const bookmarks::BookmarkNode* node,
+    bookmarks::BookmarkModel* model) {
+  if (!node->is_folder()) {
+    model->SetURL(node, GURL(specifics.url()));
+  }
+
+  model->SetTitle(node, base::UTF8ToUTF16(specifics.title()));
+  // TODO(crbug.com/516866): Add the favicon related code.
+  model->SetNodeMetaInfoMap(node, GetBookmarkMetaInfo(specifics));
+}
+
+bool IsValidBookmarkSpecifics(const sync_pb::BookmarkSpecifics& specifics,
+                              bool is_folder) {
+  if (specifics.ByteSize() == 0) {
+    DLOG(ERROR) << "Invalid bookmark: empty specifics.";
+    return false;
+  }
+  if (!is_folder && !GURL(specifics.url()).is_valid()) {
+    DLOG(ERROR) << "Invalid bookmark: invalid url in the specifics.";
+    return false;
+  }
+  // Verify all keys in meta_info are unique.
+  std::unordered_set<base::StringPiece, base::StringPieceHash> keys;
+  for (const sync_pb::MetaInfo& meta_info : specifics.meta_info()) {
+    if (!keys.insert(meta_info.key()).second) {
+      DLOG(ERROR) << "Invalid bookmark: keys in meta_info aren't unique.";
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions.h b/components/sync_bookmarks/bookmark_specifics_conversions.h
new file mode 100644
index 0000000..c03cc612c
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_specifics_conversions.h
@@ -0,0 +1,49 @@
+// Copyright 2018 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_SYNC_BOOKMARKS_BOOKMARK_SPECIFICS_CONVERSIONS_H_
+#define COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_SPECIFICS_CONVERSIONS_H_
+
+namespace bookmarks {
+class BookmarkModel;
+class BookmarkNode;
+}  // namespace bookmarks
+
+namespace sync_pb {
+class BookmarkSpecifics;
+class EntitySpecifics;
+}  // namespace sync_pb
+
+namespace sync_bookmarks {
+
+sync_pb::EntitySpecifics CreateSpecificsFromBookmarkNode(
+    const bookmarks::BookmarkNode* node);
+
+// Creates a bookmark node under the given parent node from the given specifics.
+// Returns the newly created node. Callers must verify that
+// |specifics| passes the IsValidBookmarkSpecifics().
+const bookmarks::BookmarkNode* CreateBookmarkNodeFromSpecifics(
+    const sync_pb::BookmarkSpecifics& specifics,
+    const bookmarks::BookmarkNode* parent,
+    int index,
+    bool is_folder,
+    bookmarks::BookmarkModel* model);
+
+// Updates the bookmark node |node| with the data in |specifics|. Callers must
+// verify that |specifics| passes the IsValidBookmarkSpecifics().
+void UpdateBookmarkNodeFromSpecifics(
+    const sync_pb::BookmarkSpecifics& specifics,
+    const bookmarks::BookmarkNode* node,
+    bookmarks::BookmarkModel* model);
+
+// Checks if a bookmark specifics represents a valid bookmark. |is_folder| is
+// whether this specifics is for a folder. Valid specifics must not be empty,
+// non-folders must contains a valid url, and all keys in the meta_info must be
+// unique.
+bool IsValidBookmarkSpecifics(const sync_pb::BookmarkSpecifics& specifics,
+                              bool is_folder);
+
+}  // namespace sync_bookmarks
+
+#endif  // COMPONENTS_SYNC_BOOKMARKS_BOOKMARK_SPECIFICS_CONVERSIONS_H_
diff --git a/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc b/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc
new file mode 100644
index 0000000..a93870a
--- /dev/null
+++ b/components/sync_bookmarks/bookmark_specifics_conversions_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright 2018 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/sync_bookmarks/bookmark_specifics_conversions.h"
+
+#include <memory>
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/bookmark_node.h"
+#include "components/bookmarks/test/test_bookmark_client.h"
+#include "components/sync/protocol/sync.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using testing::Eq;
+using testing::NotNull;
+
+namespace sync_bookmarks {
+
+namespace {
+
+TEST(BookmarkSpecificsConversionsTest, ShouldCreateSpecificsFromBookmarkNode) {
+  const GURL kUrl("http://www.url.com");
+  const std::string kTitle = "Title";
+  const base::Time kTime = base::Time::Now();
+  const GURL kIconUrl("http://www.icon-url.com");
+  const std::string kKey1 = "key1";
+  const std::string kValue1 = "value1";
+  const std::string kKey2 = "key2";
+  const std::string kValue2 = "value2";
+
+  bookmarks::BookmarkNode node(kUrl);
+  node.SetTitle(base::UTF8ToUTF16(kTitle));
+  node.set_date_added(kTime);
+  node.SetMetaInfo(kKey1, kValue1);
+  node.SetMetaInfo(kKey2, kValue2);
+
+  sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(&node);
+  const sync_pb::BookmarkSpecifics& bm_specifics = specifics.bookmark();
+  EXPECT_THAT(bm_specifics.title(), Eq(kTitle));
+  EXPECT_THAT(GURL(bm_specifics.url()), Eq(kUrl));
+  EXPECT_THAT(
+      base::Time::FromDeltaSinceWindowsEpoch(
+          base::TimeDelta::FromMicroseconds(bm_specifics.creation_time_us())),
+      Eq(kTime));
+  for (const sync_pb::MetaInfo& meta_info : bm_specifics.meta_info()) {
+    std::string value;
+    node.GetMetaInfo(meta_info.key(), &value);
+    EXPECT_THAT(meta_info.value(), Eq(value));
+  }
+}
+
+TEST(BookmarkSpecificsConversionsTest,
+     ShouldUpdateBookmarkNodeFromBookmarkSpecifics) {
+  const GURL kUrl("http://www.url.com");
+  const std::string kTitle = "Title";
+  const base::Time kTime = base::Time::Now();
+  const GURL kIconUrl("http://www.icon-url.com");
+  const std::string kKey1 = "key1";
+  const std::string kValue1 = "value1";
+  const std::string kKey2 = "key2";
+  const std::string kValue2 = "value2";
+
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
+  bm_specifics->set_url(kUrl.spec());
+  bm_specifics->set_title(kTitle);
+  bm_specifics->set_creation_time_us(
+      kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
+  sync_pb::MetaInfo* meta_info1 = bm_specifics->add_meta_info();
+  meta_info1->set_key(kKey1);
+  meta_info1->set_value(kValue1);
+
+  sync_pb::MetaInfo* meta_info2 = bm_specifics->add_meta_info();
+  meta_info2->set_key(kKey2);
+  meta_info2->set_value(kValue2);
+
+  std::unique_ptr<bookmarks::BookmarkModel> model =
+      bookmarks::TestBookmarkClient::CreateModel();
+  const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
+      *bm_specifics,
+      /*parent=*/model->bookmark_bar_node(), /*index=*/0,
+      /*is_folder=*/false, model.get());
+  ASSERT_THAT(node, NotNull());
+  EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kTitle)));
+  EXPECT_THAT(node->url(), Eq(kUrl));
+  EXPECT_THAT(node->date_added(), Eq(kTime));
+  std::string value1;
+  node->GetMetaInfo(kKey1, &value1);
+  EXPECT_THAT(value1, Eq(kValue1));
+  std::string value2;
+  node->GetMetaInfo(kKey2, &value2);
+  EXPECT_THAT(value2, Eq(kValue2));
+}
+
+TEST(BookmarkSpecificsConversionsTest, ShouldBeValidBookmarkSpecifics) {
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
+
+  // URL is irrelevant for a folder.
+  bm_specifics->set_url("INVALID_URL");
+  EXPECT_TRUE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/true));
+
+  bm_specifics->set_url("http://www.valid-url.com");
+  EXPECT_TRUE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/false));
+}
+
+TEST(BookmarkSpecificsConversionsTest, ShouldBeInvalidBookmarkSpecifics) {
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
+  // Empty specifics.
+  EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/false));
+  EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/true));
+
+  // Add invalid url.
+  bm_specifics->set_url("INVALID_URL");
+  EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/false));
+
+  // Add a valid url.
+  bm_specifics->set_url("http://www.valid-url.com");
+  // Add redudant keys in meta_info.
+  sync_pb::MetaInfo* meta_info1 = bm_specifics->add_meta_info();
+  meta_info1->set_key("key");
+  meta_info1->set_value("value1");
+
+  sync_pb::MetaInfo* meta_info2 = bm_specifics->add_meta_info();
+  meta_info2->set_key("key");
+  meta_info2->set_value("value2");
+  EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/false));
+  EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics, /*is_folder=*/true));
+}
+
+}  // namespace
+
+}  // namespace sync_bookmarks
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.cc b/components/sync_bookmarks/synced_bookmark_tracker.cc
index bfc9fb9..22650b66 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.cc
+++ b/components/sync_bookmarks/synced_bookmark_tracker.cc
@@ -344,6 +344,10 @@
   }
 }
 
+bool SyncedBookmarkTracker::IsEmpty() const {
+  return sync_id_to_entities_map_.empty();
+}
+
 std::size_t SyncedBookmarkTracker::TrackedEntitiesCountForTest() const {
   return sync_id_to_entities_map_.size();
 }
diff --git a/components/sync_bookmarks/synced_bookmark_tracker.h b/components/sync_bookmarks/synced_bookmark_tracker.h
index 9ac1abc..a8bd878 100644
--- a/components/sync_bookmarks/synced_bookmark_tracker.h
+++ b/components/sync_bookmarks/synced_bookmark_tracker.h
@@ -146,6 +146,9 @@
                                 int64_t acked_sequence_number,
                                 int64_t server_version);
 
+  // Whether the tracker is empty or not.
+  bool IsEmpty() const;
+
   // Returns number of tracked entities. Used only in test.
   std::size_t TrackedEntitiesCountForTest() const;
 
diff --git a/components/ukm/BUILD.gn b/components/ukm/BUILD.gn
index 2f7e2099..ff802a9 100644
--- a/components/ukm/BUILD.gn
+++ b/components/ukm/BUILD.gn
@@ -56,6 +56,10 @@
     "//components/history/core/browser",
     "//components/sync",
   ]
+
+  public_deps = [
+    "//components/unified_consent",
+  ]
 }
 
 static_library("test_support") {
@@ -95,6 +99,7 @@
     "//components/prefs:test_support",
     "//components/sync",
     "//components/sync:test_support_driver",
+    "//components/sync_preferences:test_support",
     "//components/variations",
     "//net:test_support",
     "//services/metrics/public/cpp:ukm_builders",
diff --git a/components/ukm/DEPS b/components/ukm/DEPS
index 74b50a0..053cb841a 100644
--- a/components/ukm/DEPS
+++ b/components/ukm/DEPS
@@ -1,9 +1,16 @@
 include_rules = [
   "+components/metrics",
   "+components/prefs",
+  "+components/unified_consent",
   "+components/variations",
   "+mojo/public",
   "+services/metrics/public",
   "+third_party/metrics_proto",
   "+third_party/zlib/google",
 ]
+
+specific_include_rules = {
+  ".*unittest\.cc": [
+    "+components/sync_preferences/testing_pref_service_syncable.h",
+  ]
+}
\ No newline at end of file
diff --git a/components/ukm/observers/sync_disable_observer.cc b/components/ukm/observers/sync_disable_observer.cc
index f6d72866..820dbb3c 100644
--- a/components/ukm/observers/sync_disable_observer.cc
+++ b/components/ukm/observers/sync_disable_observer.cc
@@ -9,6 +9,9 @@
 #include "base/stl_util.h"
 #include "components/sync/driver/sync_token_status.h"
 #include "components/sync/engine/connection_status.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+using unified_consent::UrlKeyedDataCollectionConsentHelper;
 
 namespace ukm {
 
@@ -34,6 +37,7 @@
   DISABLED_BY_HISTORY_CONNECTED_PASSPHRASE,
   DISABLED_BY_INITIALIZED_CONNECTED_PASSPHRASE,
   DISABLED_BY_HISTORY_INITIALIZED_CONNECTED_PASSPHRASE,
+  DISABLED_BY_ANONYMIZED_DATA_COLLECTION,
   MAX_DISABLE_INFO
 };
 
@@ -43,16 +47,30 @@
 
 }  // namespace
 
-SyncDisableObserver::SyncDisableObserver()
-    : sync_observer_(this),
-      all_histories_enabled_(false),
-      all_extensions_enabled_(false) {}
+SyncDisableObserver::SyncDisableObserver() : sync_observer_(this) {}
 
-SyncDisableObserver::~SyncDisableObserver() {}
+SyncDisableObserver::~SyncDisableObserver() {
+  for (const auto& entry : consent_helpers_) {
+    entry.second->RemoveObserver(this);
+  }
+}
+
+bool SyncDisableObserver::SyncState::AllowsUkm() const {
+  if (anonymized_data_collection_state == DataCollectionState::kIgnored)
+    return history_enabled && initialized && connected && !passphrase_protected;
+  else
+    return anonymized_data_collection_state == DataCollectionState::kEnabled;
+}
+
+bool SyncDisableObserver::SyncState::AllowsUkmWithExtension() const {
+  return AllowsUkm() && extensions_enabled && initialized && connected &&
+         !passphrase_protected;
+}
 
 // static
 SyncDisableObserver::SyncState SyncDisableObserver::GetSyncState(
-    syncer::SyncService* sync_service) {
+    syncer::SyncService* sync_service,
+    UrlKeyedDataCollectionConsentHelper* consent_helper) {
   syncer::SyncTokenStatus status = sync_service->GetSyncTokenStatus();
   SyncState state;
   state.history_enabled = sync_service->GetPreferredDataTypes().Has(
@@ -64,26 +82,46 @@
                     status.connection_status == syncer::CONNECTION_OK;
   state.passphrase_protected =
       state.initialized && sync_service->IsUsingSecondaryPassphrase();
+  if (consent_helper) {
+    state.anonymized_data_collection_state =
+        consent_helper->IsEnabled() ? DataCollectionState::kEnabled
+                                    : DataCollectionState::kDisabled;
+  }
   return state;
 }
 
 void SyncDisableObserver::ObserveServiceForSyncDisables(
-    syncer::SyncService* sync_service) {
-  previous_states_[sync_service] = GetSyncState(sync_service);
+    syncer::SyncService* sync_service,
+    PrefService* prefs,
+    bool is_unified_consent_enabled) {
+  std::unique_ptr<UrlKeyedDataCollectionConsentHelper> consent_helper;
+  if (is_unified_consent_enabled) {
+    consent_helper = UrlKeyedDataCollectionConsentHelper::
+        NewAnonymizedDataCollectionConsentHelper(true, prefs, sync_service);
+  }
+
+  SyncState state = GetSyncState(sync_service, consent_helper.get());
+  previous_states_[sync_service] = state;
+
+  if (consent_helper) {
+    consent_helper->AddObserver(this);
+    consent_helpers_[sync_service] = std::move(consent_helper);
+  }
   sync_observer_.Add(sync_service);
   UpdateAllProfileEnabled(false);
 }
 
 void SyncDisableObserver::UpdateAllProfileEnabled(bool must_purge) {
-  bool all_enabled = CheckSyncStateOnAllProfiles();
-  bool all_extensions_enabled =
-      all_enabled && CheckSyncStateForExtensionsOnAllProfiles();
+  bool all_sync_states_allow_ukm = CheckSyncStateOnAllProfiles();
+  bool all_sync_states_allow_extension_ukm =
+      all_sync_states_allow_ukm && CheckSyncStateForExtensionsOnAllProfiles();
   // Any change in sync settings needs to call OnSyncPrefsChanged so that the
   // new settings take effect.
-  if (must_purge || (all_enabled != all_histories_enabled_) ||
-      (all_extensions_enabled != all_extensions_enabled_)) {
-    all_histories_enabled_ = all_enabled;
-    all_extensions_enabled_ = all_extensions_enabled;
+  if (must_purge || (all_sync_states_allow_ukm != all_sync_states_allow_ukm_) ||
+      (all_sync_states_allow_extension_ukm !=
+       all_sync_states_allow_extension_ukm_)) {
+    all_sync_states_allow_ukm_ = all_sync_states_allow_ukm;
+    all_sync_states_allow_extension_ukm_ = all_sync_states_allow_extension_ukm;
     OnSyncPrefsChanged(must_purge);
   }
 }
@@ -93,17 +131,23 @@
     return false;
   for (const auto& kv : previous_states_) {
     const SyncDisableObserver::SyncState& state = kv.second;
-    if (!state.history_enabled || !state.initialized || !state.connected ||
-        state.passphrase_protected) {
+    if (!state.AllowsUkm()) {
       int disabled_by = 0;
-      if (!state.history_enabled)
-        disabled_by |= 1 << 0;
-      if (!state.initialized)
-        disabled_by |= 1 << 1;
-      if (!state.connected)
-        disabled_by |= 1 << 2;
-      if (state.passphrase_protected)
-        disabled_by |= 1 << 3;
+      if (state.anonymized_data_collection_state ==
+          DataCollectionState::kIgnored) {
+        if (!state.history_enabled)
+          disabled_by |= 1 << 0;
+        if (!state.initialized)
+          disabled_by |= 1 << 1;
+        if (!state.connected)
+          disabled_by |= 1 << 2;
+        if (state.passphrase_protected)
+          disabled_by |= 1 << 3;
+      } else {
+        DCHECK_EQ(DataCollectionState::kDisabled,
+                  state.anonymized_data_collection_state);
+        disabled_by |= 1 << 4;
+      }
       RecordDisableInfo(DisableInfo(disabled_by));
       return false;
     }
@@ -124,36 +168,60 @@
 }
 
 void SyncDisableObserver::OnStateChanged(syncer::SyncService* sync) {
+  UrlKeyedDataCollectionConsentHelper* consent_helper = nullptr;
+  auto found = consent_helpers_.find(sync);
+  if (found != consent_helpers_.end())
+    consent_helper = found->second.get();
+  UpdateSyncState(sync, consent_helper);
+}
+
+void SyncDisableObserver::OnUrlKeyedDataCollectionConsentStateChanged(
+    unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper) {
+  DCHECK(consent_helper);
+  syncer::SyncService* sync_service = nullptr;
+  for (const auto& entry : consent_helpers_) {
+    if (consent_helper == entry.second.get()) {
+      sync_service = entry.first;
+      break;
+    }
+  }
+  DCHECK(sync_service);
+  UpdateSyncState(sync_service, consent_helper);
+}
+
+void SyncDisableObserver::UpdateSyncState(
+    syncer::SyncService* sync,
+    UrlKeyedDataCollectionConsentHelper* consent_helper) {
   DCHECK(base::ContainsKey(previous_states_, sync));
-  SyncDisableObserver::SyncState state = GetSyncState(sync);
   const SyncDisableObserver::SyncState& previous_state = previous_states_[sync];
-  bool must_purge =
-      // Trigger a purge if history sync was disabled.
-      (previous_state.history_enabled && !state.history_enabled) ||
-      // Trigger a purge if engine has become disabled.
-      (previous_state.initialized && !state.initialized) ||
-      // Trigger a purge if the user added a passphrase.  Since we can't detect
-      // the use of a passphrase while the engine is not initialized, we may
-      // miss the transition if the user adds a passphrase in this state.
-      (previous_state.initialized && state.initialized &&
-       !previous_state.passphrase_protected && state.passphrase_protected);
+  DCHECK(previous_state.anonymized_data_collection_state ==
+             DataCollectionState::kIgnored ||
+         consent_helper);
+  SyncDisableObserver::SyncState state = GetSyncState(sync, consent_helper);
+  // Trigger a purge if sync state no longer allows UKM.
+  bool must_purge = previous_state.AllowsUkm() && !state.AllowsUkm();
   previous_states_[sync] = state;
   UpdateAllProfileEnabled(must_purge);
 }
 
 void SyncDisableObserver::OnSyncShutdown(syncer::SyncService* sync) {
   DCHECK(base::ContainsKey(previous_states_, sync));
+  auto found = consent_helpers_.find(sync);
+  if (found != consent_helpers_.end()) {
+    found->second->RemoveObserver(this);
+    consent_helpers_.erase(found);
+  }
   sync_observer_.Remove(sync);
   previous_states_.erase(sync);
   UpdateAllProfileEnabled(false);
 }
 
 bool SyncDisableObserver::SyncStateAllowsUkm() {
-  return all_histories_enabled_;
+  return all_sync_states_allow_ukm_;
 }
 
 bool SyncDisableObserver::SyncStateAllowsExtensionUkm() {
-  return all_extensions_enabled_;
+  return all_sync_states_allow_extension_ukm_;
 }
 
 }  // namespace ukm
diff --git a/components/ukm/observers/sync_disable_observer.h b/components/ukm/observers/sync_disable_observer.h
index 2de4618..e051443 100644
--- a/components/ukm/observers/sync_disable_observer.h
+++ b/components/ukm/observers/sync_disable_observer.h
@@ -10,25 +10,38 @@
 #include "base/scoped_observer.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_service_observer.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+class PrefService;
 
 namespace ukm {
 
-// Observes the state of a set of SyncServices for changes to history sync
-// preferences.  This is for used to trigger purging of local state when
-// sync is disabled on a profile and disabling recording when any non-syncing
-// profiles are active.
-class SyncDisableObserver : public syncer::SyncServiceObserver {
+// Observer that monitors whether UKM is allowed for all profiles.
+//
+// For one profile, UKM is allowed under the following conditions:
+// * If unified consent is disabled, then UKM is allowed for the profile iff
+//   sync history is active;
+// * If unified consent is enabled, then UKM is allowed for the profile iff
+//   URL-keyed anonymized data collectiion is enabled.
+class SyncDisableObserver
+    : public syncer::SyncServiceObserver,
+      public unified_consent::UrlKeyedDataCollectionConsentHelper::Observer {
  public:
   SyncDisableObserver();
   ~SyncDisableObserver() override;
 
   // Starts observing a service for sync disables.
-  void ObserveServiceForSyncDisables(syncer::SyncService* sync_service);
+  void ObserveServiceForSyncDisables(syncer::SyncService* sync_service,
+                                     PrefService* pref_service,
+                                     bool is_unified_consent_enabled);
 
-  // Returns true iff sync is in a state that allows UKM to be enabled.
-  // This means that for all profiles, sync is initialized, connected, has the
-  // HISTORY_DELETE_DIRECTIVES data type enabled, and does not have a secondary
-  // passphrase enabled.
+  // Returns true iff all sync states alllow UKM to be enabled. This means that
+  // for all profiles:
+  // * If unified consent is disabled, then sync is initialized, connected, has
+  //   the HISTORY_DELETE_DIRECTIVES data type enabled, and does not have a
+  //   secondary passphrase enabled.
+  // * If unified consent is enabled, then URL-keyed anonymized data collection
+  //   is enabled for that profile.
   virtual bool SyncStateAllowsUkm();
 
   // Returns true iff sync is in a state that allows UKM to capture extensions.
@@ -46,6 +59,11 @@
   void OnStateChanged(syncer::SyncService* sync) override;
   void OnSyncShutdown(syncer::SyncService* sync) override;
 
+  // unified_consent::UrlKeyedDataCollectionConsentHelper::Observer:
+  void OnUrlKeyedDataCollectionConsentStateChanged(
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper)
+      override;
+
   // Recomputes all_profiles_enabled_ state from previous_states_;
   void UpdateAllProfileEnabled(bool must_purge);
 
@@ -61,8 +79,29 @@
   ScopedObserver<syncer::SyncService, syncer::SyncServiceObserver>
       sync_observer_;
 
+  enum class DataCollectionState {
+    // Matches the case when unified consent feature is disabled
+    kIgnored,
+    // Unified consent feature is enabled and the user has disabled URL-keyed
+    // anonymized data collection.
+    kDisabled,
+    // Unified consent feature is enabled and the user has enabled URL-keyed
+    // anonymized data collection.
+    kEnabled
+  };
+
   // State data about sync services that we need to remember.
   struct SyncState {
+    // Returns true if this sync state allows UKM:
+    // * If unified consent is disabled, then sync is initialized, connected,
+    //   has history data type enabled, and does not have a secondary passphrase
+    //   enabled.
+    // * If unified consent is enabled, then URL-keyed anonymized data
+    //   collection is enabled.
+    bool AllowsUkm() const;
+    // Returns true if |AllowUkm| and if sync extensions are enabled.
+    bool AllowsUkmWithExtension() const;
+
     // If the user has history sync enabled.
     bool history_enabled = false;
     // If the user has extension sync enabled.
@@ -74,21 +113,45 @@
     // Whether user data is hidden by a secondary passphrase.
     // This is not valid if the state is not initialized.
     bool passphrase_protected = false;
+
+    // Whether anonymized data collection is enabled.
+    // Note: This is not managed by sync service. It was added in this enum
+    // for convenience.
+    DataCollectionState anonymized_data_collection_state =
+        DataCollectionState::kIgnored;
   };
 
-  // Gets the current state of a SyncService.
-  static SyncState GetSyncState(syncer::SyncService* sync);
+  // Updates the sync state for |sync| service. Updates all profiles if needed.
+  void UpdateSyncState(
+      syncer::SyncService* sync,
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper);
 
-  // The list of services that had sync enabled when we last checked.
+  // Gets the current state of a SyncService.
+  // A non-null |consent_helper| implies that Unified Consent is enabled.
+  static SyncState GetSyncState(
+      syncer::SyncService* sync,
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper);
+
+  // The state of the sync services being observed.
   std::map<syncer::SyncService*, SyncState> previous_states_;
 
-  // Tracks if history sync was enabled on all profiles after the last state
-  // change.
-  bool all_histories_enabled_;
+  // The list of URL-keyed anonymized data collection consent helpers.
+  //
+  // Note: UrlKeyedDataCollectionConsentHelper do not rely on sync when
+  // unified consent feature is enabled but there must be exactly one per
+  // Chromium profile. As there is a single sync service per profile, it is safe
+  // to key them by sync service instead of introducing an additional map.
+  std::map<
+      syncer::SyncService*,
+      std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>>
+      consent_helpers_;
+
+  // Tracks if UKM is allowed on all profiles after the last state change.
+  bool all_sync_states_allow_ukm_ = false;
 
   // Tracks if extension sync was enabled on all profiles after the last state
   // change.
-  bool all_extensions_enabled_;
+  bool all_sync_states_allow_extension_ukm_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(SyncDisableObserver);
 };
diff --git a/components/ukm/observers/sync_disable_observer_unittest.cc b/components/ukm/observers/sync_disable_observer_unittest.cc
index 9c9722b57..86ee238 100644
--- a/components/ukm/observers/sync_disable_observer_unittest.cc
+++ b/components/ukm/observers/sync_disable_observer_unittest.cc
@@ -8,6 +8,9 @@
 #include "components/sync/driver/fake_sync_service.h"
 #include "components/sync/driver/sync_token_status.h"
 #include "components/sync/engine/connection_status.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/unified_consent/pref_names.h"
+#include "components/unified_consent/unified_consent_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace ukm {
@@ -19,12 +22,13 @@
   MockSyncService() {}
   ~MockSyncService() override { Shutdown(); }
 
-  void SetStatus(bool has_passphrase, bool enabled) {
+  void SetStatus(bool has_passphrase, bool history_enabled) {
     initialized_ = true;
     has_passphrase_ = has_passphrase;
     preferred_data_types_ =
-        enabled ? syncer::ModelTypeSet(syncer::HISTORY_DELETE_DIRECTIVES)
-                : syncer::ModelTypeSet();
+        history_enabled
+            ? syncer::ModelTypeSet(syncer::HISTORY_DELETE_DIRECTIVES)
+            : syncer::ModelTypeSet();
     NotifyObserversOfStateChanged();
   }
 
@@ -108,6 +112,17 @@
 class SyncDisableObserverTest : public testing::Test {
  public:
   SyncDisableObserverTest() {}
+  void RegisterUrlKeyedAnonymizedDataCollectionPref(
+      sync_preferences::TestingPrefServiceSyncable& prefs) {
+    unified_consent::UnifiedConsentService::RegisterPrefs(prefs.registry());
+  }
+
+  void SetUrlKeyedAnonymizedDataCollectionEnabled(PrefService* prefs,
+                                                  bool enabled) {
+    prefs->SetBoolean(
+        unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
+        enabled);
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(SyncDisableObserverTest);
@@ -122,41 +137,58 @@
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, OneEnabled) {
+TEST_F(SyncDisableObserverTest, OneEnabled_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync;
   sync.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_TRUE(observer.SyncStateAllowsUkm());
   EXPECT_TRUE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, Passphrase) {
-  TestSyncDisableObserver observer;
+TEST_F(SyncDisableObserverTest, OneEnabled_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  MockSyncService sync;
+  for (bool has_passphrase : {true, false}) {
+    for (bool history_enabled : {true, false}) {
+      TestSyncDisableObserver observer;
+      sync.SetStatus(has_passphrase, history_enabled);
+      observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+      EXPECT_TRUE(observer.SyncStateAllowsUkm());
+      EXPECT_TRUE(observer.ResetNotified());
+      EXPECT_FALSE(observer.ResetPurged());
+    }
+  }
+}
+
+TEST_F(SyncDisableObserverTest, Passphrased_UnifiedConsentDisabled) {
   MockSyncService sync;
   sync.SetStatus(true, true);
-  observer.ObserveServiceForSyncDisables(&sync);
+  TestSyncDisableObserver observer;
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
   EXPECT_FALSE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, HistoryDisabled) {
+TEST_F(SyncDisableObserverTest, HistoryDisabled_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync;
   sync.SetStatus(false, false);
-  observer.ObserveServiceForSyncDisables(&sync);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
   EXPECT_FALSE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, AuthError) {
+TEST_F(SyncDisableObserverTest, AuthError_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync;
   sync.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_TRUE(observer.SyncStateAllowsUkm());
   sync.SetConnectionStatus(syncer::CONNECTION_AUTH_ERROR);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
@@ -164,28 +196,29 @@
   EXPECT_TRUE(observer.SyncStateAllowsUkm());
 }
 
-TEST_F(SyncDisableObserverTest, MixedProfiles1) {
+TEST_F(SyncDisableObserverTest, MixedProfiles1_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync1;
   sync1.SetStatus(false, false);
-  observer.ObserveServiceForSyncDisables(&sync1);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
   MockSyncService sync2;
   sync2.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync2);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
   EXPECT_FALSE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, MixedProfiles2) {
+TEST_F(SyncDisableObserverTest, MixedProfiles2_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync1;
   sync1.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync1);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
   EXPECT_TRUE(observer.ResetNotified());
+
   MockSyncService sync2;
   sync2.SetStatus(false, false);
-  observer.ObserveServiceForSyncDisables(&sync2);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
   EXPECT_TRUE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
@@ -195,24 +228,44 @@
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, TwoEnabled) {
+TEST_F(SyncDisableObserverTest, TwoEnabled_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync1;
   sync1.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync1);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
   EXPECT_TRUE(observer.ResetNotified());
   MockSyncService sync2;
   sync2.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync2);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
   EXPECT_TRUE(observer.SyncStateAllowsUkm());
   EXPECT_FALSE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, OneAddRemove) {
+TEST_F(SyncDisableObserverTest, TwoEnabled_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs2;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);
+  TestSyncDisableObserver observer;
+
+  // First profile has sync enabled.
+  MockSyncService sync1;
+  sync1.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
+  EXPECT_TRUE(observer.ResetNotified());
+
+  // Second profile has URL-keyed anonymized data collection enabled.
+  MockSyncService sync2;
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, true);
+  observer.ObserveServiceForSyncDisables(&sync2, &prefs2, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, OneAddRemove_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync;
-  observer.ObserveServiceForSyncDisables(&sync);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_FALSE(observer.SyncStateAllowsUkm());
   EXPECT_FALSE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
@@ -226,11 +279,30 @@
   EXPECT_FALSE(observer.ResetPurged());
 }
 
-TEST_F(SyncDisableObserverTest, PurgeOnDisable) {
+TEST_F(SyncDisableObserverTest, OneAddRemove_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, PurgeOnDisable_UnifiedConsentDisabled) {
   TestSyncDisableObserver observer;
   MockSyncService sync;
   sync.SetStatus(false, true);
-  observer.ObserveServiceForSyncDisables(&sync);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
   EXPECT_TRUE(observer.SyncStateAllowsUkm());
   EXPECT_TRUE(observer.ResetNotified());
   EXPECT_FALSE(observer.ResetPurged());
@@ -244,4 +316,24 @@
   EXPECT_FALSE(observer.ResetPurged());
 }
 
+TEST_F(SyncDisableObserverTest, PurgeOnDisable_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_TRUE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
 }  // namespace ukm
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
index 104b684a..fbd3578b 100644
--- a/components/ukm/ukm_recorder_impl.h
+++ b/components/ukm/ukm_recorder_impl.h
@@ -21,7 +21,7 @@
 #include "services/metrics/public/mojom/ukm_interface.mojom.h"
 
 namespace metrics {
-class UkmBrowserTest;
+class UkmBrowserTestBase;
 class UkmEGTestHelper;
 }
 
@@ -90,7 +90,7 @@
   virtual bool ShouldRestrictToWhitelistedEntries() const;
 
  private:
-  friend ::metrics::UkmBrowserTest;
+  friend ::metrics::UkmBrowserTestBase;
   friend ::metrics::UkmEGTestHelper;
   friend ::ukm::debug::UkmDebugDataExtractor;
   friend ::ukm::UkmUtilsForTest;
diff --git a/components/ukm/ukm_service.h b/components/ukm/ukm_service.h
index 40be605..e5c6136 100644
--- a/components/ukm/ukm_service.h
+++ b/components/ukm/ukm_service.h
@@ -24,7 +24,7 @@
 
 namespace metrics {
 class MetricsServiceClient;
-class UkmBrowserTest;
+class UkmBrowserTestBase;
 class UkmEGTestHelper;
 }
 
@@ -81,7 +81,7 @@
   int32_t report_count() const { return report_count_; }
 
  private:
-  friend ::metrics::UkmBrowserTest;
+  friend ::metrics::UkmBrowserTestBase;
   friend ::metrics::UkmEGTestHelper;
   friend ::ukm::debug::UkmDebugDataExtractor;
   friend ::ukm::UkmUtilsForTest;
diff --git a/components/unified_consent/BUILD.gn b/components/unified_consent/BUILD.gn
index f3f93ba..419332e8d 100644
--- a/components/unified_consent/BUILD.gn
+++ b/components/unified_consent/BUILD.gn
@@ -10,6 +10,7 @@
     "pref_names.h",
     "unified_consent_service.cc",
     "unified_consent_service.h",
+    "unified_consent_service_client.cc",
     "unified_consent_service_client.h",
     "url_keyed_data_collection_consent_helper.cc",
     "url_keyed_data_collection_consent_helper.h",
diff --git a/components/unified_consent/feature.cc b/components/unified_consent/feature.cc
index b9c1feb..edb7dc0 100644
--- a/components/unified_consent/feature.cc
+++ b/components/unified_consent/feature.cc
@@ -14,6 +14,8 @@
 const base::Feature kUnifiedConsent{"UnifiedConsent",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
 const char kUnifiedConsentShowBumpParameter[] = "show_consent_bump";
+const base::Feature kForceUnifiedConsentBump{"ForceUnifiedConsentBump",
+                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
 namespace internal {
 UnifiedConsentFeatureState GetUnifiedConsentFeatureState() {
diff --git a/components/unified_consent/feature.h b/components/unified_consent/feature.h
index e602df3..b42de35 100644
--- a/components/unified_consent/feature.h
+++ b/components/unified_consent/feature.h
@@ -22,6 +22,7 @@
 // Improved and unified consent for privacy-related features.
 extern const base::Feature kUnifiedConsent;
 extern const char kUnifiedConsentShowBumpParameter[];
+extern const base::Feature kForceUnifiedConsentBump;
 
 namespace internal {
 // Returns the state of the "Unified Consent" feature.
diff --git a/components/unified_consent/unified_consent_service.cc b/components/unified_consent/unified_consent_service.cc
index 5b1b9b3..55c3e11 100644
--- a/components/unified_consent/unified_consent_service.cc
+++ b/components/unified_consent/unified_consent_service.cc
@@ -12,6 +12,7 @@
 #include "components/sync/base/model_type.h"
 #include "components/sync/base/sync_prefs.h"
 #include "components/sync/driver/sync_service.h"
+#include "components/unified_consent/feature.h"
 #include "components/unified_consent/pref_names.h"
 #include "components/unified_consent/unified_consent_service_client.h"
 
@@ -26,15 +27,18 @@
       pref_service_(pref_service),
       identity_manager_(identity_manager),
       sync_service_(sync_service) {
+  DCHECK(service_client_);
   DCHECK(pref_service_);
   DCHECK(identity_manager_);
   DCHECK(sync_service_);
-  identity_manager_->AddObserver(this);
-  sync_service_->AddObserver(this);
 
   if (GetMigrationState() == MigrationState::NOT_INITIALIZED)
     MigrateProfileToUnifiedConsent();
 
+  service_client_->AddObserver(this);
+  identity_manager_->AddObserver(this);
+  sync_service_->AddObserver(this);
+
   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
   pref_change_registrar_->Init(pref_service);
   pref_change_registrar_->Add(
@@ -75,6 +79,8 @@
 }
 
 bool UnifiedConsentService::ShouldShowConsentBump() {
+  if (base::FeatureList::IsEnabled(unified_consent::kForceUnifiedConsentBump))
+    return true;
   return GetMigrationState() ==
          MigrationState::IN_PROGRESS_SHOULD_SHOW_CONSENT_BUMP;
 }
@@ -85,10 +91,18 @@
 }
 
 void UnifiedConsentService::Shutdown() {
+  service_client_->RemoveObserver(this);
   identity_manager_->RemoveObserver(this);
   sync_service_->RemoveObserver(this);
 }
 
+void UnifiedConsentService::OnServiceStateChanged(Service service) {
+  // Unified consent is disabled when any of its dependent services gets
+  // disabled.
+  if (service_client_->GetServiceState(service) == ServiceState::kDisabled)
+    SetUnifiedConsentGiven(false);
+}
+
 void UnifiedConsentService::OnPrimaryAccountCleared(
     const AccountInfo& account_info) {
   // When signing out, the unfied consent is revoked.
@@ -98,8 +112,9 @@
   // services.
   pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                             false);
-  service_client_->SetSafeBrowsingExtendedReportingEnabled(false);
-  service_client_->SetSpellCheckEnabled(false);
+  service_client_->SetServiceEnabled(Service::kSafeBrowsingExtendedReporting,
+                                     false);
+  service_client_->SetServiceEnabled(Service::kSpellCheck, false);
 
   switch (GetMigrationState()) {
     case MigrationState::NOT_INITIALIZED:
@@ -155,27 +170,34 @@
   // Enable all non-personalized services.
   pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                             true);
-
   // Inform client to enable non-personalized services.
-  service_client_->SetAlternateErrorPagesEnabled(true);
-  service_client_->SetMetricsReportingEnabled(true);
-  service_client_->SetSearchSuggestEnabled(true);
-  service_client_->SetSafeBrowsingEnabled(true);
-  service_client_->SetSafeBrowsingExtendedReportingEnabled(true);
-  service_client_->SetNetworkPredictionEnabled(true);
-  service_client_->SetSpellCheckEnabled(true);
+  for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) {
+    Service service = static_cast<Service>(i);
+    if (service_client_->GetServiceState(service) !=
+        ServiceState::kNotSupported) {
+      service_client_->SetServiceEnabled(service, true);
+    }
+  }
 }
 
 void UnifiedConsentService::SetSyncEverythingIfPossible(bool sync_everything) {
+  syncer::SyncPrefs sync_prefs(pref_service_);
+  if (sync_everything == sync_prefs.HasKeepEverythingSynced())
+    return;
+
   if (!sync_service_->IsEngineInitialized())
     return;
 
   if (sync_everything) {
     pref_service_->SetBoolean(autofill::prefs::kAutofillWalletImportEnabled,
                               true);
+    sync_service_->OnUserChoseDatatypes(sync_everything,
+                                        syncer::UserSelectableTypes());
+  } else {
+    syncer::ModelTypeSet preferred = sync_service_->GetPreferredDataTypes();
+    preferred.RetainAll(syncer::UserSelectableTypes());
+    sync_service_->OnUserChoseDatatypes(false, preferred);
   }
-  sync_service_->OnUserChoseDatatypes(sync_everything,
-                                      syncer::UserSelectableTypes());
 }
 
 void UnifiedConsentService::MigrateProfileToUnifiedConsent() {
diff --git a/components/unified_consent/unified_consent_service.h b/components/unified_consent/unified_consent_service.h
index 3222e85..ec1d42a 100644
--- a/components/unified_consent/unified_consent_service.h
+++ b/components/unified_consent/unified_consent_service.h
@@ -6,12 +6,12 @@
 #define COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_H_
 
 #include <memory>
-#include <vector>
 
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/driver/sync_service_observer.h"
+#include "components/unified_consent/unified_consent_service_client.h"
 #include "services/identity/public/cpp/identity_manager.h"
 
 namespace user_prefs {
@@ -27,6 +27,9 @@
 
 namespace unified_consent {
 
+using Service = UnifiedConsentServiceClient::Service;
+using ServiceState = UnifiedConsentServiceClient::ServiceState;
+
 enum class MigrationState : int {
   NOT_INITIALIZED = 0,
   IN_PROGRESS_SHOULD_SHOW_CONSENT_BUMP = 1,
@@ -34,11 +37,10 @@
   COMPLETED = 10,
 };
 
-class UnifiedConsentServiceClient;
-
 // A browser-context keyed service that is used to manage the user consent
 // when UnifiedConsent feature is enabled.
 class UnifiedConsentService : public KeyedService,
+                              public UnifiedConsentServiceClient::Observer,
                               public identity::IdentityManager::Observer,
                               public syncer::SyncServiceObserver {
  public:
@@ -70,11 +72,16 @@
   // KeyedService:
   void Shutdown() override;
 
+  // UnifiedConsentServiceClient::Observer:
+  void OnServiceStateChanged(Service service) override;
+
   // IdentityManager::Observer:
   void OnPrimaryAccountCleared(
       const AccountInfo& previous_primary_account_info) override;
 
  private:
+  friend class UnifiedConsentServiceTest;
+
   // syncer::SyncServiceObserver:
   void OnStateChanged(syncer::SyncService* sync) override;
 
diff --git a/components/unified_consent/unified_consent_service_client.cc b/components/unified_consent/unified_consent_service_client.cc
new file mode 100644
index 0000000..1bd56d2
--- /dev/null
+++ b/components/unified_consent/unified_consent_service_client.cc
@@ -0,0 +1,50 @@
+// Copyright 2018 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/unified_consent/unified_consent_service_client.h"
+
+#include "base/bind.h"
+
+namespace unified_consent {
+
+UnifiedConsentServiceClient::UnifiedConsentServiceClient() {}
+UnifiedConsentServiceClient::~UnifiedConsentServiceClient() {}
+
+void UnifiedConsentServiceClient::AddObserver(Observer* observer) {
+  observer_list_.AddObserver(observer);
+}
+
+void UnifiedConsentServiceClient::RemoveObserver(Observer* observer) {
+  observer_list_.RemoveObserver(observer);
+}
+
+void UnifiedConsentServiceClient::ObserveServicePrefChange(
+    Service service,
+    const std::string& pref_name,
+    PrefService* pref_service) {
+  service_prefs_[pref_name] = service;
+
+  // First access to the pref registrar of |pref_service| in the map
+  // automatically creates an entry for it.
+  PrefChangeRegistrar* pref_change_registrar =
+      &(pref_change_registrars_[pref_service]);
+  if (!pref_change_registrar->prefs())
+    pref_change_registrar->Init(pref_service);
+  pref_change_registrar->Add(
+      pref_name,
+      base::BindRepeating(&UnifiedConsentServiceClient::OnPrefChanged,
+                          base::Unretained(this)));
+}
+
+void UnifiedConsentServiceClient::FireOnServiceStateChanged(Service service) {
+  for (auto& observer : observer_list_)
+    observer.OnServiceStateChanged(service);
+}
+
+void UnifiedConsentServiceClient::OnPrefChanged(const std::string& pref_name) {
+  DCHECK(service_prefs_.count(pref_name));
+  FireOnServiceStateChanged(service_prefs_[pref_name]);
+}
+
+}  // namespace unified_consent
diff --git a/components/unified_consent/unified_consent_service_client.h b/components/unified_consent/unified_consent_service_client.h
index 8b4a865..cb31490 100644
--- a/components/unified_consent/unified_consent_service_client.h
+++ b/components/unified_consent/unified_consent_service_client.h
@@ -5,26 +5,88 @@
 #ifndef COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_CLIENT_H_
 #define COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_CLIENT_H_
 
+#include <map>
+#include <string>
+
+#include "base/observer_list.h"
+#include "components/prefs/pref_change_registrar.h"
+
+class PrefService;
+
 namespace unified_consent {
 
 class UnifiedConsentServiceClient {
  public:
-  virtual ~UnifiedConsentServiceClient() {}
+  enum class Service {
+    // Link Doctor error pages.
+    kAlternateErrorPages,
+    // Metrics reporting.
+    kMetricsReporting,
+    // Prediction of network actions.
+    kNetworkPrediction,
+    // Safe browsing.
+    kSafeBrowsing,
+    // Extended safe browsing.
+    kSafeBrowsingExtendedReporting,
+    // Search suggestions.
+    kSearchSuggest,
+    // Spell checking.
+    kSpellCheck,
 
-  // Enables/disables Link Doctor error pages.
-  virtual void SetAlternateErrorPagesEnabled(bool enabled) = 0;
-  // Enables/disables metrics reporting.
-  virtual void SetMetricsReportingEnabled(bool enabled) = 0;
-  // Enables/disables search suggestions.
-  virtual void SetSearchSuggestEnabled(bool enabled) = 0;
-  // Enables/disables safe browsing.
-  virtual void SetSafeBrowsingEnabled(bool enabled) = 0;
-  // Enables/disables extended safe browsing.
-  virtual void SetSafeBrowsingExtendedReportingEnabled(bool enabled) = 0;
-  // Enables/disables prediction of network actions.
-  virtual void SetNetworkPredictionEnabled(bool enabled) = 0;
-  // Enables/disables spell check.
-  virtual void SetSpellCheckEnabled(bool enabled) = 0;
+    // Last element of the enum, used for iteration.
+    kLast = kSpellCheck,
+  };
+
+  enum class ServiceState {
+    // The service is not supported on this platform.
+    kNotSupported,
+    // The service is supported, but disabled.
+    kDisabled,
+    // The service is enabled.
+    kEnabled
+  };
+
+  class Observer {
+   public:
+    // Called when the service state of |service| changes.
+    virtual void OnServiceStateChanged(Service service) = 0;
+  };
+
+  UnifiedConsentServiceClient();
+  virtual ~UnifiedConsentServiceClient();
+
+  // Returns the ServiceState for |service|.
+  virtual ServiceState GetServiceState(Service service) = 0;
+  // Sets |service| enabled if it is supported on this platform.
+  virtual void SetServiceEnabled(Service service, bool enabled) = 0;
+
+  // Methods to register or remove observers.
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+ protected:
+  // This method adds |pref_name| to the list of prefs that will be observed for
+  // changes. Whenever there's a change in the pref, all
+  // |UnifiedConsentServiceClient::Observer|s are fired.
+  void ObserveServicePrefChange(Service service,
+                                const std::string& pref_name,
+                                PrefService* pref_service);
+
+  // Fires |OnServiceStateChanged| on all observers.
+  void FireOnServiceStateChanged(Service service);
+
+ private:
+  // Callback for the pref change registrars.
+  void OnPrefChanged(const std::string& pref_name);
+
+  base::ObserverList<Observer, true> observer_list_;
+
+  // Matches the pref name to it's service.
+  std::map<std::string, Service> service_prefs_;
+  // Matches pref service to it's change registrar.
+  std::map<PrefService*, PrefChangeRegistrar> pref_change_registrars_;
+
+  DISALLOW_COPY_AND_ASSIGN(UnifiedConsentServiceClient);
 };
 
 }  // namespace unified_consent
diff --git a/components/unified_consent/unified_consent_service_unittest.cc b/components/unified_consent/unified_consent_service_unittest.cc
index f90283c..c354b550 100644
--- a/components/unified_consent/unified_consent_service_unittest.cc
+++ b/components/unified_consent/unified_consent_service_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/unified_consent/unified_consent_service.h"
 
+#include <map>
 #include <memory>
 
 #include "base/message_loop/message_loop.h"
@@ -49,53 +50,96 @@
   bool is_syncing_everything_ = false;
 };
 
+const char kSpellCheckDummyEnabled[] = "spell_check_dummy.enabled";
+
+class FakeUnifiedConsentServiceClient : public UnifiedConsentServiceClient {
+ public:
+  FakeUnifiedConsentServiceClient(PrefService* pref_service)
+      : pref_service_(pref_service) {
+    // When the |kSpellCheckDummyEnabled| pref is changed, all observers should
+    // be fired.
+    ObserveServicePrefChange(Service::kSpellCheck, kSpellCheckDummyEnabled,
+                             pref_service);
+  }
+  ~FakeUnifiedConsentServiceClient() override = default;
+
+  // UnifiedConsentServiceClient:
+  ServiceState GetServiceState(Service service) override {
+    if (is_not_supported_[service])
+      return ServiceState::kNotSupported;
+    bool enabled;
+    // Special treatment for spell check.
+    if (service == Service::kSpellCheck) {
+      enabled = pref_service_->GetBoolean(kSpellCheckDummyEnabled);
+    } else {
+      enabled = service_enabled_[service];
+    }
+    return enabled ? ServiceState::kEnabled : ServiceState::kDisabled;
+  }
+  void SetServiceEnabled(Service service, bool enabled) override {
+    if (is_not_supported_[service])
+      return;
+    // Special treatment for spell check.
+    if (service == Service::kSpellCheck) {
+      pref_service_->SetBoolean(kSpellCheckDummyEnabled, enabled);
+      return;
+    }
+    bool should_notify_observers = service_enabled_[service] != enabled;
+    service_enabled_[service] = enabled;
+    if (should_notify_observers)
+      FireOnServiceStateChanged(service);
+  }
+
+  void SetServiceNotSupported(Service service) {
+    is_not_supported_[service] = true;
+  }
+
+ private:
+  std::map<Service, bool> service_enabled_;
+  std::map<Service, bool> is_not_supported_;
+
+  PrefService* pref_service_;
+};
+
+}  // namespace
+
 class UnifiedConsentServiceTest : public testing::Test {
  public:
-  class FakeUnifiedConsentServiceClient : public UnifiedConsentServiceClient {
-   public:
-    FakeUnifiedConsentServiceClient(UnifiedConsentServiceTest* test)
-        : test_(test) {}
-    // UnifiedConsentServiceClient:
-    void SetAlternateErrorPagesEnabled(bool enabled) override {
-      test_->alternate_error_pages_enabled_ = enabled;
-    }
-    void SetMetricsReportingEnabled(bool enabled) override {
-      test_->metrics_reporting_enabled_ = enabled;
-    }
-    void SetNetworkPredictionEnabled(bool enabled) override {
-      test_->network_predictions_enabled_ = enabled;
-    }
-    void SetSafeBrowsingEnabled(bool enabled) override {
-      test_->safe_browsing_enabled_ = enabled;
-    }
-    void SetSafeBrowsingExtendedReportingEnabled(bool enabled) override {
-      test_->safe_browsing_extended_reporting_enabled_ = enabled;
-    }
-    void SetSearchSuggestEnabled(bool enabled) override {
-      test_->search_suggest_enabled_ = enabled;
-    }
-    void SetSpellCheckEnabled(bool enabled) override {
-      test_->spell_check_enabled_ = enabled;
-    }
-
-   private:
-    UnifiedConsentServiceTest* test_;
-  };
-
   // testing::Test:
   void SetUp() override {
     pref_service_.registry()->RegisterBooleanPref(
         autofill::prefs::kAutofillWalletImportEnabled, false);
     UnifiedConsentService::RegisterPrefs(pref_service_.registry());
     syncer::SyncPrefs::RegisterProfilePrefs(pref_service_.registry());
+    pref_service_.registry()->RegisterBooleanPref(kSpellCheckDummyEnabled,
+                                                  false);
   }
 
   void TearDown() override { consent_service_->Shutdown(); }
 
   void CreateConsentService() {
     consent_service_ = std::make_unique<UnifiedConsentService>(
-        std::make_unique<FakeUnifiedConsentServiceClient>(this), &pref_service_,
-        identity_test_environment_.identity_manager(), &sync_service_);
+        std::make_unique<FakeUnifiedConsentServiceClient>(&pref_service_),
+        &pref_service_, identity_test_environment_.identity_manager(),
+        &sync_service_);
+    service_client_ = (FakeUnifiedConsentServiceClient*)
+                          consent_service_->service_client_.get();
+  }
+
+  // Returns true if all supported non-personalized services are enabled.
+  bool AreAllNonPersonalizedServicesEnabled() {
+    for (int service = 0; service <= static_cast<int>(Service::kLast);
+         ++service) {
+      if (service_client_->GetServiceState(static_cast<Service>(service)) ==
+          ServiceState::kDisabled) {
+        return false;
+      }
+    }
+    if (!pref_service_.GetBoolean(
+            prefs::kUrlKeyedAnonymizedDataCollectionEnabled))
+      return false;
+
+    return true;
   }
 
  protected:
@@ -104,13 +148,7 @@
   identity::IdentityTestEnvironment identity_test_environment_;
   TestSyncService sync_service_;
   std::unique_ptr<UnifiedConsentService> consent_service_;
-  bool alternate_error_pages_enabled_ = false;
-  bool metrics_reporting_enabled_ = false;
-  bool network_predictions_enabled_ = false;
-  bool safe_browsing_enabled_ = false;
-  bool safe_browsing_extended_reporting_enabled_ = false;
-  bool search_suggest_enabled_ = false;
-  bool spell_check_enabled_ = false;
+  FakeUnifiedConsentServiceClient* service_client_ = nullptr;
 };
 
 TEST_F(UnifiedConsentServiceTest, DefaultValuesWhenSignedOut) {
@@ -126,33 +164,42 @@
   EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
   EXPECT_FALSE(pref_service_.GetBoolean(
       prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
 
   // Enable Unified Consent enables all non-personaized features
   pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true);
   EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
-  EXPECT_TRUE(pref_service_.GetBoolean(
-      prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
-  EXPECT_TRUE(alternate_error_pages_enabled_);
-  EXPECT_TRUE(metrics_reporting_enabled_);
-  EXPECT_TRUE(network_predictions_enabled_);
-  EXPECT_TRUE(safe_browsing_enabled_);
-  EXPECT_TRUE(safe_browsing_extended_reporting_enabled_);
-  EXPECT_TRUE(search_suggest_enabled_);
-  EXPECT_TRUE(spell_check_enabled_);
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
 
   // Disable unified consent does not disable any of the non-personalized
   // features.
   pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, false);
   EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
-  EXPECT_TRUE(pref_service_.GetBoolean(
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
+}
+
+TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent_WithUnsupportedService) {
+  CreateConsentService();
+  identity_test_environment_.SetPrimaryAccount("testaccount");
+  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_FALSE(pref_service_.GetBoolean(
       prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
-  EXPECT_TRUE(alternate_error_pages_enabled_);
-  EXPECT_TRUE(metrics_reporting_enabled_);
-  EXPECT_TRUE(network_predictions_enabled_);
-  EXPECT_TRUE(safe_browsing_enabled_);
-  EXPECT_TRUE(safe_browsing_extended_reporting_enabled_);
-  EXPECT_TRUE(search_suggest_enabled_);
-  EXPECT_TRUE(spell_check_enabled_);
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
+  service_client_->SetServiceNotSupported(Service::kSpellCheck);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kSpellCheck),
+            ServiceState::kNotSupported);
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
+
+  // Enable Unified Consent enables all supported non-personalized features
+  pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true);
+  EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
+
+  // Disable unified consent does not disable any of the supported
+  // non-personalized features.
+  pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, false);
+  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
 }
 
 TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent_SyncNotActive) {
@@ -185,6 +232,27 @@
   EXPECT_TRUE(sync_service_.IsSyncingEverything());
 }
 
+// Test whether unified consent is disabled when any of its dependent services
+// gets disabled.
+TEST_F(UnifiedConsentServiceTest, DisableUnfiedConsentWhenServiceIsDisabled) {
+  CreateConsentService();
+  identity_test_environment_.SetPrimaryAccount("testaccount");
+  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_FALSE(pref_service_.GetBoolean(
+      prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
+
+  // Enable Unified Consent enables all supported non-personalized features
+  pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true);
+  EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
+
+  // Disabling child service disables unified consent.
+  pref_service_.SetBoolean(kSpellCheckDummyEnabled, false);
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
+  EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+}
+
 #if !defined(OS_CHROMEOS)
 TEST_F(UnifiedConsentServiceTest, Migration_SyncingEverything) {
   base::HistogramTester histogram_tester;
@@ -244,33 +312,33 @@
 
   // Precondition: Enable unified consent.
   pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true);
-  EXPECT_TRUE(pref_service_.GetBoolean(
-      prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
-  EXPECT_TRUE(alternate_error_pages_enabled_);
-  EXPECT_TRUE(metrics_reporting_enabled_);
-  EXPECT_TRUE(network_predictions_enabled_);
-  EXPECT_TRUE(safe_browsing_enabled_);
-  EXPECT_TRUE(safe_browsing_extended_reporting_enabled_);
-  EXPECT_TRUE(search_suggest_enabled_);
-  EXPECT_TRUE(spell_check_enabled_);
+  EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled());
 
   // Clearing primary account revokes unfied consent and a couple of other
   // non-personalized services.
   identity_test_environment_.ClearPrimaryAccount();
   EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven));
+  EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled());
   EXPECT_FALSE(pref_service_.GetBoolean(
       prefs::kUrlKeyedAnonymizedDataCollectionEnabled));
-  EXPECT_FALSE(spell_check_enabled_);
-  EXPECT_FALSE(safe_browsing_extended_reporting_enabled_);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kSpellCheck),
+            ServiceState::kDisabled);
+  EXPECT_EQ(
+      service_client_->GetServiceState(Service::kSafeBrowsingExtendedReporting),
+      ServiceState::kDisabled);
 
   // Consent is not revoked for the following services.
-  EXPECT_TRUE(alternate_error_pages_enabled_);
-  EXPECT_TRUE(metrics_reporting_enabled_);
-  EXPECT_TRUE(network_predictions_enabled_);
-  EXPECT_TRUE(safe_browsing_enabled_);
-  EXPECT_TRUE(search_suggest_enabled_);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kAlternateErrorPages),
+            ServiceState::kEnabled);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kMetricsReporting),
+            ServiceState::kEnabled);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kNetworkPrediction),
+            ServiceState::kEnabled);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kSearchSuggest),
+            ServiceState::kEnabled);
+  EXPECT_EQ(service_client_->GetServiceState(Service::kSafeBrowsing),
+            ServiceState::kEnabled);
 }
 #endif  // !defined(OS_CHROMEOS)
 
-}  // namespace
 }  // namespace unified_consent
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 543be58..745c26bb 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -5,14 +5,9 @@
 #include "components/viz/common/features.h"
 
 #include "base/command_line.h"
-#include "base/sys_info.h"
 #include "build/build_config.h"
 #include "components/viz/common/switches.h"
 
-#if defined(OS_ANDROID)
-#include "base/android/build_info.h"
-#endif
-
 namespace features {
 
 // Enables running draw occlusion algorithm to remove Draw Quads that are not
@@ -57,7 +52,7 @@
   auto* command_line = base::CommandLine::ForCurrentProcess();
   return base::FeatureList::IsEnabled(kEnableSurfaceSynchronization) ||
          command_line->HasSwitch(switches::kEnableSurfaceSynchronization) ||
-         IsVizDisplayCompositorEnabled();
+         base::FeatureList::IsEnabled(kVizDisplayCompositor);
 }
 
 bool IsSurfaceInvariantsViolationLoggingEnabled() {
@@ -67,7 +62,7 @@
 
 bool IsVizHitTestingDrawQuadEnabled() {
   return base::FeatureList::IsEnabled(kEnableVizHitTestDrawQuad) ||
-         IsVizDisplayCompositorEnabled();
+         base::FeatureList::IsEnabled(kVizDisplayCompositor);
 }
 
 bool IsVizHitTestingEnabled() {
@@ -95,21 +90,7 @@
 bool IsUsingSkiaDeferredDisplayList() {
   return IsUsingSkiaRenderer() &&
          base::FeatureList::IsEnabled(kUseSkiaDeferredDisplayList) &&
-         IsVizDisplayCompositorEnabled();
-}
-
-bool IsVizDisplayCompositorEnabled() {
-// To work around current bugs, some Android configs do not allow for Viz to be
-// enabled.
-// TODO(cblume): Remove this once https://crbug.com/867453 is addressed.
-#if defined(OS_ANDROID) && defined(ARCH_CPU_X86_FAMILY)
-  if (base::android::BuildInfo::GetInstance()->sdk_int() <
-      base::android::SDK_VERSION_KITKAT) {
-    return false;
-  }
-#endif  // defined(OS_ANDROID) && defined(ARCH_CPU_X86_FAMILY)
-
-  return base::FeatureList::IsEnabled(kVizDisplayCompositor);
+         base::FeatureList::IsEnabled(kVizDisplayCompositor);
 }
 
 }  // namespace features
diff --git a/components/viz/common/features.h b/components/viz/common/features.h
index 0398c12..f5f21df 100644
--- a/components/viz/common/features.h
+++ b/components/viz/common/features.h
@@ -28,7 +28,6 @@
 VIZ_COMMON_EXPORT bool IsVizHitTestingSurfaceLayerEnabled();
 VIZ_COMMON_EXPORT bool IsUsingSkiaDeferredDisplayList();
 VIZ_COMMON_EXPORT bool IsUsingSkiaRenderer();
-VIZ_COMMON_EXPORT bool IsVizDisplayCompositorEnabled();
 
 }  // namespace features
 
diff --git a/components/viz/common/surfaces/surface_range.cc b/components/viz/common/surfaces/surface_range.cc
index 9e8ed7e..21149dcb 100644
--- a/components/viz/common/surfaces/surface_range.cc
+++ b/components/viz/common/surfaces/surface_range.cc
@@ -34,9 +34,25 @@
 bool SurfaceRange::IsInRangeExclusive(const SurfaceId& surface_id) const {
   if (!start_)
     return end_.IsNewerThan(surface_id);
+
+  if (HasDifferentFrameSinkIds() ||
+      end_.local_surface_id().embed_token() !=
+          start_->local_surface_id().embed_token()) {
+    return surface_id.IsNewerThan(*start_) || end_.IsNewerThan(surface_id);
+  }
+
   return surface_id.IsNewerThan(*start_) && end_.IsNewerThan(surface_id);
 }
 
+bool SurfaceRange::IsInRangeInclusive(const SurfaceId& surface_id) const {
+  return IsInRangeExclusive(surface_id) || end_ == surface_id ||
+         start_ == surface_id;
+}
+
+bool SurfaceRange::HasDifferentFrameSinkIds() const {
+  return start_ && start_->frame_sink_id() != end_.frame_sink_id();
+}
+
 bool SurfaceRange::IsValid() const {
   if (!end_.is_valid())
     return false;
diff --git a/components/viz/common/surfaces/surface_range.h b/components/viz/common/surfaces/surface_range.h
index f4164e0..dbc80ca 100644
--- a/components/viz/common/surfaces/surface_range.h
+++ b/components/viz/common/surfaces/surface_range.h
@@ -35,10 +35,19 @@
 
   bool operator<(const SurfaceRange& other) const;
 
-  // Check if |surface_id| is in the surface range and match the frame sink id
-  // of start and end.
+  // Check if |surface_id| falls within |this| SurfaceRange but is neither the
+  // start nor end of the range. The FrameSinkId of |surface_id| must match
+  // either the start or end of the range.
   bool IsInRangeExclusive(const SurfaceId& surface_id) const;
 
+  // Check if |surface_id| falls within |this| SurfaceRange inclusive of the
+  // start and end of the range. The FrameSinkId of |surface_id| must match
+  // either the start or end of the range.
+  bool IsInRangeInclusive(const SurfaceId& surface_id) const;
+
+  // Returns whether the start and end of the range have differing FrameSinkIds.
+  bool HasDifferentFrameSinkIds() const;
+
   bool IsValid() const;
 
   const base::Optional<SurfaceId>& start() const { return start_; }
diff --git a/components/viz/common/surfaces/surface_range_unittest.cc b/components/viz/common/surfaces/surface_range_unittest.cc
index a19d09725..54a9b77 100644
--- a/components/viz/common/surfaces/surface_range_unittest.cc
+++ b/components/viz/common/surfaces/surface_range_unittest.cc
@@ -8,8 +8,9 @@
 #include "base/logging.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-// Verifying that SurfaceId::IsInRangeExclusive works properly.
-TEST(SurfaceRangeTest, VerifyInRange) {
+// Verifies that SurfaceId::IsInRangeExclusive and SurfaceId::IsInRangeInclusive
+// works properly.
+TEST(SurfaceRangeTest, InRangeTest) {
   viz::FrameSinkId FrameSink1(65564, 0);
   viz::FrameSinkId FrameSink2(65565, 0);
   const base::UnguessableToken token1 = base::UnguessableToken::Create();
@@ -17,9 +18,12 @@
 
   const viz::SurfaceId start(FrameSink1, viz::LocalSurfaceId(1, 1, token1));
   const viz::SurfaceId end(FrameSink1, viz::LocalSurfaceId(2, 2, token1));
+  const viz::SurfaceId end_token2(FrameSink2,
+                                  viz::LocalSurfaceId(2, 2, token2));
 
   const viz::SurfaceRange surface_range1(start, end);
   const viz::SurfaceRange surface_range2(base::nullopt, end);
+  const viz::SurfaceRange surface_range1_token2(start, end_token2);
 
   const viz::SurfaceId surface_id1(FrameSink1,
                                    viz::LocalSurfaceId(1, 2, token1));
@@ -31,22 +35,35 @@
                                    viz::LocalSurfaceId(3, 3, token1));
 
   // |surface_id1| has the right embed token and is inside the range
-  // (Start,end).
+  // (start,end).
   EXPECT_TRUE(surface_range1.IsInRangeExclusive(surface_id1));
 
   // |surface_id1| has the right embed token and inside the range
   // (base::nullopt,end).
   EXPECT_TRUE(surface_range2.IsInRangeExclusive(surface_id1));
 
-  // |surface_id2| has the wrong embed token/FrameSinkId so should be refused.
+  // |surface_id2| has an unmatching token.
   EXPECT_FALSE(surface_range1.IsInRangeExclusive(surface_id2));
   EXPECT_FALSE(surface_range2.IsInRangeExclusive(surface_id2));
 
-  // |surface_id3| is not included in the range exclusively.
+  // |surface_id2| doesn't match any end point either.
+  EXPECT_FALSE(surface_range1.IsInRangeInclusive(surface_id2));
+  EXPECT_FALSE(surface_range2.IsInRangeInclusive(surface_id2));
+
+  // |surface_id2| has a matching token to |end_token2|.
+  EXPECT_TRUE(surface_range1_token2.IsInRangeExclusive(surface_id2));
+
+  // |surface_id3| is not included when end points are not considered.
   EXPECT_FALSE(surface_range1.IsInRangeExclusive(surface_id3));
   EXPECT_FALSE(surface_range2.IsInRangeExclusive(surface_id3));
 
-  // |surface_id4| is not included in the range.
+  // But |surface_id3| is included when end points are considered.
+  EXPECT_TRUE(surface_range1.IsInRangeInclusive(surface_id3));
+  EXPECT_TRUE(surface_range2.IsInRangeInclusive(surface_id3));
+
+  // |surface_id4| is not included in the range in all cases.
   EXPECT_FALSE(surface_range1.IsInRangeExclusive(surface_id4));
   EXPECT_FALSE(surface_range2.IsInRangeExclusive(surface_id4));
+  EXPECT_FALSE(surface_range1.IsInRangeInclusive(surface_id4));
+  EXPECT_FALSE(surface_range2.IsInRangeInclusive(surface_id4));
 }
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 166f325..33fc82ac 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -155,6 +155,8 @@
     "hit_test/hit_test_aggregator_delegate.h",
     "hit_test/hit_test_manager.cc",
     "hit_test/hit_test_manager.h",
+    "main/viz_compositor_thread_runner.cc",
+    "main/viz_compositor_thread_runner.h",
     "surfaces/latest_local_surface_id_lookup_delegate.h",
     "surfaces/referenced_surface_tracker.cc",
     "surfaces/referenced_surface_tracker.h",
diff --git a/components/viz/service/display/overlay_candidate.cc b/components/viz/service/display/overlay_candidate.cc
index 2cac5568..cf9399c 100644
--- a/components/viz/service/display/overlay_candidate.cc
+++ b/components/viz/service/display/overlay_candidate.cc
@@ -211,8 +211,9 @@
     return false;
   // We support only kSrc (no blending) and kSrcOver (blending with premul).
   if (!(quad->shared_quad_state->blend_mode == SkBlendMode::kSrc ||
-        quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver))
+        quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver)) {
     return false;
+  }
 
   switch (quad->material) {
     case DrawQuad::TEXTURE_CONTENT:
@@ -237,13 +238,12 @@
   float opacity = quad->shared_quad_state->opacity;
   if (opacity < std::numeric_limits<float>::epsilon())
     return true;
-  if (quad->material == DrawQuad::SOLID_COLOR) {
-    SkColor color = SolidColorDrawQuad::MaterialCast(quad)->color;
-    float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
-    return quad->ShouldDrawWithBlending() &&
-           alpha < std::numeric_limits<float>::epsilon();
-  }
-  return false;
+  if (quad->material != DrawQuad::SOLID_COLOR)
+    return false;
+  const SkColor color = SolidColorDrawQuad::MaterialCast(quad)->color;
+  const float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
+  return quad->ShouldDrawWithBlending() &&
+         alpha < std::numeric_limits<float>::epsilon();
 }
 
 // static
@@ -257,8 +257,9 @@
         overlap_iter->shared_quad_state->quad_to_target_transform,
         gfx::RectF(overlap_iter->rect));
     if (candidate.display_rect.Intersects(overlap_rect) &&
-        !OverlayCandidate::IsInvisibleQuad(*overlap_iter))
+        !OverlayCandidate::IsInvisibleQuad(*overlap_iter)) {
       return true;
+    }
   }
   return false;
 }
@@ -274,8 +275,7 @@
     return false;
 
   candidate->format = resource_provider->GetBufferFormat(resource_id);
-  if (std::find(std::begin(kOverlayFormats), std::end(kOverlayFormats),
-                candidate->format) == std::end(kOverlayFormats))
+  if (!base::ContainsValue(kOverlayFormats, candidate->format))
     return false;
 
   gfx::OverlayTransform overlay_transform = GetOverlayTransform(
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 4b20b28f..9dc2839 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -200,60 +200,48 @@
     gfx::Rect* damage_rect_in_quad_space,
     bool* damage_rect_in_quad_space_valid) {
   SurfaceId primary_surface_id = surface_quad->surface_range.end();
-  Surface* primary_surface = manager_->GetSurfaceForId(primary_surface_id);
-  if (primary_surface && primary_surface->HasActiveFrame()) {
-    EmitSurfaceContent(primary_surface, parent_device_scale_factor,
-                       surface_quad->shared_quad_state, surface_quad->rect,
-                       surface_quad->visible_rect, target_transform, clip_rect,
-                       surface_quad->stretch_content_to_fill_bounds, dest_pass,
-                       ignore_undamaged, damage_rect_in_quad_space,
-                       damage_rect_in_quad_space_valid);
-    return;
-  }
-
-  Surface* latest_surface = nullptr;
-  if (surface_quad->surface_range.start()) {
-    latest_surface =
-        manager_->GetLatestInFlightSurface(surface_quad->surface_range);
-  }
 
   // If there's no fallback surface ID available, then simply emit a
   // SolidColorDrawQuad with the provided default background color. This
   // can happen after a Viz process crash.
+  Surface* latest_surface =
+      manager_->GetLatestInFlightSurface(surface_quad->surface_range);
   if (!latest_surface || !latest_surface->HasActiveFrame()) {
     EmitDefaultBackgroundColorQuad(surface_quad, target_transform, clip_rect,
                                    dest_pass);
     return;
   }
 
-  if (primary_surface_id.frame_sink_id() ==
-          latest_surface->surface_id().frame_sink_id() &&
-      primary_surface_id.local_surface_id().embed_token() ==
-          latest_surface->surface_id().local_surface_id().embed_token()) {
-    UMA_HISTOGRAM_COUNTS_100(
-        kUmaManhattanDistanceToPrimary,
-        latest_surface->surface_id().ManhattanDistanceTo(primary_surface_id));
+  if (latest_surface->surface_id() != primary_surface_id) {
+    if (primary_surface_id.frame_sink_id() ==
+            latest_surface->surface_id().frame_sink_id() &&
+        primary_surface_id.local_surface_id().embed_token() ==
+            latest_surface->surface_id().local_surface_id().embed_token()) {
+      UMA_HISTOGRAM_COUNTS_100(
+          kUmaManhattanDistanceToPrimary,
+          latest_surface->surface_id().ManhattanDistanceTo(primary_surface_id));
+    }
+
+    if (!surface_quad->stretch_content_to_fill_bounds) {
+      const CompositorFrame& fallback_frame = latest_surface->GetActiveFrame();
+
+      gfx::Rect fallback_rect(
+          latest_surface->GetActiveFrame().size_in_pixels());
+
+      float scale_ratio =
+          parent_device_scale_factor / fallback_frame.device_scale_factor();
+      fallback_rect =
+          gfx::ScaleToEnclosingRect(fallback_rect, scale_ratio, scale_ratio);
+      fallback_rect = gfx::IntersectRects(fallback_rect, surface_quad->rect);
+
+      EmitGutterQuadsIfNecessary(
+          surface_quad->rect, fallback_rect, surface_quad->shared_quad_state,
+          target_transform, clip_rect,
+          fallback_frame.metadata.root_background_color, dest_pass);
+    }
+    ++uma_stats_.using_fallback_surface;
   }
 
-  if (!surface_quad->stretch_content_to_fill_bounds) {
-    const CompositorFrame& fallback_frame = latest_surface->GetActiveFrame();
-
-    gfx::Rect fallback_rect(latest_surface->GetActiveFrame().size_in_pixels());
-
-    float scale_ratio =
-        parent_device_scale_factor / fallback_frame.device_scale_factor();
-    fallback_rect =
-        gfx::ScaleToEnclosingRect(fallback_rect, scale_ratio, scale_ratio);
-    fallback_rect = gfx::IntersectRects(fallback_rect, surface_quad->rect);
-
-    EmitGutterQuadsIfNecessary(
-        surface_quad->rect, fallback_rect, surface_quad->shared_quad_state,
-        target_transform, clip_rect,
-        fallback_frame.metadata.root_background_color, dest_pass);
-  }
-
-  ++uma_stats_.using_fallback_surface;
-
   EmitSurfaceContent(latest_surface, parent_device_scale_factor,
                      surface_quad->shared_quad_state, surface_quad->rect,
                      surface_quad->visible_rect, target_transform, clip_rect,
@@ -809,14 +797,9 @@
 
 void SurfaceAggregator::ProcessAddedAndRemovedSurfaces() {
   for (const auto& surface : previous_contained_surfaces_) {
-    if (!contained_surfaces_.count(surface.first)) {
+    if (!contained_surfaces_.count(surface.first))
       // Release resources of removed surface.
-      auto it = surface_id_to_resource_child_id_.find(surface.first);
-      if (it != surface_id_to_resource_child_id_.end()) {
-        provider_->DestroyChild(it->second);
-        surface_id_to_resource_child_id_.erase(it);
-      }
-    }
+      ReleaseResources(surface.first);
   }
 }
 
@@ -997,22 +980,19 @@
                 surface_info.surface_range.end().local_surface_id());
       }
     }
+
+    // TODO(fsamuel): Consider caching this value somewhere so that
+    // HandleSurfaceQuad doesn't need to call it again.
     Surface* child_surface =
-        manager_->GetSurfaceForId(surface_info.surface_range.end());
+        manager_->GetLatestInFlightSurface(surface_info.surface_range);
+
+    // If the primary surface is not available then we assume the damage is
+    // the full size of the SurfaceDrawQuad because we might need to introduce
+    // gutter.
     gfx::Rect surface_damage;
-    if (!child_surface || !child_surface->HasActiveFrame()) {
-      // If the primary surface is not available then we assume the damage is
-      // the full size of the SurfaceDrawQuad because we might need to introduce
-      // gutter.
+    if (!child_surface ||
+        child_surface->surface_id() != surface_info.surface_range.end()) {
       surface_damage = surface_info.quad_rect;
-      if (surface_info.surface_range.start()) {
-        // TODO(fsamuel): Consider caching this value somewhere so that
-        // HandleSurfaceQuad doesn't need to call it again.
-        Surface* fallback_surface =
-            manager_->GetLatestInFlightSurface(surface_info.surface_range);
-        if (fallback_surface && fallback_surface->HasActiveFrame())
-          child_surface = fallback_surface;
-      }
     }
 
     if (child_surface) {
diff --git a/components/viz/service/display_embedder/gpu_display_provider.cc b/components/viz/service/display_embedder/gpu_display_provider.cc
index 50422af..cf9707a 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.cc
+++ b/components/viz/service/display_embedder/gpu_display_provider.cc
@@ -15,24 +15,23 @@
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/display/display_scheduler.h"
 #include "components/viz/service/display_embedder/gl_output_surface.h"
-#include "components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/display_embedder/skia_output_surface_impl.h"
 #include "components/viz/service/display_embedder/software_output_surface.h"
 #include "components/viz/service/display_embedder/viz_process_context_provider.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
+#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/client/shared_memory_limits.h"
 #include "gpu/command_buffer/service/image_factory.h"
 #include "gpu/ipc/command_buffer_task_executor.h"
 #include "gpu/ipc/common/surface_handle.h"
-#include "gpu/ipc/service/gpu_channel_manager.h"
 #include "gpu/ipc/service/gpu_channel_manager_delegate.h"
-#include "gpu/ipc/service/gpu_memory_buffer_factory.h"
 #include "ui/base/ui_base_switches.h"
 
 #if defined(OS_WIN)
 #include "components/viz/service/display_embedder/gl_output_surface_win.h"
 #include "components/viz/service/display_embedder/software_output_device_win.h"
+#include "ui/gfx/win/rendering_window_manager.h"
 #endif
 
 #if defined(OS_ANDROID)
@@ -58,40 +57,44 @@
 #include "ui/ozone/public/surface_ozone_canvas.h"
 #endif
 
-namespace {
-
-gpu::ImageFactory* GetImageFactory(gpu::GpuChannelManager* channel_manager) {
-  auto* buffer_factory = channel_manager->gpu_memory_buffer_factory();
-  return buffer_factory ? buffer_factory->AsImageFactory() : nullptr;
-}
-
-}  // namespace
-
 namespace viz {
 
 GpuDisplayProvider::GpuDisplayProvider(
     uint32_t restart_id,
     GpuServiceImpl* gpu_service_impl,
     scoped_refptr<gpu::CommandBufferTaskExecutor> task_executor,
-    gpu::GpuChannelManager* gpu_channel_manager,
+    gpu::GpuChannelManagerDelegate* gpu_channel_manager_delegate,
+    std::unique_ptr<gpu::GpuMemoryBufferManager> gpu_memory_buffer_manager,
+    gpu::ImageFactory* image_factory,
     ServerSharedBitmapManager* server_shared_bitmap_manager,
     bool headless,
     bool wait_for_all_pipeline_stages_before_draw)
     : restart_id_(restart_id),
       gpu_service_impl_(gpu_service_impl),
       task_executor_(std::move(task_executor)),
-      gpu_channel_manager_delegate_(gpu_channel_manager->delegate()),
-      gpu_memory_buffer_manager_(
-          std::make_unique<InProcessGpuMemoryBufferManager>(
-              gpu_channel_manager)),
-      image_factory_(GetImageFactory(gpu_channel_manager)),
+      gpu_channel_manager_delegate_(gpu_channel_manager_delegate),
+      gpu_memory_buffer_manager_(std::move(gpu_memory_buffer_manager)),
+      image_factory_(image_factory),
       server_shared_bitmap_manager_(server_shared_bitmap_manager),
       task_runner_(base::ThreadTaskRunnerHandle::Get()),
       headless_(headless),
       wait_for_all_pipeline_stages_before_draw_(
-          wait_for_all_pipeline_stages_before_draw) {
-  DCHECK_NE(restart_id_, BeginFrameSource::kNotRestartableId);
-}
+          wait_for_all_pipeline_stages_before_draw) {}
+
+GpuDisplayProvider::GpuDisplayProvider(
+    uint32_t restart_id,
+    ServerSharedBitmapManager* server_shared_bitmap_manager,
+    bool headless,
+    bool wait_for_all_pipeline_stages_before_draw)
+    : GpuDisplayProvider(restart_id,
+                         /*gpu_service_impl=*/nullptr,
+                         /*task_executor=*/nullptr,
+                         /*gpu_channel_manager_delegate=*/nullptr,
+                         /*gpu_memory_buffer_manager=*/nullptr,
+                         /*image_factory=*/nullptr,
+                         server_shared_bitmap_manager,
+                         headless,
+                         wait_for_all_pipeline_stages_before_draw) {}
 
 GpuDisplayProvider::~GpuDisplayProvider() = default;
 
@@ -128,6 +131,8 @@
     skia_output_surface = static_cast<SkiaOutputSurface*>(output_surface.get());
 #endif
   } else {
+    DCHECK(task_executor_);
+
     scoped_refptr<VizProcessContextProvider> context_provider;
 
     // Retry creating and binding |context_provider| on transient failures.
@@ -209,11 +214,19 @@
   auto device = CreateSoftwareOutputDeviceWinGpu(
       surface_handle, &output_device_backing_, display_client, &child_hwnd);
 
-  // If |child_hwnd| isn't null then a new child HWND was created. Send an IPC
-  // to browser process for SetParent() syscall.
+  // If |child_hwnd| isn't null then a new child HWND was created.
   if (child_hwnd) {
-    gpu_channel_manager_delegate_->SendCreatedChildWindow(surface_handle,
-                                                          child_hwnd);
+    if (gpu_channel_manager_delegate_) {
+      // Send an IPC to browser process for SetParent().
+      gpu_channel_manager_delegate_->SendCreatedChildWindow(surface_handle,
+                                                            child_hwnd);
+    } else {
+      // We are already in the browser process.
+      if (!gfx::RenderingWindowManager::GetInstance()->RegisterChild(
+              surface_handle, child_hwnd)) {
+        LOG(ERROR) << "Bad parenting request.";
+      }
+    }
   }
 
   return device;
diff --git a/components/viz/service/display_embedder/gpu_display_provider.h b/components/viz/service/display_embedder/gpu_display_provider.h
index 9cb1e1ff..e28bca5 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.h
+++ b/components/viz/service/display_embedder/gpu_display_provider.h
@@ -23,9 +23,9 @@
 #endif
 
 namespace gpu {
-class GpuChannelManager;
-class GpuChannelManagerDelegate;
 class CommandBufferTaskExecutor;
+class GpuChannelManagerDelegate;
+class GpuMemoryBufferManager;
 class ImageFactory;
 }  // namespace gpu
 
@@ -39,10 +39,18 @@
 // In-process implementation of DisplayProvider.
 class VIZ_SERVICE_EXPORT GpuDisplayProvider : public DisplayProvider {
  public:
+  GpuDisplayProvider(
+      uint32_t restart_id,
+      GpuServiceImpl* gpu_service_impl,
+      scoped_refptr<gpu::CommandBufferTaskExecutor> task_executor,
+      gpu::GpuChannelManagerDelegate* gpu_channel_manager_delegate,
+      std::unique_ptr<gpu::GpuMemoryBufferManager> gpu_memory_buffer_manager,
+      gpu::ImageFactory* image_factory,
+      ServerSharedBitmapManager* server_shared_bitmap_manager,
+      bool headless,
+      bool wait_for_all_pipeline_stages_before_draw);
+  // Software compositing only.
   GpuDisplayProvider(uint32_t restart_id,
-                     GpuServiceImpl* gpu_service_impl,
-                     scoped_refptr<gpu::CommandBufferTaskExecutor> gpu_service,
-                     gpu::GpuChannelManager* gpu_channel_manager,
                      ServerSharedBitmapManager* server_shared_bitmap_manager,
                      bool headless,
                      bool wait_for_all_pipeline_stages_before_draw);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 3704947..5b11c845 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -273,8 +273,6 @@
       local_surface_id, std::move(frame), std::move(hit_test_region_list),
       submit_time,
       mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback());
-  UMA_HISTOGRAM_ENUMERATION(
-      "Compositing.CompositorFrameSinkSupport.SubmitResult", result);
   DCHECK_EQ(result, SubmitResult::ACCEPTED);
 }
 
@@ -295,7 +293,7 @@
   owned_bitmaps_.erase(id);
 }
 
-SubmitResult CompositorFrameSinkSupport::MaybeSubmitCompositorFrame(
+SubmitResult CompositorFrameSinkSupport::MaybeSubmitCompositorFrameInternal(
     const LocalSurfaceId& local_surface_id,
     CompositorFrame frame,
     base::Optional<HitTestRegionList> hit_test_region_list,
@@ -573,6 +571,20 @@
       frame_sink_manager_->GetPrimaryBeginFrameSource(), needs_sync_tokens_);
 }
 
+SubmitResult CompositorFrameSinkSupport::MaybeSubmitCompositorFrame(
+    const LocalSurfaceId& local_surface_id,
+    CompositorFrame frame,
+    base::Optional<HitTestRegionList> hit_test_region_list,
+    uint64_t submit_time,
+    mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback callback) {
+  SubmitResult result = MaybeSubmitCompositorFrameInternal(
+      local_surface_id, std::move(frame), std::move(hit_test_region_list),
+      submit_time, std::move(callback));
+  UMA_HISTOGRAM_ENUMERATION(
+      "Compositing.CompositorFrameSinkSupport.SubmitResult", result);
+  return result;
+}
+
 void CompositorFrameSinkSupport::AttachCaptureClient(
     CapturableFrameSink::Client* client) {
   DCHECK(!base::ContainsValue(capture_clients_, client));
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index a85f0835..7a77470 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -169,6 +169,13 @@
  private:
   friend class FrameSinkManagerTest;
 
+  SubmitResult MaybeSubmitCompositorFrameInternal(
+      const LocalSurfaceId& local_surface_id,
+      CompositorFrame frame,
+      base::Optional<HitTestRegionList> hit_test_region_list,
+      uint64_t submit_time,
+      mojom::CompositorFrameSink::SubmitCompositorFrameSyncCallback);
+
   // Creates a surface reference from the top-level root to |surface_id|.
   SurfaceReference MakeTopLevelRootReference(const SurfaceId& surface_id);
 
diff --git a/components/viz/service/frame_sinks/surface_references_unittest.cc b/components/viz/service/frame_sinks/surface_references_unittest.cc
index 32ee610b..54007fa569 100644
--- a/components/viz/service/frame_sinks/surface_references_unittest.cc
+++ b/components/viz/service/frame_sinks/surface_references_unittest.cc
@@ -101,9 +101,10 @@
   }
 
   // Returns all the references where |surface_id| is the child.
-  const base::flat_set<SurfaceId>& GetReferencesFor(
+  const base::flat_set<SurfaceId> GetReferencesFor(
       const SurfaceId& surface_id) {
-    return GetSurfaceManager().GetSurfacesThatReferenceChild(surface_id);
+    return GetSurfaceManager().GetSurfacesThatReferenceChildForTesting(
+        surface_id);
   }
 
   // Temporary references are stored as a map in SurfaceManager. This method
diff --git a/components/viz/service/frame_sinks/surface_synchronization_unittest.cc b/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
index 7da14049..f3f2f32 100644
--- a/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
+++ b/components/viz/service/frame_sinks/surface_synchronization_unittest.cc
@@ -2157,9 +2157,8 @@
             GetLatestInFlightSurface(child_id4, child_id1));
   EXPECT_THAT(GetChildReferences(parent_id), UnorderedElementsAre(child_id1));
 
-  // If the primary surface is old, then we shouldn't return an in-flight
-  // surface that is newer than the primary.
-  EXPECT_EQ(GetSurfaceForId(child_id2),
+  // If the primary surface is active, we return it.
+  EXPECT_EQ(GetSurfaceForId(child_id3),
             GetLatestInFlightSurface(child_id3, child_id1));
 }
 
@@ -2168,6 +2167,7 @@
 TEST_F(SurfaceSynchronizationTest, LatestInFlightSurfaceWithBogusFallback) {
   const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
   const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
+  const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 2);
 
   child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
                                          MakeDefaultCompositorFrame());
@@ -2186,10 +2186,15 @@
   EXPECT_FALSE(parent_surface()->HasPendingFrame());
   EXPECT_THAT(parent_surface()->activation_dependencies(), IsEmpty());
 
-  // If the fallback surface doesn't exist, then GetLatestInFlightSurface should
-  // always return nullptr.
   const SurfaceId bogus_child_id = MakeSurfaceId(kChildFrameSink1, 10);
-  EXPECT_EQ(nullptr, GetLatestInFlightSurface(child_id1, bogus_child_id));
+
+  // If primary exists and active return it regardless of the fallback.
+  EXPECT_EQ(GetSurfaceForId(child_id1),
+            GetLatestInFlightSurface(child_id1, bogus_child_id));
+
+  // If primary is not active and fallback is doesn't exist, we always return
+  // nullptr.
+  EXPECT_EQ(nullptr, GetLatestInFlightSurface(child_id2, bogus_child_id));
 }
 
 // This test verifies that GetLatestInFlightSurface will not return null if the
@@ -2283,6 +2288,7 @@
   const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
   const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
   const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink2, 1);
+  const SurfaceId child_id3 = MakeSurfaceId(kChildFrameSink2, 2);
 
   child_support1().SubmitCompositorFrame(child_id1.local_surface_id(),
                                          MakeDefaultCompositorFrame());
@@ -2297,19 +2303,13 @@
   // GetLatestInFlightSurface will always return the specified fallback.
   child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
                                          MakeDefaultCompositorFrame());
-
-  // Submit a child CompositorFrame without a different FrameSinkId and verify
-  // that if the fallback and primary differ in FrameSinkId then
-  // GetLatestInFlightSurface will always return the specified fallback.
-  child_support2().SubmitCompositorFrame(child_id2.local_surface_id(),
-                                         MakeDefaultCompositorFrame());
   EXPECT_EQ(GetSurfaceForId(child_id1),
-            GetLatestInFlightSurface(child_id2, child_id1));
+            GetLatestInFlightSurface(child_id3, child_id1));
 }
 
-// This test verifies that GetLatestInFlightSurface will never return the
-// primary surface ID even if it is in the temporary reference queue.
-TEST_F(SurfaceSynchronizationTest, LatestInFlightSurfaceSkipPrimary) {
+// This test verifies that GetLatestInFlightSurface will return the
+// primary surface ID if it is in the temporary reference queue.
+TEST_F(SurfaceSynchronizationTest, LatestInFlightSurfaceReturnPrimary) {
   const SurfaceId parent_id = MakeSurfaceId(kParentFrameSink, 1);
   const SurfaceId child_id1 = MakeSurfaceId(kChildFrameSink1, 1);
   const SurfaceId child_id2 = MakeSurfaceId(kChildFrameSink1, 2);
@@ -2336,9 +2336,9 @@
   child_support1().SubmitCompositorFrame(child_id3.local_surface_id(),
                                          MakeDefaultCompositorFrame());
 
-  // GetLatestInFlightSurface will never return the primary surface ID
-  // even if it's available.
-  EXPECT_EQ(GetSurfaceForId(child_id2),
+  // GetLatestInFlightSurface will return the primary surface ID if it's
+  // available.
+  EXPECT_EQ(GetSurfaceForId(child_id3),
             GetLatestInFlightSurface(child_id3, child_id1));
 }
 
diff --git a/components/viz/service/main/viz_compositor_thread_runner.cc b/components/viz/service/main/viz_compositor_thread_runner.cc
new file mode 100644
index 0000000..193499d2
--- /dev/null
+++ b/components/viz/service/main/viz_compositor_thread_runner.cc
@@ -0,0 +1,108 @@
+// Copyright 2018 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/viz/service/main/viz_compositor_thread_runner.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/trace_event/memory_dump_manager.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+#include "components/viz/common/switches.h"
+#include "components/viz/service/display_embedder/gpu_display_provider.h"
+#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
+#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "ui/gfx/switches.h"
+
+namespace viz {
+namespace {
+
+const char kThreadName[] = "VizCompositorThread";
+
+std::unique_ptr<base::Thread> CreateAndStartCompositorThread() {
+  auto thread = std::make_unique<base::Thread>(kThreadName);
+
+  base::Thread::Options thread_options;
+#if defined(OS_WIN)
+  // Windows needs a UI message loop for child HWND. Other platforms can use the
+  // default message loop type.
+  thread_options.message_loop_type = base::MessageLoop::TYPE_UI;
+#elif defined(OS_CHROMEOS)
+  thread_options.priority = base::ThreadPriority::DISPLAY;
+#endif
+
+  CHECK(thread->StartWithOptions(thread_options));
+  return thread;
+}
+
+}  // namespace
+
+VizCompositorThreadRunner::VizCompositorThreadRunner()
+    : thread_(CreateAndStartCompositorThread()),
+      task_runner_(thread_->task_runner()) {}
+
+VizCompositorThreadRunner::~VizCompositorThreadRunner() {
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&VizCompositorThreadRunner::TearDownOnCompositorThread,
+                     base::Unretained(this)));
+  thread_->Stop();
+}
+
+void VizCompositorThreadRunner::CreateFrameSinkManager(
+    mojom::FrameSinkManagerParamsPtr params) {
+  task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &VizCompositorThreadRunner::CreateFrameSinkManagerOnCompositorThread,
+          base::Unretained(this), std::move(params)));
+}
+
+void VizCompositorThreadRunner::CreateFrameSinkManagerOnCompositorThread(
+    mojom::FrameSinkManagerParamsPtr params) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  DCHECK(!frame_sink_manager_);
+
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+
+  server_shared_bitmap_manager_ = std::make_unique<ServerSharedBitmapManager>();
+  base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
+      server_shared_bitmap_manager_.get(), "ServerSharedBitmapManager",
+      base::ThreadTaskRunnerHandle::Get());
+
+  // Create GpuDisplayProvider usable for software compositing only.
+  display_provider_ = std::make_unique<GpuDisplayProvider>(
+      params->restart_id, server_shared_bitmap_manager_.get(),
+      command_line->HasSwitch(switches::kHeadless),
+      command_line->HasSwitch(switches::kRunAllCompositorStagesBeforeDraw));
+
+  base::Optional<uint32_t> activation_deadline_in_frames;
+  if (params->use_activation_deadline)
+    activation_deadline_in_frames = params->activation_deadline_in_frames;
+  frame_sink_manager_ = std::make_unique<FrameSinkManagerImpl>(
+      server_shared_bitmap_manager_.get(), activation_deadline_in_frames,
+      display_provider_.get());
+  frame_sink_manager_->BindAndSetClient(
+      std::move(params->frame_sink_manager), nullptr,
+      mojom::FrameSinkManagerClientPtr(
+          std::move(params->frame_sink_manager_client)));
+}
+
+void VizCompositorThreadRunner::TearDownOnCompositorThread() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  if (server_shared_bitmap_manager_) {
+    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
+        server_shared_bitmap_manager_.get());
+  }
+
+  frame_sink_manager_.reset();
+  display_provider_.reset();
+  server_shared_bitmap_manager_.reset();
+}
+
+}  // namespace viz
diff --git a/components/viz/service/main/viz_compositor_thread_runner.h b/components/viz/service/main/viz_compositor_thread_runner.h
new file mode 100644
index 0000000..93c8e9d
--- /dev/null
+++ b/components/viz/service/main/viz_compositor_thread_runner.h
@@ -0,0 +1,59 @@
+// Copyright 2018 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_VIZ_SERVICE_MAIN_VIZ_COMPOSITOR_THREAD_RUNNER_H_
+#define COMPONENTS_VIZ_SERVICE_MAIN_VIZ_COMPOSITOR_THREAD_RUNNER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "components/viz/service/viz_service_export.h"
+#include "services/viz/privileged/interfaces/viz_main.mojom.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class Thread;
+}  // namespace base
+
+namespace viz {
+class DisplayProvider;
+class FrameSinkManagerImpl;
+class ServerSharedBitmapManager;
+
+// Starts and runs the VizCompositorThread. The thread will be started when this
+// object is constructed. Objects on the thread will be initialized after
+// calling CreateFrameSinkManager(). Destructor will teardown objects on thread
+// and then stop the thread.
+// TODO(kylechar): Convert VizMainImpl to use VizCompositorThreadRunner.
+class VIZ_SERVICE_EXPORT VizCompositorThreadRunner {
+ public:
+  VizCompositorThreadRunner();
+  // Performs teardown on thread and then stops thread.
+  ~VizCompositorThreadRunner();
+
+  // Create FrameSinkManager from |params|. This can be called from the thread
+  // that owns |this| to initialize state on VizCompositorThreadRunner.
+  void CreateFrameSinkManager(mojom::FrameSinkManagerParamsPtr params);
+
+ private:
+  void CreateFrameSinkManagerOnCompositorThread(
+      mojom::FrameSinkManagerParamsPtr params);
+  void TearDownOnCompositorThread();
+
+  // Start variables to be accessed only on |task_runner_|.
+  std::unique_ptr<ServerSharedBitmapManager> server_shared_bitmap_manager_;
+  std::unique_ptr<DisplayProvider> display_provider_;
+  std::unique_ptr<FrameSinkManagerImpl> frame_sink_manager_;
+  // End variables to be accessed only on |task_runner_|.
+
+  std::unique_ptr<base::Thread> thread_;
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(VizCompositorThreadRunner);
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_SERVICE_MAIN_VIZ_COMPOSITOR_THREAD_RUNNER_H_
diff --git a/components/viz/service/main/viz_main_impl.cc b/components/viz/service/main/viz_main_impl.cc
index e547082..b4e081d 100644
--- a/components/viz/service/main/viz_main_impl.cc
+++ b/components/viz/service/main/viz_main_impl.cc
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "components/viz/common/switches.h"
 #include "components/viz/service/display_embedder/gpu_display_provider.h"
+#include "components/viz/service/display_embedder/in_process_gpu_memory_buffer_manager.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
 #include "components/viz/service/gl/gpu_service_impl.h"
@@ -332,9 +333,17 @@
       server_shared_bitmap_manager_.get(), "viz::ServerSharedBitmapManager",
       base::ThreadTaskRunnerHandle::Get());
 
+  auto* gpu_channel_manager = gpu_service_->gpu_channel_manager();
+  gpu::ImageFactory* image_factory = nullptr;
+  if (gpu_channel_manager->gpu_memory_buffer_factory()) {
+    image_factory =
+        gpu_channel_manager->gpu_memory_buffer_factory()->AsImageFactory();
+  }
   display_provider_ = std::make_unique<GpuDisplayProvider>(
       params->restart_id, gpu_service_.get(), task_executor_,
-      gpu_service_->gpu_channel_manager(), server_shared_bitmap_manager_.get(),
+      gpu_channel_manager->delegate(),
+      std::make_unique<InProcessGpuMemoryBufferManager>(gpu_channel_manager),
+      image_factory, server_shared_bitmap_manager_.get(),
       command_line->HasSwitch(switches::kHeadless),
       command_line->HasSwitch(switches::kRunAllCompositorStagesBeforeDraw));
 
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index 1ecfad1..c721604 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -139,8 +139,10 @@
     // A misbehaving client may report a non-existent surface ID as a
     // |referenced_surface|. In that case, |surface| would be nullptr, and
     // there is nothing to do here.
-    if (fallback_surface)
+    if (fallback_surface &&
+        fallback_surface->surface_id() != surface_range.end()) {
       fallback_surface->Close();
+    }
   }
 }
 
diff --git a/components/viz/service/surfaces/surface_manager.cc b/components/viz/service/surfaces/surface_manager.cc
index 11af09c..5ec96e5 100644
--- a/components/viz/service/surfaces/surface_manager.cc
+++ b/components/viz/service/surfaces/surface_manager.cc
@@ -40,10 +40,6 @@
 
 }  // namespace
 
-SurfaceManager::SurfaceReferenceInfo::SurfaceReferenceInfo() = default;
-
-SurfaceManager::SurfaceReferenceInfo::~SurfaceReferenceInfo() = default;
-
 SurfaceManager::TemporaryReferenceData::TemporaryReferenceData() = default;
 
 SurfaceManager::TemporaryReferenceData::~TemporaryReferenceData() = default;
@@ -263,15 +259,19 @@
   auto iter = references_.find(surface_id);
   if (iter == references_.end())
     return empty_surface_id_set_;
-  return iter->second.children;
+  return iter->second;
 }
 
-const base::flat_set<SurfaceId>& SurfaceManager::GetSurfacesThatReferenceChild(
+base::flat_set<SurfaceId>
+SurfaceManager::GetSurfacesThatReferenceChildForTesting(
     const SurfaceId& surface_id) const {
-  auto iter = references_.find(surface_id);
-  if (iter == references_.end())
-    return empty_surface_id_set_;
-  return iter->second.parents;
+  base::flat_set<SurfaceId> parents;
+
+  for (auto& parent : references_) {
+    if (parent.second.find(surface_id) != parent.second.end())
+      parents.insert(parent.first);
+  }
+  return parents;
 }
 
 SurfaceManager::SurfaceIdSet SurfaceManager::GetLiveSurfacesForReferences() {
@@ -329,8 +329,7 @@
     return;
   }
 
-  references_[parent_id].children.insert(child_id);
-  references_[child_id].parents.insert(parent_id);
+  references_[parent_id].insert(child_id);
 
   for (auto& observer : observer_list_)
     observer.OnAddedSurfaceReference(parent_id, child_id);
@@ -345,32 +344,19 @@
   const SurfaceId& child_id = reference.child_id();
 
   auto iter_parent = references_.find(parent_id);
-  auto iter_child = references_.find(child_id);
-  if (iter_parent == references_.end() || iter_child == references_.end())
+  if (iter_parent == references_.end())
+    return;
+
+  auto child_iter = iter_parent->second.find(child_id);
+  if (child_iter == iter_parent->second.end())
     return;
 
   for (auto& observer : observer_list_)
     observer.OnRemovedSurfaceReference(parent_id, child_id);
 
-  iter_parent->second.children.erase(child_id);
-  iter_child->second.parents.erase(parent_id);
-}
-
-void SurfaceManager::RemoveAllSurfaceReferences(const SurfaceId& surface_id) {
-  DCHECK(!HasTemporaryReference(surface_id));
-
-  auto iter = references_.find(surface_id);
-  if (iter != references_.end()) {
-    // Remove all references from |surface_id| to a child surface.
-    for (const SurfaceId& child_id : iter->second.children)
-      references_[child_id].parents.erase(surface_id);
-
-    // Remove all reference from parent surface to |surface_id|.
-    for (const SurfaceId& parent_id : iter->second.parents)
-      references_[parent_id].children.erase(surface_id);
-
-    references_.erase(iter);
-  }
+  iter_parent->second.erase(child_iter);
+  if (iter_parent->second.empty())
+    references_.erase(iter_parent);
 }
 
 bool SurfaceManager::HasTemporaryReference(const SurfaceId& surface_id) const {
@@ -446,6 +432,11 @@
   const SurfaceId& primary_surface_id = surface_range.end();
   const base::Optional<SurfaceId>& fallback_surface_id = surface_range.start();
 
+  // If primary exists, we return it.
+  Surface* primary_surface = GetSurfaceForId(primary_surface_id);
+  if (primary_surface && primary_surface->HasActiveFrame())
+    return primary_surface;
+
   if (!fallback_surface_id)
     return nullptr;
 
@@ -461,8 +452,7 @@
   const std::vector<LocalSurfaceId>& temp_surfaces = it->second;
   for (const LocalSurfaceId& local_surface_id : base::Reversed(temp_surfaces)) {
     // The in-flight surface must be older than the primary surface ID.
-    if (local_surface_id == primary_surface_id.local_surface_id() ||
-        local_surface_id.parent_sequence_number() >
+    if (local_surface_id.parent_sequence_number() >
             primary_surface_id.local_surface_id().parent_sequence_number() ||
         local_surface_id.child_sequence_number() >
             primary_surface_id.local_surface_id().child_sequence_number()) {
@@ -584,7 +574,7 @@
   // and that's not desirable.
   std::unique_ptr<Surface> doomed = std::move(it->second);
   surface_map_.erase(it);
-  RemoveAllSurfaceReferences(surface_id);
+  references_.erase(surface_id);
 }
 
 #if DCHECK_IS_ON()
diff --git a/components/viz/service/surfaces/surface_manager.h b/components/viz/service/surfaces/surface_manager.h
index 848f179..f9ca07de 100644
--- a/components/viz/service/surfaces/surface_manager.h
+++ b/components/viz/service/surfaces/surface_manager.h
@@ -182,7 +182,7 @@
 
   // Returns all surfaces that have a reference to child |surface_id|. Will
   // return an empty set if |surface_id| is unknown or has no references to it.
-  const base::flat_set<SurfaceId>& GetSurfacesThatReferenceChild(
+  base::flat_set<SurfaceId> GetSurfacesThatReferenceChildForTesting(
       const SurfaceId& surface_id) const;
 
   // Returns the most recent surface associated with the |fallback_surface_id|'s
@@ -211,17 +211,6 @@
     COUNT
   };
 
-  struct SurfaceReferenceInfo {
-    SurfaceReferenceInfo();
-    ~SurfaceReferenceInfo();
-
-    // Surfaces that have references to this surface.
-    base::flat_set<SurfaceId> parents;
-
-    // Surfaces that are referenced from this surface.
-    base::flat_set<SurfaceId> children;
-  };
-
   struct TemporaryReferenceData {
     TemporaryReferenceData();
     ~TemporaryReferenceData();
@@ -248,10 +237,6 @@
   // Removes a reference from a |parent_id| to |child_id|.
   void RemoveSurfaceReferenceImpl(const SurfaceReference& reference);
 
-  // Removes all surface references to or from |surface_id|. Used when the
-  // surface is about to be deleted.
-  void RemoveAllSurfaceReferences(const SurfaceId& surface_id);
-
   bool HasTemporaryReference(const SurfaceId& surface_id) const;
 
   // Adds a temporary reference to |surface_id|. The reference will not have an
@@ -305,9 +290,9 @@
   const base::TickClock* tick_clock_;
 
   // Keeps track of surface references for a surface. The graph of references is
-  // stored in both directions, so we know the parents and children for each
-  // surface.
-  std::unordered_map<SurfaceId, SurfaceReferenceInfo, SurfaceIdHash>
+  // stored in parent to child direction. i.e the map stores all direct children
+  // of the surface specified by |SurfaceId|.
+  std::unordered_map<SurfaceId, base::flat_set<SurfaceId>, SurfaceIdHash>
       references_;
 
   // A map of surfaces that have temporary references.
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index fedfd4a..46d38b8b 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -792,10 +792,10 @@
     "file_url_loader_factory.h",
     "fileapi/browser_file_system_helper.cc",
     "fileapi/browser_file_system_helper.h",
+    "fileapi/file_system_manager_impl.cc",
+    "fileapi/file_system_manager_impl.h",
     "fileapi/file_system_url_loader_factory.cc",
     "fileapi/file_system_url_loader_factory.h",
-    "fileapi/fileapi_message_filter.cc",
-    "fileapi/fileapi_message_filter.h",
     "find_in_page_client.cc",
     "find_in_page_client.h",
     "find_request_manager.cc",
@@ -1522,6 +1522,9 @@
     "scheduler/responsiveness/calculator.h",
     "scheduler/responsiveness/message_loop_observer.cc",
     "scheduler/responsiveness/message_loop_observer.h",
+    "scheduler/responsiveness/native_event_observer.cc",
+    "scheduler/responsiveness/native_event_observer.h",
+    "scheduler/responsiveness/native_event_observer_mac.mm",
     "scheduler/responsiveness/watcher.cc",
     "scheduler/responsiveness/watcher.h",
     "scoped_active_url.cc",
@@ -2022,6 +2025,8 @@
       "renderer_host/pepper/pepper_print_settings_manager.h",
       "renderer_host/pepper/pepper_printing_host.cc",
       "renderer_host/pepper/pepper_printing_host.h",
+      "renderer_host/pepper/pepper_proxy_lookup_helper.cc",
+      "renderer_host/pepper/pepper_proxy_lookup_helper.h",
       "renderer_host/pepper/pepper_renderer_connection.cc",
       "renderer_host/pepper/pepper_renderer_connection.h",
       "renderer_host/pepper/pepper_security_helper.cc",
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 3dd224b..95b60d9 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -761,6 +761,9 @@
     case ax::mojom::Role::kInputTime:
       message_id = IDS_AX_ROLE_INPUT_TIME;
       break;
+    case ax::mojom::Role::kKeyboard:
+      // No role description.
+      break;
     case ax::mojom::Role::kLabelText:
       // No role description.
       break;
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index 0e30f88..30303ea 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -395,12 +395,12 @@
     BrowserContext* browser_context,
     const GURL& origin,
     int64_t service_worker_registration_id,
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     const base::Callback<void(mojom::PushDeliveryStatus)>& callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   PushMessagingRouter::DeliverMessage(browser_context, origin,
-                                      service_worker_registration_id, payload,
-                                      callback);
+                                      service_worker_registration_id,
+                                      std::move(payload), callback);
 }
 
 // static
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 300bc79..eb942391 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -1301,7 +1301,7 @@
     compositing_mode_reporter_impl_ =
         std::make_unique<viz::CompositingModeReporterImpl>();
 
-    if (features::IsVizDisplayCompositorEnabled()) {
+    if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
       auto transport_factory = std::make_unique<VizProcessTransportFactory>(
           BrowserGpuChannelHostFactory::instance(), GetResizeTaskRunner(),
           compositing_mode_reporter_impl_.get());
diff --git a/content/browser/code_cache/generated_code_cache.cc b/content/browser/code_cache/generated_code_cache.cc
index d968ba7a..f1f3ec3 100644
--- a/content/browser/code_cache/generated_code_cache.cc
+++ b/content/browser/code_cache/generated_code_cache.cc
@@ -193,14 +193,14 @@
                                     ReadDataCallback read_data_callback) {
   if (backend_state_ == kFailed) {
     // Silently ignore the requests.
-    std::move(read_data_callback).Run(scoped_refptr<net::IOBufferWithSize>());
+    std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>());
     return;
   }
 
   // If the url is invalid or if it is from a unique origin, we should not
   // cache the code.
   if (!IsAllowedToCache(url, origin)) {
-    std::move(read_data_callback).Run(scoped_refptr<net::IOBufferWithSize>());
+    std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>());
     return;
   }
 
@@ -378,7 +378,7 @@
 void GeneratedCodeCache::FetchEntryImpl(const std::string& key,
                                         ReadDataCallback read_data_callback) {
   if (backend_state_ != kInitialized) {
-    std::move(read_data_callback).Run(scoped_refptr<net::IOBufferWithSize>());
+    std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>());
     return;
   }
 
@@ -402,7 +402,7 @@
     scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry,
     int rv) {
   if (rv != net::OK) {
-    std::move(read_data_callback).Run(scoped_refptr<net::IOBufferWithSize>());
+    std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>());
     return;
   }
 
@@ -427,9 +427,14 @@
     scoped_refptr<net::IOBufferWithSize> buffer,
     int rv) {
   if (rv != buffer->size()) {
-    std::move(callback).Run(scoped_refptr<net::IOBufferWithSize>());
+    std::move(callback).Run(base::Time(), std::vector<uint8_t>());
   } else {
-    std::move(callback).Run(buffer);
+    int64_t raw_response_time = *(reinterpret_cast<int64_t*>(buffer->data()));
+    base::Time response_time = base::Time::FromDeltaSinceWindowsEpoch(
+        base::TimeDelta::FromMicroseconds(raw_response_time));
+    std::vector<uint8_t> data(buffer->data() + kResponseTimeSizeInBytes,
+                              buffer->data() + buffer->size());
+    std::move(callback).Run(response_time, data);
   }
 }
 
diff --git a/content/browser/code_cache/generated_code_cache.h b/content/browser/code_cache/generated_code_cache.h
index bbeb7f92..29165790 100644
--- a/content/browser/code_cache/generated_code_cache.h
+++ b/content/browser/code_cache/generated_code_cache.h
@@ -32,7 +32,8 @@
 class CONTENT_EXPORT GeneratedCodeCache {
  public:
   using ReadDataCallback =
-      base::RepeatingCallback<void(scoped_refptr<net::IOBufferWithSize>)>;
+      base::RepeatingCallback<void(const base::Time&,
+                                   const std::vector<uint8_t>&)>;
   static const int kResponseTimeSizeInBytes = sizeof(int64_t);
 
   // Creates a GeneratedCodeCache with the specified path and the maximum size.
diff --git a/content/browser/code_cache/generated_code_cache_unittest.cc b/content/browser/code_cache/generated_code_cache_unittest.cc
index af7e23eb..4f005c8 100644
--- a/content/browser/code_cache/generated_code_cache_unittest.cc
+++ b/content/browser/code_cache/generated_code_cache_unittest.cc
@@ -39,7 +39,7 @@
   void InitializeCache() {
     GURL url(kInitialUrl);
     url::Origin origin = url::Origin::Create(GURL(kInitialOrigin));
-    WriteToCache(url, origin, kInitialData);
+    WriteToCache(url, origin, kInitialData, base::Time::Now());
     scoped_task_environment_.RunUntilIdle();
   }
 
@@ -54,9 +54,10 @@
 
   void WriteToCache(const GURL& url,
                     const url::Origin& origin,
-                    const std::string& data) {
+                    const std::string& data,
+                    base::Time response_time) {
     std::vector<uint8_t> vector_data(data.begin(), data.end());
-    generated_code_cache_->WriteData(url, origin, base::Time(), vector_data);
+    generated_code_cache_->WriteData(url, origin, response_time, vector_data);
   }
 
   void DeleteFromCache(const GURL& url, const url::Origin& origin) {
@@ -77,18 +78,19 @@
 
   void ClearCacheComplete(int rv) {}
 
-  void FetchEntryCallback(scoped_refptr<net::IOBufferWithSize> buffer) {
-    if (!buffer || !buffer->data()) {
+  void FetchEntryCallback(const base::Time& response_time,
+                          const std::vector<uint8_t>& data) {
+    if (data.size() == 0) {
       received_ = true;
       received_null_ = true;
+      received_response_time_ = response_time;
       return;
     }
-    std::string str(
-        buffer->data() + GeneratedCodeCache::kResponseTimeSizeInBytes,
-        buffer->size() - GeneratedCodeCache::kResponseTimeSizeInBytes);
+    std::string str(data.begin(), data.end());
     received_ = true;
     received_null_ = false;
     received_data_ = str;
+    received_response_time_ = response_time;
   }
 
  protected:
@@ -96,6 +98,7 @@
   std::unique_ptr<GeneratedCodeCache> generated_code_cache_;
   base::ScopedTempDir cache_dir_;
   std::string received_data_;
+  base::Time received_response_time_;
   bool received_;
   bool received_null_;
   base::FilePath cache_path_;
@@ -106,6 +109,22 @@
 constexpr char GeneratedCodeCacheTest::kInitialData[];
 const int GeneratedCodeCacheTest::kMaxSizeInBytes;
 
+TEST_F(GeneratedCodeCacheTest, CheckResponseTime) {
+  GURL url(kInitialUrl);
+  url::Origin origin = url::Origin::Create(GURL(kInitialOrigin));
+
+  std::string data = "SerializedCodeForScript";
+  base::Time response_time = base::Time::Now();
+  WriteToCache(url, origin, data, response_time);
+  scoped_task_environment_.RunUntilIdle();
+  FetchFromCache(url, origin);
+  scoped_task_environment_.RunUntilIdle();
+
+  ASSERT_TRUE(received_);
+  EXPECT_EQ(data, received_data_);
+  EXPECT_EQ(response_time, received_response_time_);
+}
+
 TEST_F(GeneratedCodeCacheTest, FetchEntry) {
   GURL url(kInitialUrl);
   url::Origin origin = url::Origin::Create(GURL(kInitialOrigin));
@@ -124,12 +143,15 @@
 
   InitializeCache();
   std::string data = "SerializedCodeForScript";
-  WriteToCache(new_url, origin, data);
+  base::Time response_time = base::Time::Now();
+  WriteToCache(new_url, origin, data, response_time);
+  scoped_task_environment_.RunUntilIdle();
   FetchFromCache(new_url, origin);
   scoped_task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(received_);
   EXPECT_EQ(data, received_data_);
+  EXPECT_EQ(response_time, received_response_time_);
 }
 
 TEST_F(GeneratedCodeCacheTest, DeleteEntry) {
@@ -163,13 +185,15 @@
 
   InitializeCache();
   std::string data = "SerializedCodeForScript";
-  WriteToCache(new_url, origin, data);
+  base::Time response_time = base::Time::Now();
+  WriteToCache(new_url, origin, data, response_time);
   scoped_task_environment_.RunUntilIdle();
   FetchFromCache(new_url, origin);
   scoped_task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(received_);
   EXPECT_EQ(data, received_data_);
+  EXPECT_EQ(response_time, received_response_time_);
 }
 
 TEST_F(GeneratedCodeCacheTest, DeleteEntryPendingOp) {
@@ -191,12 +215,15 @@
 
   InitializeCache();
   std::string new_data = "SerializedCodeForScriptOverwrite";
-  WriteToCache(url, origin, new_data);
+  base::Time response_time = base::Time::Now();
+  WriteToCache(url, origin, new_data, response_time);
+  scoped_task_environment_.RunUntilIdle();
   FetchFromCache(url, origin);
   scoped_task_environment_.RunUntilIdle();
 
   ASSERT_TRUE(received_);
   EXPECT_EQ(new_data, received_data_);
+  EXPECT_EQ(response_time, received_response_time_);
 }
 
 TEST_F(GeneratedCodeCacheTest, FetchFailsForNonexistingOrigin) {
@@ -215,10 +242,10 @@
   url::Origin origin = url::Origin::Create(GURL(kInitialOrigin));
 
   std::string data_first_resource = "SerializedCodeForFirstResource";
-  WriteToCache(url, origin, data_first_resource);
+  WriteToCache(url, origin, data_first_resource, base::Time());
 
   std::string data_second_resource = "SerializedCodeForSecondResource";
-  WriteToCache(second_url, origin, data_second_resource);
+  WriteToCache(second_url, origin, data_second_resource, base::Time());
   scoped_task_environment_.RunUntilIdle();
 
   FetchFromCache(url, origin);
@@ -238,10 +265,10 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://example1.com"));
 
   std::string data_origin = "SerializedCodeForFirstOrigin";
-  WriteToCache(url, origin, data_origin);
+  WriteToCache(url, origin, data_origin, base::Time());
 
   std::string data_origin1 = "SerializedCodeForSecondOrigin";
-  WriteToCache(url, origin1, data_origin1);
+  WriteToCache(url, origin1, data_origin1, base::Time());
   scoped_task_environment_.RunUntilIdle();
 
   FetchFromCache(url, origin);
@@ -261,7 +288,7 @@
       url::Origin::Create(GURL("data:text/html,<script></script>"));
 
   std::string data = "SerializedCodeForUniqueOrigin";
-  WriteToCache(url, origin, data);
+  WriteToCache(url, origin, data, base::Time());
   scoped_task_environment_.RunUntilIdle();
 
   FetchFromCache(url, origin);
@@ -275,7 +302,7 @@
   url::Origin origin = url::Origin::Create(GURL("invalidURL"));
 
   std::string data = "SerializedCodeForInvalidOrigin";
-  WriteToCache(url, origin, data);
+  WriteToCache(url, origin, data, base::Time());
   scoped_task_environment_.RunUntilIdle();
 
   FetchFromCache(url, origin);
@@ -290,7 +317,7 @@
   url::Origin origin = url::Origin::Create(GURL("http://example.com"));
 
   std::string data = "SerializedCodeForInvalidURL";
-  WriteToCache(url, origin, data);
+  WriteToCache(url, origin, data, base::Time());
   scoped_task_environment_.RunUntilIdle();
 
   FetchFromCache(url, origin);
diff --git a/content/browser/compositor/image_transport_factory_browsertest.cc b/content/browser/compositor/image_transport_factory_browsertest.cc
index 6ce1145..659533c 100644
--- a/content/browser/compositor/image_transport_factory_browsertest.cc
+++ b/content/browser/compositor/image_transport_factory_browsertest.cc
@@ -84,7 +84,7 @@
 
   // TODO(crbug.com/844469): Delete after OOP-D is launched because GLHelper
   // and OwnedMailbox aren't used.
-  if (features::IsVizDisplayCompositorEnabled())
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
     return;
 
   // This test doesn't make sense in software compositing mode.
diff --git a/content/browser/compositor/test/test_image_transport_factory.cc b/content/browser/compositor/test/test_image_transport_factory.cc
index 9ce7cab..0e74486 100644
--- a/content/browser/compositor/test/test_image_transport_factory.cc
+++ b/content/browser/compositor/test/test_image_transport_factory.cc
@@ -35,7 +35,8 @@
 }  // namespace
 
 TestImageTransportFactory::TestImageTransportFactory()
-    : enable_viz_(features::IsVizDisplayCompositorEnabled()),
+    : enable_viz_(
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor)),
       frame_sink_id_allocator_(kDefaultClientId) {
   if (enable_viz_) {
     test_frame_sink_manager_impl_ =
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index b541375c..af692ae 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -16,6 +16,7 @@
 #include "components/viz/client/local_surface_id_provider.h"
 #include "components/viz/common/gpu/context_provider.h"
 #include "components/viz/common/gpu/raster_context_provider.h"
+#include "components/viz/common/switches.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "components/viz/service/display_embedder/compositing_mode_reporter_impl.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
@@ -152,25 +153,47 @@
       std::move(frame_sink_manager_client_request), resize_task_runner(),
       std::move(frame_sink_manager));
 
-  // Hop to the IO thread, then send the other side of interface to viz process.
-  auto connect_on_io_thread =
-      [](viz::mojom::FrameSinkManagerRequest request,
-         viz::mojom::FrameSinkManagerClientPtrInfo client) {
-        // There should always be a GpuProcessHost instance, and GPU process,
-        // for running the compositor thread. The exception is during shutdown
-        // the GPU process won't be restarted and GpuProcessHost::Get() can
-        // return null.
-        auto* gpu_process_host = GpuProcessHost::Get();
-        if (gpu_process_host) {
-          gpu_process_host->ConnectFrameSinkManager(std::move(request),
-                                                    std::move(client));
-        }
-      };
-  BrowserThread::PostTask(
-      BrowserThread::IO, FROM_HERE,
-      base::BindOnce(connect_on_io_thread,
-                     std::move(frame_sink_manager_request),
-                     frame_sink_manager_client.PassInterface()));
+  if (GpuDataManagerImpl::GetInstance()->GpuProcessStartAllowed()) {
+    // Hop to the IO thread, then send the other side of interface to viz
+    // process.
+    auto connect_on_io_thread =
+        [](viz::mojom::FrameSinkManagerRequest request,
+           viz::mojom::FrameSinkManagerClientPtrInfo client) {
+          // There should always be a GpuProcessHost instance, and GPU process,
+          // for running the compositor thread. The exception is during shutdown
+          // the GPU process won't be restarted and GpuProcessHost::Get() can
+          // return null.
+          auto* gpu_process_host = GpuProcessHost::Get();
+          if (gpu_process_host) {
+            gpu_process_host->ConnectFrameSinkManager(std::move(request),
+                                                      std::move(client));
+          }
+        };
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::BindOnce(connect_on_io_thread,
+                       std::move(frame_sink_manager_request),
+                       frame_sink_manager_client.PassInterface()));
+  } else {
+    DCHECK(!viz_compositor_thread_);
+
+    // GPU process access is disabled. Start a new thread to run the display
+    // compositor in-process and connect HostFrameSinkManager to it.
+    viz_compositor_thread_ = std::make_unique<viz::VizCompositorThreadRunner>();
+
+    viz::mojom::FrameSinkManagerParamsPtr params =
+        viz::mojom::FrameSinkManagerParams::New();
+    params->restart_id = viz::BeginFrameSource::kNotRestartableId;
+    base::Optional<uint32_t> activation_deadline_in_frames =
+        switches::GetDeadlineToSynchronizeSurfaces();
+    params->use_activation_deadline = activation_deadline_in_frames.has_value();
+    params->activation_deadline_in_frames =
+        activation_deadline_in_frames.value_or(0u);
+    params->frame_sink_manager = std::move(frame_sink_manager_request);
+    params->frame_sink_manager_client =
+        frame_sink_manager_client.PassInterface();
+    viz_compositor_thread_->CreateFrameSinkManager(std::move(params));
+  }
 }
 
 void VizProcessTransportFactory::CreateLayerTreeFrameSink(
diff --git a/content/browser/compositor/viz_process_transport_factory.h b/content/browser/compositor/viz_process_transport_factory.h
index 42f91b1..731a309d 100644
--- a/content/browser/compositor/viz_process_transport_factory.h
+++ b/content/browser/compositor/viz_process_transport_factory.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "components/viz/common/gpu/context_lost_observer.h"
+#include "components/viz/service/main/viz_compositor_thread_runner.h"
 #include "content/browser/compositor/image_transport_factory.h"
 #include "gpu/command_buffer/common/context_result.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -128,6 +129,10 @@
 
   std::unique_ptr<cc::SingleThreadTaskGraphRunner> task_graph_runner_;
 
+  // Will start and run the VizCompositorThread for using an in-process display
+  // compositor.
+  std::unique_ptr<viz::VizCompositorThreadRunner> viz_compositor_thread_;
+
   base::WeakPtrFactory<VizProcessTransportFactory> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(VizProcessTransportFactory);
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 9d1a23b3..b7addc8a 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -207,7 +207,7 @@
       observer_(this),
       weak_factory_(this) {
   bool create_video_consumer =
-      features::IsVizDisplayCompositorEnabled() ||
+      base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       base::FeatureList::IsEnabled(
           features::kUseVideoCaptureApiForDevToolsSnapshots);
 #ifdef OS_ANDROID
diff --git a/content/browser/devtools/protocol/service_worker_handler.cc b/content/browser/devtools/protocol/service_worker_handler.cc
index 77d1cc9..06c7cf4 100644
--- a/content/browser/devtools/protocol/service_worker_handler.cc
+++ b/content/browser/devtools/protocol/service_worker_handler.cc
@@ -27,7 +27,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/push_event_payload.h"
 #include "content/public/common/push_messaging_status.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider_type.mojom.h"
@@ -309,11 +308,11 @@
   int64_t id = 0;
   if (!base::StringToInt64(registration_id, &id))
     return CreateInvalidVersionIdErrorResponse();
-  PushEventPayload payload;
+  base::Optional<std::string> payload;
   if (data.size() > 0)
-    payload.setData(data);
+    payload = data;
   BrowserContext::DeliverPushMessage(
-      browser_context_, GURL(origin), id, payload,
+      browser_context_, GURL(origin), id, std::move(payload),
       base::BindRepeating([](mojom::PushDeliveryStatus status) {}));
 
   return Response::OK();
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index 957e1d5ecd..296270a 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -214,7 +214,7 @@
       return_as_stream_(false),
       gzip_compression_(false),
       weak_factory_(this) {
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       base::FeatureList::IsEnabled(
           features::kUseVideoCaptureApiForDevToolsSnapshots)) {
     video_consumer_ =
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 1149853b..f131c03 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -477,7 +477,7 @@
 
   if (sessions().size() == 1) {
     // Taking screenshots using the video capture API is done in TracingHandler.
-    if (!features::IsVizDisplayCompositorEnabled() &&
+    if (!base::FeatureList::IsEnabled(features::kVizDisplayCompositor) &&
         !base::FeatureList::IsEnabled(
             features::kUseVideoCaptureApiForDevToolsSnapshots)) {
       frame_trace_recorder_.reset(new DevToolsFrameTraceRecorder());
diff --git a/content/browser/dom_storage/dom_storage_context_wrapper.cc b/content/browser/dom_storage/dom_storage_context_wrapper.cc
index cf8f777..683c0d3 100644
--- a/content/browser/dom_storage/dom_storage_context_wrapper.cc
+++ b/content/browser/dom_storage/dom_storage_context_wrapper.cc
@@ -396,6 +396,7 @@
 void DOMStorageContextWrapper::OpenSessionStorage(
     int process_id,
     const std::string& namespace_id,
+    mojo::ReportBadMessageCallback bad_message_callback,
     blink::mojom::SessionStorageNamespaceRequest request) {
   if (!mojo_session_state_)
     return;
@@ -404,9 +405,11 @@
   // as soon as that task is posted, mojo_state_ is set to null, preventing
   // further tasks from being queued.
   mojo_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
-                                base::Unretained(mojo_session_state_),
-                                process_id, namespace_id, std::move(request)));
+      FROM_HERE,
+      base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
+                     base::Unretained(mojo_session_state_), process_id,
+                     namespace_id, std::move(bad_message_callback),
+                     std::move(request)));
 }
 
 void DOMStorageContextWrapper::SetLocalStorageDatabaseForTesting(
diff --git a/content/browser/dom_storage/dom_storage_context_wrapper.h b/content/browser/dom_storage/dom_storage_context_wrapper.h
index 9ab2eed..ac7e069f 100644
--- a/content/browser/dom_storage/dom_storage_context_wrapper.h
+++ b/content/browser/dom_storage/dom_storage_context_wrapper.h
@@ -18,6 +18,7 @@
 #include "content/browser/dom_storage/dom_storage_context_impl.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/dom_storage_context.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "third_party/blink/public/mojom/dom_storage/session_storage_namespace.mojom.h"
 #include "third_party/blink/public/mojom/dom_storage/storage_area.mojom.h"
 #include "url/origin.h"
@@ -83,6 +84,7 @@
                         blink::mojom::StorageAreaRequest request);
   void OpenSessionStorage(int process_id,
                           const std::string& namespace_id,
+                          mojo::ReportBadMessageCallback bad_message_callback,
                           blink::mojom::SessionStorageNamespaceRequest request);
 
   void SetLocalStorageDatabaseForTesting(
diff --git a/content/browser/dom_storage/session_storage_context_mojo.cc b/content/browser/dom_storage/session_storage_context_mojo.cc
index 5b5d80b..f64bab5 100644
--- a/content/browser/dom_storage/session_storage_context_mojo.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo.cc
@@ -121,17 +121,18 @@
 void SessionStorageContextMojo::OpenSessionStorage(
     int process_id,
     const std::string& namespace_id,
+    mojo::ReportBadMessageCallback bad_message_callback,
     blink::mojom::SessionStorageNamespaceRequest request) {
   if (connection_state_ != CONNECTION_FINISHED) {
     RunWhenConnected(
         base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
                        weak_ptr_factory_.GetWeakPtr(), process_id, namespace_id,
-                       std::move(request)));
+                       std::move(bad_message_callback), std::move(request)));
     return;
   }
   auto found = namespaces_.find(namespace_id);
   if (found == namespaces_.end()) {
-    mojo::ReportBadMessage("Namespace not found: " + namespace_id);
+    std::move(bad_message_callback).Run("Namespace not found: " + namespace_id);
     return;
   }
 
@@ -533,6 +534,7 @@
   if (it != namespaces_.end()) {
     found = true;
     if (it->second->IsPopulated()) {
+      // Assumes this method is called on a stack handling a mojo message.
       mojo::ReportBadMessage("Cannot clone to already populated namespace");
       return;
     }
diff --git a/content/browser/dom_storage/session_storage_context_mojo.h b/content/browser/dom_storage/session_storage_context_mojo.h
index 55e36f3e..3cf456d 100644
--- a/content/browser/dom_storage/session_storage_context_mojo.h
+++ b/content/browser/dom_storage/session_storage_context_mojo.h
@@ -23,6 +23,7 @@
 #include "content/browser/dom_storage/session_storage_namespace_impl_mojo.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/session_storage_usage_info.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "services/file/public/mojom/file_system.mojom.h"
 #include "third_party/blink/public/mojom/dom_storage/session_storage_namespace.mojom.h"
 #include "url/origin.h"
@@ -83,6 +84,7 @@
 
   void OpenSessionStorage(int process_id,
                           const std::string& namespace_id,
+                          mojo::ReportBadMessageCallback bad_message_callback,
                           blink::mojom::SessionStorageNamespaceRequest request);
 
   void CreateSessionNamespace(const std::string& namespace_id);
diff --git a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
index 499b578e1..d758432 100644
--- a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
@@ -72,6 +72,11 @@
         mojo::core::ProcessErrorCallback());
   }
 
+  mojo::ReportBadMessageCallback GetBadMessageCallback() {
+    return base::BindOnce(&SessionStorageContextMojoTest::OnBadMessage,
+                          base::Unretained(this));
+  }
+
   void OnBadMessage(const std::string& reason) { bad_message_called_ = true; }
 
   void SetBackingMode(SessionStorageContextMojo::BackingMode backing_mode) {
@@ -111,6 +116,7 @@
     context()->CreateSessionNamespace(namespace_id);
     blink::mojom::SessionStorageNamespacePtr ss_namespace;
     context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
@@ -127,6 +133,7 @@
     context()->CreateSessionNamespace(namespace_id);
     blink::mojom::SessionStorageNamespacePtr ss_namespace;
     context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
@@ -188,9 +195,11 @@
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
   context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace2));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
@@ -220,6 +229,7 @@
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
@@ -247,6 +257,7 @@
   // This will re-open the context, and load the persisted namespace.
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -261,6 +272,7 @@
   // This will re-open the context, and the namespace should be empty.
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -276,6 +288,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -296,6 +309,7 @@
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
   context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
@@ -313,6 +327,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -334,6 +349,7 @@
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
   context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
@@ -358,6 +374,7 @@
   // Re-open namespace 1, check that we don't have the extra data.
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -374,6 +391,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -387,6 +405,7 @@
   {
     blink::mojom::SessionStorageNamespacePtr ss_namespace2;
     context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace2));
     blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
     ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
@@ -411,6 +430,7 @@
   {
     blink::mojom::SessionStorageNamespacePtr ss_namespace2;
     context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace2));
     blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
     ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
@@ -457,6 +477,7 @@
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -490,6 +511,7 @@
   // data.
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   std::vector<blink::mojom::KeyValuePtr> data;
@@ -509,6 +531,7 @@
   }
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
@@ -616,6 +639,7 @@
     base::RunLoop loop;
     fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
     context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace));
     ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
     ss_namespace->OpenArea(origin2, mojo::MakeRequest(&area2));
@@ -689,6 +713,7 @@
 
   // Reconnect area1 to the database, and try to read a value.
   context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
 
@@ -751,6 +776,7 @@
     base::RunLoop loop;
     fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
     context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace));
     ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
     loop.Run();
@@ -822,6 +848,7 @@
   // This time all should just keep getting written, and commit errors are
   // getting ignored.
   context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
   old_value = base::nullopt;
@@ -861,10 +888,10 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
-
   // Put some data.
   EXPECT_TRUE(test::PutSync(
       leveldb_n1_o1.get(), leveldb::StringPieceToUint8Vector("key1"),
@@ -889,6 +916,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -919,6 +947,7 @@
 
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   data.clear();
@@ -934,6 +963,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
@@ -961,6 +991,7 @@
   for (int i = 1; i <= 100; ++i) {
     blink::mojom::SessionStorageNamespacePtr ss_namespace1;
     context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                  GetBadMessageCallback(),
                                   mojo::MakeRequest(&ss_namespace1));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace1->OpenArea(url::Origin::Create(GURL(base::StringPrintf(
@@ -973,6 +1004,7 @@
 
   // And make sure caches were actually cleared.
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
   std::vector<blink::mojom::KeyValuePtr> data;
@@ -988,6 +1020,7 @@
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
@@ -1012,6 +1045,7 @@
   // should have been deleted due to our backing mode.
   context()->CreateSessionNamespace(namespace_id1);
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -1031,6 +1065,7 @@
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
   context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -1038,6 +1073,7 @@
   context()->CreateSessionNamespace(namespace_id2);
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
   context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
                                 mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
diff --git a/content/browser/fileapi/file_system_manager_impl.cc b/content/browser/fileapi/file_system_manager_impl.cc
new file mode 100644
index 0000000..318e47b77
--- /dev/null
+++ b/content/browser/fileapi/file_system_manager_impl.cc
@@ -0,0 +1,729 @@
+// Copyright (c) 2012 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 "content/browser/fileapi/file_system_manager_impl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/services/filesystem/public/interfaces/types.mojom.h"
+#include "content/browser/bad_message.h"
+#include "content/browser/blob_storage/chrome_blob_storage_context.h"
+#include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/fileapi/browser_file_system_helper.h"
+#include "content/common/fileapi/webblob_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "ipc/ipc_platform_file.h"
+#include "net/base/mime_util.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "storage/browser/blob/blob_data_builder.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/shareable_file_reference.h"
+#include "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_permission_policy.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/common/fileapi/file_system_info.h"
+#include "storage/common/fileapi/file_system_type_converters.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using storage::FileSystemFileUtil;
+using storage::FileSystemBackend;
+using storage::FileSystemOperation;
+using storage::FileSystemURL;
+using storage::BlobDataBuilder;
+using storage::BlobStorageContext;
+
+namespace content {
+
+namespace {
+
+void RevokeFilePermission(int child_id, const base::FilePath& path) {
+  ChildProcessSecurityPolicyImpl::GetInstance()->RevokeAllPermissionsForFile(
+      child_id, path);
+}
+
+}  // namespace
+
+class FileSystemManagerImpl::FileSystemCancellableOperationImpl
+    : public blink::mojom::FileSystemCancellableOperation {
+  using OperationID = storage::FileSystemOperationRunner::OperationID;
+
+ public:
+  FileSystemCancellableOperationImpl(
+      OperationID id,
+      FileSystemManagerImpl* file_system_manager_impl)
+      : id_(id), file_system_manager_impl_(file_system_manager_impl) {}
+  ~FileSystemCancellableOperationImpl() override = default;
+
+ private:
+  void Cancel(CancelCallback callback) override {
+    file_system_manager_impl_->Cancel(id_, std::move(callback));
+  }
+
+  const OperationID id_;
+  // |file_system_manager_impl| owns |this| through a StrongBindingSet.
+  FileSystemManagerImpl* const file_system_manager_impl_;
+};
+
+class FileSystemManagerImpl::ReceivedSnapshotListenerImpl
+    : public blink::mojom::ReceivedSnapshotListener {
+ public:
+  ReceivedSnapshotListenerImpl(int snapshot_id,
+                               FileSystemManagerImpl* file_system_manager_impl)
+      : snapshot_id_(snapshot_id),
+        file_system_manager_impl_(file_system_manager_impl) {}
+  ~ReceivedSnapshotListenerImpl() override = default;
+
+ private:
+  void DidReceiveSnapshotFile() override {
+    file_system_manager_impl_->DidReceiveSnapshotFile(snapshot_id_);
+  }
+
+  const int snapshot_id_;
+  // |file_system_manager_impl| owns |this| through a StrongBindingSet.
+  FileSystemManagerImpl* const file_system_manager_impl_;
+};
+
+FileSystemManagerImpl::FileSystemManagerImpl(
+    int process_id,
+    scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+    storage::FileSystemContext* file_system_context,
+    scoped_refptr<ChromeBlobStorageContext> blob_storage_context)
+    : process_id_(process_id),
+      context_(file_system_context),
+      security_policy_(ChildProcessSecurityPolicyImpl::GetInstance()),
+      request_context_getter_(request_context_getter),
+      blob_storage_context_(blob_storage_context),
+      weak_factory_(this) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(context_);
+  DCHECK(request_context_getter_);
+  DCHECK(blob_storage_context);
+  bindings_.set_connection_error_handler(base::BindRepeating(
+      &FileSystemManagerImpl::OnConnectionError, base::Unretained(this)));
+}
+
+FileSystemManagerImpl::~FileSystemManagerImpl() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+}
+
+base::WeakPtr<FileSystemManagerImpl> FileSystemManagerImpl::GetWeakPtr() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  return weak_factory_.GetWeakPtr();
+}
+
+void FileSystemManagerImpl::BindRequest(
+    blink::mojom::FileSystemManagerRequest request) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (!operation_runner_)
+    operation_runner_ = context_->CreateFileSystemOperationRunner();
+  bindings_.AddBinding(this, std::move(request));
+}
+
+void FileSystemManagerImpl::Open(const GURL& origin_url,
+                                 blink::mojom::FileSystemType file_system_type,
+                                 OpenCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (file_system_type == blink::mojom::FileSystemType::kTemporary) {
+    RecordAction(base::UserMetricsAction("OpenFileSystemTemporary"));
+  } else if (file_system_type == blink::mojom::FileSystemType::kPersistent) {
+    RecordAction(base::UserMetricsAction("OpenFileSystemPersistent"));
+  }
+  context_->OpenFileSystem(
+      origin_url, mojo::ConvertTo<storage::FileSystemType>(file_system_type),
+      storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+      base::BindOnce(&FileSystemManagerImpl::DidOpenFileSystem, GetWeakPtr(),
+                     std::move(callback)));
+};
+
+void FileSystemManagerImpl::ResolveURL(const GURL& filesystem_url,
+                                       ResolveURLCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(filesystem_url));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(blink::mojom::FileSystemInfo::New(),
+                            base::FilePath(), false, opt_error.value());
+    return;
+  }
+
+  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(blink::mojom::FileSystemInfo::New(),
+                            base::FilePath(), false,
+                            base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  context_->ResolveURL(
+      url, base::BindOnce(&FileSystemManagerImpl::DidResolveURL, GetWeakPtr(),
+                          std::move(callback)));
+}
+
+void FileSystemManagerImpl::Move(const GURL& src_path,
+                                 const GURL& dest_path,
+                                 MoveCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL src_url(context_->CrackURL(src_path));
+  FileSystemURL dest_url(context_->CrackURL(dest_path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(src_url);
+  if (!opt_error)
+    opt_error = ValidateFileSystemURL(dest_url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
+      !security_policy_->CanDeleteFileSystemFile(process_id_, src_url) ||
+      !security_policy_->CanCreateFileSystemFile(process_id_, dest_url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  operation_runner()->Move(
+      src_url, dest_url, storage::FileSystemOperation::OPTION_NONE,
+      base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                          base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::Copy(const GURL& src_path,
+                                 const GURL& dest_path,
+                                 CopyCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL src_url(context_->CrackURL(src_path));
+  FileSystemURL dest_url(context_->CrackURL(dest_path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(src_url);
+  if (!opt_error)
+    opt_error = ValidateFileSystemURL(dest_url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
+      !security_policy_->CanCopyIntoFileSystemFile(process_id_, dest_url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  operation_runner()->Copy(
+      src_url, dest_url, storage::FileSystemOperation::OPTION_NONE,
+      FileSystemOperation::ERROR_BEHAVIOR_ABORT,
+      storage::FileSystemOperationRunner::CopyProgressCallback(),
+      base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                          base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::Remove(const GURL& path,
+                                   bool recursive,
+                                   RemoveCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanDeleteFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  operation_runner()->Remove(
+      url, recursive,
+      base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                          base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::ReadMetadata(const GURL& path,
+                                         ReadMetadataCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(base::File::Info(), opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::Info(),
+                            base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  operation_runner()->GetMetadata(
+      url,
+      FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
+          FileSystemOperation::GET_METADATA_FIELD_SIZE |
+          FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
+      base::BindRepeating(&FileSystemManagerImpl::DidGetMetadata, GetWeakPtr(),
+                          base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::Create(const GURL& path,
+                                   bool exclusive,
+                                   bool is_directory,
+                                   bool recursive,
+                                   CreateCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  if (is_directory) {
+    operation_runner()->CreateDirectory(
+        url, exclusive, recursive,
+        base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                            base::Passed(&callback)));
+  } else {
+    operation_runner()->CreateFile(
+        url, exclusive,
+        base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                            base::Passed(&callback)));
+  }
+}
+
+void FileSystemManagerImpl::Exists(const GURL& path,
+                                   bool is_directory,
+                                   ExistsCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  if (is_directory) {
+    operation_runner()->DirectoryExists(
+        url, base::BindRepeating(&FileSystemManagerImpl::DidFinish,
+                                 GetWeakPtr(), base::Passed(&callback)));
+  } else {
+    operation_runner()->FileExists(
+        url, base::BindRepeating(&FileSystemManagerImpl::DidFinish,
+                                 GetWeakPtr(), base::Passed(&callback)));
+  }
+}
+
+void FileSystemManagerImpl::ReadDirectory(
+    const GURL& path,
+    blink::mojom::FileSystemOperationListenerPtr listener) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    listener->ErrorOccurred(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
+    listener->ErrorOccurred(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  OperationListenerID listener_id = AddOpListener(std::move(listener));
+  operation_runner()->ReadDirectory(
+      url, base::BindRepeating(&FileSystemManagerImpl::DidReadDirectory,
+                               GetWeakPtr(), listener_id));
+}
+
+void FileSystemManagerImpl::Write(
+    const GURL& file_path,
+    const std::string& blob_uuid,
+    int64_t position,
+    blink::mojom::FileSystemCancellableOperationRequest op_request,
+    blink::mojom::FileSystemOperationListenerPtr listener) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  net::URLRequestContext* request_context =
+      request_context_getter_->GetURLRequestContext();
+  if (!request_context) {
+    mojo::ReportBadMessage("Cannot write without URL request context");
+    return;
+  }
+
+  FileSystemURL url(context_->CrackURL(file_path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    listener->ErrorOccurred(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
+    listener->ErrorOccurred(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+  std::unique_ptr<storage::BlobDataHandle> blob =
+      blob_storage_context_->context()->GetBlobDataFromUUID(blob_uuid);
+
+  OperationListenerID listener_id = AddOpListener(std::move(listener));
+
+  OperationID op_id = operation_runner()->Write(
+      request_context, url, std::move(blob), position,
+      base::BindRepeating(&FileSystemManagerImpl::DidWrite, GetWeakPtr(),
+                          listener_id));
+  cancellable_operations_.AddBinding(
+      std::make_unique<FileSystemCancellableOperationImpl>(op_id, this),
+      std::move(op_request));
+}
+
+void FileSystemManagerImpl::Truncate(
+    const GURL& file_path,
+    int64_t length,
+    blink::mojom::FileSystemCancellableOperationRequest op_request,
+    TruncateCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(file_path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  OperationID op_id = operation_runner()->Truncate(
+      url, length,
+      base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                          base::Passed(&callback)));
+  cancellable_operations_.AddBinding(
+      std::make_unique<FileSystemCancellableOperationImpl>(op_id, this),
+      std::move(op_request));
+}
+
+void FileSystemManagerImpl::TouchFile(const GURL& path,
+                                      base::Time last_access_time,
+                                      base::Time last_modified_time,
+                                      TouchFileCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(path));
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(opt_error.value());
+    return;
+  }
+  if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::FILE_ERROR_SECURITY);
+    return;
+  }
+
+  operation_runner()->TouchFile(
+      url, last_access_time, last_modified_time,
+      base::BindRepeating(&FileSystemManagerImpl::DidFinish, GetWeakPtr(),
+                          base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::Cancel(
+    OperationID op_id,
+    FileSystemCancellableOperationImpl::CancelCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  operation_runner()->Cancel(
+      op_id, base::BindRepeating(&FileSystemManagerImpl::DidFinish,
+                                 GetWeakPtr(), base::Passed(&callback)));
+}
+
+void FileSystemManagerImpl::GetPlatformPath(const GURL& path,
+                                            GetPlatformPathCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  base::FilePath platform_path;
+  context_->default_file_task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&FileSystemManagerImpl::GetPlatformPathOnFileThread, path,
+                     process_id_, base::Unretained(context_), GetWeakPtr(),
+                     std::move(callback)));
+}
+
+void FileSystemManagerImpl::CreateSnapshotFile(
+    const GURL& file_path,
+    CreateSnapshotFileCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  FileSystemURL url(context_->CrackURL(file_path));
+
+  // Make sure if this file can be read by the renderer as this is
+  // called when the renderer is about to create a new File object
+  // (for reading the file).
+  base::Optional<base::File::Error> opt_error = ValidateFileSystemURL(url);
+  if (opt_error) {
+    std::move(callback).Run(base::File::Info(), base::FilePath(),
+                            opt_error.value(), nullptr);
+    return;
+  }
+  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
+    std::move(callback).Run(base::File::Info(), base::FilePath(),
+                            base::File::FILE_ERROR_SECURITY, nullptr);
+    return;
+  }
+
+  FileSystemBackend* backend = context_->GetFileSystemBackend(url.type());
+  if (backend->SupportsStreaming(url)) {
+    operation_runner()->GetMetadata(
+        url,
+        FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
+            FileSystemOperation::GET_METADATA_FIELD_SIZE |
+            FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
+        base::BindRepeating(&FileSystemManagerImpl::DidGetMetadataForStreaming,
+                            GetWeakPtr(), base::Passed(&callback)));
+  } else {
+    operation_runner()->CreateSnapshotFile(
+        url, base::BindRepeating(&FileSystemManagerImpl::DidCreateSnapshot,
+                                 GetWeakPtr(), base::Passed(&callback), url));
+  }
+}
+
+void FileSystemManagerImpl::DidReceiveSnapshotFile(int snapshot_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  in_transit_snapshot_files_.Remove(snapshot_id);
+}
+
+void FileSystemManagerImpl::OnConnectionError() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (bindings_.empty()) {
+    in_transit_snapshot_files_.Clear();
+    operation_runner_.reset();
+  }
+}
+
+void FileSystemManagerImpl::DidFinish(
+    base::OnceCallback<void(base::File::Error)> callback,
+    base::File::Error error_code) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::move(callback).Run(error_code);
+}
+
+void FileSystemManagerImpl::DidGetMetadata(ReadMetadataCallback callback,
+                                           base::File::Error result,
+                                           const base::File::Info& info) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::move(callback).Run(info, result);
+}
+
+void FileSystemManagerImpl::DidGetMetadataForStreaming(
+    CreateSnapshotFileCallback callback,
+    base::File::Error result,
+    const base::File::Info& info) {
+  // For now, streaming Blobs are implemented as a successful snapshot file
+  // creation with an empty path.
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::move(callback).Run(info, base::FilePath(), result, nullptr);
+}
+
+void FileSystemManagerImpl::DidReadDirectory(
+    OperationListenerID listener_id,
+    base::File::Error result,
+    std::vector<filesystem::mojom::DirectoryEntry> entries,
+    bool has_more) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  blink::mojom::FileSystemOperationListener* listener =
+      GetOpListener(listener_id);
+  if (!listener)
+    return;
+  if (result != base::File::FILE_OK) {
+    DCHECK(!has_more);
+    listener->ErrorOccurred(result);
+    RemoveOpListener(listener_id);
+    return;
+  }
+  std::vector<filesystem::mojom::DirectoryEntryPtr> entry_struct_ptrs;
+  for (const auto& entry : entries) {
+    entry_struct_ptrs.emplace_back(
+        filesystem::mojom::DirectoryEntry::New(entry));
+  }
+  listener->ResultsRetrieved(std::move(entry_struct_ptrs), has_more);
+  if (!has_more)
+    RemoveOpListener(listener_id);
+}
+
+void FileSystemManagerImpl::DidWrite(OperationListenerID listener_id,
+                                     base::File::Error result,
+                                     int64_t bytes,
+                                     bool complete) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  blink::mojom::FileSystemOperationListener* listener =
+      GetOpListener(listener_id);
+  if (!listener)
+    return;
+  if (result == base::File::FILE_OK) {
+    listener->DidWrite(bytes, complete);
+    if (complete)
+      RemoveOpListener(listener_id);
+  } else {
+    listener->ErrorOccurred(result);
+    RemoveOpListener(listener_id);
+  }
+}
+
+void FileSystemManagerImpl::DidOpenFileSystem(
+    OpenCallback callback,
+    const GURL& root,
+    const std::string& filesystem_name,
+    base::File::Error result) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(root.is_valid() || result != base::File::FILE_OK);
+  std::move(callback).Run(filesystem_name, root, result);
+  // For OpenFileSystem we do not create a new operation, so no unregister here.
+}
+
+void FileSystemManagerImpl::DidResolveURL(
+    ResolveURLCallback callback,
+    base::File::Error result,
+    const storage::FileSystemInfo& info,
+    const base::FilePath& file_path,
+    storage::FileSystemContext::ResolvedEntryType type) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (result == base::File::FILE_OK &&
+      type == storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND)
+    result = base::File::FILE_ERROR_NOT_FOUND;
+
+  std::move(callback).Run(
+      mojo::ConvertTo<blink::mojom::FileSystemInfoPtr>(info), file_path,
+      type == storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY, result);
+  // For ResolveURL we do not create a new operation, so no unregister here.
+}
+
+void FileSystemManagerImpl::DidCreateSnapshot(
+    CreateSnapshotFileCallback callback,
+    const storage::FileSystemURL& url,
+    base::File::Error result,
+    const base::File::Info& info,
+    const base::FilePath& platform_path,
+    scoped_refptr<storage::ShareableFileReference> /* unused */) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (result != base::File::FILE_OK) {
+    std::move(callback).Run(base::File::Info(), base::FilePath(), result,
+                            nullptr);
+    return;
+  }
+
+  scoped_refptr<storage::ShareableFileReference> file_ref =
+      storage::ShareableFileReference::Get(platform_path);
+  if (!security_policy_->CanReadFile(process_id_, platform_path)) {
+    // Give per-file read permission to the snapshot file if it hasn't it yet.
+    // In order for the renderer to be able to read the file via File object,
+    // it must be granted per-file read permission for the file's platform
+    // path. By now, it has already been verified that the renderer has
+    // sufficient permissions to read the file, so giving per-file permission
+    // here must be safe.
+    security_policy_->GrantReadFile(process_id_, platform_path);
+
+    // Revoke all permissions for the file when the last ref of the file
+    // is dropped.
+    if (!file_ref.get()) {
+      // Create a reference for temporary permission handling.
+      file_ref = storage::ShareableFileReference::GetOrCreate(
+          platform_path,
+          storage::ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE,
+          context_->default_file_task_runner());
+    }
+    file_ref->AddFinalReleaseCallback(
+        base::BindOnce(&RevokeFilePermission, process_id_));
+  }
+
+  if (file_ref.get()) {
+    // This ref is held until DidReceiveSnapshotFile is called.
+    int request_id = in_transit_snapshot_files_.Add(file_ref);
+    blink::mojom::ReceivedSnapshotListenerPtr listener_ptr;
+    snapshot_listeners_.AddBinding(
+        std::make_unique<ReceivedSnapshotListenerImpl>(request_id, this),
+        mojo::MakeRequest<blink::mojom::ReceivedSnapshotListener>(
+            &listener_ptr));
+    // Return the file info and platform_path.
+    std::move(callback).Run(info, platform_path, result,
+                            std::move(listener_ptr));
+    return;
+  }
+
+  // Return the file info and platform_path.
+  std::move(callback).Run(info, platform_path, result, nullptr);
+}
+
+void FileSystemManagerImpl::DidGetPlatformPath(GetPlatformPathCallback callback,
+                                               base::FilePath platform_path) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  std::move(callback).Run(platform_path);
+}
+
+// static
+void FileSystemManagerImpl::GetPlatformPathOnFileThread(
+    const GURL& path,
+    int process_id,
+    storage::FileSystemContext* context,
+    base::WeakPtr<FileSystemManagerImpl> file_system_manager,
+    GetPlatformPathCallback callback) {
+  base::FilePath platform_path;
+  SyncGetPlatformPath(context, process_id, path, &platform_path);
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&FileSystemManagerImpl::DidGetPlatformPath,
+                     file_system_manager, std::move(callback), platform_path));
+}
+
+base::Optional<base::File::Error> FileSystemManagerImpl::ValidateFileSystemURL(
+    const storage::FileSystemURL& url) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (!FileSystemURLIsValid(context_, url))
+    return base::File::FILE_ERROR_INVALID_URL;
+
+  // Deny access to files in PluginPrivate FileSystem from JavaScript.
+  // TODO(nhiroki): Move this filter somewhere else since this is not for
+  // validation.
+  if (url.type() == storage::kFileSystemTypePluginPrivate)
+    return base::File::FILE_ERROR_SECURITY;
+
+  return base::nullopt;
+}
+
+FileSystemManagerImpl::OperationListenerID FileSystemManagerImpl::AddOpListener(
+    blink::mojom::FileSystemOperationListenerPtr listener) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  int op_id = next_operation_listener_id_++;
+  listener.set_connection_error_handler(
+      base::BindOnce(&FileSystemManagerImpl::OnConnectionErrorForOpListeners,
+                     base::Unretained(this), op_id));
+  op_listeners_[op_id] = std::move(listener);
+  return op_id;
+}
+
+void FileSystemManagerImpl::RemoveOpListener(OperationListenerID listener_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(op_listeners_.find(listener_id) != op_listeners_.end());
+  op_listeners_.erase(listener_id);
+}
+
+blink::mojom::FileSystemOperationListener* FileSystemManagerImpl::GetOpListener(
+    OperationListenerID listener_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (op_listeners_.find(listener_id) == op_listeners_.end())
+    return nullptr;
+  return &*op_listeners_[listener_id];
+}
+
+void FileSystemManagerImpl::OnConnectionErrorForOpListeners(
+    OperationListenerID listener_id) {
+  RemoveOpListener(listener_id);
+}
+
+}  // namespace content
diff --git a/content/browser/fileapi/file_system_manager_impl.h b/content/browser/fileapi/file_system_manager_impl.h
new file mode 100644
index 0000000..471e28b9
--- /dev/null
+++ b/content/browser/fileapi/file_system_manager_impl.h
@@ -0,0 +1,222 @@
+// Copyright (c) 2018 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 CONTENT_BROWSER_FILEAPI_FILE_SYSTEM_MANAGER_IMPL_H_
+#define CONTENT_BROWSER_FILEAPI_FILE_SYSTEM_MANAGER_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory.h"
+#include "components/services/filesystem/public/interfaces/types.mojom.h"
+#include "content/browser/streams/stream.h"
+#include "content/browser/streams/stream_context.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/browser_message_filter.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/strong_binding_set.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}  // namespace base
+
+namespace storage {
+class FileSystemURL;
+class FileSystemOperationRunner;
+struct FileSystemInfo;
+}  // namespace storage
+
+namespace net {
+class URLRequestContextGetter;
+}  // namespace net
+
+namespace storage {
+class ShareableFileReference;
+}
+
+namespace content {
+class ChildProcessSecurityPolicyImpl;
+class ChromeBlobStorageContext;
+
+// All methods for this class are expected to be called on the IO thread,
+// except for the constructor. The destructor must also be called on the IO
+// thread as weak refs are created on that thread. A single instance of this
+// class is owned by RenderProcessHostImpl.
+class CONTENT_EXPORT FileSystemManagerImpl
+    : public blink::mojom::FileSystemManager {
+ public:
+  // Used by the renderer process host on the UI thread.
+  FileSystemManagerImpl(
+      int process_id,
+      scoped_refptr<net::URLRequestContextGetter> request_context_getter,
+      storage::FileSystemContext* file_system_context,
+      scoped_refptr<ChromeBlobStorageContext> blob_storage_context);
+  ~FileSystemManagerImpl() override;
+  base::WeakPtr<FileSystemManagerImpl> GetWeakPtr();
+
+  void BindRequest(blink::mojom::FileSystemManagerRequest request);
+
+  // blink::mojom::FileSystem
+  void Open(const GURL& origin_url,
+            blink::mojom::FileSystemType file_system_type,
+            OpenCallback callback) override;
+  void ResolveURL(const GURL& filesystem_url,
+                  ResolveURLCallback callback) override;
+  void Move(const GURL& src_path,
+            const GURL& dest_path,
+            MoveCallback callback) override;
+  void Copy(const GURL& src_path,
+            const GURL& dest_path,
+            CopyCallback callback) override;
+  void Remove(const GURL& path,
+              bool recursive,
+              RemoveCallback callback) override;
+  void ReadMetadata(const GURL& path, ReadMetadataCallback callback) override;
+  void Create(const GURL& path,
+              bool exclusive,
+              bool is_directory,
+              bool recursive,
+              CreateCallback callback) override;
+  void Exists(const GURL& path,
+              bool is_directory,
+              ExistsCallback callback) override;
+  void ReadDirectory(
+      const GURL& path,
+      blink::mojom::FileSystemOperationListenerPtr listener) override;
+  void Write(const GURL& file_path,
+             const std::string& blob_uuid,
+             int64_t position,
+             blink::mojom::FileSystemCancellableOperationRequest op_request,
+             blink::mojom::FileSystemOperationListenerPtr listener) override;
+  void Truncate(const GURL& file_path,
+                int64_t length,
+                blink::mojom::FileSystemCancellableOperationRequest op_request,
+                TruncateCallback callback) override;
+  void TouchFile(const GURL& path,
+                 base::Time last_access_time,
+                 base::Time last_modified_time,
+                 TouchFileCallback callback) override;
+  void CreateSnapshotFile(const GURL& file_path,
+                          CreateSnapshotFileCallback callback) override;
+  void GetPlatformPath(const GURL& file_path,
+                       GetPlatformPathCallback callback) override;
+
+ private:
+  class FileSystemCancellableOperationImpl;
+  class ReceivedSnapshotListenerImpl;
+  using OperationID = storage::FileSystemOperationRunner::OperationID;
+  using OperationListenerID = int;
+
+  void Cancel(
+      OperationID op_id,
+      blink::mojom::FileSystemCancellableOperation::CancelCallback callback);
+  void DidReceiveSnapshotFile(int snapshot_id);
+  void OnConnectionError();
+
+  // Callback functions to be used when each file operation is finished.
+  void DidFinish(base::OnceCallback<void(base::File::Error)> callback,
+                 base::File::Error error_code);
+  void DidGetMetadata(ReadMetadataCallback callback,
+                      base::File::Error result,
+                      const base::File::Info& info);
+  void DidGetMetadataForStreaming(CreateSnapshotFileCallback callback,
+                                  base::File::Error result,
+                                  const base::File::Info& info);
+  void DidReadDirectory(OperationListenerID listener_id,
+                        base::File::Error result,
+                        std::vector<filesystem::mojom::DirectoryEntry> entries,
+                        bool has_more);
+  void DidWrite(OperationListenerID listener_id,
+                base::File::Error result,
+                int64_t bytes,
+                bool complete);
+  void DidOpenFileSystem(OpenCallback callback,
+                         const GURL& root,
+                         const std::string& filesystem_name,
+                         base::File::Error result);
+  void DidResolveURL(ResolveURLCallback callback,
+                     base::File::Error result,
+                     const storage::FileSystemInfo& info,
+                     const base::FilePath& file_path,
+                     storage::FileSystemContext::ResolvedEntryType type);
+  void DidCreateSnapshot(
+      CreateSnapshotFileCallback callback,
+      const storage::FileSystemURL& url,
+      base::File::Error result,
+      const base::File::Info& info,
+      const base::FilePath& platform_path,
+      scoped_refptr<storage::ShareableFileReference> file_ref);
+  void DidGetPlatformPath(GetPlatformPathCallback callback,
+                          base::FilePath platform_path);
+
+  static void GetPlatformPathOnFileThread(
+      const GURL& path,
+      int process_id,
+      storage::FileSystemContext* context,
+      base::WeakPtr<FileSystemManagerImpl> file_system_manager,
+      GetPlatformPathCallback callback);
+  // Returns an error if |url| is invalid.
+  base::Optional<base::File::Error> ValidateFileSystemURL(
+      const storage::FileSystemURL& url);
+
+  storage::FileSystemOperationRunner* operation_runner() {
+    return operation_runner_.get();
+  }
+
+  OperationListenerID AddOpListener(
+      blink::mojom::FileSystemOperationListenerPtr listener);
+  void RemoveOpListener(OperationListenerID listener_id);
+  blink::mojom::FileSystemOperationListener* GetOpListener(
+      OperationListenerID listener_id);
+  void OnConnectionErrorForOpListeners(OperationListenerID listener_id);
+
+  const int process_id_;
+  storage::FileSystemContext* const context_;
+  ChildProcessSecurityPolicyImpl* const security_policy_;
+  const scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+  const scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
+  std::unique_ptr<storage::FileSystemOperationRunner> operation_runner_;
+
+  mojo::BindingSet<blink::mojom::FileSystemManager> bindings_;
+  mojo::StrongBindingSet<blink::mojom::FileSystemCancellableOperation>
+      cancellable_operations_;
+  mojo::StrongBindingSet<blink::mojom::ReceivedSnapshotListener>
+      snapshot_listeners_;
+
+  std::unordered_map<OperationListenerID,
+                     blink::mojom::FileSystemOperationListenerPtr>
+      op_listeners_;
+  OperationListenerID next_operation_listener_id_ = 0;
+
+  // Used to keep snapshot files alive while a DidCreateSnapshot
+  // is being sent to the renderer.
+  base::IDMap<scoped_refptr<storage::ShareableFileReference>>
+      in_transit_snapshot_files_;
+
+  base::WeakPtrFactory<FileSystemManagerImpl> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileSystemManagerImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_FILEAPI_FILE_SYSTEM_MANAGER_IMPL_H_
diff --git a/content/browser/fileapi/fileapi_message_filter.cc b/content/browser/fileapi/fileapi_message_filter.cc
deleted file mode 100644
index 1e42768..0000000
--- a/content/browser/fileapi/fileapi_message_filter.cc
+++ /dev/null
@@ -1,642 +0,0 @@
-// Copyright (c) 2012 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 "content/browser/fileapi/fileapi_message_filter.h"
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/metrics/user_metrics.h"
-#include "base/sequenced_task_runner.h"
-#include "base/strings/string_util.h"
-#include "base/threading/thread.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "components/services/filesystem/public/interfaces/types.mojom.h"
-#include "content/browser/bad_message.h"
-#include "content/browser/blob_storage/chrome_blob_storage_context.h"
-#include "content/browser/child_process_security_policy_impl.h"
-#include "content/browser/fileapi/browser_file_system_helper.h"
-#include "content/common/fileapi/file_system_messages.h"
-#include "content/common/fileapi/webblob_messages.h"
-#include "content/public/browser/browser_thread.h"
-#include "ipc/ipc_platform_file.h"
-#include "net/base/mime_util.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "storage/browser/blob/blob_data_builder.h"
-#include "storage/browser/blob/blob_storage_context.h"
-#include "storage/browser/blob/shareable_file_reference.h"
-#include "storage/browser/fileapi/file_observers.h"
-#include "storage/browser/fileapi/file_permission_policy.h"
-#include "storage/browser/fileapi/file_system_context.h"
-#include "storage/browser/fileapi/isolated_context.h"
-#include "storage/common/fileapi/file_system_info.h"
-#include "storage/common/fileapi/file_system_types.h"
-#include "storage/common/fileapi/file_system_util.h"
-#include "url/gurl.h"
-
-using storage::FileSystemFileUtil;
-using storage::FileSystemBackend;
-using storage::FileSystemOperation;
-using storage::FileSystemURL;
-using storage::BlobDataBuilder;
-using storage::BlobStorageContext;
-
-namespace content {
-
-namespace {
-
-const uint32_t kFileApiFilteredMessageClasses[] = {FileSystemMsgStart,
-                                                   BlobMsgStart};
-
-void RevokeFilePermission(int child_id, const base::FilePath& path) {
-  ChildProcessSecurityPolicyImpl::GetInstance()->RevokeAllPermissionsForFile(
-    child_id, path);
-}
-
-}  // namespace
-
-FileAPIMessageFilter::FileAPIMessageFilter(
-    int process_id,
-    net::URLRequestContextGetter* request_context_getter,
-    storage::FileSystemContext* file_system_context,
-    ChromeBlobStorageContext* blob_storage_context)
-    : BrowserMessageFilter(kFileApiFilteredMessageClasses,
-                           arraysize(kFileApiFilteredMessageClasses)),
-      process_id_(process_id),
-      context_(file_system_context),
-      security_policy_(ChildProcessSecurityPolicyImpl::GetInstance()),
-      request_context_getter_(request_context_getter),
-      request_context_(nullptr),
-      blob_storage_context_(blob_storage_context) {
-  DCHECK(context_);
-  DCHECK(request_context_getter_.get());
-  DCHECK(blob_storage_context);
-}
-
-FileAPIMessageFilter::FileAPIMessageFilter(
-    int process_id,
-    net::URLRequestContext* request_context,
-    storage::FileSystemContext* file_system_context,
-    ChromeBlobStorageContext* blob_storage_context)
-    : BrowserMessageFilter(kFileApiFilteredMessageClasses,
-                           arraysize(kFileApiFilteredMessageClasses)),
-      process_id_(process_id),
-      context_(file_system_context),
-      security_policy_(ChildProcessSecurityPolicyImpl::GetInstance()),
-      request_context_(request_context),
-      blob_storage_context_(blob_storage_context) {
-  DCHECK(context_);
-  DCHECK(request_context_);
-  DCHECK(blob_storage_context);
-}
-
-void FileAPIMessageFilter::OnChannelConnected(int32_t peer_pid) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  if (request_context_getter_.get()) {
-    DCHECK(!request_context_);
-    request_context_ = request_context_getter_->GetURLRequestContext();
-    request_context_getter_ = nullptr;
-    DCHECK(request_context_);
-  }
-
-  operation_runner_ = context_->CreateFileSystemOperationRunner();
-}
-
-void FileAPIMessageFilter::OnChannelClosing() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  in_transit_snapshot_files_.clear();
-
-  operation_runner_.reset();
-  operations_.clear();
-}
-
-base::TaskRunner* FileAPIMessageFilter::OverrideTaskRunnerForMessage(
-    const IPC::Message& message) {
-  if (message.type() == FileSystemHostMsg_SyncGetPlatformPath::ID)
-    return context_->default_file_task_runner();
-  return nullptr;
-}
-
-bool FileAPIMessageFilter::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(FileAPIMessageFilter, message)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_OpenFileSystem, OnOpenFileSystem)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_ResolveURL, OnResolveURL)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Move, OnMove)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Copy, OnCopy)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Remove, OnRemove)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadMetadata, OnReadMetadata)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Create, OnCreate)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Exists, OnExists)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadDirectory, OnReadDirectory)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Write, OnWrite)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_Truncate, OnTruncate)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_TouchFile, OnTouchFile)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_CancelWrite, OnCancel)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_CreateSnapshotFile,
-                        OnCreateSnapshotFile)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidReceiveSnapshotFile,
-                        OnDidReceiveSnapshotFile)
-    IPC_MESSAGE_HANDLER(FileSystemHostMsg_SyncGetPlatformPath,
-                        OnSyncGetPlatformPath)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
-}
-
-FileAPIMessageFilter::~FileAPIMessageFilter() {}
-
-void FileAPIMessageFilter::OnOpenFileSystem(int request_id,
-                                            const GURL& origin_url,
-                                            storage::FileSystemType type) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (type == storage::kFileSystemTypeTemporary) {
-    RecordAction(base::UserMetricsAction("OpenFileSystemTemporary"));
-  } else if (type == storage::kFileSystemTypePersistent) {
-    RecordAction(base::UserMetricsAction("OpenFileSystemPersistent"));
-  }
-  storage::OpenFileSystemMode mode =
-      storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT;
-  context_->OpenFileSystem(
-      origin_url, type, mode,
-      base::BindOnce(&FileAPIMessageFilter::DidOpenFileSystem, this,
-                     request_id));
-}
-
-void FileAPIMessageFilter::OnResolveURL(
-    int request_id,
-    const GURL& filesystem_url) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(filesystem_url));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  context_->ResolveURL(url, base::BindOnce(&FileAPIMessageFilter::DidResolveURL,
-                                           this, request_id));
-}
-
-void FileAPIMessageFilter::OnMove(
-    int request_id, const GURL& src_path, const GURL& dest_path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL src_url(context_->CrackURL(src_path));
-  FileSystemURL dest_url(context_->CrackURL(dest_path));
-  if (!ValidateFileSystemURL(request_id, src_url) ||
-      !ValidateFileSystemURL(request_id, dest_url)) {
-    return;
-  }
-  if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
-      !security_policy_->CanDeleteFileSystemFile(process_id_, src_url) ||
-      !security_policy_->CanCreateFileSystemFile(process_id_, dest_url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->Move(
-      src_url,
-      dest_url,
-      storage::FileSystemOperation::OPTION_NONE,
-      base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-}
-
-void FileAPIMessageFilter::OnCopy(
-    int request_id, const GURL& src_path, const GURL& dest_path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL src_url(context_->CrackURL(src_path));
-  FileSystemURL dest_url(context_->CrackURL(dest_path));
-  if (!ValidateFileSystemURL(request_id, src_url) ||
-      !ValidateFileSystemURL(request_id, dest_url)) {
-    return;
-  }
-  if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
-      !security_policy_->CanCopyIntoFileSystemFile(process_id_, dest_url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->Copy(
-      src_url, dest_url, storage::FileSystemOperation::OPTION_NONE,
-      FileSystemOperation::ERROR_BEHAVIOR_ABORT,
-      storage::FileSystemOperationRunner::CopyProgressCallback(),
-      base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-}
-
-void FileAPIMessageFilter::OnRemove(
-    int request_id, const GURL& path, bool recursive) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanDeleteFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->Remove(
-      url, recursive,
-      base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-}
-
-void FileAPIMessageFilter::OnReadMetadata(
-    int request_id, const GURL& path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->GetMetadata(
-      url, FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
-               FileSystemOperation::GET_METADATA_FIELD_SIZE |
-               FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
-      base::Bind(&FileAPIMessageFilter::DidGetMetadata, this, request_id));
-}
-
-void FileAPIMessageFilter::OnCreate(
-    int request_id, const GURL& path, bool exclusive,
-    bool is_directory, bool recursive) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  if (is_directory) {
-    operations_[request_id] = operation_runner()->CreateDirectory(
-        url, exclusive, recursive,
-        base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-  } else {
-    operations_[request_id] = operation_runner()->CreateFile(
-        url, exclusive,
-        base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-  }
-}
-
-void FileAPIMessageFilter::OnExists(
-    int request_id, const GURL& path, bool is_directory) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  if (is_directory) {
-    operations_[request_id] = operation_runner()->DirectoryExists(
-        url,
-        base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-  } else {
-    operations_[request_id] = operation_runner()->FileExists(
-        url,
-        base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-  }
-}
-
-void FileAPIMessageFilter::OnReadDirectory(
-    int request_id, const GURL& path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->ReadDirectory(
-      url, base::BindRepeating(&FileAPIMessageFilter::DidReadDirectory, this,
-                               request_id));
-}
-
-void FileAPIMessageFilter::OnWrite(int request_id,
-                                   const GURL& path,
-                                   const std::string& blob_uuid,
-                                   int64_t offset) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (!request_context_) {
-    // We can't write w/o a request context, trying to do so will crash.
-    NOTREACHED();
-    return;
-  }
-
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  std::unique_ptr<storage::BlobDataHandle> blob =
-      blob_storage_context_->context()->GetBlobDataFromUUID(blob_uuid);
-
-  operations_[request_id] = operation_runner()->Write(
-      request_context_, url, std::move(blob), offset,
-      base::Bind(&FileAPIMessageFilter::DidWrite, this, request_id));
-}
-
-void FileAPIMessageFilter::OnTruncate(int request_id,
-                                      const GURL& path,
-                                      int64_t length) {
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->Truncate(
-      url, length,
-      base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-}
-
-void FileAPIMessageFilter::OnTouchFile(
-    int request_id,
-    const GURL& path,
-    const base::Time& last_access_time,
-    const base::Time& last_modified_time) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  operations_[request_id] = operation_runner()->TouchFile(
-      url, last_access_time, last_modified_time,
-      base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-}
-
-void FileAPIMessageFilter::OnCancel(
-    int request_id,
-    int request_id_to_cancel) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  OperationsMap::iterator found = operations_.find(request_id_to_cancel);
-  if (found != operations_.end()) {
-    // The cancel will eventually send both the write failure and the cancel
-    // success.
-    operation_runner()->Cancel(
-        found->second,
-        base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
-  } else {
-    // The write already finished; report that we failed to stop it.
-    Send(new FileSystemMsg_DidFail(
-        request_id, base::File::FILE_ERROR_INVALID_OPERATION));
-  }
-}
-
-void FileAPIMessageFilter::OnSyncGetPlatformPath(
-    const GURL& path, base::FilePath* platform_path) {
-  SyncGetPlatformPath(context_, process_id_, path, platform_path);
-}
-
-void FileAPIMessageFilter::OnCreateSnapshotFile(
-    int request_id, const GURL& path) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  FileSystemURL url(context_->CrackURL(path));
-
-  // Make sure if this file can be read by the renderer as this is
-  // called when the renderer is about to create a new File object
-  // (for reading the file).
-  if (!ValidateFileSystemURL(request_id, url))
-    return;
-  if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return;
-  }
-
-  FileSystemBackend* backend = context_->GetFileSystemBackend(url.type());
-  if (backend->SupportsStreaming(url)) {
-    operations_[request_id] = operation_runner()->GetMetadata(
-        url, FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
-                 FileSystemOperation::GET_METADATA_FIELD_SIZE |
-                 FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
-        base::Bind(&FileAPIMessageFilter::DidGetMetadataForStreaming, this,
-                   request_id));
-  } else {
-    operations_[request_id] = operation_runner()->CreateSnapshotFile(
-        url,
-        base::Bind(&FileAPIMessageFilter::DidCreateSnapshot,
-                   this, request_id, url));
-  }
-}
-
-void FileAPIMessageFilter::OnDidReceiveSnapshotFile(int request_id) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  in_transit_snapshot_files_.erase(request_id);
-}
-
-void FileAPIMessageFilter::DidFinish(int request_id,
-                                     base::File::Error result) {
-  if (result == base::File::FILE_OK)
-    Send(new FileSystemMsg_DidSucceed(request_id));
-  else
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  operations_.erase(request_id);
-}
-
-void FileAPIMessageFilter::DidGetMetadata(
-    int request_id,
-    base::File::Error result,
-    const base::File::Info& info) {
-  if (result == base::File::FILE_OK)
-    Send(new FileSystemMsg_DidReadMetadata(request_id, info));
-  else
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  operations_.erase(request_id);
-}
-
-void FileAPIMessageFilter::DidGetMetadataForStreaming(
-    int request_id,
-    base::File::Error result,
-    const base::File::Info& info) {
-  if (result == base::File::FILE_OK) {
-    // For now, streaming Blobs are implemented as a successful snapshot file
-    // creation with an empty path.
-    Send(new FileSystemMsg_DidCreateSnapshotFile(request_id, info,
-                                                 base::FilePath()));
-  } else {
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  }
-  operations_.erase(request_id);
-}
-
-void FileAPIMessageFilter::DidReadDirectory(
-    int request_id,
-    base::File::Error result,
-    std::vector<filesystem::mojom::DirectoryEntry> entries,
-    bool has_more) {
-  if (result == base::File::FILE_OK) {
-    if (!entries.empty() || !has_more)
-      Send(new FileSystemMsg_DidReadDirectory(request_id, std::move(entries),
-                                              has_more));
-  } else {
-    DCHECK(!has_more);
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  }
-  if (!has_more)
-    operations_.erase(request_id);
-}
-
-void FileAPIMessageFilter::DidWrite(int request_id,
-                                    base::File::Error result,
-                                    int64_t bytes,
-                                    bool complete) {
-  if (result == base::File::FILE_OK) {
-    Send(new FileSystemMsg_DidWrite(request_id, bytes, complete));
-    if (complete)
-      operations_.erase(request_id);
-  } else {
-    Send(new FileSystemMsg_DidFail(request_id, result));
-    operations_.erase(request_id);
-  }
-}
-
-void FileAPIMessageFilter::DidOpenFileSystem(int request_id,
-                                             const GURL& root,
-                                             const std::string& filesystem_name,
-                                             base::File::Error result) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (result == base::File::FILE_OK) {
-    DCHECK(root.is_valid());
-    Send(new FileSystemMsg_DidOpenFileSystem(
-        request_id, filesystem_name, root));
-  } else {
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  }
-  // For OpenFileSystem we do not create a new operation, so no unregister here.
-}
-
-void FileAPIMessageFilter::DidResolveURL(
-    int request_id,
-    base::File::Error result,
-    const storage::FileSystemInfo& info,
-    const base::FilePath& file_path,
-    storage::FileSystemContext::ResolvedEntryType type) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  if (result == base::File::FILE_OK &&
-      type == storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND)
-    result = base::File::FILE_ERROR_NOT_FOUND;
-
-  if (result == base::File::FILE_OK) {
-    DCHECK(info.root_url.is_valid());
-    Send(new FileSystemMsg_DidResolveURL(
-        request_id,
-        info,
-        file_path,
-        type == storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY));
-  } else {
-    Send(new FileSystemMsg_DidFail(request_id, result));
-  }
-  // For ResolveURL we do not create a new operation, so no unregister here.
-}
-
-void FileAPIMessageFilter::DidCreateSnapshot(
-    int request_id,
-    const storage::FileSystemURL& url,
-    base::File::Error result,
-    const base::File::Info& info,
-    const base::FilePath& platform_path,
-    scoped_refptr<storage::ShareableFileReference> /* unused */) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  operations_.erase(request_id);
-
-  if (result != base::File::FILE_OK) {
-    Send(new FileSystemMsg_DidFail(request_id, result));
-    return;
-  }
-
-  scoped_refptr<storage::ShareableFileReference> file_ref =
-      storage::ShareableFileReference::Get(platform_path);
-  if (!security_policy_->CanReadFile(process_id_, platform_path)) {
-    // Give per-file read permission to the snapshot file if it hasn't it yet.
-    // In order for the renderer to be able to read the file via File object,
-    // it must be granted per-file read permission for the file's platform
-    // path. By now, it has already been verified that the renderer has
-    // sufficient permissions to read the file, so giving per-file permission
-    // here must be safe.
-    security_policy_->GrantReadFile(process_id_, platform_path);
-
-    // Revoke all permissions for the file when the last ref of the file
-    // is dropped.
-    if (!file_ref.get()) {
-      // Create a reference for temporary permission handling.
-      file_ref = storage::ShareableFileReference::GetOrCreate(
-          platform_path,
-          storage::ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE,
-          context_->default_file_task_runner());
-    }
-    file_ref->AddFinalReleaseCallback(
-        base::BindOnce(&RevokeFilePermission, process_id_));
-  }
-
-  if (file_ref.get()) {
-    // This ref is held until OnDidReceiveSnapshotFile is called.
-    in_transit_snapshot_files_[request_id] = file_ref;
-  }
-
-  // Return the file info and platform_path.
-  Send(new FileSystemMsg_DidCreateSnapshotFile(
-      request_id, info, platform_path));
-}
-
-bool FileAPIMessageFilter::ValidateFileSystemURL(
-    int request_id,
-    const storage::FileSystemURL& url) {
-  if (!FileSystemURLIsValid(context_, url)) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_INVALID_URL));
-    return false;
-  }
-
-  // Deny access to files in PluginPrivate FileSystem from JavaScript.
-  // TODO(nhiroki): Move this filter somewhere else since this is not for
-  // validation.
-  if (url.type() == storage::kFileSystemTypePluginPrivate) {
-    Send(new FileSystemMsg_DidFail(request_id,
-                                   base::File::FILE_ERROR_SECURITY));
-    return false;
-  }
-
-  return true;
-}
-
-}  // namespace content
diff --git a/content/browser/fileapi/fileapi_message_filter.h b/content/browser/fileapi/fileapi_message_filter.h
deleted file mode 100644
index 7188c68..0000000
--- a/content/browser/fileapi/fileapi_message_filter.h
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright (c) 2012 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 CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
-#define CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <map>
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "base/callback.h"
-#include "base/containers/hash_tables.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/shared_memory.h"
-#include "components/services/filesystem/public/interfaces/types.mojom.h"
-#include "content/browser/streams/stream.h"
-#include "content/browser/streams/stream_context.h"
-#include "content/common/content_export.h"
-#include "content/public/browser/browser_message_filter.h"
-#include "storage/browser/fileapi/file_system_context.h"
-#include "storage/browser/fileapi/file_system_operation_runner.h"
-#include "storage/common/fileapi/file_system_types.h"
-
-class GURL;
-
-namespace base {
-class FilePath;
-class Time;
-}
-
-namespace storage {
-class FileSystemURL;
-class FileSystemOperationRunner;
-struct FileSystemInfo;
-}
-
-namespace net {
-class URLRequestContext;
-class URLRequestContextGetter;
-}  // namespace net
-
-namespace storage {
-class ShareableFileReference;
-}
-
-namespace content {
-class ChildProcessSecurityPolicyImpl;
-class ChromeBlobStorageContext;
-
-// TODO(tyoshino): Factor out code except for IPC gluing from
-// FileAPIMessageFilter into separate classes. See crbug.com/263741.
-class CONTENT_EXPORT FileAPIMessageFilter : public BrowserMessageFilter {
- public:
-  // Used by the renderer process host on the UI thread.
-  FileAPIMessageFilter(int process_id,
-                       net::URLRequestContextGetter* request_context_getter,
-                       storage::FileSystemContext* file_system_context,
-                       ChromeBlobStorageContext* blob_storage_context);
-  // Used by the worker process host on the IO thread.
-  FileAPIMessageFilter(int process_id,
-                       net::URLRequestContext* request_context,
-                       storage::FileSystemContext* file_system_context,
-                       ChromeBlobStorageContext* blob_storage_context);
-
-  // BrowserMessageFilter implementation.
-  void OnChannelConnected(int32_t peer_pid) override;
-  void OnChannelClosing() override;
-  base::TaskRunner* OverrideTaskRunnerForMessage(
-      const IPC::Message& message) override;
-  bool OnMessageReceived(const IPC::Message& message) override;
-
- protected:
-  ~FileAPIMessageFilter() override;
-
- private:
-  typedef storage::FileSystemOperationRunner::OperationID OperationID;
-
-  void OnOpenFileSystem(int request_id,
-                        const GURL& origin_url,
-                        storage::FileSystemType type);
-  void OnResolveURL(int request_id,
-                    const GURL& filesystem_url);
-  void OnMove(int request_id,
-              const GURL& src_path,
-              const GURL& dest_path);
-  void OnCopy(int request_id,
-              const GURL& src_path,
-              const GURL& dest_path);
-  void OnRemove(int request_id, const GURL& path, bool recursive);
-  void OnReadMetadata(int request_id, const GURL& path);
-  void OnCreate(int request_id,
-                const GURL& path,
-                bool exclusive,
-                bool is_directory,
-                bool recursive);
-  void OnExists(int request_id, const GURL& path, bool is_directory);
-  void OnReadDirectory(int request_id, const GURL& path);
-  void OnWrite(int request_id,
-               const GURL& path,
-               const std::string& blob_uuid,
-               int64_t offset);
-  void OnTruncate(int request_id, const GURL& path, int64_t length);
-  void OnTouchFile(int request_id,
-                   const GURL& path,
-                   const base::Time& last_access_time,
-                   const base::Time& last_modified_time);
-  void OnCancel(int request_id, int request_to_cancel);
-  void OnSyncGetPlatformPath(const GURL& path,
-                             base::FilePath* platform_path);
-  void OnCreateSnapshotFile(int request_id,
-                            const GURL& path);
-  void OnDidReceiveSnapshotFile(int request_id);
-
-  // Callback functions to be used when each file operation is finished.
-  void DidFinish(int request_id, base::File::Error result);
-  void DidGetMetadata(int request_id,
-                      base::File::Error result,
-                      const base::File::Info& info);
-  void DidGetMetadataForStreaming(int request_id,
-                                  base::File::Error result,
-                                  const base::File::Info& info);
-  void DidReadDirectory(int request_id,
-                        base::File::Error result,
-                        std::vector<filesystem::mojom::DirectoryEntry> entries,
-                        bool has_more);
-  void DidWrite(int request_id,
-                base::File::Error result,
-                int64_t bytes,
-                bool complete);
-  void DidOpenFileSystem(int request_id,
-                         const GURL& root,
-                         const std::string& filesystem_name,
-                         base::File::Error result);
-  void DidResolveURL(int request_id,
-                     base::File::Error result,
-                     const storage::FileSystemInfo& info,
-                     const base::FilePath& file_path,
-                     storage::FileSystemContext::ResolvedEntryType type);
-  void DidCreateSnapshot(
-      int request_id,
-      const storage::FileSystemURL& url,
-      base::File::Error result,
-      const base::File::Info& info,
-      const base::FilePath& platform_path,
-      scoped_refptr<storage::ShareableFileReference> file_ref);
-
-  // Sends a FileSystemMsg_DidFail and returns false if |url| is invalid.
-  bool ValidateFileSystemURL(int request_id, const storage::FileSystemURL& url);
-
-  storage::FileSystemOperationRunner* operation_runner() {
-    return operation_runner_.get();
-  }
-
-  int process_id_;
-
-  storage::FileSystemContext* context_;
-  ChildProcessSecurityPolicyImpl* security_policy_;
-
-  // Keeps map from request_id to OperationID for ongoing operations.
-  // (Primarily for Cancel operation)
-  typedef std::map<int, OperationID> OperationsMap;
-  OperationsMap operations_;
-
-  // The getter holds the context until OnChannelConnected() can be called from
-  // the IO thread, which will extract the net::URLRequestContext from it.
-  scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
-  net::URLRequestContext* request_context_;
-
-  scoped_refptr<ChromeBlobStorageContext> blob_storage_context_;
-
-  std::unique_ptr<storage::FileSystemOperationRunner> operation_runner_;
-
-  // Keep track of stream URLs registered in this process. Need to unregister
-  // all of them when the renderer process dies.
-  base::hash_set<std::string> stream_urls_;
-
-  // Used to keep snapshot files alive while a DidCreateSnapshot
-  // is being sent to the renderer.
-  std::map<int, scoped_refptr<storage::ShareableFileReference> >
-      in_transit_snapshot_files_;
-
-  DISALLOW_COPY_AND_ASSIGN(FileAPIMessageFilter);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_FILEAPI_FILEAPI_MESSAGE_FILTER_H_
diff --git a/content/browser/fileapi/fileapi_message_filter_unittest.cc b/content/browser/fileapi/fileapi_message_filter_unittest.cc
deleted file mode 100644
index c996e7c..0000000
--- a/content/browser/fileapi/fileapi_message_filter_unittest.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/fileapi/fileapi_message_filter.h"
-
-#include <stddef.h>
-
-#include <string>
-#include <vector>
-
-#include "base/memory/ref_counted.h"
-#include "base/memory/shared_memory.h"
-#include "base/process/process_handle.h"
-#include "base/run_loop.h"
-#include "content/browser/blob_storage/chrome_blob_storage_context.h"
-#include "content/browser/child_process_security_policy_impl.h"
-#include "content/common/fileapi/file_system_messages.h"
-#include "content/common/fileapi/webblob_messages.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/common/common_param_traits.h"
-#include "content/public/test/mock_render_process_host.h"
-#include "content/public/test/test_browser_context.h"
-#include "content/public/test/test_browser_thread.h"
-#include "content/public/test/test_browser_thread_bundle.h"
-#include "net/base/io_buffer.h"
-#include "storage/browser/blob/blob_storage_context.h"
-#include "storage/browser/fileapi/file_system_context.h"
-#include "storage/browser/test/test_file_system_context.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace content {
-
-class FileAPIMessageFilterTest : public testing::Test {
- public:
-  FileAPIMessageFilterTest()
-      : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {
-  }
-
- protected:
-  void SetUp() override {
-    file_system_context_ =
-        CreateFileSystemContextForTesting(nullptr, base::FilePath());
-
-    for (const storage::FileSystemType& type :
-         file_system_context_->GetFileSystemTypes()) {
-      ChildProcessSecurityPolicyImpl::GetInstance()
-          ->RegisterFileSystemPermissionPolicy(
-              type, storage::FileSystemContext::GetPermissionPolicy(type));
-    }
-
-    blob_storage_context_ = ChromeBlobStorageContext::GetFor(&browser_context_);
-
-    filter_ = new FileAPIMessageFilter(
-        0 /* process_id */,
-        BrowserContext::GetDefaultStoragePartition(&browser_context_)
-            ->GetURLRequestContext(),
-        file_system_context_.get(), blob_storage_context_);
-
-    // Complete initialization.
-    base::RunLoop().RunUntilIdle();
-  }
-
-  TestBrowserThreadBundle browser_thread_bundle_;
-  TestBrowserContext browser_context_;
-  scoped_refptr<storage::FileSystemContext> file_system_context_;
-  ChromeBlobStorageContext* blob_storage_context_;
-
-  scoped_refptr<FileAPIMessageFilter> filter_;
-};
-
-TEST_F(FileAPIMessageFilterTest, CloseChannelWithInflightRequest) {
-  scoped_refptr<FileAPIMessageFilter> filter(new FileAPIMessageFilter(
-      0 /* process_id */,
-      BrowserContext::GetDefaultStoragePartition(&browser_context_)
-          ->GetURLRequestContext(),
-      file_system_context_.get(),
-      ChromeBlobStorageContext::GetFor(&browser_context_)));
-  filter->OnChannelConnected(0);
-
-  // Complete initialization.
-  base::RunLoop().RunUntilIdle();
-
-  int request_id = 0;
-  const GURL kUrl("filesystem:http://example.com/temporary/foo");
-  FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl);
-  EXPECT_TRUE(filter->OnMessageReceived(read_metadata));
-
-  // Close the filter while it has inflight request.
-  filter->OnChannelClosing();
-
-  // This shouldn't cause DCHECK failure.
-  base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(FileAPIMessageFilterTest, MultipleFilters) {
-  scoped_refptr<FileAPIMessageFilter> filter1(new FileAPIMessageFilter(
-      0 /* process_id */,
-      BrowserContext::GetDefaultStoragePartition(&browser_context_)
-          ->GetURLRequestContext(),
-      file_system_context_.get(),
-      ChromeBlobStorageContext::GetFor(&browser_context_)));
-  scoped_refptr<FileAPIMessageFilter> filter2(new FileAPIMessageFilter(
-      1 /* process_id */,
-      BrowserContext::GetDefaultStoragePartition(&browser_context_)
-          ->GetURLRequestContext(),
-      file_system_context_.get(),
-      ChromeBlobStorageContext::GetFor(&browser_context_)));
-  filter1->OnChannelConnected(0);
-  filter2->OnChannelConnected(1);
-
-  // Complete initialization.
-  base::RunLoop().RunUntilIdle();
-
-  int request_id = 0;
-  const GURL kUrl("filesystem:http://example.com/temporary/foo");
-  FileSystemHostMsg_ReadMetadata read_metadata(request_id++, kUrl);
-  EXPECT_TRUE(filter1->OnMessageReceived(read_metadata));
-
-  // Close the other filter before the request for filter1 is processed.
-  filter2->OnChannelClosing();
-
-  // This shouldn't cause DCHECK failure.
-  base::RunLoop().RunUntilIdle();
-}
-
-}  // namespace content
diff --git a/content/browser/gpu/browser_gpu_channel_host_factory.cc b/content/browser/gpu/browser_gpu_channel_host_factory.cc
index 261946e..4b14d65a 100644
--- a/content/browser/gpu/browser_gpu_channel_host_factory.cc
+++ b/content/browser/gpu/browser_gpu_channel_host_factory.cc
@@ -399,7 +399,7 @@
     int gpu_client_id,
     const base::FilePath& cache_dir) {
   GetShaderCacheFactorySingleton()->SetCacheInfo(gpu_client_id, cache_dir);
-  if (features::IsVizDisplayCompositorEnabled()) {
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
     GetShaderCacheFactorySingleton()->SetCacheInfo(
         gpu::kInProcessCommandBufferClientId, cache_dir);
   }
diff --git a/content/browser/gpu/compositor_util.cc b/content/browser/gpu/compositor_util.cc
index 95bc987..9bcf1916 100644
--- a/content/browser/gpu/compositor_util.cc
+++ b/content/browser/gpu/compositor_util.cc
@@ -190,7 +190,7 @@
        "WebGL2 has been disabled via blacklist or the command line.", false,
        true},
       {"viz_display_compositor", gpu::kGpuFeatureStatusEnabled,
-       !features::IsVizDisplayCompositorEnabled(),
+       !base::FeatureList::IsEnabled(features::kVizDisplayCompositor),
        "Viz service display compositor is not enabled by default.", false,
        false},
       {"skia_renderer", gpu::kGpuFeatureStatusEnabled,
@@ -223,8 +223,12 @@
     const GpuFeatureData gpu_feature_data =
         GetGpuFeatureData(gpu_feature_info, type, i, &eof);
     std::string status;
-    if (gpu_feature_data.disabled || gpu_access_blocked ||
-        gpu_feature_data.status == gpu::kGpuFeatureStatusDisabled) {
+    if (gpu_feature_data.name == "surface_synchronization") {
+      status = (!gpu_feature_data.disabled ? "enabled_on" : "disabled_off");
+    } else if (gpu_feature_data.name == "viz_display_compositor") {
+      status = (!gpu_feature_data.disabled ? "enabled_on" : "disabled_off");
+    } else if (gpu_feature_data.disabled || gpu_access_blocked ||
+               gpu_feature_data.status == gpu::kGpuFeatureStatusDisabled) {
       status = "disabled";
       if (gpu_feature_data.fallback_to_software)
         status += "_software";
@@ -252,14 +256,6 @@
           status += "_force";
         status += "_on";
       }
-      if (gpu_feature_data.name == "surface_synchronization") {
-        if (features::IsSurfaceSynchronizationEnabled())
-          status += "_on";
-      }
-      if (gpu_feature_data.name == "viz_display_compositor") {
-        if (features::IsVizDisplayCompositorEnabled())
-          status += "_on";
-      }
       if (gpu_feature_data.name == "skia_renderer") {
         if (features::IsUsingSkiaRenderer())
           status += "_on";
diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc
index 971fe8b..63797576 100644
--- a/content/browser/gpu/gpu_data_manager_impl_private.cc
+++ b/content/browser/gpu/gpu_data_manager_impl_private.cc
@@ -353,7 +353,19 @@
 }
 
 bool GpuDataManagerImplPrivate::GpuProcessStartAllowed() const {
-  return features::IsVizDisplayCompositorEnabled() || GpuAccessAllowed(nullptr);
+  if (GpuAccessAllowed(nullptr))
+    return true;
+
+#if defined(USE_X11) || defined(OS_MACOSX)
+  // If GPU access is disabled with OOP-D we run the display compositor in:
+  //   Browser process: Windows
+  //   GPU process: Linux and Mac
+  //   N/A: Android and Chrome OS (GPU access can't be disabled)
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
+    return true;
+#endif
+
+  return false;
 }
 
 void GpuDataManagerImplPrivate::RequestCompleteGpuInfoIfNeeded() {
@@ -839,7 +851,7 @@
     return gpu::GpuMode::HARDWARE_ACCELERATED;
   } else if (SwiftShaderAllowed()) {
     return gpu::GpuMode::SWIFTSHADER;
-  } else if (features::IsVizDisplayCompositorEnabled()) {
+  } else if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
     return gpu::GpuMode::DISPLAY_COMPOSITOR;
   } else {
     return gpu::GpuMode::DISABLED;
@@ -861,7 +873,7 @@
   } else if (SwiftShaderAllowed()) {
     swiftshader_blocked_ = true;
     OnGpuBlocked();
-  } else if (features::IsVizDisplayCompositorEnabled()) {
+  } else if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
     // The GPU process is frequently crashing with only the display compositor
     // running. This should never happen so something is wrong. Crash the
     // browser process to reset everything.
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 6d83be4..21b77535 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -1018,7 +1018,8 @@
       switches::kDisableGpuShaderDiskCache)) {
     CreateChannelCache(client_id);
 
-    bool oopd_enabled = features::IsVizDisplayCompositorEnabled();
+    bool oopd_enabled =
+        base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
     if (oopd_enabled)
       CreateChannelCache(gpu::kInProcessCommandBufferClientId);
 
diff --git a/content/browser/indexed_db/indexed_db_callbacks.cc b/content/browser/indexed_db/indexed_db_callbacks.cc
index 8e16c5d..7a9113b 100644
--- a/content/browser/indexed_db/indexed_db_callbacks.cc
+++ b/content/browser/indexed_db/indexed_db_callbacks.cc
@@ -17,7 +17,6 @@
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "content/browser/child_process_security_policy_impl.h"
-#include "content/browser/fileapi/fileapi_message_filter.h"
 #include "content/browser/indexed_db/cursor_impl.h"
 #include "content/browser/indexed_db/database_impl.h"
 #include "content/browser/indexed_db/indexed_db_connection.h"
diff --git a/content/browser/media/media_color_browsertest.cc b/content/browser/media/media_color_browsertest.cc
index 0f56a7d..1a541b2 100644
--- a/content/browser/media/media_color_browsertest.cc
+++ b/content/browser/media/media_color_browsertest.cc
@@ -39,13 +39,7 @@
 }
 #endif  // !defined(OS_ANDROID)
 
-// This fails on Linux: http://crbug.com/767926.
-#if defined(OS_LINUX)
-#define MAYBE_Yuv420pVp8 DISABLED_Yuv420pVp8
-#else
-#define MAYBE_Yuv420pVp8 Yuv420pVp8
-#endif
-IN_PROC_BROWSER_TEST_F(MediaColorTest, MAYBE_Yuv420pVp8) {
+IN_PROC_BROWSER_TEST_F(MediaColorTest, Yuv420pVp8) {
   RunColorTest("yuv420p.webm");
 }
 
@@ -77,9 +71,8 @@
 }
 
 // This fails on ChromeOS: http://crbug.com/647400,
-// Windows: http://crbug.com/647842, and Android: http://crbug.com/649199,
-// http://crbug.com/649185.
-#if defined(OS_CHROMEOS) || defined(OS_WIN) || defined(OS_ANDROID)
+// and Android: http://crbug.com/649199, http://crbug.com/649185.
+#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
 #define MAYBE_Yuv420pRec709H264 DISABLED_Yuv420pRec709H264
 #else
 #define MAYBE_Yuv420pRec709H264 Yuv420pRec709H264
diff --git a/content/browser/notifications/blink_notification_service_impl.cc b/content/browser/notifications/blink_notification_service_impl.cc
index afec6b0..73c5d6b4 100644
--- a/content/browser/notifications/blink_notification_service_impl.cc
+++ b/content/browser/notifications/blink_notification_service_impl.cc
@@ -171,7 +171,8 @@
     DisplayPersistentNotificationCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  // TODO(awdf): Necessary to validate resources here?
+  // TODO(https://crbug.com/870258): Validate resources are not too big (either
+  // here or in the mojo struct traits).
 
   NotificationDatabaseData database_data;
   database_data.origin = origin_.GetURL();
diff --git a/content/browser/picture_in_picture/overlay_surface_embedder.cc b/content/browser/picture_in_picture/overlay_surface_embedder.cc
index 6b8c3c7..412d889 100644
--- a/content/browser/picture_in_picture/overlay_surface_embedder.cc
+++ b/content/browser/picture_in_picture/overlay_surface_embedder.cc
@@ -39,14 +39,14 @@
 
 OverlaySurfaceEmbedder::~OverlaySurfaceEmbedder() = default;
 
-void OverlaySurfaceEmbedder::SetPrimarySurfaceId(
-    const viz::SurfaceId& surface_id) {
+void OverlaySurfaceEmbedder::SetSurfaceId(const viz::SurfaceId& surface_id) {
   video_layer_ = window_->GetVideoLayer();
   // SurfaceInfo has information about the embedded surface.
   video_layer_->SetShowPrimarySurface(
       surface_id, window_->GetBounds().size(), SK_ColorBLACK,
       cc::DeadlinePolicy::UseDefaultDeadline(),
       true /* stretch_content_to_fill_bounds */);
+  video_layer_->SetFallbackSurfaceId(surface_id);
 }
 
 void OverlaySurfaceEmbedder::UpdateLayerBounds() {
diff --git a/content/browser/picture_in_picture/overlay_surface_embedder.h b/content/browser/picture_in_picture/overlay_surface_embedder.h
index c5d154b2..9307f38 100644
--- a/content/browser/picture_in_picture/overlay_surface_embedder.h
+++ b/content/browser/picture_in_picture/overlay_surface_embedder.h
@@ -23,7 +23,7 @@
   explicit OverlaySurfaceEmbedder(OverlayWindow* window);
   ~OverlaySurfaceEmbedder();
 
-  void SetPrimarySurfaceId(const viz::SurfaceId& surface_id);
+  void SetSurfaceId(const viz::SurfaceId&);
   void UpdateLayerBounds();
 
  private:
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
index f59eca0..4332ca93d 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
@@ -117,7 +117,7 @@
 
   if (!embedder_)
     embedder_.reset(new OverlaySurfaceEmbedder(window_.get()));
-  embedder_->SetPrimarySurfaceId(surface_id_);
+  embedder_->SetSurfaceId(surface_id_);
 }
 
 OverlayWindow* PictureInPictureWindowControllerImpl::GetWindowForTesting() {
diff --git a/content/browser/push_messaging/push_messaging_router.cc b/content/browser/push_messaging/push_messaging_router.cc
index a648b5d..4904c59f 100644
--- a/content/browser/push_messaging/push_messaging_router.cc
+++ b/content/browser/push_messaging/push_messaging_router.cc
@@ -15,7 +15,6 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/storage_partition.h"
-#include "content/public/common/push_event_payload.h"
 #include "content/public/common/push_messaging_status.mojom.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 
@@ -39,7 +38,7 @@
     BrowserContext* browser_context,
     const GURL& origin,
     int64_t service_worker_registration_id,
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     const DeliverMessageCallback& deliver_message_callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   StoragePartition* partition =
@@ -50,7 +49,7 @@
   BrowserThread::PostTask(
       BrowserThread::IO, FROM_HERE,
       base::BindOnce(&PushMessagingRouter::FindServiceWorkerRegistration,
-                     origin, service_worker_registration_id, payload,
+                     origin, service_worker_registration_id, std::move(payload),
                      deliver_message_callback, service_worker_context));
 }
 
@@ -58,7 +57,7 @@
 void PushMessagingRouter::FindServiceWorkerRegistration(
     const GURL& origin,
     int64_t service_worker_registration_id,
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     const DeliverMessageCallback& deliver_message_callback,
     scoped_refptr<ServiceWorkerContextWrapper> service_worker_context) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -67,13 +66,13 @@
   service_worker_context->FindReadyRegistrationForId(
       service_worker_registration_id, origin,
       base::BindOnce(
-          &PushMessagingRouter::FindServiceWorkerRegistrationCallback, payload,
-          deliver_message_callback));
+          &PushMessagingRouter::FindServiceWorkerRegistrationCallback,
+          std::move(payload), deliver_message_callback));
 }
 
 // static
 void PushMessagingRouter::FindServiceWorkerRegistrationCallback(
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     const DeliverMessageCallback& deliver_message_callback,
     blink::ServiceWorkerStatusCode service_worker_status,
     scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
@@ -102,14 +101,14 @@
       ServiceWorkerMetrics::EventType::PUSH,
       base::BindOnce(&PushMessagingRouter::DeliverMessageToWorker,
                      base::WrapRefCounted(version), service_worker_registration,
-                     payload, deliver_message_callback));
+                     std::move(payload), deliver_message_callback));
 }
 
 // static
 void PushMessagingRouter::DeliverMessageToWorker(
     const scoped_refptr<ServiceWorkerVersion>& service_worker,
     const scoped_refptr<ServiceWorkerRegistration>& service_worker_registration,
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     const DeliverMessageCallback& deliver_message_callback,
     blink::ServiceWorkerStatusCode start_worker_status) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
diff --git a/content/browser/push_messaging/push_messaging_router.h b/content/browser/push_messaging/push_messaging_router.h
index 71bb530..773bed1 100644
--- a/content/browser/push_messaging/push_messaging_router.h
+++ b/content/browser/push_messaging/push_messaging_router.h
@@ -10,6 +10,7 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "url/gurl.h"
 
@@ -20,7 +21,6 @@
 }
 
 class BrowserContext;
-struct PushEventPayload;
 class ServiceWorkerContextWrapper;
 class ServiceWorkerRegistration;
 class ServiceWorkerVersion;
@@ -37,7 +37,7 @@
       BrowserContext* browser_context,
       const GURL& origin,
       int64_t service_worker_registration_id,
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       const DeliverMessageCallback& deliver_message_callback);
 
  private:
@@ -46,7 +46,7 @@
   static void FindServiceWorkerRegistration(
       const GURL& origin,
       int64_t service_worker_registration_id,
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       const DeliverMessageCallback& deliver_message_callback,
       scoped_refptr<ServiceWorkerContextWrapper> service_worker_context);
 
@@ -54,7 +54,7 @@
   // |data| on the Service Worker identified by |service_worker_registration|.
   // Must be called on the IO thread.
   static void FindServiceWorkerRegistrationCallback(
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       const DeliverMessageCallback& deliver_message_callback,
       blink::ServiceWorkerStatusCode service_worker_status,
       scoped_refptr<ServiceWorkerRegistration> service_worker_registration);
@@ -65,7 +65,7 @@
       const scoped_refptr<ServiceWorkerVersion>& service_worker,
       const scoped_refptr<ServiceWorkerRegistration>&
           service_worker_registration,
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       const DeliverMessageCallback& deliver_message_callback,
       blink::ServiceWorkerStatusCode start_worker_status);
 
diff --git a/content/browser/renderer_host/browser_compositor_view_mac.mm b/content/browser/renderer_host/browser_compositor_view_mac.mm
index a652801..92afcc7 100644
--- a/content/browser/renderer_host/browser_compositor_view_mac.mm
+++ b/content/browser/renderer_host/browser_compositor_view_mac.mm
@@ -60,7 +60,8 @@
   // content (otherwise this solid color will be flashed during navigation).
   root_layer_->SetColor(SK_ColorTRANSPARENT);
   delegated_frame_host_.reset(new DelegatedFrameHost(
-      frame_sink_id, this, features::IsVizDisplayCompositorEnabled(),
+      frame_sink_id, this,
+      base::FeatureList::IsEnabled(features::kVizDisplayCompositor),
       true /* should_register_frame_sink_id */));
 
   SetRenderWidgetHostIsHidden(render_widget_host_is_hidden);
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index e9754af0..26f7761 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -191,7 +191,8 @@
   friend class base::NoDestructor<CompositorDependencies>;
 
   CompositorDependencies() : frame_sink_id_allocator(kDefaultClientId) {
-    bool enable_viz = features::IsVizDisplayCompositorEnabled();
+    bool enable_viz =
+        base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
     if (!enable_viz) {
       // The SharedBitmapManager can be null as software compositing is not
       // supported or used on Android.
@@ -699,7 +700,8 @@
       lock_manager_(base::ThreadTaskRunnerHandle::Get(), this),
       enable_surface_synchronization_(
           features::IsSurfaceSynchronizationEnabled()),
-      enable_viz_(features::IsVizDisplayCompositorEnabled()),
+      enable_viz_(
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor)),
       weak_factory_(this) {
   GetHostFrameSinkManager()->RegisterFrameSinkId(frame_sink_id_, this);
   GetHostFrameSinkManager()->SetFrameSinkDebugLabel(frame_sink_id_,
diff --git a/content/browser/renderer_host/input/fling_browsertest.cc b/content/browser/renderer_host/input/fling_browsertest.cc
index eb26d87..a3d18b8 100644
--- a/content/browser/renderer_host/input/fling_browsertest.cc
+++ b/content/browser/renderer_host/input/fling_browsertest.cc
@@ -87,7 +87,7 @@
 #if defined(OS_MACOSX)
   // TODO(jonross): Re-enable once fling on Mac works with Viz.
   // https://crbug.com/842325
-  if (features::IsVizDisplayCompositorEnabled())
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
     return;
 #endif  // defined(OS_MACOSX)
 
@@ -127,7 +127,7 @@
 #if defined(OS_MACOSX)
   // TODO(jonross): Re-enable once fling on Mac works with Viz.
   // https://crbug.com/842325
-  if (features::IsVizDisplayCompositorEnabled())
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor))
     return;
 #endif  // defined(OS_MACOSX)
 
diff --git a/content/browser/renderer_host/input/fling_controller_unittest.cc b/content/browser/renderer_host/input/fling_controller_unittest.cc
index 6d423d9..128a4703 100644
--- a/content/browser/renderer_host/input/fling_controller_unittest.cc
+++ b/content/browser/renderer_host/input/fling_controller_unittest.cc
@@ -154,7 +154,8 @@
   EXPECT_EQ(WebInputEvent::kGestureScrollEnd, last_sent_gesture_.GetType());
 }
 
-TEST_F(FlingControllerTest, ControllerHandlesTouchpadGestureFling) {
+// TODO(https://crbug.com/836996): Timing-dependent flakes on some platforms.
+TEST_F(FlingControllerTest, DISABLED_ControllerHandlesTouchpadGestureFling) {
   base::TimeTicks progress_time = base::TimeTicks::Now();
   SimulateFlingStart(blink::kWebGestureDeviceTouchpad, gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
diff --git a/content/browser/renderer_host/pepper/pepper_file_io_host.cc b/content/browser/renderer_host/pepper/pepper_file_io_host.cc
index 474c168..33d619f 100644
--- a/content/browser/renderer_host/pepper/pepper_file_io_host.cc
+++ b/content/browser/renderer_host/pepper/pepper_file_io_host.cc
@@ -14,7 +14,6 @@
 #include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
 #include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
 #include "content/browser/renderer_host/pepper/pepper_security_helper.h"
-#include "content/common/fileapi/file_system_messages.h"
 #include "content/common/view_messages.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_browser_client.h"
diff --git a/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc b/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc
index 63924d8..75774080 100644
--- a/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc
+++ b/content/browser/renderer_host/pepper/pepper_network_proxy_host.cc
@@ -6,44 +6,65 @@
 
 #include "base/bind.h"
 #include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
+#include "content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h"
 #include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/site_instance.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/socket_permission_request.h"
-#include "net/base/net_errors.h"
-#include "net/log/net_log_with_source.h"
 #include "net/proxy_resolution/proxy_info.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
 #include "ppapi/c/pp_errors.h"
 #include "ppapi/host/dispatch_host_message.h"
 #include "ppapi/host/ppapi_host.h"
 #include "ppapi/proxy/ppapi_messages.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
 
 namespace content {
 
+namespace {
+
+bool LookUpProxyForURLCallback(
+    int render_process_host_id,
+    int render_frame_host_id,
+    const GURL& url,
+    network::mojom::ProxyLookupClientPtr proxy_lookup_client) {
+  RenderFrameHost* render_frame_host =
+      RenderFrameHost::FromID(render_process_host_id, render_frame_host_id);
+  if (!render_frame_host)
+    return false;
+
+  SiteInstance* site_instance = render_frame_host->GetSiteInstance();
+  StoragePartition* storage_partition = BrowserContext::GetStoragePartition(
+      site_instance->GetBrowserContext(), site_instance);
+
+  storage_partition->GetNetworkContext()->LookUpProxyForURL(
+      url, std::move(proxy_lookup_client));
+  return true;
+}
+
+}  // namespace
+
 PepperNetworkProxyHost::PepperNetworkProxyHost(BrowserPpapiHostImpl* host,
                                                PP_Instance instance,
                                                PP_Resource resource)
     : ResourceHost(host->GetPpapiHost(), instance, resource),
-      proxy_resolution_service_(nullptr),
+      render_process_id_(0),
+      render_frame_id_(0),
       is_allowed_(false),
       waiting_for_ui_thread_data_(true),
       weak_factory_(this) {
-  int render_process_id(0), render_frame_id(0);
-  host->GetRenderFrameIDsForInstance(
-      instance, &render_process_id, &render_frame_id);
+  host->GetRenderFrameIDsForInstance(instance, &render_process_id_,
+                                     &render_frame_id_);
   BrowserThread::PostTaskAndReplyWithResult(
-      BrowserThread::UI,
-      FROM_HERE,
-      base::Bind(&GetUIThreadDataOnUIThread,
-                 render_process_id,
-                 render_frame_id,
-                 host->external_plugin()),
-      base::Bind(&PepperNetworkProxyHost::DidGetUIThreadData,
-                 weak_factory_.GetWeakPtr()));
+      BrowserThread::UI, FROM_HERE,
+      base::BindOnce(&GetUIThreadDataOnUIThread, render_process_id_,
+                     render_frame_id_, host->external_plugin()),
+      base::BindOnce(&PepperNetworkProxyHost::DidGetUIThreadData,
+                     weak_factory_.GetWeakPtr()));
 }
 
 PepperNetworkProxyHost::~PepperNetworkProxyHost() = default;
@@ -62,9 +83,6 @@
                                                   bool is_external_plugin) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   PepperNetworkProxyHost::UIThreadData result;
-  RenderProcessHost* rph = RenderProcessHost::FromID(render_process_id);
-  if (rph)
-    result.context_getter = rph->GetStoragePartition()->GetURLRequestContext();
 
   SocketPermissionRequest request(
       content::SocketPermissionRequest::RESOLVE_PROXY, std::string(), 0);
@@ -80,16 +98,7 @@
 void PepperNetworkProxyHost::DidGetUIThreadData(
     const UIThreadData& ui_thread_data) {
   is_allowed_ = ui_thread_data.is_allowed;
-  if (ui_thread_data.context_getter.get() &&
-      ui_thread_data.context_getter->GetURLRequestContext()) {
-    proxy_resolution_service_ =
-        ui_thread_data.context_getter->GetURLRequestContext()->proxy_resolution_service();
-  }
   waiting_for_ui_thread_data_ = false;
-  if (!proxy_resolution_service_) {
-    DLOG_IF(WARNING, proxy_resolution_service_)
-        << "Failed to find a ProxyResolutionService for Pepper plugin.";
-  }
   TryToSendUnsentRequests();
 }
 
@@ -123,27 +132,23 @@
 
   while (!unsent_requests_.empty()) {
     const UnsentRequest& request = unsent_requests_.front();
-    if (!proxy_resolution_service_) {
-      SendFailureReply(PP_ERROR_FAILED, request.reply_context);
-    } else if (!is_allowed_) {
+    if (!is_allowed_) {
       SendFailureReply(PP_ERROR_NOACCESS, request.reply_context);
     } else {
       // Everything looks valid, so try to resolve the proxy.
-      net::ProxyInfo* proxy_info = new net::ProxyInfo;
-      std::unique_ptr<net::ProxyResolutionService::Request> pending_request;
-      base::Callback<void(int)> callback =
-          base::Bind(&PepperNetworkProxyHost::OnResolveProxyCompleted,
-                     weak_factory_.GetWeakPtr(),
-                     request.reply_context,
-                     base::Owned(proxy_info));
-      int result = proxy_resolution_service_->ResolveProxy(
-          request.url, std::string(), proxy_info, callback, &pending_request,
-          nullptr, net::NetLogWithSource());
-      pending_requests_.push(std::move(pending_request));
-      // If it was handled synchronously, we must run the callback now;
-      // proxy_resolution_service_ won't run it for us in this case.
-      if (result != net::ERR_IO_PENDING)
-        std::move(callback).Run(result);
+      auto lookup_helper = std::make_unique<PepperProxyLookupHelper>();
+      PepperProxyLookupHelper::LookUpProxyForURLCallback
+          look_up_proxy_for_url_callback = base::BindOnce(
+              &LookUpProxyForURLCallback, render_process_id_, render_frame_id_);
+      PepperProxyLookupHelper::LookUpCompleteCallback
+          look_up_complete_callback =
+              base::BindOnce(&PepperNetworkProxyHost::OnResolveProxyCompleted,
+                             weak_factory_.GetWeakPtr(), request.reply_context,
+                             lookup_helper.get());
+      lookup_helper->Start(request.url,
+                           std::move(look_up_proxy_for_url_callback),
+                           std::move(look_up_complete_callback));
+      pending_requests_.insert(std::move(lookup_helper));
     }
     unsent_requests_.pop();
   }
@@ -151,20 +156,24 @@
 
 void PepperNetworkProxyHost::OnResolveProxyCompleted(
     ppapi::host::ReplyMessageContext context,
-    net::ProxyInfo* proxy_info,
-    int result) {
-  pending_requests_.pop();
+    PepperProxyLookupHelper* pending_request,
+    base::Optional<net::ProxyInfo> proxy_info) {
+  auto it = pending_requests_.find(pending_request);
+  DCHECK(it != pending_requests_.end());
+  pending_requests_.erase(it);
 
-  if (result != net::OK) {
-    // Currently, the only proxy-specific error we could get is
-    // MANDATORY_PROXY_CONFIGURATION_FAILED. There's really no action a plugin
-    // can take in this case, so there's no need to distinguish it from other
-    // failures.
+  std::string pac_string;
+  if (!proxy_info) {
+    // This can happen in cases of network service crash, shutdown, or when
+    // the request fails with ERR_MANDATORY_PROXY_CONFIGURATION_FAILED. There's
+    // really no action a plugin can take, so there's no need to distinguish
+    // which error occurred.
     context.params.set_result(PP_ERROR_FAILED);
+  } else {
+    pac_string = proxy_info->ToPacString();
   }
-  host()->SendReply(context,
-                    PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(
-                        proxy_info->ToPacString()));
+  host()->SendReply(
+      context, PpapiPluginMsg_NetworkProxy_GetProxyForURLReply(pac_string));
 }
 
 void PepperNetworkProxyHost::SendFailureReply(
diff --git a/content/browser/renderer_host/pepper/pepper_network_proxy_host.h b/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
index f1c5fe68..081c4045 100644
--- a/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
+++ b/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
@@ -8,20 +8,22 @@
 #include <stdint.h>
 
 #include <queue>
+#include <set>
 #include <string>
 
 #include "base/compiler_specific.h"
 #include "base/containers/queue.h"
+#include "base/containers/unique_ptr_adapters.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "content/common/content_export.h"
-#include "net/proxy_resolution/proxy_resolution_service.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
+#include "url/gurl.h"
 
 namespace net {
 class ProxyInfo;
-class URLRequestContextGetter;
 }
 
 namespace ppapi {
@@ -33,6 +35,7 @@
 namespace content {
 
 class BrowserPpapiHostImpl;
+class PepperProxyLookupHelper;
 
 // The host for PPB_NetworkProxy. This class lives on the IO thread.
 class CONTENT_EXPORT PepperNetworkProxyHost : public ppapi::host::ResourceHost {
@@ -44,15 +47,14 @@
   ~PepperNetworkProxyHost() override;
 
  private:
-  // We retrieve the appropriate URLRequestContextGetter and whether this API
-  // is allowed for the instance on the UI thread and pass those to
-  // DidGetUIThreadData, which sets allowed_ and proxy_resolution_service_.
+  // We retrieve whether this API is allowed for the instance on the UI thread
+  // and pass it to DidGetUIThreadData, which sets allowed_.
   struct UIThreadData {
     UIThreadData();
     UIThreadData(const UIThreadData& other);
     ~UIThreadData();
+
     bool is_allowed;
-    scoped_refptr<net::URLRequestContextGetter> context_getter;
   };
   static UIThreadData GetUIThreadDataOnUIThread(int render_process_id,
                                                 int render_frame_id,
@@ -67,39 +69,38 @@
   int32_t OnMsgGetProxyForURL(ppapi::host::HostMessageContext* context,
                               const std::string& url);
 
-  // If we have a valid proxy_resolution_service_, send all messages in
-  // unsent_requests_.
+  // Send all messages in |unsent_requests_|.
   void TryToSendUnsentRequests();
 
   void OnResolveProxyCompleted(ppapi::host::ReplyMessageContext context,
-                               net::ProxyInfo* proxy_info,
-                               int result);
+                               PepperProxyLookupHelper* pending_request,
+                               base::Optional<net::ProxyInfo> proxy_info);
   void SendFailureReply(int32_t error,
                         ppapi::host::ReplyMessageContext context);
 
-  // The following two members are invalid until we get some information from
-  // the UI thread. However, these are only ever set or accessed on the IO
-  // thread.
-  net::ProxyResolutionService* proxy_resolution_service_;
+  // Used to find correct NetworkContext to perform proxy lookups.
+  int render_process_id_;
+  int render_frame_id_;
+
+  // The following member is invalid until we get some information from the UI
+  // thread. However, it is only ever set or accessed on the IO thread.
   bool is_allowed_;
 
-  // True initially, but set to false once the values for
-  // proxy_resolution_service_ and is_allowed_ have been set.
+  // True initially, but set to false once is_allowed_ has been set.
   bool waiting_for_ui_thread_data_;
 
-  // We have to get the URLRequestContextGetter from the UI thread before we
-  // can retrieve proxy_resolution_service_. If we receive any calls for
-  // GetProxyForURL before proxy_resolution_service_ is available, we save them
-  // in unsent_requests_.
+  // We have to get is_allowed_ from the UI thread before we can start a
+  // request. If we receive any calls for GetProxyForURL before is_allowed_ is
+  // available, we save them in unsent_requests_.
   struct UnsentRequest {
     GURL url;
     ppapi::host::ReplyMessageContext reply_context;
   };
   base::queue<UnsentRequest> unsent_requests_;
 
-  // Requests awaiting a response from ProxyResolutionService. We need to store
+  // Requests awaiting a response from the network service. We need to store
   // these so that we can cancel them if we get destroyed.
-  base::queue<std::unique_ptr<net::ProxyResolutionService::Request>>
+  std::set<std::unique_ptr<PepperProxyLookupHelper>, base::UniquePtrComparator>
       pending_requests_;
 
   base::WeakPtrFactory<PepperNetworkProxyHost> weak_factory_;
diff --git a/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.cc b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.cc
new file mode 100644
index 0000000..9592839
--- /dev/null
+++ b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.cc
@@ -0,0 +1,106 @@
+// Copyright 2018 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 "content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "url/gurl.h"
+
+namespace content {
+
+// Runs the proxy lookup on the UI thread. Created on the
+// PepperProxyLookupHelper's thread, but then does all work on the UI thread,
+// and is deleted there by a task posted by the PepperProxyLookupHelper.
+class PepperProxyLookupHelper::UIThreadHelper
+    : public network::mojom::ProxyLookupClient {
+ public:
+  UIThreadHelper(const GURL& url,
+                 LookUpProxyForURLCallback look_up_proxy_for_url_callback,
+                 LookUpCompleteCallback look_up_complete_callback)
+      : binding_(this),
+        look_up_complete_callback_(std::move(look_up_complete_callback)),
+        callback_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+    BrowserThread::PostTask(
+        BrowserThread::UI, FROM_HERE,
+        base::BindOnce(&UIThreadHelper::StartLookup, base::Unretained(this),
+                       url, std::move(look_up_proxy_for_url_callback)));
+  }
+
+  ~UIThreadHelper() override { DCHECK_CURRENTLY_ON(BrowserThread::UI); }
+
+ private:
+  void StartLookup(const GURL& url,
+                   LookUpProxyForURLCallback look_up_proxy_for_url_callback) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    network::mojom::ProxyLookupClientPtr proxy_lookup_client;
+    binding_.Bind(mojo::MakeRequest(&proxy_lookup_client));
+    binding_.set_connection_error_handler(
+        base::BindOnce(&UIThreadHelper::OnProxyLookupComplete,
+                       base::Unretained(this), base::nullopt));
+    if (!std::move(look_up_proxy_for_url_callback)
+             .Run(url, std::move(proxy_lookup_client))) {
+      OnProxyLookupComplete(base::nullopt);
+    }
+  }
+
+  void OnProxyLookupComplete(
+      const base::Optional<net::ProxyInfo>& proxy_info) override {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    binding_.Close();
+    callback_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(look_up_complete_callback_), proxy_info));
+  }
+
+  mojo::Binding<network::mojom::ProxyLookupClient> binding_;
+
+  LookUpCompleteCallback look_up_complete_callback_;
+  scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(UIThreadHelper);
+};
+
+PepperProxyLookupHelper::PepperProxyLookupHelper() : weak_factory_(this) {}
+
+PepperProxyLookupHelper::~PepperProxyLookupHelper() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE,
+                            std::move(ui_thread_helper_));
+}
+
+void PepperProxyLookupHelper::Start(
+    const GURL& url,
+    LookUpProxyForURLCallback look_up_proxy_for_url_callback,
+    LookUpCompleteCallback look_up_complete_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!look_up_complete_callback_);
+  DCHECK(!ui_thread_helper_);
+
+  look_up_complete_callback_ = std::move(look_up_complete_callback);
+
+  ui_thread_helper_ = std::make_unique<UIThreadHelper>(
+      url, std::move(look_up_proxy_for_url_callback),
+      base::BindOnce(&PepperProxyLookupHelper::OnProxyLookupComplete,
+                     weak_factory_.GetWeakPtr()));
+}
+
+void PepperProxyLookupHelper::OnProxyLookupComplete(
+    base::Optional<net::ProxyInfo> proxy_info) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::move(look_up_complete_callback_).Run(std::move(proxy_info));
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h
new file mode 100644
index 0000000..f6ea386
--- /dev/null
+++ b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h
@@ -0,0 +1,70 @@
+// Copyright 2018 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 CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PROXY_LOOKUP_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PROXY_LOOKUP_HELPER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "content/common/content_export.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
+
+class GURL;
+
+namespace net {
+class ProxyInfo;
+}
+
+namespace content {
+
+// Class that runs a single proxy resolution on the UI thread. Lives on the
+// thread its created on, and uses a helper to run tasks off-thread. Can be
+// destroyed at any time.
+class CONTENT_EXPORT PepperProxyLookupHelper {
+ public:
+  // Callback to call LookUpProxyForURL. Called on the UI thread. Needed for
+  // testing. Returns false if unable to make the call, for whatever reason.
+  using LookUpProxyForURLCallback = base::OnceCallback<bool(
+      const GURL& url,
+      network::mojom::ProxyLookupClientPtr proxy_lookup_client)>;
+
+  // Callback to invoke when complete. Invoked on thread the
+  // PepperProxyLookupHelper was created on.
+  using LookUpCompleteCallback =
+      base::OnceCallback<void(base::Optional<net::ProxyInfo> proxy_info)>;
+
+  PepperProxyLookupHelper();
+  ~PepperProxyLookupHelper();
+
+  // Starts a lookup for |url| on the UI thread. Invokes
+  // |look_up_proxy_for_url_callback| on the UI thread to start the lookup, and
+  // calls |look_up_complete_callback| on the thread Start() was called on when
+  // complete. May only be invoked once.
+  void Start(const GURL& url,
+             LookUpProxyForURLCallback look_up_proxy_for_url_callback,
+             LookUpCompleteCallback look_up_complete_callback);
+
+ private:
+  class UIThreadHelper;
+
+  void OnProxyLookupComplete(base::Optional<net::ProxyInfo> proxy_info);
+
+  LookUpCompleteCallback look_up_complete_callback_;
+  std::unique_ptr<UIThreadHelper> ui_thread_helper_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<PepperProxyLookupHelper> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PepperProxyLookupHelper);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_PROXY_LOOKUP_HELPER_H_
diff --git a/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper_unittest.cc b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper_unittest.cc
new file mode 100644
index 0000000..25f87fdc
--- /dev/null
+++ b/content/browser/renderer_host/pepper/pepper_proxy_lookup_helper_unittest.cc
@@ -0,0 +1,203 @@
+// Copyright 2018 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 "content/browser/renderer_host/pepper/pepper_proxy_lookup_helper.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/run_loop.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+constexpr char kTestURL[] = "http://foo/";
+
+class PepperProxyLookupHelperTest : public testing::Test {
+ public:
+  PepperProxyLookupHelperTest() = default;
+  ~PepperProxyLookupHelperTest() override = default;
+
+  // Initializes |lookup_helper_| on the IO thread, and starts it there. Returns
+  // once it has called into LookUpProxyForURLOnUIThread on the UI thread.
+  void StartLookup() {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    base::RunLoop run_loop;
+    BrowserThread::PostTask(
+        BrowserThread::IO, FROM_HERE,
+        base::BindOnce(&PepperProxyLookupHelperTest::StartLookupOnIOThread,
+                       base::Unretained(this), run_loop.QuitClosure()));
+    run_loop.Run();
+
+    EXPECT_TRUE(lookup_helper_);
+    if (!fail_to_start_request_)
+      EXPECT_TRUE(proxy_lookup_client_);
+  }
+
+  // Takes the |ProxyLookupClientPtr| passed by |lookup_helper_| to
+  // LookUpProxyForURLOnUIThread(). May only be called after |lookup_helper_|
+  // has successfully called into LookUpProxyForURLOnUIThread().
+  network::mojom::ProxyLookupClientPtr ClaimProxyLookupClient() {
+    EXPECT_TRUE(proxy_lookup_client_);
+    return std::move(proxy_lookup_client_);
+  }
+
+  void DestroyLookupHelper() {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    base::RunLoop run_loop;
+
+    BrowserThread::PostTaskAndReply(
+        BrowserThread::IO, FROM_HERE,
+        base::BindOnce(
+            &PepperProxyLookupHelperTest::DestroyLookupHelperOnIOThread,
+            base::Unretained(this)),
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  // Waits for |lookup_helper_| to call into OnLookupCompleteOnIOThread(),
+  // signally proxy lookup completion.
+  void WaitForLookupCompletion() {
+    EXPECT_TRUE(lookup_helper_);
+    lookup_complete_run_loop_.Run();
+  }
+
+  // Get the proxy information passed into OnLookupCompleteOnIOThread().
+  const base::Optional<net::ProxyInfo>& proxy_info() const {
+    return proxy_info_;
+  }
+
+  // Setting this to true will make LookUpProxyForURLOnUIThread, the callback
+  // invoked to start looking up the proxy, return false.
+  void set_fail_to_start_request(bool fail_to_start_request) {
+    fail_to_start_request_ = fail_to_start_request;
+  }
+
+ private:
+  // Must be called on the IO thread. Initializes |lookup_helper_| and starts a
+  // proxy lookup. Invokes |closure| on the UI thread once the |lookup_helper_|
+  // has invoked LookUpProxyForURLOnUIThread on the UI thread.
+  void StartLookupOnIOThread(base::OnceClosure closure) {
+    DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    DCHECK(!lookup_helper_);
+
+    lookup_helper_ = std::make_unique<PepperProxyLookupHelper>();
+    lookup_helper_->Start(
+        GURL(kTestURL),
+        base::BindOnce(
+            &PepperProxyLookupHelperTest::LookUpProxyForURLOnUIThread,
+            base::Unretained(this), std::move(closure)),
+        base::BindOnce(&PepperProxyLookupHelperTest::OnLookupCompleteOnIOThread,
+                       base::Unretained(this)));
+  }
+
+  // Callback passed to |lookup_helper_| to start the proxy lookup.
+  bool LookUpProxyForURLOnUIThread(
+      base::OnceClosure closure,
+      const GURL& url,
+      network::mojom::ProxyLookupClientPtr proxy_lookup_client) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+    std::move(closure).Run();
+
+    if (fail_to_start_request_)
+      return false;
+
+    EXPECT_EQ(GURL(kTestURL), url);
+    proxy_lookup_client_ = std::move(proxy_lookup_client);
+    return true;
+  }
+
+  // Invoked by |lookup_helper_| on the IO thread once the proxy lookup has
+  // completed.
+  void OnLookupCompleteOnIOThread(base::Optional<net::ProxyInfo> proxy_info) {
+    DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+    proxy_info_ = std::move(proxy_info);
+    lookup_helper_.reset();
+    lookup_complete_run_loop_.Quit();
+  }
+
+  void DestroyLookupHelperOnIOThread() {
+    DCHECK_CURRENTLY_ON(BrowserThread::IO);
+    lookup_helper_.reset();
+  }
+
+  TestBrowserThreadBundle test_browser_thread_bundle_;
+
+  bool fail_to_start_request_ = false;
+
+  std::unique_ptr<PepperProxyLookupHelper> lookup_helper_;
+
+  base::Optional<net::ProxyInfo> proxy_info_;
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client_;
+
+  base::RunLoop lookup_complete_run_loop_;
+};
+
+TEST_F(PepperProxyLookupHelperTest, Success) {
+  StartLookup();
+  net::ProxyInfo proxy_info_response;
+  proxy_info_response.UseNamedProxy("result:80");
+  ClaimProxyLookupClient()->OnProxyLookupComplete(proxy_info_response);
+  WaitForLookupCompletion();
+  ASSERT_TRUE(proxy_info());
+  EXPECT_EQ("PROXY result:80", proxy_info()->ToPacString());
+}
+
+// Basic failure case - an error is passed to the PepperProxyLookupHelper
+// through the ProxyLookupClient API.
+TEST_F(PepperProxyLookupHelperTest, Failure) {
+  StartLookup();
+  ClaimProxyLookupClient()->OnProxyLookupComplete(base::nullopt);
+  WaitForLookupCompletion();
+  EXPECT_FALSE(proxy_info());
+}
+
+// The mojo pipe is closed before the PepperProxyLookupHelper's callback is
+// invoked.
+TEST_F(PepperProxyLookupHelperTest, PipeClosed) {
+  StartLookup();
+  ClaimProxyLookupClient().reset();
+  WaitForLookupCompletion();
+  EXPECT_FALSE(proxy_info());
+}
+
+// The proxy lookup fails to start - instead, the callback to start the lookup
+// returns false.
+TEST_F(PepperProxyLookupHelperTest, FailToStartRequest) {
+  set_fail_to_start_request(true);
+
+  StartLookup();
+  WaitForLookupCompletion();
+  EXPECT_FALSE(proxy_info());
+}
+
+// Destroy the helper before it completes a lookup. Make sure it cancels the
+// connection, and memory tools don't detect a leak.
+TEST_F(PepperProxyLookupHelperTest, DestroyBeforeComplete) {
+  StartLookup();
+  base::RunLoop run_loop;
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      ClaimProxyLookupClient();
+  proxy_lookup_client.set_connection_error_handler(run_loop.QuitClosure());
+  DestroyLookupHelper();
+  run_loop.Run();
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc
index a410952..a3ca4f4 100644
--- a/content/browser/renderer_host/render_message_filter.cc
+++ b/content/browser/renderer_host/render_message_filter.cc
@@ -265,12 +265,12 @@
   // If the url isn't valid then render process may not be locked to an origin
   // yet so we don't allow fetches from code cache.
   if (!requesting_url.is_valid() || !url.is_valid()) {
-    std::move(callback).Run(std::vector<uint8_t>());
+    std::move(callback).Run(base::Time(), std::vector<uint8_t>());
     return;
   }
 
   url::Origin requesting_origin = url::Origin::Create(requesting_url);
-  base::RepeatingCallback<void(scoped_refptr<net::IOBufferWithSize>)>
+  base::RepeatingCallback<void(const base::Time&, const std::vector<uint8_t>&)>
       read_callback = base::BindRepeating(
           &RenderMessageFilter::OnReceiveCachedCode,
           weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback));
@@ -280,15 +280,11 @@
 
 void RenderMessageFilter::OnReceiveCachedCode(
     FetchCachedCodeCallback callback,
-    scoped_refptr<net::IOBufferWithSize> buffer) {
-  if (!buffer) {
-    std::move(callback).Run(std::vector<uint8_t>());
-    return;
-  }
+    const base::Time& response_time,
+    const std::vector<uint8_t>& data) {
   // TODO(crbug.com/867848): Pass the data as a mojo data pipe instead
   // of vector<uint8>
-  std::vector<uint8_t> data(buffer->data(), buffer->data() + buffer->size());
-  std::move(callback).Run(data);
+  std::move(callback).Run(response_time, data);
 }
 
 void RenderMessageFilter::DidGenerateCacheableMetadataInCacheStorage(
diff --git a/content/browser/renderer_host/render_message_filter.h b/content/browser/renderer_host/render_message_filter.h
index 4bd7f96..bcbd85b 100644
--- a/content/browser/renderer_host/render_message_filter.h
+++ b/content/browser/renderer_host/render_message_filter.h
@@ -126,7 +126,8 @@
 #endif
 
   void OnReceiveCachedCode(FetchCachedCodeCallback callback,
-                           scoped_refptr<net::IOBufferWithSize> buffer);
+                           const base::Time& response_time,
+                           const std::vector<uint8_t>& data);
   void OnCacheStorageOpenCallback(const GURL& url,
                                   base::Time expected_response_time,
                                   scoped_refptr<net::IOBuffer> buf,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 1f1fef9..e2060bc 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -86,7 +86,7 @@
 #include "content/browser/dom_storage/dom_storage_context_wrapper.h"
 #include "content/browser/dom_storage/dom_storage_message_filter.h"
 #include "content/browser/field_trial_recorder.h"
-#include "content/browser/fileapi/fileapi_message_filter.h"
+#include "content/browser/fileapi/file_system_manager_impl.h"
 #include "content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h"
 #include "content/browser/frame_host/render_frame_message_filter.h"
 #include "content/browser/gpu/compositor_util.h"
@@ -1845,10 +1845,6 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
   AddFilter(new PepperRendererConnection(GetID()));
 #endif
-  AddFilter(new FileAPIMessageFilter(
-      GetID(), storage_partition_impl_->GetURLRequestContext(),
-      storage_partition_impl_->GetFileSystemContext(),
-      blob_storage_context.get()));
   AddFilter(new BlobDispatcherHost(GetID(), blob_storage_context));
 #if defined(OS_MACOSX)
   AddFilter(new TextInputClientMessageFilter());
@@ -1859,7 +1855,7 @@
   AddFilter(p2p_socket_dispatcher_host_.get());
 
   AddFilter(new TraceMessageFilter(GetID()));
-  AddFilter(new ResolveProxyMsgHelper(request_context.get()));
+  AddFilter(new ResolveProxyMsgHelper(GetID()));
 
   scoped_refptr<ServiceWorkerContextWrapper> service_worker_context(
       static_cast<ServiceWorkerContextWrapper*>(
@@ -1990,6 +1986,14 @@
       base::Bind(&PushMessagingManager::BindRequest,
                  base::Unretained(push_messaging_manager_.get())));
 
+  file_system_manager_impl_.reset(new FileSystemManagerImpl(
+      GetID(), storage_partition_impl_->GetURLRequestContext(),
+      storage_partition_impl_->GetFileSystemContext(),
+      ChromeBlobStorageContext::GetFor(GetBrowserContext())));
+  registry->AddInterface(
+      base::BindRepeating(&FileSystemManagerImpl::BindRequest,
+                          base::Unretained(file_system_manager_impl_.get())));
+
   if (gpu_client_) {
     // |gpu_client_| outlives the registry, because its destruction is posted to
     // IO thread from the destructor of |this|.
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index 562e020..1b153ba 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -73,6 +73,7 @@
 namespace content {
 class BrowserPluginMessageFilter;
 class ChildConnection;
+class FileSystemManagerImpl;
 class GpuClientImpl;
 class IndexedDBDispatcherHost;
 class InProcessChildThreadParams;
@@ -414,6 +415,10 @@
   // before the process shuts down.
   void DelayProcessShutdownForUnload(const base::TimeDelta& timeout);
 
+  FileSystemManagerImpl* GetFileSystemManagerForTesting() {
+    return file_system_manager_impl_.get();
+  }
+
  protected:
   // A proxy for our IPC::Channel that lives on the IO thread.
   std::unique_ptr<IPC::ChannelProxy> channel_;
@@ -814,6 +819,8 @@
 #endif
 
   scoped_refptr<ResourceMessageFilter> resource_message_filter_;
+  std::unique_ptr<FileSystemManagerImpl, BrowserThread::DeleteOnIOThread>
+      file_system_manager_impl_;
   std::unique_ptr<GpuClientImpl, BrowserThread::DeleteOnIOThread> gpu_client_;
   std::unique_ptr<PushMessagingManager, BrowserThread::DeleteOnIOThread>
       push_messaging_manager_;
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index e00ac640..cede6e7 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -419,7 +419,7 @@
   }
 
   enable_surface_synchronization_ = features::IsSurfaceSynchronizationEnabled();
-  enable_viz_ = features::IsVizDisplayCompositorEnabled();
+  enable_viz_ = base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
 
   if (!enable_viz_) {
 #if !defined(OS_ANDROID)
@@ -2465,7 +2465,8 @@
 
   if (delegate_) {
     if (event.GetType() == WebInputEvent::kMouseDown ||
-        event.GetType() == WebInputEvent::kTouchStart) {
+        event.GetType() == WebInputEvent::kTouchStart ||
+        event.GetType() == WebInputEvent::kGestureTap) {
       delegate_->FocusOwningWebContents(this);
     }
     delegate_->DidReceiveInputEvent(this, event.GetType());
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 7f06211..e5de66d 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -569,6 +569,15 @@
 
   double GetPendingPageZoomLevel() const override { return zoom_level_; }
 
+  void FocusOwningWebContents(
+      RenderWidgetHostImpl* render_widget_host) override {
+    focus_owning_web_contents_call_count++;
+  }
+
+  int GetFocusOwningWebContentsCallCount() const {
+    return focus_owning_web_contents_call_count;
+  }
+
   RenderViewHostDelegateView* GetDelegateView() override {
     return mock_delegate_view();
   }
@@ -628,6 +637,8 @@
       render_view_host_delegate_view_;
 
   double zoom_level_ = 0;
+
+  int focus_owning_web_contents_call_count = 0;
 };
 
 // RenderWidgetHostTest --------------------------------------------------------
@@ -1403,6 +1414,27 @@
   EXPECT_EQ(0, view_->unhandled_wheel_event_count());
 }
 
+TEST_F(RenderWidgetHostTest, EventsCausingFocus) {
+  SimulateMouseEvent(WebInputEvent::kMouseDown);
+  EXPECT_EQ(1, delegate_->GetFocusOwningWebContentsCallCount());
+
+  PressTouchPoint(0, 1);
+  SendTouchEvent();
+  EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount());
+
+  ReleaseTouchPoint(0);
+  SendTouchEvent();
+  EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount());
+
+  SimulateGestureEvent(WebInputEvent::kGestureTapDown,
+                       blink::kWebGestureDeviceTouchscreen);
+  EXPECT_EQ(2, delegate_->GetFocusOwningWebContentsCallCount());
+
+  SimulateGestureEvent(WebInputEvent::kGestureTap,
+                       blink::kWebGestureDeviceTouchscreen);
+  EXPECT_EQ(3, delegate_->GetFocusOwningWebContentsCallCount());
+}
+
 TEST_F(RenderWidgetHostTest, UnhandledGestureEvent) {
   SimulateGestureEvent(WebInputEvent::kGestureTwoFingerTap,
                        blink::kWebGestureDeviceTouchscreen);
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 084e9e7..0eec5c7 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -1941,7 +1941,8 @@
 
   delegated_frame_host_client_ =
       std::make_unique<DelegatedFrameHostClientAura>(this);
-  const bool enable_viz = features::IsVizDisplayCompositorEnabled();
+  const bool enable_viz =
+      base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
   delegated_frame_host_ = std::make_unique<DelegatedFrameHost>(
       frame_sink_id_, delegated_frame_host_client_.get(), enable_viz,
       false /* should_register_frame_sink_id */);
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index 2b13f71..9a9492b 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -504,7 +504,8 @@
       return;
 
     view->delegated_frame_host_client_ = std::move(delegated_frame_host_client);
-    const bool enable_viz = features::IsVizDisplayCompositorEnabled();
+    const bool enable_viz =
+        base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
     view->delegated_frame_host_ = nullptr;
     view->delegated_frame_host_ = std::make_unique<DelegatedFrameHost>(
         view->frame_sink_id_, view->delegated_frame_host_client_.get(),
@@ -3078,7 +3079,7 @@
 TEST_F(RenderWidgetHostViewAuraTest, TwoOutputSurfaces) {
   // TODO(jonross): Delete this test once Viz launches as it will be obsolete.
   // https://crbug.com/844469
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       !features::IsAshInBrowserProcess()) {
     return;
   }
@@ -3385,7 +3386,7 @@
 TEST_F(RenderWidgetHostViewAuraTest, OutputSurfaceIdChange) {
   // TODO(jonross): Delete this test once Viz launches as it will be obsolete.
   // https://crbug.com/844469
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       !features::IsAshInBrowserProcess()) {
     return;
   }
@@ -3543,7 +3544,7 @@
        CompositorFrameSinkChange) {
   // TODO(jonross): Delete this test once Viz launches as it will be obsolete.
   // https://crbug.com/844469
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       !features::IsAshInBrowserProcess()) {
     return;
   }
@@ -3899,7 +3900,7 @@
 TEST_F(RenderWidgetHostViewAuraTest, ForwardsBeginFrameAcks) {
   // TODO(jonross): Delete this test once Viz launches as it will be obsolete.
   // https://crbug.com/844469
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       !features::IsAshInBrowserProcess()) {
     return;
   }
@@ -5840,7 +5841,7 @@
 TEST_F(RenderWidgetHostViewAuraTest, HitTestRegionListSubmitted) {
   // TODO(jonross): Delete this test once Viz launches as it will be obsolete.
   // https://crbug.com/844469
-  if (features::IsVizDisplayCompositorEnabled() ||
+  if (base::FeatureList::IsEnabled(features::kVizDisplayCompositor) ||
       !features::IsAshInBrowserProcess()) {
     return;
   }
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 0b2eed0..bb9aa275 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -564,6 +564,11 @@
 void RenderWidgetHostViewBase::ProcessMouseEvent(
     const blink::WebMouseEvent& event,
     const ui::LatencyInfo& latency) {
+  // TODO(crbug.com/814674): Figure out the reason |host| is null here in all
+  // Process* functions.
+  if (!host())
+    return;
+
   PreProcessMouseEvent(event);
   host()->ForwardMouseEventWithLatencyInfo(event, latency);
 }
@@ -571,12 +576,17 @@
 void RenderWidgetHostViewBase::ProcessMouseWheelEvent(
     const blink::WebMouseWheelEvent& event,
     const ui::LatencyInfo& latency) {
+  if (!host())
+    return;
   host()->ForwardWheelEventWithLatencyInfo(event, latency);
 }
 
 void RenderWidgetHostViewBase::ProcessTouchEvent(
     const blink::WebTouchEvent& event,
     const ui::LatencyInfo& latency) {
+  if (!host())
+    return;
+
   PreProcessTouchEvent(event);
   host()->ForwardTouchEventWithLatencyInfo(event, latency);
 }
@@ -584,6 +594,8 @@
 void RenderWidgetHostViewBase::ProcessGestureEvent(
     const blink::WebGestureEvent& event,
     const ui::LatencyInfo& latency) {
+  if (!host())
+    return;
   host()->ForwardGestureEventWithLatencyInfo(event, latency);
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.cc b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
index cea6a0b..46bfcdb2 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
@@ -71,7 +71,8 @@
           base::checked_cast<uint32_t>(widget_host->GetProcess()->GetID()),
           base::checked_cast<uint32_t>(widget_host->GetRoutingID())),
       frame_connector_(nullptr),
-      enable_viz_(features::IsVizDisplayCompositorEnabled()),
+      enable_viz_(
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor)),
       weak_factory_(this) {
   if (!features::IsAshInBrowserProcess()) {
     // In Mus the RenderFrameProxy will eventually assign a viz::FrameSinkId
diff --git a/content/browser/resolve_proxy_msg_helper.cc b/content/browser/resolve_proxy_msg_helper.cc
index 7b77c5f..1912f4c 100644
--- a/content/browser/resolve_proxy_msg_helper.cc
+++ b/content/browser/resolve_proxy_msg_helper.cc
@@ -5,25 +5,33 @@
 #include "content/browser/resolve_proxy_msg_helper.h"
 
 #include "base/bind.h"
-#include "base/bind_helpers.h"
 #include "base/compiler_specific.h"
 #include "content/common/view_messages.h"
-#include "net/base/net_errors.h"
-#include "net/log/net_log_with_source.h"
-#include "net/url_request/url_request_context.h"
-#include "net/url_request/url_request_context_getter.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/storage_partition.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "services/network/public/mojom/network_context.mojom.h"
 
 namespace content {
 
-ResolveProxyMsgHelper::ResolveProxyMsgHelper(
-    net::URLRequestContextGetter* getter)
+ResolveProxyMsgHelper::ResolveProxyMsgHelper(int render_process_host_id)
     : BrowserMessageFilter(ViewMsgStart),
-      context_getter_(getter),
-      proxy_resolution_service_(nullptr) {}
+      render_process_host_id_(render_process_host_id),
+      binding_(this) {}
 
-ResolveProxyMsgHelper::ResolveProxyMsgHelper(
-    net::ProxyResolutionService* proxy_resolution_service)
-    : BrowserMessageFilter(ViewMsgStart), proxy_resolution_service_(proxy_resolution_service) {}
+void ResolveProxyMsgHelper::OnDestruct() const {
+  // Have to delete on the UI thread, since Mojo objects aren't threadsafe.
+  BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, this);
+}
+
+void ResolveProxyMsgHelper::OverrideThreadForMessage(
+    const IPC::Message& message,
+    BrowserThread::ID* thread) {
+  if (message.type() == ViewHostMsg_ResolveProxy::ID)
+    *thread = BrowserThread::UI;
+}
 
 bool ResolveProxyMsgHelper::OnMessageReceived(const IPC::Message& message) {
   bool handled = true;
@@ -36,26 +44,67 @@
 
 void ResolveProxyMsgHelper::OnResolveProxy(const GURL& url,
                                            IPC::Message* reply_msg) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   // Enqueue the pending request.
   pending_requests_.push_back(PendingRequest(url, reply_msg));
 
   // If nothing is in progress, start.
-  if (pending_requests_.size() == 1)
+  if (!binding_.is_bound()) {
+    DCHECK_EQ(1u, pending_requests_.size());
     StartPendingRequest();
+  }
 }
 
 ResolveProxyMsgHelper::~ResolveProxyMsgHelper() = default;
 
-void ResolveProxyMsgHelper::OnResolveProxyCompleted(int result) {
-  CHECK(!pending_requests_.empty());
+void ResolveProxyMsgHelper::StartPendingRequest() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!binding_.is_bound());
+  DCHECK(!pending_requests_.empty());
+
+  // Start the request.
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client;
+  binding_.Bind(mojo::MakeRequest(&proxy_lookup_client));
+  binding_.set_connection_error_handler(
+      base::BindOnce(&ResolveProxyMsgHelper::OnProxyLookupComplete,
+                     base::Unretained(this), base::nullopt));
+  if (!SendRequestToNetworkService(pending_requests_.front().url,
+                                   std::move(proxy_lookup_client))) {
+    OnProxyLookupComplete(base::nullopt);
+  }
+}
+
+bool ResolveProxyMsgHelper::SendRequestToNetworkService(
+    const GURL& url,
+    network::mojom::ProxyLookupClientPtr proxy_lookup_client) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  RenderProcessHost* render_process_host =
+      RenderProcessHost::FromID(render_process_host_id_);
+  // Fail the request if there's no such RenderProcessHost;
+  if (!render_process_host)
+    return false;
+  render_process_host->GetStoragePartition()
+      ->GetNetworkContext()
+      ->LookUpProxyForURL(url, std::move(proxy_lookup_client));
+  return true;
+}
+
+void ResolveProxyMsgHelper::OnProxyLookupComplete(
+    const base::Optional<net::ProxyInfo>& proxy_info) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!pending_requests_.empty());
+
+  binding_.Close();
 
   // Clear the current (completed) request.
   PendingRequest completed_req = std::move(pending_requests_.front());
   pending_requests_.pop_front();
 
-  ViewHostMsg_ResolveProxy::WriteReplyParams(completed_req.reply_msg.get(),
-                                             result == net::OK,
-                                             proxy_info_.ToPacString());
+  ViewHostMsg_ResolveProxy::WriteReplyParams(
+      completed_req.reply_msg.get(), !!proxy_info,
+      proxy_info ? proxy_info->ToPacString() : std::string());
   Send(completed_req.reply_msg.release());
 
   // Start the next request.
@@ -63,27 +112,6 @@
     StartPendingRequest();
 }
 
-void ResolveProxyMsgHelper::StartPendingRequest() {
-  // Verify the request wasn't started yet.
-  DCHECK(nullptr == pending_requests_.front().request);
-
-  if (context_getter_.get()) {
-    proxy_resolution_service_ = context_getter_->GetURLRequestContext()->proxy_resolution_service();
-    context_getter_ = nullptr;
-  }
-
-  // Start the request.
-  int result = proxy_resolution_service_->ResolveProxy(
-      pending_requests_.front().url, std::string(), &proxy_info_,
-      base::BindOnce(&ResolveProxyMsgHelper::OnResolveProxyCompleted,
-                     base::Unretained(this)),
-      &pending_requests_.front().request, nullptr, net::NetLogWithSource());
-
-  // Completed synchronously.
-  if (result != net::ERR_IO_PENDING)
-    OnResolveProxyCompleted(result);
-}
-
 ResolveProxyMsgHelper::PendingRequest::PendingRequest(const GURL& url,
                                                       IPC::Message* reply_msg)
     : url(url), reply_msg(reply_msg) {}
diff --git a/content/browser/resolve_proxy_msg_helper.h b/content/browser/resolve_proxy_msg_helper.h
index ae5c0ff7..13b2f22 100644
--- a/content/browser/resolve_proxy_msg_helper.h
+++ b/content/browser/resolve_proxy_msg_helper.h
@@ -8,21 +8,24 @@
 #include <string>
 
 #include "base/containers/circular_deque.h"
+#include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/sequenced_task_runner_helpers.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_message_filter.h"
-#include "net/base/completion_callback.h"
-#include "net/proxy_resolution/proxy_resolution_service.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
 #include "url/gurl.h"
 
 namespace net {
-class URLRequestContextGetter;
+class ProxyInfo;
 }
 
 namespace content {
 
-// Responds to ChildProcessHostMsg_ResolveProxy, kicking off a ProxyResolve
-// request on the IO thread using the specified proxy service.  Completion is
+// Responds to ChildProcessHostMsg_ResolveProxy, kicking off a proxy lookup
+// request on the UI thread using the specified proxy service.  Completion is
 // notified through the delegate.  If multiple requests are started at the same
 // time, they will run in FIFO order, with only 1 being outstanding at a time.
 //
@@ -30,15 +33,16 @@
 // outstanding proxy resolve requests with the proxy service. It also deletes
 // the stored IPC::Message pointers for pending requests.
 //
-// This object is expected to live on the IO thread.
-class CONTENT_EXPORT ResolveProxyMsgHelper : public BrowserMessageFilter {
+// This object does most of its work, and destroys itself, on the UI thread.
+class CONTENT_EXPORT ResolveProxyMsgHelper : public BrowserMessageFilter,
+                                             network::mojom::ProxyLookupClient {
  public:
-  explicit ResolveProxyMsgHelper(net::URLRequestContextGetter* getter);
-  // Constructor used by unittests.
-  explicit ResolveProxyMsgHelper(
-      net::ProxyResolutionService* proxy_resolution_service);
+  explicit ResolveProxyMsgHelper(int render_process_host_id);
 
   // BrowserMessageFilter implementation
+  void OnDestruct() const override;
+  void OverrideThreadForMessage(const IPC::Message& message,
+                                BrowserThread::ID* thread) override;
   bool OnMessageReceived(const IPC::Message& message) override;
 
   void OnResolveProxy(const GURL& url, IPC::Message* reply_msg);
@@ -49,12 +53,22 @@
   ~ResolveProxyMsgHelper() override;
 
  private:
-  // Callback for the ProxyResolutionService (bound to |callback_|).
-  void OnResolveProxyCompleted(int result);
+  // Used to destroy the |ResolveProxyMsgHelper| on the UI thread.
+  friend class base::DeleteHelper<ResolveProxyMsgHelper>;
 
   // Starts the first pending request.
   void StartPendingRequest();
 
+  // Virtual for testing. Returns false if unable to get a network service, due
+  // to the RenderProcessHost no longer existing.
+  virtual bool SendRequestToNetworkService(
+      const GURL& url,
+      network::mojom::ProxyLookupClientPtr proxy_lookup_client);
+
+  // network::mojom::ProxyLookupClient implementation.
+  void OnProxyLookupComplete(
+      const base::Optional<net::ProxyInfo>& proxy_info) override;
+
   // A PendingRequest is a resolve request that is in progress, or queued.
   struct PendingRequest {
    public:
@@ -70,22 +84,20 @@
     // Data to pass back to the delegate on completion (we own it until then).
     std::unique_ptr<IPC::Message> reply_msg;
 
-    // Handle for cancelling the current request if it has started (else NULL).
-    std::unique_ptr<net::ProxyResolutionService::Request> request;
-
    private:
     DISALLOW_COPY_AND_ASSIGN(PendingRequest);
   };
 
-  // Info about the current outstanding proxy request.
-  net::ProxyInfo proxy_info_;
+  const int render_process_host_id_;
 
   // FIFO queue of pending requests. The first entry is always the current one.
   using PendingRequestList = base::circular_deque<PendingRequest>;
   PendingRequestList pending_requests_;
 
-  scoped_refptr<net::URLRequestContextGetter> context_getter_;
-  net::ProxyResolutionService* proxy_resolution_service_;
+  // Binding for the currently in-progress request, if any.
+  mojo::Binding<network::mojom::ProxyLookupClient> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(ResolveProxyMsgHelper);
 };
 
 }  // namespace content
diff --git a/content/browser/resolve_proxy_msg_helper_unittest.cc b/content/browser/resolve_proxy_msg_helper_unittest.cc
index d9e5b3e..32e8b09c2 100644
--- a/content/browser/resolve_proxy_msg_helper_unittest.cc
+++ b/content/browser/resolve_proxy_msg_helper_unittest.cc
@@ -7,47 +7,66 @@
 #include <tuple>
 
 #include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
 #include "content/common/view_messages.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "ipc/ipc_test_sink.h"
 #include "net/base/net_errors.h"
-#include "net/proxy_resolution/mock_proxy_resolver.h"
-#include "net/proxy_resolution/proxy_config_service.h"
-#include "net/proxy_resolution/proxy_resolution_service.h"
+#include "net/proxy_resolution/proxy_info.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
 
-// This ProxyConfigService always returns "http://pac" as the PAC url to use.
-class MockProxyConfigService : public net::ProxyConfigService {
- public:
-  void AddObserver(Observer* observer) override {}
-  void RemoveObserver(Observer* observer) override {}
-  ConfigAvailability GetLatestProxyConfig(
-      net::ProxyConfigWithAnnotation* results) override {
-    *results = net::ProxyConfigWithAnnotation(
-        net::ProxyConfig::CreateFromCustomPacURL(GURL("http://pac")),
-        TRAFFIC_ANNOTATION_FOR_TESTS);
-    return CONFIG_VALID;
-  }
-};
-
 class TestResolveProxyMsgHelper : public ResolveProxyMsgHelper {
  public:
-  TestResolveProxyMsgHelper(net::ProxyResolutionService* proxy_resolution_service,
-                            IPC::Listener* listener)
-      : ResolveProxyMsgHelper(proxy_resolution_service), listener_(listener) {}
+  explicit TestResolveProxyMsgHelper(IPC::Listener* listener)
+      : ResolveProxyMsgHelper(0 /* renderer_process_host_id */),
+        listener_(listener) {}
+
   bool Send(IPC::Message* message) override {
     listener_->OnMessageReceived(*message);
     delete message;
     return true;
   }
 
+  bool SendRequestToNetworkService(
+      const GURL& url,
+      network::mojom::ProxyLookupClientPtr proxy_lookup_client) override {
+    // Only one request should be send at a time.
+    EXPECT_FALSE(proxy_lookup_client_);
+
+    if (fail_to_send_request_)
+      return false;
+
+    pending_url_ = url;
+    proxy_lookup_client_ = std::move(proxy_lookup_client);
+    return true;
+  }
+
+  network::mojom::ProxyLookupClientPtr ClaimProxyLookupClient() {
+    EXPECT_TRUE(proxy_lookup_client_);
+    return std::move(proxy_lookup_client_);
+  }
+
+  const GURL& pending_url() const { return pending_url_; }
+
+  void set_fail_to_send_request(bool fail_to_send_request) {
+    fail_to_send_request_ = fail_to_send_request;
+  }
+
  protected:
   ~TestResolveProxyMsgHelper() override {}
 
   IPC::Listener* listener_;
+
+  bool fail_to_send_request_ = false;
+
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client_;
+  GURL pending_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestResolveProxyMsgHelper);
 };
 
 class ResolveProxyMsgHelperTest : public testing::Test, public IPC::Listener {
@@ -63,12 +82,7 @@
   };
 
   ResolveProxyMsgHelperTest()
-      : resolver_factory_(new net::MockAsyncProxyResolverFactory(false)),
-        service_(new net::ProxyResolutionService(
-            base::WrapUnique(new MockProxyConfigService),
-            base::WrapUnique(resolver_factory_),
-            nullptr)),
-        helper_(new TestResolveProxyMsgHelper(service_.get(), this)) {
+      : helper_(base::MakeRefCounted<TestResolveProxyMsgHelper>(this)) {
     test_sink_.AddFilter(this);
   }
 
@@ -86,13 +100,6 @@
     return IPC::SyncMessage::GenerateReply(&message);
   }
 
-  net::MockAsyncProxyResolverFactory* resolver_factory_;
-  net::MockAsyncProxyResolver resolver_;
-  std::unique_ptr<net::ProxyResolutionService> service_;
-  scoped_refptr<ResolveProxyMsgHelper> helper_;
-  std::unique_ptr<PendingResult> pending_result_;
-
- private:
   bool OnMessageReceived(const IPC::Message& msg) override {
     ViewHostMsg_ResolveProxy::ReplyParam reply_data;
     EXPECT_TRUE(ViewHostMsg_ResolveProxy::ReadReplyParam(&msg, &reply_data));
@@ -104,6 +111,10 @@
   }
 
   TestBrowserThreadBundle thread_bundle_;
+
+  scoped_refptr<TestResolveProxyMsgHelper> helper_;
+  std::unique_ptr<PendingResult> pending_result_;
+
   IPC::TestSink test_sink_;
 };
 
@@ -123,15 +134,15 @@
 
   helper_->OnResolveProxy(url1, msg1);
 
-  // Finish ProxyResolutionService's initialization.
-  ASSERT_EQ(1u, resolver_factory_->pending_requests().size());
-  resolver_factory_->pending_requests()[0]->CompleteNowWithForwarder(
-      net::OK, &resolver_);
-
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url1, resolver_.pending_jobs()[0]->url());
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result1:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  // There should be a pending proxy lookup request. Respond to it.
+  EXPECT_EQ(url1, helper_->pending_url());
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  net::ProxyInfo proxy_info;
+  proxy_info.UseNamedProxy("result1:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
@@ -140,10 +151,12 @@
 
   helper_->OnResolveProxy(url2, msg2);
 
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url2, resolver_.pending_jobs()[0]->url());
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result2:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  EXPECT_EQ(url2, helper_->pending_url());
+  proxy_lookup_client = helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_info.UseNamedProxy("result2:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
@@ -152,10 +165,12 @@
 
   helper_->OnResolveProxy(url3, msg3);
 
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url3, resolver_.pending_jobs()[0]->url());
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result3:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  EXPECT_EQ(url3, helper_->pending_url());
+  proxy_lookup_client = helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_info.UseNamedProxy("result3:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
@@ -173,48 +188,47 @@
   IPC::Message* msg2 = GenerateReply();
   IPC::Message* msg3 = GenerateReply();
 
-  // Start three requests. Since the proxy resolver is async, all the
-  // requests will be pending.
+  // Start three requests. All the requests will be pending.
 
   helper_->OnResolveProxy(url1, msg1);
-
-  // Finish ProxyResolutionService's initialization.
-  ASSERT_EQ(1u, resolver_factory_->pending_requests().size());
-  resolver_factory_->pending_requests()[0]->CompleteNowWithForwarder(
-      net::OK, &resolver_);
-
   helper_->OnResolveProxy(url2, msg2);
   helper_->OnResolveProxy(url3, msg3);
 
-  // ResolveProxyHelper only keeps 1 request outstanding in
-  // ProxyResolutionService at a time.
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url1, resolver_.pending_jobs()[0]->url());
-
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result1:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  // Complete first request.
+  EXPECT_EQ(url1, helper_->pending_url());
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  net::ProxyInfo proxy_info;
+  proxy_info.UseNamedProxy("result1:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
   EXPECT_EQ("PROXY result1:80", pending_result()->proxy_list);
   clear_pending_result();
 
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url2, resolver_.pending_jobs()[0]->url());
-
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result2:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  // Complete second request.
+  EXPECT_EQ(url2, helper_->pending_url());
+  proxy_lookup_client = helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_info.UseNamedProxy("result2:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
   EXPECT_EQ("PROXY result2:80", pending_result()->proxy_list);
   clear_pending_result();
 
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url3, resolver_.pending_jobs()[0]->url());
-
-  resolver_.pending_jobs()[0]->results()->UseNamedProxy("result3:80");
-  resolver_.pending_jobs()[0]->CompleteNow(net::OK);
+  // Complete third request.
+  EXPECT_EQ(url3, helper_->pending_url());
+  proxy_lookup_client = helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_info.UseNamedProxy("result3:80");
+  proxy_lookup_client->OnProxyLookupComplete(proxy_info);
+  base::RunLoop().RunUntilIdle();
 
   // Check result.
   EXPECT_EQ(true, pending_result()->result);
@@ -237,32 +251,93 @@
   // requests will be pending.
 
   helper_->OnResolveProxy(url1, msg1);
-
-  // Finish ProxyResolutionService's initialization.
-  ASSERT_EQ(1u, resolver_factory_->pending_requests().size());
-  resolver_factory_->pending_requests()[0]->CompleteNowWithForwarder(
-      net::OK, &resolver_);
-
   helper_->OnResolveProxy(url2, msg2);
   helper_->OnResolveProxy(url3, msg3);
 
-  // ResolveProxyHelper only keeps 1 request outstanding in
-  // ProxyResolutionService at a time.
-  ASSERT_EQ(1u, resolver_.pending_jobs().size());
-  EXPECT_EQ(url1, resolver_.pending_jobs()[0]->url());
+  // Check the first request is pending.
+  EXPECT_EQ(url1, helper_->pending_url());
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      helper_->ClaimProxyLookupClient();
+  base::RunLoop run_loop;
+  proxy_lookup_client.set_connection_error_handler(run_loop.QuitClosure());
 
   // Delete the underlying ResolveProxyMsgHelper -- this should cancel all
   // the requests which are outstanding.
   helper_ = nullptr;
 
-  // The pending requests sent to the proxy resolver should have been cancelled.
-
-  EXPECT_EQ(0u, resolver_.pending_jobs().size());
+  // The pending request sent to the proxy resolver should have been cancelled.
+  run_loop.Run();
 
   EXPECT_TRUE(pending_result() == nullptr);
 
   // It should also be the case that msg1, msg2, msg3 were deleted by the
-  // cancellation. (Else will show up as a leak in Valgrind).
+  // cancellation. (Else will show up as a leak).
+}
+
+// Issue a request that fails.
+TEST_F(ResolveProxyMsgHelperTest, RequestFails) {
+  GURL url("http://www.google.com/");
+
+  // Message will be deleted by the sink.
+  IPC::Message* msg = GenerateReply();
+
+  helper_->OnResolveProxy(url, msg);
+
+  // There should be a pending proxy lookup request. Respond to it.
+  EXPECT_EQ(url, helper_->pending_url());
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_lookup_client->OnProxyLookupComplete(base::nullopt);
+  base::RunLoop().RunUntilIdle();
+
+  // Check result.
+  EXPECT_EQ(false, pending_result()->result);
+  EXPECT_EQ("", pending_result()->proxy_list);
+  clear_pending_result();
+}
+
+// Issue a request, only to have the Mojo pipe closed.
+TEST_F(ResolveProxyMsgHelperTest, PipeClosed) {
+  GURL url("http://www.google.com/");
+
+  // Message will be deleted by the sink.
+  IPC::Message* msg = GenerateReply();
+
+  helper_->OnResolveProxy(url, msg);
+
+  // There should be a pending proxy lookup request. Respond to it by closing
+  // the pipe.
+  EXPECT_EQ(url, helper_->pending_url());
+  network::mojom::ProxyLookupClientPtr proxy_lookup_client =
+      helper_->ClaimProxyLookupClient();
+  ASSERT_TRUE(proxy_lookup_client);
+  proxy_lookup_client.reset();
+  base::RunLoop().RunUntilIdle();
+
+  // Check result.
+  EXPECT_EQ(false, pending_result()->result);
+  EXPECT_EQ("", pending_result()->proxy_list);
+  clear_pending_result();
+}
+
+// Fail to send a request to the network service.
+TEST_F(ResolveProxyMsgHelperTest, FailToSendRequest) {
+  GURL url("http://www.google.com/");
+
+  // Message will be deleted by the sink.
+  IPC::Message* msg = GenerateReply();
+
+  helper_->set_fail_to_send_request(true);
+
+  helper_->OnResolveProxy(url, msg);
+  // No request should be pending.
+  EXPECT_TRUE(helper_->pending_url().is_empty());
+
+  // Check result.
+  EXPECT_EQ(false, pending_result()->result);
+  EXPECT_EQ("", pending_result()->proxy_list);
+  clear_pending_result();
 }
 
 }  // namespace content
diff --git a/content/browser/scheduler/responsiveness/calculator.cc b/content/browser/scheduler/responsiveness/calculator.cc
index e042113..9e009a97 100644
--- a/content/browser/scheduler/responsiveness/calculator.cc
+++ b/content/browser/scheduler/responsiveness/calculator.cc
@@ -9,6 +9,7 @@
 
 #include "content/public/browser/browser_thread.h"
 
+namespace content {
 namespace responsiveness {
 
 namespace {
@@ -189,28 +190,25 @@
 Calculator::JankList Calculator::TakeJanksOlderThanTime(
     JankList* janks,
     base::TimeTicks end_time) {
-  // Copy all janks with Jank.start_time < |end_time|.
-  auto it =
-      std::lower_bound(janks->begin(), janks->end(), end_time,
-                       [](const Jank& jank, const base::TimeTicks& end_time) {
-                         return jank.start_time < end_time;
-                       });
+  // Find all janks with Jank.start_time < |end_time|.
+  auto it = std::partition(
+      janks->begin(), janks->end(),
+      [&end_time](const Jank& jank) { return jank.start_time < end_time; });
 
-  // We don't need to remove any Janks either, since Jank.end_time >=
-  // Jank.start_time.
+  // Early exit. We don't need to remove any Janks either, since Jank.end_time
+  // >= Jank.start_time.
   if (it == janks->begin())
     return JankList();
 
   JankList janks_to_return(janks->begin(), it);
 
   // Remove all janks with Jank.end_time < |end_time|.
-  auto first_jank_to_keep =
-      std::lower_bound(janks->begin(), janks->end(), end_time,
-                       [](const Jank& jank, const base::TimeTicks& end_time) {
-                         return jank.end_time < end_time;
-                       });
+  auto first_jank_to_keep = std::partition(
+      janks->begin(), janks->end(),
+      [&end_time](const Jank& jank) { return jank.end_time < end_time; });
   janks->erase(janks->begin(), first_jank_to_keep);
   return janks_to_return;
 }
 
 }  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/calculator.h b/content/browser/scheduler/responsiveness/calculator.h
index 289b431..a5c7c40 100644
--- a/content/browser/scheduler/responsiveness/calculator.h
+++ b/content/browser/scheduler/responsiveness/calculator.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "content/common/content_export.h"
 
+namespace content {
 namespace responsiveness {
 
 // This class receives execution latency on events and tasks, and uses that to
@@ -26,6 +27,10 @@
 
   // Must be called from the UI thread.
   // virtual for testing.
+  // The implementation will gracefully handle calls where finish_time <
+  // schedule_time.
+  // The implementation will gracefully handle successive calls with
+  // |schedule_times| that are out of order.
   virtual void TaskOrEventFinishedOnUIThread(base::TimeTicks schedule_time,
                                              base::TimeTicks finish_time);
 
@@ -84,8 +89,9 @@
   // already been taken. May be called from any thread.
   JankList& GetJanksOnIOThread();
 
-  // |janks| must be sorted by Jank.end_time. This method modifies |janks| to
-  // remove all janks older than |end_time|, and returns those.
+  // This method:
+  //   1) Removes all Janks with Jank.end_time < |end_time| from |janks|.
+  //   2) Returns all Janks with Jank.start_time < |end_time|.
   JankList TakeJanksOlderThanTime(JankList* janks, base::TimeTicks end_time);
 
   // This should only be accessed via the accessor, which checks that the caller
@@ -119,5 +125,6 @@
 };
 
 }  // namespace responsiveness
+}  // namespace content
 
 #endif  // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_CALCULATOR_H_
diff --git a/content/browser/scheduler/responsiveness/calculator_unittest.cc b/content/browser/scheduler/responsiveness/calculator_unittest.cc
index 613a7b63..25f81ad2 100644
--- a/content/browser/scheduler/responsiveness/calculator_unittest.cc
+++ b/content/browser/scheduler/responsiveness/calculator_unittest.cc
@@ -7,6 +7,7 @@
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace content {
 namespace responsiveness {
 
 namespace {
@@ -211,4 +212,24 @@
   EXPECT_EQ(2, calculator_->Emissions()[1]);
 }
 
+// Events may not be ordered by start or end time.
+TEST_F(ResponsivenessCalculatorTest, UnorderedEvents) {
+  // We add the following events:
+  //   [100, 250]
+  //   [150, 300]
+  //   [50, 200]
+  //   [50, 390]
+  // The event [50, 400] subsumes all previous events.
+  AddEventUI(kJankThresholdInMs, 2.5 * kJankThresholdInMs);
+  AddEventUI(1.5 * kJankThresholdInMs, 3 * kJankThresholdInMs);
+  AddEventUI(0.5 * kJankThresholdInMs, 2 * kJankThresholdInMs);
+  AddEventUI(0.5 * kJankThresholdInMs, 3.9 * kJankThresholdInMs);
+
+  TriggerCalculation();
+
+  ASSERT_EQ(1u, calculator_->Emissions().size());
+  EXPECT_EQ(3, calculator_->Emissions()[0]);
+}
+
 }  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/message_loop_observer.cc b/content/browser/scheduler/responsiveness/message_loop_observer.cc
index 50b4c1b0..0128402 100644
--- a/content/browser/scheduler/responsiveness/message_loop_observer.cc
+++ b/content/browser/scheduler/responsiveness/message_loop_observer.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/scheduler/responsiveness/message_loop_observer.h"
 
+namespace content {
 namespace responsiveness {
 
 MessageLoopObserver::MessageLoopObserver(TaskCallback will_run_task_callback,
@@ -30,3 +31,4 @@
 }
 
 }  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/message_loop_observer.h b/content/browser/scheduler/responsiveness/message_loop_observer.h
index 1b823f7..e6eed71 100644
--- a/content/browser/scheduler/responsiveness/message_loop_observer.h
+++ b/content/browser/scheduler/responsiveness/message_loop_observer.h
@@ -13,6 +13,7 @@
 struct PendingTask;
 }  // namespace base
 
+namespace content {
 namespace responsiveness {
 
 // This object is not thread safe. It must be constructed and destroyed on the
@@ -41,5 +42,6 @@
 };
 
 }  // namespace responsiveness
+}  // namespace content
 
 #endif  // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_MESSAGE_LOOP_OBSERVER_H_
diff --git a/content/browser/scheduler/responsiveness/native_event_observer.cc b/content/browser/scheduler/responsiveness/native_event_observer.cc
new file mode 100644
index 0000000..5f18b7d
--- /dev/null
+++ b/content/browser/scheduler/responsiveness/native_event_observer.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 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 "content/browser/scheduler/responsiveness/native_event_observer.h"
+
+namespace content {
+namespace responsiveness {
+
+NativeEventObserver::NativeEventObserver(
+    WillRunEventCallback will_run_event_callback,
+    DidRunEventCallback did_run_event_callback)
+    : will_run_event_callback_(will_run_event_callback),
+      did_run_event_callback_(did_run_event_callback) {
+  RegisterObserver();
+}
+
+NativeEventObserver::~NativeEventObserver() {
+  DeregisterObserver();
+}
+
+#if !defined(OS_MACOSX)
+// TODO(erikchen): Implement this for non-macOS platforms.
+// https://crbug.com/859155.
+void NativeEventObserver::RegisterObserver() {}
+void NativeEventObserver::DeregisterObserver() {}
+#endif
+
+}  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/native_event_observer.h b/content/browser/scheduler/responsiveness/native_event_observer.h
new file mode 100644
index 0000000..dd4a403
--- /dev/null
+++ b/content/browser/scheduler/responsiveness/native_event_observer.h
@@ -0,0 +1,73 @@
+// Copyright 2018 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 CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_NATIVE_EVENT_OBSERVER_H_
+#define CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_NATIVE_EVENT_OBSERVER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "build/build_config.h"
+#include "content/common/content_export.h"
+
+#if defined(OS_MACOSX)
+#include "content/public/browser/native_event_processor_observer_mac.h"
+#endif
+
+namespace content {
+namespace responsiveness {
+
+// This class must only be used from the UI thread.
+//
+// This class hooks itself into the native event processor for the platform and
+// forwards will_run/did_run callbacks to Watcher. Native events are processed
+// at different layers for each platform, so the interface for this class is
+// necessarily messy.
+//
+// On macOS, the hook should be in -[BrowserCrApplication sendEvent:].
+// On Linux, the hook should be in ui::PlatformEventSource::DispatchEvent.
+// On Windows, the hook should be in MessagePumpForUI::ProcessMessageHelper.
+// On Android, the hook should be in <TBD>.
+class CONTENT_EXPORT NativeEventObserver
+#if defined(OS_MACOSX)
+    : public NativeEventProcessorObserver
+#endif
+{
+ public:
+  using WillRunEventCallback =
+      base::RepeatingCallback<void(const void* opaque_identifier)>;
+
+  // |creation_time| refers to the time at which the native event was created.
+  using DidRunEventCallback =
+      base::RepeatingCallback<void(const void* opaque_identifier,
+                                   base::TimeTicks creation_time)>;
+
+  // The constructor will register the object as an observer of the native event
+  // processor. The destructor will unregister the object.
+  NativeEventObserver(WillRunEventCallback will_run_event_callback,
+                      DidRunEventCallback did_run_event_callback);
+  virtual ~NativeEventObserver();
+
+ protected:
+#if defined(OS_MACOSX)
+  // NativeEventProcessorObserver overrides:
+  // Exposed for tests.
+  void WillRunNativeEvent(const void* opaque_identifier) override;
+  void DidRunNativeEvent(const void* opaque_identifier,
+                         base::TimeTicks creation_time) override;
+#endif
+
+ private:
+  void RegisterObserver();
+  void DeregisterObserver();
+
+  WillRunEventCallback will_run_event_callback_;
+  DidRunEventCallback did_run_event_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(NativeEventObserver);
+};
+
+}  // namespace responsiveness
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_NATIVE_EVENT_OBSERVER_H_
diff --git a/content/browser/scheduler/responsiveness/native_event_observer_browsertest.mm b/content/browser/scheduler/responsiveness/native_event_observer_browsertest.mm
new file mode 100644
index 0000000..e620742
--- /dev/null
+++ b/content/browser/scheduler/responsiveness/native_event_observer_browsertest.mm
@@ -0,0 +1,72 @@
+// Copyright 2018 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 "content/browser/scheduler/responsiveness/native_event_observer.h"
+
+#include "base/bind_helpers.h"
+#include "content/public/test/content_browser_test.h"
+#include "ui/events/test/cocoa_test_event_utils.h"
+
+#import <Carbon/Carbon.h>
+
+namespace content {
+namespace responsiveness {
+
+namespace {
+
+class FakeNativeEventObserver : public NativeEventObserver {
+ public:
+  FakeNativeEventObserver()
+      : NativeEventObserver(base::DoNothing(), base::DoNothing()) {}
+  ~FakeNativeEventObserver() override = default;
+
+  void WillRunNativeEvent(const void* opaque_identifier) override {
+    ASSERT_FALSE(will_run_id_);
+    will_run_id_ = opaque_identifier;
+  }
+  void DidRunNativeEvent(const void* opaque_identifier,
+                         base::TimeTicks creation_time) override {
+    ASSERT_FALSE(did_run_id_);
+    did_run_id_ = opaque_identifier;
+    creation_time_ = creation_time;
+  }
+
+  const void* will_run_id() { return will_run_id_; }
+  const void* did_run_id() { return did_run_id_; }
+  base::TimeTicks creation_time() { return creation_time_; }
+
+ private:
+  const void* will_run_id_ = nullptr;
+  const void* did_run_id_ = nullptr;
+  base::TimeTicks creation_time_;
+};
+
+}  // namespace
+
+class ResponsivenessNativeEventObserverBrowserTest : public ContentBrowserTest {
+};
+
+IN_PROC_BROWSER_TEST_F(ResponsivenessNativeEventObserverBrowserTest,
+                       EventForwarding) {
+  FakeNativeEventObserver observer;
+
+  EXPECT_FALSE(observer.will_run_id());
+  EXPECT_FALSE(observer.did_run_id());
+  base::TimeTicks time_at_creation = base::TimeTicks::Now();
+  NSEvent* event = cocoa_test_event_utils::KeyEventWithKeyCode(kVK_Return, '\r',
+                                                               NSKeyDown, 0);
+  [NSApp sendEvent:event];
+
+  EXPECT_EQ(observer.will_run_id(), event);
+  EXPECT_EQ(observer.did_run_id(), event);
+
+  // time_at_creation should be really similar to creation_time. As a sanity
+  // check, make sure they're within a second of each other.
+  EXPECT_LT(
+      fabs((observer.creation_time() - time_at_creation).InMilliseconds()),
+      1000);
+}
+
+}  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/native_event_observer_mac.mm b/content/browser/scheduler/responsiveness/native_event_observer_mac.mm
new file mode 100644
index 0000000..a074d877
--- /dev/null
+++ b/content/browser/scheduler/responsiveness/native_event_observer_mac.mm
@@ -0,0 +1,36 @@
+// Copyright 2018 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 "content/browser/scheduler/responsiveness/native_event_observer.h"
+
+#import <AppKit/AppKit.h>
+
+#import "content/public/browser/native_event_processor_mac.h"
+
+namespace content {
+namespace responsiveness {
+
+void NativeEventObserver::RegisterObserver() {
+  DCHECK([NSApp conformsToProtocol:@protocol(NativeEventProcessor)]);
+  id<NativeEventProcessor> processor =
+      static_cast<id<NativeEventProcessor>>(NSApp);
+  [processor addNativeEventProcessorObserver:this];
+}
+void NativeEventObserver::DeregisterObserver() {
+  DCHECK([NSApp conformsToProtocol:@protocol(NativeEventProcessor)]);
+  id<NativeEventProcessor> processor =
+      static_cast<id<NativeEventProcessor>>(NSApp);
+  [processor removeNativeEventProcessorObserver:this];
+}
+
+void NativeEventObserver::WillRunNativeEvent(const void* opaque_identifier) {
+  will_run_event_callback_.Run(opaque_identifier);
+}
+void NativeEventObserver::DidRunNativeEvent(const void* opaque_identifier,
+                                            base::TimeTicks creation_time) {
+  did_run_event_callback_.Run(opaque_identifier, creation_time);
+}
+
+}  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/watcher.cc b/content/browser/scheduler/responsiveness/watcher.cc
index e8c81ac8..2e61f5db 100644
--- a/content/browser/scheduler/responsiveness/watcher.cc
+++ b/content/browser/scheduler/responsiveness/watcher.cc
@@ -7,8 +7,10 @@
 #include "base/pending_task.h"
 #include "content/browser/scheduler/responsiveness/calculator.h"
 #include "content/browser/scheduler/responsiveness/message_loop_observer.h"
+#include "content/browser/scheduler/responsiveness/native_event_observer.h"
 #include "content/public/browser/browser_thread.h"
 
+namespace content {
 namespace responsiveness {
 
 Watcher::Metadata::Metadata(const void* identifier) : identifier(identifier) {}
@@ -26,7 +28,8 @@
   // and destruction.
   AddRef();
 
-  calculator_ = MakeCalculator();
+  calculator_ = CreateCalculator();
+  native_event_observer_ui_ = CreateNativeEventObserver();
 
   RegisterMessageLoopObserverUI();
 
@@ -43,16 +46,28 @@
   destroy_was_called_ = true;
 
   message_loop_observer_ui_.reset();
+  native_event_observer_ui_.reset();
 
   content::BrowserThread::PostTask(
       content::BrowserThread::IO, FROM_HERE,
       base::BindOnce(&Watcher::TearDownOnIOThread, base::Unretained(this)));
 }
 
-std::unique_ptr<Calculator> Watcher::MakeCalculator() {
+std::unique_ptr<Calculator> Watcher::CreateCalculator() {
   return std::make_unique<Calculator>();
 }
 
+std::unique_ptr<NativeEventObserver> Watcher::CreateNativeEventObserver() {
+  NativeEventObserver::WillRunEventCallback will_run_callback =
+      base::BindRepeating(&Watcher::WillRunEventOnUIThread,
+                          base::Unretained(this));
+  NativeEventObserver::DidRunEventCallback did_run_callback =
+      base::BindRepeating(&Watcher::DidRunEventOnUIThread,
+                          base::Unretained(this));
+  return std::make_unique<NativeEventObserver>(std::move(will_run_callback),
+                                               std::move(did_run_callback));
+}
+
 Watcher::~Watcher() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(destroy_was_called_);
@@ -162,8 +177,8 @@
                          TaskOrEventFinishedCallback callback) {
   // Calls to DidRunTask should always be paired with WillRunTask. The only time
   // the identifier should differ is when Watcher is first constructed. The
-  // TaskRunner Observers are added while a task is being run, which means that
-  // there was no corresponding WillRunTask.
+  // TaskRunner Observers may be added while a task is being run, which means
+  // that there was no corresponding WillRunTask.
   if (UNLIKELY(currently_running_metadata->empty() ||
                (task != currently_running_metadata->top().identifier))) {
     *mismatched_task_identifiers += 1;
@@ -199,4 +214,44 @@
   std::move(callback).Run(schedule_time, base::TimeTicks::Now());
 }
 
+void Watcher::WillRunEventOnUIThread(const void* opaque_identifier) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  // Reentrancy should be rare.
+  if (UNLIKELY(!currently_running_metadata_ui_.empty())) {
+    currently_running_metadata_ui_.top().caused_reentrancy = true;
+  }
+
+  currently_running_metadata_ui_.emplace(opaque_identifier);
+}
+
+void Watcher::DidRunEventOnUIThread(const void* opaque_identifier,
+                                    base::TimeTicks creation_time) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  // Calls to DidRunEventOnUIThread should always be paired with
+  // WillRunEventOnUIThread. The only time the identifier should differ is when
+  // Watcher is first constructed. The TaskRunner Observers may be added while a
+  // task is being run, which means that there was no corresponding WillRunTask.
+  if (UNLIKELY(currently_running_metadata_ui_.empty() ||
+               (opaque_identifier !=
+                currently_running_metadata_ui_.top().identifier))) {
+    mismatched_event_identifiers_ui_ += 1;
+    DCHECK_LE(mismatched_event_identifiers_ui_, 1);
+    return;
+  }
+
+  bool caused_reentrancy =
+      currently_running_metadata_ui_.top().caused_reentrancy;
+  currently_running_metadata_ui_.pop();
+
+  // Ignore events that caused reentrancy, since their execution latency will
+  // be very large, but Chrome was still responsive.
+  if (UNLIKELY(caused_reentrancy))
+    return;
+
+  calculator_->TaskOrEventFinishedOnUIThread(creation_time,
+                                             base::TimeTicks::Now());
+}
+
 }  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/scheduler/responsiveness/watcher.h b/content/browser/scheduler/responsiveness/watcher.h
index bbeeb20..0e6d3ed 100644
--- a/content/browser/scheduler/responsiveness/watcher.h
+++ b/content/browser/scheduler/responsiveness/watcher.h
@@ -18,10 +18,12 @@
 struct PendingTask;
 }  // namespace base
 
+namespace content {
 namespace responsiveness {
 
 class Calculator;
 class MessageLoopObserver;
+class NativeEventObserver;
 
 // This class watches events and tasks processed on the UI and IO threads of the
 // browser process. It forwards stats on execution latency to Calculator, which
@@ -50,7 +52,8 @@
 
  protected:
   // Exposed for tests.
-  virtual std::unique_ptr<Calculator> MakeCalculator();
+  virtual std::unique_ptr<Calculator> CreateCalculator();
+  virtual std::unique_ptr<NativeEventObserver> CreateNativeEventObserver();
   virtual ~Watcher();
   virtual void RegisterMessageLoopObserverUI();
   virtual void RegisterMessageLoopObserverIO();
@@ -105,18 +108,30 @@
                   int* mismatched_task_identifiers,
                   TaskOrEventFinishedCallback callback);
 
+  // These methods are called by the NativeEventObserver of the UI thread to
+  // allow Watcher to collect metadata about the events being run.
+  void WillRunEventOnUIThread(const void* opaque_identifier);
+  void DidRunEventOnUIThread(const void* opaque_identifier,
+                             base::TimeTicks creation_time);
+
   // The following members are all affine to the UI thread.
   std::unique_ptr<Calculator> calculator_;
   std::unique_ptr<MessageLoopObserver> message_loop_observer_ui_;
+  std::unique_ptr<NativeEventObserver> native_event_observer_ui_;
 
   // Metadata for currently running tasks and events on the UI thread.
   std::stack<Metadata> currently_running_metadata_ui_;
 
-  // Task identifiers should only be mismatched once, since the Watcher
-  // registers itself during a Task execution, and thus doesn't capture the
+  // Task identifiers should only be mismatched once, since the Watcher may
+  // register itself during a Task execution, and thus doesn't capture the
   // initial WillRunTask() callback.
   int mismatched_task_identifiers_ui_ = 0;
 
+  // Event identifiers should be mismatched at most once, since the Watcher may
+  // register itself during an event execution, and thus doesn't capture the
+  // initial WillRunEventOnUIThread callback.
+  int mismatched_event_identifiers_ui_ = 0;
+
   // The following members are all affine to the IO thread.
   std::stack<Metadata> currently_running_metadata_io_;
   int mismatched_task_identifiers_io_ = 0;
@@ -136,5 +151,6 @@
 };
 
 }  // namespace responsiveness
+}  // namespace content
 
 #endif  // CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_WATCHER_H_
diff --git a/content/browser/scheduler/responsiveness/watcher_unittest.cc b/content/browser/scheduler/responsiveness/watcher_unittest.cc
index 128e823..4ee5f61 100644
--- a/content/browser/scheduler/responsiveness/watcher_unittest.cc
+++ b/content/browser/scheduler/responsiveness/watcher_unittest.cc
@@ -9,10 +9,12 @@
 #include "base/run_loop.h"
 #include "base/synchronization/lock.h"
 #include "content/browser/scheduler/responsiveness/calculator.h"
+#include "content/browser/scheduler/responsiveness/native_event_observer.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace content {
 namespace responsiveness {
 
 namespace {
@@ -49,13 +51,17 @@
 
 class FakeWatcher : public Watcher {
  public:
-  std::unique_ptr<Calculator> MakeCalculator() override {
+  std::unique_ptr<Calculator> CreateCalculator() override {
     std::unique_ptr<FakeCalculator> calculator =
         std::make_unique<FakeCalculator>();
     calculator_ = calculator.get();
     return calculator;
   }
 
+  std::unique_ptr<NativeEventObserver> CreateNativeEventObserver() override {
+    return nullptr;
+  }
+
   void RegisterMessageLoopObserverUI() override {
     if (register_message_loop_observer_)
       Watcher::RegisterMessageLoopObserverUI();
@@ -220,3 +226,4 @@
 }
 
 }  // namespace responsiveness
+}  // namespace content
diff --git a/content/browser/service_worker/embedded_worker_test_helper.cc b/content/browser/service_worker/embedded_worker_test_helper.cc
index c8d4ab3..8d58f4b6 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.cc
+++ b/content/browser/service_worker/embedded_worker_test_helper.cc
@@ -25,7 +25,6 @@
 #include "content/common/renderer.mojom.h"
 #include "content/common/service_worker/service_worker.mojom.h"
 #include "content/common/service_worker/service_worker_messages.h"
-#include "content/public/common/push_event_payload.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_browser_context.h"
 #include "mojo/public/cpp/bindings/associated_binding_set.h"
@@ -305,7 +304,7 @@
                                           std::move(callback));
   }
 
-  void DispatchPushEvent(const PushEventPayload& payload,
+  void DispatchPushEvent(const base::Optional<std::string>& payload,
                          DispatchPushEventCallback callback) override {
     if (!helper_)
       return;
@@ -659,7 +658,7 @@
 }
 
 void EmbeddedWorkerTestHelper::OnPushEvent(
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     mojom::ServiceWorker::DispatchPushEventCallback callback) {
   std::move(callback).Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED,
                           base::Time::Now());
@@ -978,11 +977,12 @@
 }
 
 void EmbeddedWorkerTestHelper::OnPushEventStub(
-    const PushEventPayload& payload,
+    base::Optional<std::string> payload,
     mojom::ServiceWorker::DispatchPushEventCallback callback) {
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&EmbeddedWorkerTestHelper::OnPushEvent,
-                                AsWeakPtr(), payload, std::move(callback)));
+      FROM_HERE,
+      base::BindOnce(&EmbeddedWorkerTestHelper::OnPushEvent, AsWeakPtr(),
+                     std::move(payload), std::move(callback)));
 }
 
 void EmbeddedWorkerTestHelper::OnAbortPaymentEventStub(
diff --git a/content/browser/service_worker/embedded_worker_test_helper.h b/content/browser/service_worker/embedded_worker_test_helper.h
index b453495e..79235c59 100644
--- a/content/browser/service_worker/embedded_worker_test_helper.h
+++ b/content/browser/service_worker/embedded_worker_test_helper.h
@@ -43,7 +43,6 @@
 class ServiceWorkerContextWrapper;
 class TestBrowserContext;
 struct PlatformNotificationData;
-struct PushEventPayload;
 
 // In-Process EmbeddedWorker test helper.
 //
@@ -212,7 +211,7 @@
       const PlatformNotificationData& notification_data,
       mojom::ServiceWorker::DispatchNotificationCloseEventCallback callback);
   virtual void OnPushEvent(
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       mojom::ServiceWorker::DispatchPushEventCallback callback);
   virtual void OnAbortPaymentEvent(
       payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
@@ -312,7 +311,7 @@
       const PlatformNotificationData& notification_data,
       mojom::ServiceWorker::DispatchNotificationCloseEventCallback callback);
   void OnPushEventStub(
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       mojom::ServiceWorker::DispatchPushEventCallback callback);
   void OnAbortPaymentEventStub(
       payments::mojom::PaymentHandlerResponseCallbackPtr response_callback,
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index 7150c41..7071672 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -1797,7 +1797,7 @@
   // TODO(sunxd): Hit test regions are not submitted for overlapping surfaces,
   // causing /2 to fail outside of Viz. https::/crbug.com/846798
   if (base::FeatureList::IsEnabled(features::kEnableVizHitTestSurfaceLayer) &&
-      !features::IsVizDisplayCompositorEnabled()) {
+      !base::FeatureList::IsEnabled(features::kVizDisplayCompositor)) {
     return;
   }
 
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index e5e1d0f..98f5408bc 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -871,6 +871,7 @@
     blink::mojom::SessionStorageNamespaceRequest request) {
   int process_id = bindings_.dispatch_context();
   dom_storage_context_->OpenSessionStorage(process_id, namespace_id,
+                                           bindings_.GetBadMessageCallback(),
                                            std::move(request));
 }
 
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index ec8d0e0..d58a94c 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -295,17 +295,18 @@
   void AddEntry(GURL url, url::Origin origin, const std::string& data) {
     std::vector<uint8_t> data_vector(data.begin(), data.end());
     code_cache_context_->generated_code_cache()->WriteData(
-        url, origin, base::Time(), data_vector);
+        url, origin, base::Time::Now(), data_vector);
     base::RunLoop().RunUntilIdle();
   }
 
   std::string received_data() { return received_data_; }
 
  private:
-  void FetchEntryCallback(scoped_refptr<net::IOBufferWithSize> buffer) {
-    if (buffer && buffer->data()) {
+  void FetchEntryCallback(const base::Time& response_time,
+                          const std::vector<uint8_t>& data) {
+    if (!response_time.is_null()) {
       entry_exists_ = true;
-      received_data_ = std::string(buffer->data(), buffer->size());
+      received_data_ = std::string(data.begin(), data.end());
     } else {
       entry_exists_ = false;
     }
diff --git a/content/browser/tracing/background_tracing_manager_impl.cc b/content/browser/tracing/background_tracing_manager_impl.cc
index 5cc034cf..9ce32f1 100644
--- a/content/browser/tracing/background_tracing_manager_impl.cc
+++ b/content/browser/tracing/background_tracing_manager_impl.cc
@@ -561,6 +561,8 @@
 
 std::unique_ptr<base::DictionaryValue>
 BackgroundTracingManagerImpl::GenerateMetadataDict() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
   auto metadata_dict = std::make_unique<base::DictionaryValue>();
   if (config_) {
     auto config_dict = std::make_unique<base::DictionaryValue>();
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 1fdbc22..424c2447 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -161,6 +161,7 @@
 
 std::unique_ptr<base::DictionaryValue>
 TracingControllerImpl::GenerateMetadataDict() const {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   auto metadata_dict = std::make_unique<base::DictionaryValue>();
 
   // trace_config_ can be null if the tracing controller finishes flushing
diff --git a/content/browser/web_package/signed_exchange_request_handler.cc b/content/browser/web_package/signed_exchange_request_handler.cc
index e5d33126..05aa1f0c 100644
--- a/content/browser/web_package/signed_exchange_request_handler.cc
+++ b/content/browser/web_package/signed_exchange_request_handler.cc
@@ -62,10 +62,6 @@
     const network::ResourceRequest& resource_request,
     ResourceContext* resource_context,
     LoaderCallback callback) {
-  // TODO(https://crbug.com/803774): Ask WebPackageFetchManager to get the
-  // ongoing matching SignedExchangeHandler which was created by a
-  // WebPackagePrefetcher.
-
   if (!signed_exchange_loader_) {
     std::move(callback).Run({});
     return;
@@ -89,9 +85,11 @@
   network::mojom::URLLoaderClientPtr client;
   *client_request = mojo::MakeRequest(&client);
 
-  // TODO(https://crbug.com/803774): Consider creating a new ThrottlingURLLoader
-  // or reusing the existing ThrottlingURLLoader by reattaching URLLoaderClient,
-  // to support SafeBrowsing checking of the content of the WebPackage.
+  // This lets the SignedExchangeLoader directly returns an artificial redirect
+  // to the downstream client without going through ThrottlingURLLoader, which
+  // means some checks like SafeBrowsing may not see the redirect. Given that
+  // the redirected request will be checked when it's restarted we suppose
+  // this is fine.
   signed_exchange_loader_ = std::make_unique<SignedExchangeLoader>(
       url_, response, std::move(client), url_loader->Unbind(),
       std::move(request_initiator_), url_loader_options_, load_flags_,
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index e6bc5c6..1d154c8d 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -682,7 +682,7 @@
 };
 
 // Verify behavior for various combinations of origins and RP IDs.
-TEST_F(AuthenticatorImplTest, AppIdExtension) {
+TEST_F(AuthenticatorImplTest, AppIdExtensionValues) {
   TestServiceManagerContext smc;
   device::test::ScopedVirtualFidoDevice virtual_device;
 
@@ -711,6 +711,31 @@
   }
 }
 
+// Verify that a credential registered with U2F can be used via webauthn.
+TEST_F(AuthenticatorImplTest, AppIdExtension) {
+  SimulateNavigation(GURL(kTestOrigin1));
+  PublicKeyCredentialRequestOptionsPtr options =
+      GetTestPublicKeyCredentialRequestOptions();
+  TestGetAssertionCallback callback_receiver;
+  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
+      base::Time::Now(), base::TimeTicks::Now());
+  auto authenticator = ConstructAuthenticatorWithTimer(task_runner);
+  device::test::ScopedVirtualFidoDevice virtual_device;
+
+  // Inject a registration for the URL (which is a U2F AppID).
+  ASSERT_TRUE(virtual_device.mutable_state()->InjectRegistration(
+      options->allow_credentials[0]->id, kTestOrigin1));
+
+  // Set the same URL as the appid parameter.
+  options->appid = kTestOrigin1;
+
+  authenticator->GetAssertion(std::move(options), callback_receiver.callback());
+
+  // Trigger timer.
+  callback_receiver.WaitForCallback();
+  EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
+}
+
 TEST_F(AuthenticatorImplTest, TestGetAssertionTimeout) {
   SimulateNavigation(GURL(kTestOrigin1));
   PublicKeyCredentialRequestOptionsPtr options =
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index c2f9308..e7d6358 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -109,7 +109,6 @@
     "drag_messages.h",
     "drag_traits.h",
     "edit_command.h",
-    "fileapi/file_system_messages.h",
     "fileapi/webblob_messages.h",
     "font_cache_dispatcher_win.cc",
     "font_list.cc",
diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h
index f04091f..0b56714 100644
--- a/content/common/content_message_generator.h
+++ b/content/common/content_message_generator.h
@@ -27,11 +27,6 @@
 #error "Failed to include content/common/drag_messages.h"
 #endif
 #include "content/common/drag_traits.h"
-#undef CONTENT_COMMON_FILEAPI_FILE_SYSTEM_MESSAGES_H_
-#include "content/common/fileapi/file_system_messages.h"
-#ifndef CONTENT_COMMON_FILEAPI_FILE_SYSTEM_MESSAGES_H_
-#error "Failed to include content/common/fileapi/file_system_messages.h"
-#endif
 #undef CONTENT_COMMON_FILEAPI_WEBBLOB_MESSAGES_H_
 #include "content/common/fileapi/webblob_messages.h"
 #ifndef CONTENT_COMMON_FILEAPI_WEBBLOB_MESSAGES_H_
diff --git a/content/common/fileapi/file_system_messages.h b/content/common/fileapi/file_system_messages.h
deleted file mode 100644
index 971610bd..0000000
--- a/content/common/fileapi/file_system_messages.h
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright (c) 2012 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 CONTENT_COMMON_FILEAPI_FILE_SYSTEM_MESSAGES_H_
-#define CONTENT_COMMON_FILEAPI_FILE_SYSTEM_MESSAGES_H_
-
-// IPC messages for the file system.
-
-#include <stdint.h>
-
-#include <string>
-#include <vector>
-
-#include "components/services/filesystem/public/interfaces/types.mojom.h"
-#include "ipc/ipc_message_macros.h"
-#include "ipc/ipc_platform_file.h"
-#include "storage/common/fileapi/file_system_info.h"
-#include "storage/common/fileapi/file_system_types.h"
-#include "storage/common/quota/quota_limit_type.h"
-#include "url/gurl.h"
-
-#undef IPC_MESSAGE_EXPORT
-#define IPC_MESSAGE_EXPORT CONTENT_EXPORT
-#define IPC_MESSAGE_START FileSystemMsgStart
-
-IPC_STRUCT_TRAITS_BEGIN(filesystem::mojom::DirectoryEntry)
-  IPC_STRUCT_TRAITS_MEMBER(name)
-  IPC_STRUCT_TRAITS_MEMBER(type)
-IPC_STRUCT_TRAITS_END()
-
-IPC_STRUCT_TRAITS_BEGIN(storage::FileSystemInfo)
-  IPC_STRUCT_TRAITS_MEMBER(name)
-  IPC_STRUCT_TRAITS_MEMBER(root_url)
-  IPC_STRUCT_TRAITS_MEMBER(mount_type)
-IPC_STRUCT_TRAITS_END()
-
-IPC_ENUM_TRAITS_MAX_VALUE(filesystem::mojom::FsFileType,
-                          filesystem::mojom::FsFileType::DIRECTORY)
-IPC_ENUM_TRAITS_MAX_VALUE(storage::FileSystemType,
-                          storage::FileSystemType::kFileSystemTypeLast)
-IPC_ENUM_TRAITS_MAX_VALUE(storage::QuotaLimitType, storage::kQuotaLimitTypeLast)
-
-// File system messages sent from the browser to the child process.
-
-// WebLocalFrameClient::openFileSystem response messages.
-IPC_MESSAGE_CONTROL3(FileSystemMsg_DidOpenFileSystem,
-                     int /* request_id */,
-                     std::string /* name */,
-                     GURL /* root_url */)
-
-// WebFileSystem response messages.
-IPC_MESSAGE_CONTROL4(FileSystemMsg_DidResolveURL,
-                     int /* request_id */,
-                     storage::FileSystemInfo /* filesystem_info */,
-                     base::FilePath /* file_path */,
-                     bool /* is_directory */)
-IPC_MESSAGE_CONTROL1(FileSystemMsg_DidSucceed,
-                     int /* request_id */)
-IPC_MESSAGE_CONTROL2(FileSystemMsg_DidReadMetadata,
-                     int /* request_id */,
-                     base::File::Info)
-IPC_MESSAGE_CONTROL3(FileSystemMsg_DidCreateSnapshotFile,
-                     int /* request_id */,
-                     base::File::Info,
-                     base::FilePath /* true platform path */)
-IPC_MESSAGE_CONTROL3(
-    FileSystemMsg_DidReadDirectory,
-    int /* request_id */,
-    std::vector<filesystem::mojom::DirectoryEntry> /* entries */,
-    bool /* has_more */)
-IPC_MESSAGE_CONTROL3(FileSystemMsg_DidWrite,
-                     int /* request_id */,
-                     int64_t /* byte count */,
-                     bool /* complete */)
-IPC_MESSAGE_CONTROL2(FileSystemMsg_DidFail,
-                     int /* request_id */,
-                     base::File::Error /* error_code */)
-
-// File system messages sent from the child process to the browser.
-
-// WebLocalFrameClient::openFileSystem() message.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_OpenFileSystem,
-                     int /* request_id */,
-                     GURL /* origin_url */,
-                     storage::FileSystemType /* type */)
-
-// WevFrameClient::resolveURL() message.
-IPC_MESSAGE_CONTROL2(FileSystemHostMsg_ResolveURL,
-                     int /* request_id */,
-                     GURL /* filesystem_url */)
-
-// WebFileSystem::move() message.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_Move,
-                     int /* request_id */,
-                     GURL /* src path */,
-                     GURL /* dest path */)
-
-// WebFileSystem::copy() message.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_Copy,
-                     int /* request_id */,
-                     GURL /* src path */,
-                     GURL /* dest path */)
-
-// WebFileSystem::remove() message.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_Remove,
-                     int /* request_id */,
-                     GURL /* path */,
-                     bool /* recursive */)
-
-// WebFileSystem::readMetadata() message.
-IPC_MESSAGE_CONTROL2(FileSystemHostMsg_ReadMetadata,
-                     int /* request_id */,
-                     GURL /* path */)
-
-// WebFileSystem::create() message.
-IPC_MESSAGE_CONTROL5(FileSystemHostMsg_Create,
-                     int /* request_id */,
-                     GURL /* path */,
-                     bool /* exclusive */,
-                     bool /* is_directory */,
-                     bool /* recursive */)
-
-// WebFileSystem::exists() messages.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_Exists,
-                     int /* request_id */,
-                     GURL /* path */,
-                     bool /* is_directory */)
-
-// WebFileSystem::readDirectory() message.
-IPC_MESSAGE_CONTROL2(FileSystemHostMsg_ReadDirectory,
-                     int /* request_id */,
-                     GURL /* path */)
-
-// WebFileWriter::write() message.
-IPC_MESSAGE_CONTROL4(FileSystemHostMsg_Write,
-                     int /* request id */,
-                     GURL /* file path */,
-                     std::string /* blob uuid */,
-                     int64_t /* position */)
-
-// WebFileWriter::truncate() message.
-IPC_MESSAGE_CONTROL3(FileSystemHostMsg_Truncate,
-                     int /* request id */,
-                     GURL /* file path */,
-                     int64_t /* length */)
-
-// Pepper's Touch() message.
-IPC_MESSAGE_CONTROL4(FileSystemHostMsg_TouchFile,
-                     int /* request_id */,
-                     GURL /* path */,
-                     base::Time /* last_access_time */,
-                     base::Time /* last_modified_time */)
-
-// WebFileWriter::cancel() message.
-IPC_MESSAGE_CONTROL2(FileSystemHostMsg_CancelWrite,
-                     int /* request id */,
-                     int /* id of request to cancel */)
-
-// WebFileSystem::createSnapshotFileAndReadMetadata() message.
-IPC_MESSAGE_CONTROL2(FileSystemHostMsg_CreateSnapshotFile,
-                     int /* request_id */,
-                     GURL /* file_path */)
-
-// Renderers are expected to send this message after having processed
-// the FileSystemMsg_DidCreateSnapshotFile message. In particular,
-// after having created a BlobDataHandle backed by the snapshot file.
-IPC_MESSAGE_CONTROL1(FileSystemHostMsg_DidReceiveSnapshotFile,
-                     int /* request_id */)
-
-// For Pepper's URL loader.
-IPC_SYNC_MESSAGE_CONTROL1_1(FileSystemHostMsg_SyncGetPlatformPath,
-                            GURL /* file path */,
-                            base::FilePath /* platform_path */)
-
-#endif  // CONTENT_COMMON_FILEAPI_FILE_SYSTEM_MESSAGES_H_
diff --git a/content/common/render_message_filter.mojom b/content/common/render_message_filter.mojom
index 4b94587a..40372669 100644
--- a/content/common/render_message_filter.mojom
+++ b/content/common/render_message_filter.mojom
@@ -33,7 +33,8 @@
 
   // TODO(crbug.com/867848) Pass the data as mojo data_pipe instead of
   // array<unit8>.
-  FetchCachedCode(url.mojom.Url url) => (array<uint8> data);
+  FetchCachedCode(url.mojom.Url url) => (mojo_base.mojom.Time response_time,
+                                         array<uint8> data);
 
   // Requests that the browser cache |data| for the specified CacheStorage entry.
   DidGenerateCacheableMetadataInCacheStorage(
diff --git a/content/common/service_worker/service_worker.mojom b/content/common/service_worker/service_worker.mojom
index ba81409..e468a14 100644
--- a/content/common/service_worker/service_worker.mojom
+++ b/content/common/service_worker/service_worker.mojom
@@ -22,9 +22,6 @@
 import "url/mojom/url.mojom";
 
 [Native]
-struct PushEventPayload;
-
-[Native]
 struct ServiceWorkerResponse;
 
 // TODO(peter): Move this to Blink when ServiceWorkerResponse has a Mojo
@@ -155,7 +152,9 @@
       blink.mojom.NotificationData notification_data)
       => (blink.mojom.ServiceWorkerEventStatus status,
           mojo_base.mojom.Time dispatch_event_time);
-  DispatchPushEvent(PushEventPayload payload)
+  // The payload of a push message can be valid with content, valid with empty
+  // content, or null.
+  DispatchPushEvent(string? payload)
       => (blink.mojom.ServiceWorkerEventStatus status,
           mojo_base.mojom.Time dispatch_event_time);
   // Arguments are passed to the event handler as parameters of SyncEvent.
diff --git a/content/common/service_worker/service_worker.typemap b/content/common/service_worker/service_worker.typemap
index c607ffb..e829c003 100644
--- a/content/common/service_worker/service_worker.typemap
+++ b/content/common/service_worker/service_worker.typemap
@@ -7,12 +7,10 @@
   "//content/common/background_fetch/background_fetch_types.h",
   "//third_party/blink/public/common/service_worker/service_worker_status_code.h",
   "//content/common/service_worker/service_worker_types.h",
-  "//content/public/common/push_event_payload.h",
 ]
 traits_headers = [ "//content/common/service_worker/service_worker_messages.h" ]
 type_mappings = [
   "content.mojom.ExtendableMessageEventSource=::content::ExtendableMessageEventSource",
-  "content.mojom.PushEventPayload=::content::PushEventPayload",
   "content.mojom.ServiceWorkerFetchRequest=::content::ServiceWorkerFetchRequest",
   "content.mojom.ServiceWorkerResponse=::content::ServiceWorkerResponse",
 ]
diff --git a/content/common/service_worker/service_worker_messages.h b/content/common/service_worker/service_worker_messages.h
index 3fa8620e..9141f28 100644
--- a/content/common/service_worker/service_worker_messages.h
+++ b/content/common/service_worker/service_worker_messages.h
@@ -14,7 +14,6 @@
 #include "base/time/time.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/common/platform_notification_data.h"
-#include "content/public/common/push_event_payload.h"
 #include "ipc/ipc_message_macros.h"
 #include "ipc/ipc_param_traits.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
@@ -67,9 +66,4 @@
   IPC_STRUCT_TRAITS_MEMBER(side_data_blob)
 IPC_STRUCT_TRAITS_END()
 
-IPC_STRUCT_TRAITS_BEGIN(content::PushEventPayload)
-  IPC_STRUCT_TRAITS_MEMBER(data)
-  IPC_STRUCT_TRAITS_MEMBER(is_null)
-IPC_STRUCT_TRAITS_END()
-
 #endif  // CONTENT_COMMON_SERVICE_WORKER_SERVICE_WORKER_MESSAGES_H_
diff --git a/content/gpu/gpu_child_thread.cc b/content/gpu/gpu_child_thread.cc
index 1abaafd..fdcef72 100644
--- a/content/gpu/gpu_child_thread.cc
+++ b/content/gpu/gpu_child_thread.cc
@@ -144,7 +144,8 @@
 viz::VizMainImpl::ExternalDependencies CreateVizMainDependencies(
     service_manager::Connector* connector) {
   viz::VizMainImpl::ExternalDependencies deps;
-  deps.create_display_compositor = features::IsVizDisplayCompositorEnabled();
+  deps.create_display_compositor =
+      base::FeatureList::IsEnabled(features::kVizDisplayCompositor);
   if (GetContentClient()->gpu())
     deps.sync_point_manager = GetContentClient()->gpu()->GetSyncPointManager();
   auto* process = ChildProcess::current();
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
index 5886047..3d50e19 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentUiEventHandler.java
@@ -13,10 +13,10 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.device.gamepad.GamepadList;
 import org.chromium.ui.base.EventForwarder;
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/Gamepad.java b/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
index b960fba7..0da266c 100644
--- a/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
+++ b/content/public/android/java/src/org/chromium/content/browser/Gamepad.java
@@ -7,8 +7,9 @@
 import android.content.Context;
 
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.device.gamepad.GamepadList;
 
 /**
@@ -23,7 +24,8 @@
     }
 
     public static Gamepad from(WebContents webContents) {
-        return webContents.getOrSetUserData(Gamepad.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, Gamepad.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public Gamepad(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
index 9538d032..47c794f 100644
--- a/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/GestureListenerManagerImpl.java
@@ -16,12 +16,12 @@
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.GestureListenerManager;
 import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.GestureEventType;
 import org.chromium.ui.base.ViewAndroidDelegate;
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
index cc27a8e9..5fa36f0 100644
--- a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
@@ -8,10 +8,10 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 
 import java.lang.annotation.Annotation;
 import java.util.HashMap;
diff --git a/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java b/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
index a984f3b..39695e9 100644
--- a/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
+++ b/content/public/android/java/src/org/chromium/content/browser/JoystickHandler.java
@@ -8,10 +8,10 @@
 import android.view.MotionEvent;
 
 import org.chromium.content.browser.input.ImeAdapterImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.EventForwarder;
 
 /**
diff --git a/content/public/android/java/src/org/chromium/content/browser/PopupController.java b/content/public/android/java/src/org/chromium/content/browser/PopupController.java
index 27ca475..a905fa4 100644
--- a/content/public/android/java/src/org/chromium/content/browser/PopupController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/PopupController.java
@@ -5,9 +5,9 @@
 package org.chromium.content.browser;
 
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/content/public/android/java/src/org/chromium/content/browser/TapDisambiguator.java b/content/public/android/java/src/org/chromium/content/browser/TapDisambiguator.java
deleted file mode 100644
index b022bd7..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/TapDisambiguator.java
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.view.ViewGroup;
-
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.content.browser.PopupZoomer.OnTapListener;
-import org.chromium.content.browser.PopupZoomer.OnVisibilityChangedListener;
-import org.chromium.content.browser.input.ImeAdapterImpl;
-import org.chromium.content.browser.webcontents.WebContentsImpl;
-import org.chromium.content_public.browser.ImeEventObserver;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
-
-/**
- * Class that handles tap disambiguation feature.  When a tap lands ambiguously
- * between two tiny touch targets (usually links) on a desktop site viewed on a phone,
- * a magnified view of the content is shown, the screen is grayed out and the user
- * must re-tap the magnified content in order to clarify their intent.
- */
-@JNINamespace("content")
-public class TapDisambiguator implements ImeEventObserver, PopupController.HideablePopup {
-    private final WebContentsImpl mWebContents;
-    private PopupZoomer mPopupView;
-    private long mNativeTapDisambiguator;
-
-    private static final class UserDataFactoryLazyHolder {
-        private static final UserDataFactory<TapDisambiguator> INSTANCE = TapDisambiguator::new;
-    }
-
-    public static TapDisambiguator fromWebContents(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                TapDisambiguator.class, UserDataFactoryLazyHolder.INSTANCE);
-    }
-
-    /**
-     * Creates TapDisambiguation instance.
-     * @param webContents WebContents instance with which this TapDisambiguator is associated.
-     */
-    public TapDisambiguator(WebContents webContents) {
-        mWebContents = (WebContentsImpl) webContents;
-        Context context = mWebContents.getContext();
-        ViewGroup containerView = mWebContents.getViewAndroidDelegate().getContainerView();
-
-        // OnVisibilityChangedListener, OnTapListener can only be used to add and remove views
-        // from the container view at creation.
-        OnVisibilityChangedListener visibilityListener = new OnVisibilityChangedListener() {
-            @Override
-            public void onPopupZoomerShown(final PopupZoomer zoomer) {
-                containerView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (containerView.indexOfChild(zoomer) == -1) {
-                            containerView.addView(zoomer);
-                        }
-                    }
-                });
-            }
-
-            @Override
-            public void onPopupZoomerHidden(final PopupZoomer zoomer) {
-                containerView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (containerView.indexOfChild(zoomer) != -1) {
-                            containerView.removeView(zoomer);
-                            containerView.invalidate();
-                        }
-                    }
-                });
-            }
-        };
-
-        OnTapListener tapListener = new OnTapListener() {
-            @Override
-            public void onResolveTapDisambiguation(
-                    long timeMs, float x, float y, boolean isLongPress) {
-                if (mNativeTapDisambiguator == 0) return;
-                containerView.requestFocus();
-                nativeResolveTapDisambiguation(mNativeTapDisambiguator, timeMs, x, y, isLongPress);
-            }
-        };
-        mPopupView = new PopupZoomer(
-                mWebContents.getContext(), containerView, visibilityListener, tapListener);
-        mNativeTapDisambiguator = nativeInit(mWebContents);
-        ImeAdapterImpl.fromWebContents(mWebContents).addEventObserver(this);
-        PopupController.register(mWebContents, this);
-    }
-
-    // ImeEventObserver
-
-    @Override
-    public void onImeEvent() {
-        hidePopup(true);
-    }
-
-    // HideablePopup
-
-    @Override
-    public void hide() {
-        hidePopup(false);
-    }
-
-    /**
-     * Returns true if the view is currently being shown (or is animating).
-     */
-    public boolean isShowing() {
-        return mPopupView.isShowing();
-    }
-
-    /**
-     * Sets the last touch point (on the unzoomed view).
-     */
-    public void setLastTouch(float x, float y) {
-        mPopupView.setLastTouch(x, y);
-    }
-
-    /**
-     * Show the TapDisambiguator view with given target bounds.
-     */
-    public void showPopup(Rect rect) {
-        mPopupView.show(rect);
-    }
-
-    /**
-     * Hide the TapDisambiguator view because of some external event such as focus
-     * change, JS-originating scroll, etc.
-     * @param animation true if hide with animation.
-     */
-    public void hidePopup(boolean animation) {
-        mPopupView.hide(animation);
-    }
-
-    /**
-     * Called when back button is pressed.
-     */
-    public void backButtonPressed() {
-        mPopupView.backButtonPressed();
-    }
-
-    @CalledByNative
-    private void destroy() {
-        mNativeTapDisambiguator = 0;
-    }
-
-    @CalledByNative
-    private void showPopup(Rect targetRect, Bitmap zoomedBitmap) {
-        mPopupView.setBitmap(zoomedBitmap);
-        mPopupView.show(targetRect);
-    }
-
-    @CalledByNative
-    private void hidePopup() {
-        hidePopup(false);
-    }
-
-    @CalledByNative
-    private static Rect createRect(int x, int y, int right, int bottom) {
-        return new Rect(x, y, right, bottom);
-    }
-
-    @VisibleForTesting
-    void setPopupZoomerForTest(PopupZoomer view) {
-        mPopupView = view;
-    }
-
-    private native long nativeInit(WebContents webContents);
-    private native void nativeResolveTapDisambiguation(
-            long nativeTapDisambiguator, long timeMs, float x, float y, boolean isLongPress);
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java b/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
index 6c3f4f0b..bc8b5b27 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ViewEventSinkImpl.java
@@ -8,9 +8,10 @@
 
 import org.chromium.base.TraceEvent;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid.ActivityStateObserver;
 
@@ -39,8 +40,8 @@
     }
 
     public static ViewEventSinkImpl from(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                ViewEventSinkImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, ViewEventSinkImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     public ViewEventSinkImpl(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java b/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
index ca63a30..1cd90d9 100644
--- a/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
+++ b/content/public/android/java/src/org/chromium/content/browser/WindowEventObserverManager.java
@@ -9,8 +9,9 @@
 import org.chromium.base.ActivityState;
 import org.chromium.base.ObserverList;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.display.DisplayAndroid;
 import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
@@ -35,8 +36,8 @@
     }
 
     public static WindowEventObserverManager from(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                WindowEventObserverManager.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, WindowEventObserverManager.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     private WindowEventObserverManager(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
index 9bc7922..0cf23c8c 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
@@ -35,10 +35,11 @@
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.accessibility.captioning.CaptioningController;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.AccessibilitySnapshotCallback;
 import org.chromium.content_public.browser.AccessibilitySnapshotNode;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.content_public.browser.WebContentsAccessibility;
 
 import java.util.ArrayList;
@@ -139,8 +140,8 @@
     }
 
     public static WebContentsAccessibilityImpl fromWebContents(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                WebContentsAccessibilityImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(webContents, WebContentsAccessibilityImpl.class,
+                UserDataFactoryLazyHolder.INSTANCE);
     }
 
     protected WebContentsAccessibilityImpl(WebContents webContents) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
index ad9bc52..a80191f 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapterImpl.java
@@ -42,11 +42,12 @@
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.picker.InputDialogContainer;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ImeAdapter;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.InputMethodManagerWrapper;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.base.ime.TextInputType;
@@ -172,8 +173,8 @@
      * @return {@link ImeAdapter} object.
      */
     public static ImeAdapterImpl fromWebContents(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                ImeAdapterImpl.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, ImeAdapterImpl.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java b/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
index 75ba6ac2..87d6545 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/SelectPopup.java
@@ -17,8 +17,9 @@
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.accessibility.WebContentsAccessibilityImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
@@ -61,7 +62,8 @@
      * @return {@link SelectPopup} object.
      */
     public static SelectPopup fromWebContents(WebContents webContents) {
-        return webContents.getOrSetUserData(SelectPopup.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, SelectPopup.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     @CalledByNative
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
index ae8a70a3..e5c42a5f 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionHost.java
@@ -14,8 +14,9 @@
 import org.chromium.content.browser.WindowEventObserver;
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
+import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -48,8 +49,8 @@
      * @return {@link TextSuggestionHost} object.
      */
     public static TextSuggestionHost fromWebContents(WebContents webContents) {
-        return webContents.getOrSetUserData(
-                TextSuggestionHost.class, UserDataFactoryLazyHolder.INSTANCE);
+        return WebContentsUserData.fromWebContents(
+                webContents, TextSuggestionHost.class, UserDataFactoryLazyHolder.INSTANCE);
     }
 
     /**
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
index 000284b..f39237e 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
@@ -50,13 +50,13 @@
 import org.chromium.content.browser.WindowEventObserverManager;
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content.browser.webcontents.WebContentsUserData;
 import org.chromium.content_public.browser.ActionModeCallbackHelper;
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.SelectionClient;
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.MenuSourceType;
 import org.chromium.ui.base.ViewAndroidDelegate;
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
index 048edab..cf0fdc0 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
@@ -44,7 +44,6 @@
 import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.ViewEventSink.InternalAccessDelegate;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.content_public.browser.WebContentsInternals;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.OverscrollRefreshHandler;
@@ -112,6 +111,30 @@
                 }
             };
 
+    /**
+     * Factory interface passed to {@link #getOrSetUserData()} for instantiation of
+     * class as user data.
+     *
+     * Constructor method reference comes handy for class Foo to provide the factory.
+     * Use lazy initialization to avoid having to generate too many anonymous references.
+     *
+     * <code>
+     * public class Foo {
+     *     static final class FoofactoryLazyHolder {
+     *         private static final UserDataFactory<Foo> INSTANCE = Foo::new;
+     *     }
+     *     ....
+     *
+     *     webContents.getOrsetUserData(Foo.class, FooFactoryLazyHolder.INSTANCE);
+     *
+     *     ....
+     * }
+     * </code>
+     *
+     * @param <T> Class to instantiate.
+     */
+    public interface UserDataFactory<T> { T create(WebContents webContents); }
+
     // Note this list may be incomplete. Frames that never had to initialize java side would
     // not have an entry here. This is here mainly to keep the java RenderFrameHosts alive, since
     // native side generally cannot safely hold strong references to them.
@@ -785,7 +808,15 @@
         return mRenderCoordinates;
     }
 
-    @Override
+    /**
+     * Retrieves or stores a user data object for this WebContents.
+     * @param key Class instance of the object used as the key.
+     * @param userDataFactory Factory that creates an object of the generic class. A new object
+     *        is created if it hasn't been created and non-null factory is given.
+     * @return The created or retrieved user data object. Can be null if the object was
+     *         not created yet, or {@code userDataFactory} is null, or the internal data
+     *         storage is already garbage-collected.
+     */
     public <T> T getOrSetUserData(Class<T> key, UserDataFactory<T> userDataFactory) {
         // For tests that go without calling |initialize|.
         if (!mInitialized) return null;
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java
index 24094574..0bf39a5 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsUserData.java
@@ -4,8 +4,8 @@
 
 package org.chromium.content.browser.webcontents;
 
+import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 
 /**
  * Holds an object to be stored in {@code userDataMap} in {@link WebContents} for those
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
index e2baf1fb..d827a25 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContents.java
@@ -80,30 +80,6 @@
     }
 
     /**
-     * Factory interface passed to {@link #setUserData()} for instantiation of
-     * class as user data.
-     *
-     * Constructor method reference comes handy for class Foo to provide the factory.
-     * Use lazy initialization to avoid having to generate too many anonymous reference.
-     *
-     * <code>
-     * public class Foo {
-     *     static final class FoofactoryLazyHolder {
-     *         private static final UserDataFactory<Foo> INSTANCE = Foo::new;
-     *     }
-     *     ....
-     *
-     *     webContents.setUserData(Foo.class, FooFactoryLazyHolder.INSTANCE);
-     *
-     *     ....
-     * }
-     * </code>
-     *
-     * @param <T> Class to instantiate.
-     */
-    public interface UserDataFactory<T> { T create(WebContents webContents); }
-
-    /**
      * Initialize various content objects of {@link WebContents} lifetime.
      * @param productVersion Product version for accessibility.
      * @param viewDelegate Delegate to add/remove anchor views.
@@ -145,17 +121,6 @@
     boolean isDestroyed();
 
     /**
-     * Retrieves or stores a user data object for this WebContents.
-     * @param key Class instance of the object used as the key.
-     * @param userDataFactory Factory that creates an object of the generic class. A new object
-     *        is created if it hasn't been created and non-null factory is given.
-     * @return The created or retrieved user data object. Can be null if the object was
-     *         not created yet, or {@code userDataFactory} is null, or the internal data
-     *         storage is already garbage-collected.
-     */
-    public <T> T getOrSetUserData(Class<T> key, UserDataFactory<T> userDataFactory);
-
-    /**
      * @return The navigation controller associated with this WebContents.
      */
     NavigationController getNavigationController();
diff --git a/content/public/app/mojo/content_browser_manifest.json b/content/public/app/mojo/content_browser_manifest.json
index 9718b97..c189047 100644
--- a/content/public/app/mojo/content_browser_manifest.json
+++ b/content/public/app/mojo/content_browser_manifest.json
@@ -41,6 +41,7 @@
           "blink.mojom.FontUniqueNameLookup",
           "blink.mojom.EmbeddedFrameSinkProvider",
           "blink.mojom.FileUtilitiesHost",
+          "blink.mojom.FileSystemManager",
           "blink.mojom.LockManager",
           "blink.mojom.Hyphenation",
           "blink.mojom.MimeRegistry",
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index f550de4..b9b8b3ee 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -168,6 +168,9 @@
     "memory_coordinator_delegate.h",
     "message_port_provider.h",
     "mhtml_extra_parts.h",
+    "native_event_processor_mac.h",
+    "native_event_processor_observer_mac.h",
+    "native_event_processor_observer_mac.mm",
     "native_web_keyboard_event.h",
     "navigation_controller.cc",
     "navigation_controller.h",
diff --git a/content/public/browser/browser_context.h b/content/public/browser/browser_context.h
index 28ba0306..e49eaaa 100644
--- a/content/public/browser/browser_context.h
+++ b/content/public/browser/browser_context.h
@@ -15,6 +15,7 @@
 
 #include "base/callback_forward.h"
 #include "base/containers/hash_tables.h"
+#include "base/optional.h"
 #include "base/supports_user_data.h"
 #include "content/common/content_export.h"
 #include "net/url_request/url_request_interceptor.h"
@@ -73,7 +74,6 @@
 class DownloadManagerDelegate;
 class PermissionController;
 class PermissionControllerDelegate;
-struct PushEventPayload;
 class PushMessagingService;
 class ResourceContext;
 class ServiceManagerConnection;
@@ -171,7 +171,7 @@
       BrowserContext* browser_context,
       const GURL& origin,
       int64_t service_worker_registration_id,
-      const PushEventPayload& payload,
+      base::Optional<std::string> payload,
       const base::Callback<void(mojom::PushDeliveryStatus)>& callback);
 
   static void NotifyWillBeDestroyed(BrowserContext* browser_context);
diff --git a/content/public/browser/native_event_processor_mac.h b/content/public/browser/native_event_processor_mac.h
new file mode 100644
index 0000000..43eee24
--- /dev/null
+++ b/content/public/browser/native_event_processor_mac.h
@@ -0,0 +1,22 @@
+// Copyright 2018 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 CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_MAC_H_
+#define CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_MAC_H_
+
+namespace content {
+class NativeEventProcessorObserver;
+}  // namespace content
+
+// The application's NSApplication subclass should implement this protocol to
+// give observers additional information about the native events being run in
+// -[NSApplication sendEvent:].
+@protocol NativeEventProcessor
+- (void)addNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer;
+- (void)removeNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer;
+@end
+
+#endif  // CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_MAC_H_
diff --git a/content/public/browser/native_event_processor_observer_mac.h b/content/public/browser/native_event_processor_observer_mac.h
new file mode 100644
index 0000000..2d33dd59
--- /dev/null
+++ b/content/public/browser/native_event_processor_observer_mac.h
@@ -0,0 +1,49 @@
+// Copyright 2018 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 CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_OBSERVER_MAC_H_
+#define CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_OBSERVER_MAC_H_
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "content/common/content_export.h"
+
+#if defined(__OBJC__)
+@class NSEvent;
+#else   // __OBJC__
+class NSEvent;
+#endif  // __OBJC__
+
+namespace content {
+
+class NativeEventProcessorObserver {
+ public:
+  // Called right before a native event is run.
+  virtual void WillRunNativeEvent(const void* opaque_identifier) = 0;
+
+  // Called right after a native event is run.
+  // |creation_time| refers to the time at which the native event was created.
+  virtual void DidRunNativeEvent(const void* opaque_identifier,
+                                 base::TimeTicks creation_time) = 0;
+};
+
+// The constructor sends a WillRunNativeEvent callback to each observer.
+// The destructor sends a DidRunNativeEvent callback to each observer.
+class CONTENT_EXPORT ScopedNotifyNativeEventProcessorObserver {
+ public:
+  ScopedNotifyNativeEventProcessorObserver(
+      base::ObserverList<NativeEventProcessorObserver>* observer_list,
+      NSEvent* event);
+  ~ScopedNotifyNativeEventProcessorObserver();
+
+ private:
+  base::ObserverList<NativeEventProcessorObserver>* observer_list_;
+  NSEvent* event_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedNotifyNativeEventProcessorObserver);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_NATIVE_EVENT_PROCESSOR_OBSERVER_MAC_H_
diff --git a/content/public/browser/native_event_processor_observer_mac.mm b/content/public/browser/native_event_processor_observer_mac.mm
new file mode 100644
index 0000000..b33b909e
--- /dev/null
+++ b/content/public/browser/native_event_processor_observer_mac.mm
@@ -0,0 +1,56 @@
+// Copyright 2018 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 "content/public/browser/native_event_processor_observer_mac.h"
+
+#import <AppKit/AppKit.h>
+
+#include "base/observer_list.h"
+#include "base/time/time.h"
+
+namespace content {
+
+namespace {
+
+base::TimeTicks TimeTicksForEvent(NSEvent* event) {
+  // NSEvent.timestamp gives the creation time of the event in seconds
+  // since system startup. The baseline it should be compared agaainst is
+  // NSProcessInfo.systemUptime. To convert to base::TimeTicks, we take
+  // the difference and subtract from base::TimeTicks::Now().
+  // Observations:
+  //  1) This implementation is fast, since both systemUptime and
+  //  base::TimeTicks::Now() use the commpage [no syscalls].
+  //  2) systemUptime's implementation uses mach_absolute_time() -- see
+  //  CoreFoundation.framework. Presumably, so does NSEvent.timestamp.
+  //  mach_absolute_time() does not advance while the machine is asleep.
+  NSTimeInterval current_system_uptime =
+      [[NSProcessInfo processInfo] systemUptime];
+  return base::TimeTicks::Now() +
+         base::TimeDelta::FromSecondsD(event.timestamp - current_system_uptime);
+}
+
+}  // namespace
+
+ScopedNotifyNativeEventProcessorObserver::
+    ScopedNotifyNativeEventProcessorObserver(
+        base::ObserverList<NativeEventProcessorObserver>* observer_list,
+        NSEvent* event)
+    : observer_list_(observer_list), event_(event) {
+  for (auto& observer : *observer_list_)
+    observer.WillRunNativeEvent(event_);
+}
+
+ScopedNotifyNativeEventProcessorObserver::
+    ~ScopedNotifyNativeEventProcessorObserver() {
+  base::TimeTicks event_creation_time;
+  for (auto& obs : *observer_list_) {
+    // Compute the value in the loop to avoid doing work if there are no
+    // observers.
+    if (event_creation_time.is_null())
+      event_creation_time = TimeTicksForEvent(event_);
+    obs.DidRunNativeEvent(event_, event_creation_time);
+  }
+}
+
+}  // namespace content
diff --git a/content/public/browser/network_connection_tracker_unittest.cc b/content/public/browser/network_connection_tracker_unittest.cc
index 1f3ffa0..6286c4a 100644
--- a/content/public/browser/network_connection_tracker_unittest.cc
+++ b/content/public/browser/network_connection_tracker_unittest.cc
@@ -24,7 +24,8 @@
   explicit TestNetworkConnectionObserver(NetworkConnectionTracker* tracker)
       : num_notifications_(0),
         tracker_(tracker),
-        run_loop_(std::make_unique<base::RunLoop>()),
+        expected_connection_type_(
+            network::mojom::ConnectionType::CONNECTION_UNKNOWN),
         connection_type_(network::mojom::ConnectionType::CONNECTION_UNKNOWN) {
     tracker_->AddNetworkConnectionObserver(this);
   }
@@ -52,13 +53,22 @@
 
     num_notifications_++;
     connection_type_ = type;
-    run_loop_->Quit();
+    if (run_loop_ && expected_connection_type_ == type)
+      run_loop_->Quit();
   }
 
   size_t num_notifications() const { return num_notifications_; }
-  void WaitForNotification() {
+  void WaitForNotification(
+      network::mojom::ConnectionType expected_connection_type) {
+    expected_connection_type_ = expected_connection_type;
+
+    if (connection_type_ == expected_connection_type)
+      return;
+    // WaitForNotification() should not be called twice.
+    EXPECT_EQ(nullptr, run_loop_);
+    run_loop_ = std::make_unique<base::RunLoop>();
     run_loop_->Run();
-    run_loop_.reset(new base::RunLoop());
+    run_loop_.reset();
   }
 
   network::mojom::ConnectionType connection_type() const {
@@ -75,7 +85,9 @@
 
   size_t num_notifications_;
   NetworkConnectionTracker* tracker_;
+  // May be null.
   std::unique_ptr<base::RunLoop> run_loop_;
+  network::mojom::ConnectionType expected_connection_type_;
   network::mojom::ConnectionType connection_type_;
 
   DISALLOW_COPY_AND_ASSIGN(TestNetworkConnectionObserver);
@@ -217,7 +229,8 @@
   SimulateConnectionTypeChange(
       net::NetworkChangeNotifier::ConnectionType::CONNECTION_3G);
 
-  network_connection_observer()->WaitForNotification();
+  network_connection_observer()->WaitForNotification(
+      network::mojom::ConnectionType::CONNECTION_3G);
   EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_3G,
             network_connection_observer()->connection_type());
   base::RunLoop().RunUntilIdle();
@@ -234,10 +247,12 @@
   SimulateConnectionTypeChange(
       net::NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI);
 
-  network_connection_observer2->WaitForNotification();
+  network_connection_observer2->WaitForNotification(
+      network::mojom::ConnectionType::CONNECTION_WIFI);
   EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_WIFI,
             network_connection_observer2->connection_type());
-  network_connection_observer()->WaitForNotification();
+  network_connection_observer()->WaitForNotification(
+      network::mojom::ConnectionType::CONNECTION_WIFI);
   EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_WIFI,
             network_connection_observer()->connection_type());
   base::RunLoop().RunUntilIdle();
@@ -247,7 +262,8 @@
   // Simulate an another network change.
   SimulateConnectionTypeChange(
       net::NetworkChangeNotifier::ConnectionType::CONNECTION_2G);
-  network_connection_observer()->WaitForNotification();
+  network_connection_observer()->WaitForNotification(
+      network::mojom::ConnectionType::CONNECTION_2G);
   EXPECT_EQ(network::mojom::ConnectionType::CONNECTION_2G,
             network_connection_observer()->connection_type());
   EXPECT_EQ(2u, network_connection_observer()->num_notifications());
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index bf008317..b022eff1 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -186,7 +186,6 @@
     "platform_notification_data.h",
     "previews_state.h",
     "process_type.h",
-    "push_event_payload.h",
     "push_subscription_options.h",
     "referrer.cc",
     "referrer.h",
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index f158ae2c..2bca834c 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -240,6 +240,11 @@
 const base::Feature kMemoryCoordinator{"MemoryCoordinator",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Instead of BrowserPlugin or GuestViews, MimeHandlerView will use a cross
+// process frame to render its handler.
+const base::Feature kMimeHandlerViewInCrossProcessFrame{
+    "MimeHandlerViewInCrossProcessFrame", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // ES6 Modules dynamic imports.
 const base::Feature kModuleScriptsDynamicImport{
     "ModuleScriptsDynamicImport", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 54302587..0592f7f 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -62,6 +62,7 @@
 CONTENT_EXPORT extern const base::Feature kLowPriorityIframes;
 CONTENT_EXPORT extern const base::Feature kMediaDevicesSystemMonitorCache;
 CONTENT_EXPORT extern const base::Feature kMemoryCoordinator;
+CONTENT_EXPORT extern const base::Feature kMimeHandlerViewInCrossProcessFrame;
 CONTENT_EXPORT extern const base::Feature kModuleScriptsDynamicImport;
 CONTENT_EXPORT extern const base::Feature kModuleScriptsImportMetaUrl;
 CONTENT_EXPORT extern const base::Feature kMojoSessionStorage;
diff --git a/content/public/common/push_event_payload.h b/content/public/common/push_event_payload.h
deleted file mode 100644
index a560cda..0000000
--- a/content/public/common/push_event_payload.h
+++ /dev/null
@@ -1,37 +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 CONTENT_PUBLIC_COMMON_PUSH_EVENT_PAYLOAD_H_
-#define CONTENT_PUBLIC_COMMON_PUSH_EVENT_PAYLOAD_H_
-
-#include <string>
-
-#include "content/common/content_export.h"
-
-namespace content {
-
-// Structure representing the payload delivered as part of a push message.
-// This struct contains the decrypted information sent from the push
-// service as part of a PushEvent as well as metadata about the information.
-struct CONTENT_EXPORT PushEventPayload {
-  PushEventPayload() : is_null(true) {}
-  ~PushEventPayload() {}
-
-  // Method to both set the data string and update the null status.
-  void setData(const std::string& data_in) {
-    data = data_in;
-    is_null = false;
-  }
-
-  // Data contained in the payload.
-  std::string data;
-
-  // Whether the payload is null or not. Payloads can be valid with non-empty
-  // content, valid with empty content, or null.
-  bool is_null;
-};
-
-}  // namespace content
-
-#endif  // CONTENT_PUBLIC_COMMON_PUSH_EVENT_PAYLOAD_H_
diff --git a/content/public/test/android/javatests/src/org/chromium/content/browser/test/mock/MockWebContents.java b/content/public/test/android/javatests/src/org/chromium/content/browser/test/mock/MockWebContents.java
index 5b7ff0a61..4d18417 100644
--- a/content/public/test/android/javatests/src/org/chromium/content/browser/test/mock/MockWebContents.java
+++ b/content/public/test/android/javatests/src/org/chromium/content/browser/test/mock/MockWebContents.java
@@ -18,7 +18,6 @@
 import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContents.UserDataFactory;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.OverscrollRefreshHandler;
 import org.chromium.ui.base.EventForwarder;
@@ -67,11 +66,6 @@
     }
 
     @Override
-    public <T> T getOrSetUserData(Class<T> key, UserDataFactory<T> userDataFactory) {
-        return null;
-    }
-
-    @Override
     public NavigationController getNavigationController() {
         return null;
     }
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index cac1283..705b2b3 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -37,6 +37,7 @@
 #include "content/browser/browser_plugin/browser_plugin_guest.h"
 #include "content/browser/browser_plugin/browser_plugin_message_filter.h"
 #include "content/browser/compositor/surface_utils.h"
+#include "content/browser/fileapi/file_system_manager_impl.h"
 #include "content/browser/frame_host/cross_process_frame_connector.h"
 #include "content/browser/frame_host/frame_tree_node.h"
 #include "content/browser/frame_host/interstitial_page_impl.h"
@@ -49,7 +50,6 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/web_contents/web_contents_view.h"
 #include "content/common/browser_plugin/browser_plugin_messages.h"
-#include "content/common/fileapi/file_system_messages.h"
 #include "content/common/fileapi/webblob_messages.h"
 #include "content/common/frame_messages.h"
 #include "content/common/frame_visual_properties.h"
@@ -105,6 +105,7 @@
 #include "services/service_manager/public/cpp/connector.h"
 #include "storage/browser/fileapi/file_system_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
 #include "ui/base/clipboard/clipboard.h"
 #include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -2388,6 +2389,21 @@
       process->GetChannel(), BlobHostMsg_RegisterPublicURL(url, uuid));
 }
 
+namespace {
+blink::mojom::FileSystemManagerPtr GetFileSystemManager(
+    RenderProcessHost* rph) {
+  FileSystemManagerImpl* file_system = static_cast<RenderProcessHostImpl*>(rph)
+                                           ->GetFileSystemManagerForTesting();
+  blink::mojom::FileSystemManagerPtr file_system_manager_ptr;
+  BrowserThread::PostTask(
+      BrowserThread::IO, FROM_HERE,
+      base::BindOnce(&FileSystemManagerImpl::BindRequest,
+                     base::Unretained(file_system),
+                     mojo::MakeRequest(&file_system_manager_ptr)));
+  return file_system_manager_ptr;
+}
+}  // namespace
+
 // static
 void PwnMessageHelper::FileSystemCreate(RenderProcessHost* process,
                                         int request_id,
@@ -2395,17 +2411,14 @@
                                         bool exclusive,
                                         bool is_directory,
                                         bool recursive) {
-  TestFileapiOperationWaiter waiter(
-      process->GetStoragePartition()->GetFileSystemContext());
-
-  IPC::IpcSecurityTestUtil::PwnMessageReceived(
-      process->GetChannel(),
-      FileSystemHostMsg_Create(request_id, path, exclusive, is_directory,
-                               recursive));
-
-  // If this started an async operation, wait for it to complete.
-  if (waiter.did_start_update())
-    waiter.WaitForEndUpdate();
+  TestFileapiOperationWaiter waiter;
+  blink::mojom::FileSystemManagerPtr file_system_manager_ptr =
+      GetFileSystemManager(process);
+  file_system_manager_ptr->Create(
+      path, exclusive, is_directory, recursive,
+      base::BindOnce(&TestFileapiOperationWaiter::DidCreate,
+                     base::Unretained(&waiter)));
+  waiter.WaitForOperationToFinish();
 }
 
 // static
@@ -2414,16 +2427,18 @@
                                        GURL file_path,
                                        std::string blob_uuid,
                                        int64_t position) {
-  TestFileapiOperationWaiter waiter(
-      process->GetStoragePartition()->GetFileSystemContext());
+  TestFileapiOperationWaiter waiter;
+  blink::mojom::FileSystemManagerPtr file_system_manager_ptr =
+      GetFileSystemManager(process);
+  blink::mojom::FileSystemOperationListenerPtr listener_ptr;
+  mojo::Binding<blink::mojom::FileSystemOperationListener> binding(
+      &waiter, mojo::MakeRequest(&listener_ptr));
+  blink::mojom::FileSystemCancellableOperationPtr op_ptr;
 
-  IPC::IpcSecurityTestUtil::PwnMessageReceived(
-      process->GetChannel(),
-      FileSystemHostMsg_Write(request_id, file_path, blob_uuid, position));
-
-  // If this started an async operation, wait for it to complete.
-  if (waiter.did_start_update())
-    waiter.WaitForEndUpdate();
+  file_system_manager_ptr->Write(file_path, blob_uuid, position,
+                                 mojo::MakeRequest(&op_ptr),
+                                 std::move(listener_ptr));
+  waiter.WaitForOperationToFinish();
 }
 
 void PwnMessageHelper::LockMouse(RenderProcessHost* process,
diff --git a/content/public/test/test_fileapi_operation_waiter.cc b/content/public/test/test_fileapi_operation_waiter.cc
index 211e88d8..1c5bfc5 100644
--- a/content/public/test/test_fileapi_operation_waiter.cc
+++ b/content/public/test/test_fileapi_operation_waiter.cc
@@ -5,109 +5,36 @@
 #include "content/public/test/test_fileapi_operation_waiter.h"
 
 #include "base/bind_helpers.h"
-#include "base/lazy_instance.h"
-#include "base/observer_list.h"
-#include "content/public/browser/browser_thread.h"
-#include "storage/browser/fileapi/file_system_context.h"
-#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
 
 namespace content {
 
-using storage::FileSystemContext;
-using storage::FileSystemURL;
-using storage::FileUpdateObserver;
+TestFileapiOperationWaiter::TestFileapiOperationWaiter() = default;
 
-namespace {
+TestFileapiOperationWaiter::~TestFileapiOperationWaiter() = default;
 
-// Because of how fileapi internally creates copies of its observer lists,
-// removing an observer is not a supported operation. So to support temporary,
-// test-style observers, we create one long-lived global observer instance that
-// dispatches to a list of short-lived observers.
-//
-// This object operates on the UI thread, though it registers itself as an
-// observer on the IO thread.
-class FileUpdateObserverMultiplexer : public FileUpdateObserver {
- public:
-  FileUpdateObserverMultiplexer() {}
-
-  void AddObserver(FileSystemContext* context, FileUpdateObserver* observer) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-
-    // On first initialization, install ourself as an observer. We never
-    // uninstall, because we expect to leak.
-    if (!context_) {
-      // Currently we only listen to kFileSystemTypeTemporary; it should be fine
-      // to add other filesystem types as needed.
-      context_ = context;
-      base::Closure task = base::Bind(
-          &storage::SandboxFileSystemBackendDelegate::AddFileUpdateObserver,
-          base::Unretained(context_->sandbox_delegate()),
-          storage::kFileSystemTypeTemporary, base::Unretained(this),
-          base::RetainedRef(
-              BrowserThread::GetTaskRunnerForThread(BrowserThread::UI)));
-      BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, std::move(task));
-    }
-
-    CHECK_EQ(context, context_) << "Multiprofile is not implemented";
-
-    observers_.AddObserver(observer);
-  }
-
-  void RemoveObserver(FileUpdateObserver* observer) {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    observers_.RemoveObserver(observer);
-  }
-
-  // FileUpdateObserver overrides:
-  void OnStartUpdate(const FileSystemURL& url) override {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    for (auto& observer : observers_)
-      observer.OnStartUpdate(url);
-  }
-  void OnUpdate(const FileSystemURL& url, int64_t delta) override {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    for (auto& observer : observers_)
-      observer.OnUpdate(url, delta);
-  }
-  void OnEndUpdate(const FileSystemURL& url) override {
-    DCHECK_CURRENTLY_ON(BrowserThread::UI);
-    for (auto& observer : observers_)
-      observer.OnEndUpdate(url);
-  }
-
- private:
-  FileSystemContext* context_ = nullptr;
-  base::ObserverList<FileUpdateObserver> observers_;
-  DISALLOW_COPY_AND_ASSIGN(FileUpdateObserverMultiplexer);
-};
-
-static base::LazyInstance<FileUpdateObserverMultiplexer>::Leaky g_multiplexer =
-    LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-TestFileapiOperationWaiter::TestFileapiOperationWaiter(
-    FileSystemContext* context) {
-  g_multiplexer.Get().AddObserver(context, this);
-}
-
-TestFileapiOperationWaiter::~TestFileapiOperationWaiter() {
-  g_multiplexer.Get().RemoveObserver(this);
-}
-
-void TestFileapiOperationWaiter::WaitForEndUpdate() {
+void TestFileapiOperationWaiter::WaitForOperationToFinish() {
   run_loop_.Run();
 }
 
-void TestFileapiOperationWaiter::OnStartUpdate(const FileSystemURL& url) {
-  did_start_update_ = true;
+void TestFileapiOperationWaiter::DidCreate(base::File::Error error_code) {
+  run_loop_.Quit();
 }
 
-void TestFileapiOperationWaiter::OnUpdate(const FileSystemURL& url,
-                                          int64_t delta) {}
+void TestFileapiOperationWaiter::ResultsRetrieved(
+    std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
+    bool has_more) {
+  // blink::mojom::FileSystemManager::ReadDirectory isn't being called by tests
+  // right now, so this callback shouldn't be called.
+  NOTREACHED();
+}
 
-void TestFileapiOperationWaiter::OnEndUpdate(const FileSystemURL& url) {
+void TestFileapiOperationWaiter::ErrorOccurred(base::File::Error error_code) {
   run_loop_.Quit();
 }
 
+void TestFileapiOperationWaiter::DidWrite(int64_t byte_count, bool complete) {
+  if (complete)
+    run_loop_.Quit();
+}
+
 }  // namespace content
diff --git a/content/public/test/test_fileapi_operation_waiter.h b/content/public/test/test_fileapi_operation_waiter.h
index 48ff6d9..18313fa 100644
--- a/content/public/test/test_fileapi_operation_waiter.h
+++ b/content/public/test/test_fileapi_operation_waiter.h
@@ -8,37 +8,35 @@
 #include <stdint.h>
 
 #include "base/compiler_specific.h"
+#include "base/files/file.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "storage/browser/fileapi/file_observers.h"
-
-namespace storage {
-class FileSystemContext;
-class FileSystemURL;
-}
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
 
 namespace content {
 
 // Installs a temporary storage::FileUpdateObserver for use in browser tests
 // that need to wait for a specific fileapi operation to complete.
-class TestFileapiOperationWaiter : public storage::FileUpdateObserver {
+class TestFileapiOperationWaiter
+    : public blink::mojom::FileSystemOperationListener {
  public:
-  explicit TestFileapiOperationWaiter(storage::FileSystemContext* context);
+  TestFileapiOperationWaiter();
   ~TestFileapiOperationWaiter() override;
 
-  // Returns true if OnStartUpdate has occurred.
-  bool did_start_update() { return did_start_update_; }
+  void WaitForOperationToFinish();
 
-  // Wait for the next OnEndUpdate event.
-  void WaitForEndUpdate();
+  // Callback for passing into blink::mojom::FileSystem::Create.
+  void DidCreate(base::File::Error error_code);
 
-  // FileUpdateObserver overrides.
-  void OnStartUpdate(const storage::FileSystemURL& url) override;
-  void OnUpdate(const storage::FileSystemURL& url, int64_t delta) override;
-  void OnEndUpdate(const storage::FileSystemURL& url) override;
+  // blink::mojom::FileSystemOperationListener
+  void ResultsRetrieved(
+      std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
+      bool has_more) override;
+  void ErrorOccurred(base::File::Error error_code) override;
+  void DidWrite(int64_t byte_count, bool complete) override;
 
  private:
-  bool did_start_update_ = false;
   base::RunLoop run_loop_;
 
   DISALLOW_COPY_AND_ASSIGN(TestFileapiOperationWaiter);
diff --git a/content/renderer/fileapi/file_system_dispatcher.cc b/content/renderer/fileapi/file_system_dispatcher.cc
index 77d8e5b..6f3bdb1 100644
--- a/content/renderer/fileapi/file_system_dispatcher.cc
+++ b/content/renderer/fileapi/file_system_dispatcher.cc
@@ -14,8 +14,10 @@
 #include "base/process/process.h"
 #include "components/services/filesystem/public/interfaces/types.mojom.h"
 #include "content/child/child_thread_impl.h"
-#include "content/common/fileapi/file_system_messages.h"
+#include "content/public/common/service_names.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "storage/common/fileapi/file_system_info.h"
+#include "storage/common/fileapi/file_system_type_converters.h"
 
 namespace content {
 
@@ -85,16 +87,17 @@
     error_callback_.Run(error_code);
   }
 
-  void DidReadMetadata(
-      const base::File::Info& file_info) {
+  void DidReadMetadata(const base::File::Info& file_info) {
     metadata_callback_.Run(file_info);
   }
 
   void DidCreateSnapshotFile(
       const base::File::Info& file_info,
       const base::FilePath& platform_path,
+      base::Optional<blink::mojom::ReceivedSnapshotListenerPtr> opt_listener,
       int request_id) {
-    snapshot_callback_.Run(file_info, platform_path, request_id);
+    snapshot_callback_.Run(file_info, platform_path, std::move(opt_listener),
+                           request_id);
   }
 
   void DidReadDirectory(
@@ -103,8 +106,7 @@
     directory_callback_.Run(entries, has_more);
   }
 
-  void DidOpenFileSystem(const std::string& name,
-                         const GURL& root) {
+  void DidOpenFileSystem(const std::string& name, const GURL& root) {
     filesystem_callback_.Run(name, root);
   }
 
@@ -134,8 +136,26 @@
   DISALLOW_COPY_AND_ASSIGN(CallbackDispatcher);
 };
 
-FileSystemDispatcher::FileSystemDispatcher() {
-}
+class FileSystemDispatcher::FileSystemOperationListenerImpl
+    : public blink::mojom::FileSystemOperationListener {
+ public:
+  FileSystemOperationListenerImpl(int request_id,
+                                  FileSystemDispatcher* dispatcher)
+      : request_id_(request_id), dispatcher_(dispatcher) {}
+
+ private:
+  // blink::mojom::FileSystemOperationListener
+  void ResultsRetrieved(
+      std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
+      bool has_more) override;
+  void ErrorOccurred(base::File::Error error_code) override;
+  void DidWrite(int64_t byte_count, bool complete) override;
+
+  const int request_id_;
+  FileSystemDispatcher* const dispatcher_;
+};
+
+FileSystemDispatcher::FileSystemDispatcher() {}
 
 FileSystemDispatcher::~FileSystemDispatcher() {
   // Make sure we fire all the remaining callbacks.
@@ -150,21 +170,16 @@
   }
 }
 
-bool FileSystemDispatcher::OnMessageReceived(const IPC::Message& msg) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(FileSystemDispatcher, msg)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidOpenFileSystem, OnDidOpenFileSystem)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidResolveURL, OnDidResolveURL)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidSucceed, OnDidSucceed)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidReadDirectory, OnDidReadDirectory)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidReadMetadata, OnDidReadMetadata)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidCreateSnapshotFile,
-                        OnDidCreateSnapshotFile)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidFail, OnDidFail)
-    IPC_MESSAGE_HANDLER(FileSystemMsg_DidWrite, OnDidWrite)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-  return handled;
+blink::mojom::FileSystemManager& FileSystemDispatcher::GetFileSystemManager() {
+  if (!file_system_manager_ptr_) {
+    ChildThreadImpl::current()->GetConnector()->BindInterface(
+        mojom::kBrowserServiceName,
+        mojo::MakeRequest(&file_system_manager_ptr_));
+    file_system_manager_ptr_.set_connection_error_handler(
+        base::BindOnce(&FileSystemDispatcher::OnConnectionErrorHandler,
+                       base::Unretained(this)));
+  }
+  return *file_system_manager_ptr_;
 }
 
 void FileSystemDispatcher::OpenFileSystem(
@@ -174,8 +189,10 @@
     const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_OpenFileSystem(
-      request_id, origin_url, type));
+  GetFileSystemManager().Open(
+      origin_url, mojo::ConvertTo<blink::mojom::FileSystemType>(type),
+      base::BindOnce(&FileSystemDispatcher::DidOpenFileSystem,
+                     base::Unretained(this), request_id));
 }
 
 void FileSystemDispatcher::ResolveURL(
@@ -184,35 +201,39 @@
     const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_ResolveURL(
-          request_id, filesystem_url));
+  GetFileSystemManager().ResolveURL(
+      filesystem_url, base::BindOnce(&FileSystemDispatcher::DidResolveURL,
+                                     base::Unretained(this), request_id));
 }
 
-void FileSystemDispatcher::Move(
-    const GURL& src_path,
-    const GURL& dest_path,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::Move(const GURL& src_path,
+                                const GURL& dest_path,
+                                const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_Move(
-          request_id, src_path, dest_path));
+  GetFileSystemManager().Move(
+      src_path, dest_path,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
-void FileSystemDispatcher::Copy(
-    const GURL& src_path,
-    const GURL& dest_path,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::Copy(const GURL& src_path,
+                                const GURL& dest_path,
+                                const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_Copy(
-      request_id, src_path, dest_path));
+  GetFileSystemManager().Copy(
+      src_path, dest_path,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
-void FileSystemDispatcher::Remove(
-    const GURL& path,
-    bool recursive,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::Remove(const GURL& path,
+                                  bool recursive,
+                                  const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_Remove(request_id, path, recursive));
+  GetFileSystemManager().Remove(
+      path, recursive,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
 void FileSystemDispatcher::ReadMetadata(
@@ -221,37 +242,40 @@
     const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_ReadMetadata(request_id, path));
+  GetFileSystemManager().ReadMetadata(
+      path, base::BindOnce(&FileSystemDispatcher::DidReadMetadata,
+                           base::Unretained(this), request_id));
 }
 
-void FileSystemDispatcher::CreateFile(
-    const GURL& path,
-    bool exclusive,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::CreateFile(const GURL& path,
+                                      bool exclusive,
+                                      const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_Create(
-      request_id, path, exclusive,
-      false /* is_directory */, false /* recursive */));
+  GetFileSystemManager().Create(
+      path, exclusive, false, false,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
-void FileSystemDispatcher::CreateDirectory(
-    const GURL& path,
-    bool exclusive,
-    bool recursive,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::CreateDirectory(const GURL& path,
+                                           bool exclusive,
+                                           bool recursive,
+                                           const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_Create(
-      request_id, path, exclusive, true /* is_directory */, recursive));
+  GetFileSystemManager().Create(
+      path, exclusive, true, recursive,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
-void FileSystemDispatcher::Exists(
-    const GURL& path,
-    bool is_directory,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::Exists(const GURL& path,
+                                  bool is_directory,
+                                  const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_Exists(request_id, path, is_directory));
+  GetFileSystemManager().Exists(
+      path, is_directory,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
 void FileSystemDispatcher::ReadDirectory(
@@ -260,8 +284,13 @@
     const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_ReadDirectory(request_id, path));
+  blink::mojom::FileSystemOperationListenerPtr ptr;
+  blink::mojom::FileSystemOperationListenerRequest request =
+      mojo::MakeRequest(&ptr);
+  op_listeners_.AddBinding(
+      std::make_unique<FileSystemOperationListenerImpl>(request_id, this),
+      std::move(request));
+  GetFileSystemManager().ReadDirectory(path, std::move(ptr));
 }
 
 void FileSystemDispatcher::Truncate(const GURL& path,
@@ -269,8 +298,17 @@
                                     int* request_id_out,
                                     const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_Truncate(request_id, path, offset));
+  blink::mojom::FileSystemCancellableOperationPtr op_ptr;
+  blink::mojom::FileSystemCancellableOperationRequest op_request =
+      mojo::MakeRequest(&op_ptr);
+  op_ptr.set_connection_error_handler(
+      base::BindOnce(&FileSystemDispatcher::RemoveOperationPtr,
+                     base::Unretained(this), request_id));
+  cancellable_operations_[request_id] = std::move(op_ptr);
+  GetFileSystemManager().Truncate(
+      path, offset, std::move(op_request),
+      base::BindOnce(&FileSystemDispatcher::DidTruncate, base::Unretained(this),
+                     request_id));
 
   if (request_id_out)
     *request_id_out = request_id;
@@ -284,30 +322,51 @@
                                  const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_Write(request_id, path, blob_id, offset));
+
+  blink::mojom::FileSystemCancellableOperationPtr op_ptr;
+  blink::mojom::FileSystemCancellableOperationRequest op_request =
+      mojo::MakeRequest(&op_ptr);
+  op_ptr.set_connection_error_handler(
+      base::BindOnce(&FileSystemDispatcher::RemoveOperationPtr,
+                     base::Unretained(this), request_id));
+  cancellable_operations_[request_id] = std::move(op_ptr);
+
+  blink::mojom::FileSystemOperationListenerPtr listener_ptr;
+  blink::mojom::FileSystemOperationListenerRequest request =
+      mojo::MakeRequest(&listener_ptr);
+  op_listeners_.AddBinding(
+      std::make_unique<FileSystemOperationListenerImpl>(request_id, this),
+      std::move(request));
+
+  GetFileSystemManager().Write(path, blob_id, offset, std::move(op_request),
+                               std::move(listener_ptr));
 
   if (request_id_out)
     *request_id_out = request_id;
 }
 
-void FileSystemDispatcher::Cancel(
-    int request_id_to_cancel,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::Cancel(int request_id_to_cancel,
+                                  const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(new FileSystemHostMsg_CancelWrite(
-      request_id, request_id_to_cancel));
+  if (cancellable_operations_.find(request_id_to_cancel) ==
+      cancellable_operations_.end()) {
+    DidFail(request_id, base::File::FILE_ERROR_INVALID_OPERATION);
+    return;
+  }
+  cancellable_operations_[request_id_to_cancel]->Cancel(
+      base::BindOnce(&FileSystemDispatcher::DidCancel, base::Unretained(this),
+                     request_id, request_id_to_cancel));
 }
 
-void FileSystemDispatcher::TouchFile(
-    const GURL& path,
-    const base::Time& last_access_time,
-    const base::Time& last_modified_time,
-    const StatusCallback& callback) {
+void FileSystemDispatcher::TouchFile(const GURL& path,
+                                     const base::Time& last_access_time,
+                                     const base::Time& last_modified_time,
+                                     const StatusCallback& callback) {
   int request_id = dispatchers_.Add(CallbackDispatcher::Create(callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_TouchFile(
-          request_id, path, last_access_time, last_modified_time));
+  GetFileSystemManager().TouchFile(
+      path, last_access_time, last_modified_time,
+      base::BindOnce(&FileSystemDispatcher::DidFinish, base::Unretained(this),
+                     request_id));
 }
 
 void FileSystemDispatcher::CreateSnapshotFile(
@@ -316,83 +375,156 @@
     const StatusCallback& error_callback) {
   int request_id = dispatchers_.Add(
       CallbackDispatcher::Create(success_callback, error_callback));
-  ChildThreadImpl::current()->Send(
-      new FileSystemHostMsg_CreateSnapshotFile(
-          request_id, file_path));
+  GetFileSystemManager().CreateSnapshotFile(
+      file_path, base::BindOnce(&FileSystemDispatcher::DidCreateSnapshotFile,
+                                base::Unretained(this), request_id));
 }
 
-void FileSystemDispatcher::OnDidOpenFileSystem(int request_id,
-                                               const std::string& name,
-                                               const GURL& root) {
-  DCHECK(root.is_valid());
-  CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
-  DCHECK(dispatcher);
-  dispatcher->DidOpenFileSystem(name, root);
-  dispatchers_.Remove(request_id);
+void FileSystemDispatcher::DidOpenFileSystem(int request_id,
+                                             const std::string& name,
+                                             const GURL& root,
+                                             base::File::Error error_code) {
+  if (error_code == base::File::Error::FILE_OK) {
+    CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
+    DCHECK(dispatcher);
+    dispatcher->DidOpenFileSystem(name, root);
+    dispatchers_.Remove(request_id);
+  } else {
+    DidFail(request_id, error_code);
+  }
 }
 
-void FileSystemDispatcher::OnDidResolveURL(int request_id,
-                                           const storage::FileSystemInfo& info,
-                                           const base::FilePath& file_path,
-                                           bool is_directory) {
-  DCHECK(info.root_url.is_valid());
-  CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
-  DCHECK(dispatcher);
-  dispatcher->DidResolveURL(info, file_path, is_directory);
-  dispatchers_.Remove(request_id);
+void FileSystemDispatcher::DidResolveURL(int request_id,
+                                         blink::mojom::FileSystemInfoPtr info,
+                                         const base::FilePath& file_path,
+                                         bool is_directory,
+                                         base::File::Error error_code) {
+  if (error_code == base::File::Error::FILE_OK) {
+    DCHECK(info->root_url.is_valid());
+    CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
+    DCHECK(dispatcher);
+    dispatcher->DidResolveURL(mojo::ConvertTo<storage::FileSystemInfo>(info),
+                              file_path, is_directory);
+    dispatchers_.Remove(request_id);
+  } else {
+    DidFail(request_id, error_code);
+  }
 }
 
-void FileSystemDispatcher::OnDidSucceed(int request_id) {
-  CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
-  DCHECK(dispatcher);
-  dispatcher->DidSucceed();
-  dispatchers_.Remove(request_id);
+void FileSystemDispatcher::DidFinish(int request_id,
+                                     base::File::Error error_code) {
+  if (error_code == base::File::Error::FILE_OK) {
+    CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
+    DCHECK(dispatcher);
+    dispatcher->DidSucceed();
+    dispatchers_.Remove(request_id);
+  } else {
+    DidFail(request_id, error_code);
+  }
 }
 
-void FileSystemDispatcher::OnDidReadMetadata(
-    int request_id, const base::File::Info& file_info) {
-  CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
-  DCHECK(dispatcher);
-  dispatcher->DidReadMetadata(file_info);
-  dispatchers_.Remove(request_id);
+void FileSystemDispatcher::DidReadMetadata(int request_id,
+                                           const base::File::Info& file_info,
+                                           base::File::Error error_code) {
+  if (error_code == base::File::FILE_OK) {
+    CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
+    DCHECK(dispatcher);
+    dispatcher->DidReadMetadata(file_info);
+    dispatchers_.Remove(request_id);
+  } else {
+    DidFail(request_id, error_code);
+  }
 }
 
-void FileSystemDispatcher::OnDidCreateSnapshotFile(
-    int request_id, const base::File::Info& file_info,
-    const base::FilePath& platform_path) {
-  CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
-  DCHECK(dispatcher);
-  dispatcher->DidCreateSnapshotFile(file_info, platform_path, request_id);
-  dispatchers_.Remove(request_id);
-}
-
-void FileSystemDispatcher::OnDidReadDirectory(
+void FileSystemDispatcher::DidCreateSnapshotFile(
     int request_id,
-    const std::vector<filesystem::mojom::DirectoryEntry>& entries,
+    const base::File::Info& file_info,
+    const base::FilePath& platform_path,
+    base::File::Error error_code,
+    blink::mojom::ReceivedSnapshotListenerPtr listener) {
+  base::Optional<blink::mojom::ReceivedSnapshotListenerPtr> opt_listener;
+  if (listener)
+    opt_listener = std::move(listener);
+  if (error_code == base::File::FILE_OK) {
+    CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
+    DCHECK(dispatcher);
+    dispatcher->DidCreateSnapshotFile(file_info, platform_path,
+                                      std::move(opt_listener), request_id);
+    dispatchers_.Remove(request_id);
+  } else {
+    DidFail(request_id, error_code);
+  }
+}
+
+void FileSystemDispatcher::DidReadDirectory(
+    int request_id,
+    std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
     bool has_more) {
   CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
   DCHECK(dispatcher);
-  dispatcher->DidReadDirectory(entries, has_more);
-  if (!has_more)
-    dispatchers_.Remove(request_id);
+  std::vector<filesystem::mojom::DirectoryEntry> entries_copy;
+  for (const auto& entry : entries) {
+    entries_copy.push_back(*entry);
+  }
+  dispatcher->DidReadDirectory(std::move(entries_copy), has_more);
 }
 
-void FileSystemDispatcher::OnDidFail(
-    int request_id, base::File::Error error_code) {
+void FileSystemDispatcher::DidFail(int request_id,
+                                   base::File::Error error_code) {
   CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
   DCHECK(dispatcher);
   dispatcher->DidFail(error_code);
   dispatchers_.Remove(request_id);
 }
 
-void FileSystemDispatcher::OnDidWrite(int request_id,
-                                      int64_t bytes,
-                                      bool complete) {
+void FileSystemDispatcher::DidWrite(int request_id,
+                                    int64_t bytes,
+                                    bool complete) {
   CallbackDispatcher* dispatcher = dispatchers_.Lookup(request_id);
   DCHECK(dispatcher);
   dispatcher->DidWrite(bytes, complete);
-  if (complete)
+  if (complete) {
     dispatchers_.Remove(request_id);
+    RemoveOperationPtr(request_id);
+  }
+}
+
+void FileSystemDispatcher::DidTruncate(int request_id,
+                                       base::File::Error error_code) {
+  // If |error_code| is ABORT, it means the operation was cancelled,
+  // so we let DidCancel clean up the interface pointer.
+  if (error_code != base::File::FILE_ERROR_ABORT)
+    RemoveOperationPtr(request_id);
+  DidFinish(request_id, error_code);
+}
+
+void FileSystemDispatcher::DidCancel(int request_id,
+                                     int cancelled_request_id,
+                                     base::File::Error error_code) {
+  if (error_code == base::File::FILE_OK)
+    RemoveOperationPtr(cancelled_request_id);
+  DidFinish(request_id, error_code);
+}
+
+void FileSystemDispatcher::OnConnectionErrorHandler() {
+  file_system_manager_ptr_.reset();
+}
+
+void FileSystemDispatcher::FileSystemOperationListenerImpl::ResultsRetrieved(
+    std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
+    bool has_more) {
+  dispatcher_->DidReadDirectory(request_id_, std::move(entries), has_more);
+}
+
+void FileSystemDispatcher::FileSystemOperationListenerImpl::ErrorOccurred(
+    base::File::Error error_code) {
+  dispatcher_->DidFail(request_id_, error_code);
+}
+
+void FileSystemDispatcher::FileSystemOperationListenerImpl::DidWrite(
+    int64_t byte_count,
+    bool complete) {
+  dispatcher_->DidWrite(request_id_, byte_count, complete);
 }
 
 }  // namespace content
diff --git a/content/renderer/fileapi/file_system_dispatcher.h b/content/renderer/fileapi/file_system_dispatcher.h
index 676e471..c913ad15 100644
--- a/content/renderer/fileapi/file_system_dispatcher.h
+++ b/content/renderer/fileapi/file_system_dispatcher.h
@@ -18,8 +18,10 @@
 #include "components/services/filesystem/public/interfaces/types.mojom.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_platform_file.h"
+#include "mojo/public/cpp/bindings/strong_binding_set.h"
 #include "storage/common/fileapi/file_system_types.h"
 #include "storage/common/quota/quota_limit_type.h"
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
 
 namespace base {
 class FilePath;
@@ -32,18 +34,21 @@
 class GURL;
 
 namespace content {
+class FileSystemOperationListenerImpl;
 
 // Dispatches and sends file system related messages sent to/from a child
 // process from/to the main browser process.  There is one instance
-// per child process.  Messages are dispatched on the main child thread.
-class FileSystemDispatcher : public IPC::Listener {
+// per child process.
+class FileSystemDispatcher {
  public:
   typedef base::Callback<void(base::File::Error error)> StatusCallback;
   typedef base::Callback<void(const base::File::Info& file_info)>
       MetadataCallback;
-  typedef base::Callback<void(const base::File::Info& file_info,
-                              const base::FilePath& platform_path,
-                              int request_id)>
+  typedef base::Callback<void(
+      const base::File::Info& file_info,
+      const base::FilePath& platform_path,
+      base::Optional<blink::mojom::ReceivedSnapshotListenerPtr> opt_listener,
+      int request_id)>
       CreateSnapshotFileCallback;
 
   typedef base::Callback<void(
@@ -64,10 +69,7 @@
       OpenFileCallback;
 
   FileSystemDispatcher();
-  ~FileSystemDispatcher() override;
-
-  // IPC::Listener implementation.
-  bool OnMessageReceived(const IPC::Message& msg) override;
+  ~FileSystemDispatcher();
 
   void OpenFileSystem(const GURL& origin_url,
                       storage::FileSystemType type,
@@ -114,40 +116,65 @@
                  const base::Time& last_access_time,
                  const base::Time& last_modified_time,
                  const StatusCallback& callback);
-
-  // The caller must send FileSystemHostMsg_DidReceiveSnapshot message
-  // with |request_id| passed to |success_callback| after the snapshot file
-  // is successfully received.
   void CreateSnapshotFile(const GURL& file_path,
                           const CreateSnapshotFileCallback& success_callback,
                           const StatusCallback& error_callback);
 
  private:
   class CallbackDispatcher;
+  class FileSystemOperationListenerImpl;
 
-  // Message handlers.
-  void OnDidOpenFileSystem(int request_id,
-                           const std::string& name,
-                           const GURL& root);
-  void OnDidResolveURL(int request_id,
-                       const storage::FileSystemInfo& info,
-                       const base::FilePath& file_path,
-                       bool is_directory);
-  void OnDidSucceed(int request_id);
-  void OnDidReadMetadata(int request_id,
-                         const base::File::Info& file_info);
-  void OnDidCreateSnapshotFile(int request_id,
-                               const base::File::Info& file_info,
-                               const base::FilePath& platform_path);
-  void OnDidReadDirectory(
+  void DidOpenFileSystem(int request_id,
+                         const std::string& name,
+                         const GURL& root,
+                         base::File::Error error_code);
+  void DidResolveURL(int request_id,
+                     blink::mojom::FileSystemInfoPtr info,
+                     const base::FilePath& file_path,
+                     bool is_directory,
+                     base::File::Error error_code);
+  void DidFinish(int request_id, base::File::Error error_code);
+  void DidReadMetadata(int request_id,
+                       const base::File::Info& file_info,
+                       base::File::Error error);
+  void DidCreateSnapshotFile(
       int request_id,
-      const std::vector<filesystem::mojom::DirectoryEntry>& entries,
+      const base::File::Info& file_info,
+      const base::FilePath& platform_path,
+      base::File::Error error_code,
+      blink::mojom::ReceivedSnapshotListenerPtr listener);
+  void DidReadDirectory(
+      int request_id,
+      std::vector<filesystem::mojom::DirectoryEntryPtr> entries,
       bool has_more);
-  void OnDidFail(int request_id, base::File::Error error_code);
-  void OnDidWrite(int request_id, int64_t bytes, bool complete);
+  void DidFail(int request_id, base::File::Error error_code);
+  void DidWrite(int request_id, int64_t bytes, bool complete);
+  void DidTruncate(int request_id, base::File::Error error_code);
+  void DidCancel(int request_id,
+                 int cancelled_request_id,
+                 base::File::Error error_code);
+
+  void RemoveOperationPtr(int request_id) {
+    DCHECK(cancellable_operations_.find(request_id) !=
+           cancellable_operations_.end());
+    cancellable_operations_.erase(request_id);
+  }
+
+  void OnConnectionErrorHandler();
+
+  blink::mojom::FileSystemManager& GetFileSystemManager();
+
+  blink::mojom::FileSystemManagerPtr file_system_manager_ptr_;
 
   base::IDMap<std::unique_ptr<CallbackDispatcher>> dispatchers_;
 
+  mojo::StrongBindingSet<blink::mojom::FileSystemOperationListener>
+      op_listeners_;
+
+  using OperationsMap =
+      std::unordered_map<int, blink::mojom::FileSystemCancellableOperationPtr>;
+  OperationsMap cancellable_operations_;
+
   DISALLOW_COPY_AND_ASSIGN(FileSystemDispatcher);
 };
 
diff --git a/content/renderer/fileapi/webfilesystem_impl.cc b/content/renderer/fileapi/webfilesystem_impl.cc
index f5969dc7..95ed24c1 100644
--- a/content/renderer/fileapi/webfilesystem_impl.cc
+++ b/content/renderer/fileapi/webfilesystem_impl.cc
@@ -18,7 +18,6 @@
 #include "base/threading/thread_local.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/services/filesystem/public/interfaces/types.mojom.h"
-#include "content/common/fileapi/file_system_messages.h"
 #include "content/renderer/file_info_util.h"
 #include "content/renderer/fileapi/file_system_dispatcher.h"
 #include "content/renderer/fileapi/webfilewriter_impl.h"
@@ -88,12 +87,6 @@
 base::LazyInstance<base::ThreadLocalPointer<WebFileSystemImpl>>::Leaky
     g_webfilesystem_tls = LAZY_INSTANCE_INITIALIZER;
 
-void DidReceiveSnapshotFile(int request_id) {
-  if (ChildThreadImpl::current())
-    ChildThreadImpl::current()->Send(
-        new FileSystemHostMsg_DidReceiveSnapshotFile(request_id));
-}
-
 template <typename Method, typename Params>
 void CallDispatcherOnMainThread(
     const scoped_refptr<base::SingleThreadTaskRunner>& main_thread_task_runner,
@@ -336,6 +329,7 @@
     const scoped_refptr<base::SingleThreadTaskRunner>& main_thread_task_runner,
     const base::File::Info& file_info,
     const base::FilePath& platform_path,
+    base::Optional<blink::mojom::ReceivedSnapshotListenerPtr> opt_listener,
     int request_id) {
   WebFileSystemImpl* filesystem =
       WebFileSystemImpl::ThreadSpecificInstance(nullptr);
@@ -352,8 +346,14 @@
 
   // TODO(michaeln,kinuko): Use ThreadSafeSender when Blob becomes
   // non-bridge model.
-  main_thread_task_runner->PostTask(
-      FROM_HERE, base::BindOnce(&DidReceiveSnapshotFile, request_id));
+  if (opt_listener) {
+    main_thread_task_runner->PostTask(
+        FROM_HERE, base::BindOnce(
+                       [](blink::mojom::ReceivedSnapshotListenerPtr listener) {
+                         listener->DidReceiveSnapshotFile();
+                       },
+                       std::move(opt_listener.value())));
+  }
 }
 
 void CreateSnapshotFileCallbackAdapter(
@@ -363,11 +363,13 @@
     const scoped_refptr<base::SingleThreadTaskRunner>& main_thread_task_runner,
     const base::File::Info& file_info,
     const base::FilePath& platform_path,
+    base::Optional<blink::mojom::ReceivedSnapshotListenerPtr> opt_listener,
     int request_id) {
   DispatchResultsClosure(
       task_runner, callbacks_id, waitable_results,
       base::Bind(&DidCreateSnapshotFile, callbacks_id, main_thread_task_runner,
-                 file_info, platform_path, request_id));
+                 file_info, platform_path, base::Passed(&opt_listener),
+                 request_id));
 }
 
 }  // namespace
diff --git a/content/renderer/indexed_db/indexed_db_callbacks_impl.cc b/content/renderer/indexed_db/indexed_db_callbacks_impl.cc
index 3c4baafbd..7057e78 100644
--- a/content/renderer/indexed_db/indexed_db_callbacks_impl.cc
+++ b/content/renderer/indexed_db/indexed_db_callbacks_impl.cc
@@ -110,12 +110,10 @@
     std::unique_ptr<WebIDBCallbacks> callbacks,
     int64_t transaction_id,
     const base::WeakPtr<WebIDBCursorImpl>& cursor,
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner,
     scoped_refptr<base::SingleThreadTaskRunner> callback_runner)
     : internal_state_(new InternalState(std::move(callbacks),
                                         transaction_id,
                                         cursor,
-                                        std::move(io_runner),
                                         callback_runner)),
       callback_runner_(std::move(callback_runner)) {}
 
@@ -237,12 +235,10 @@
     std::unique_ptr<blink::WebIDBCallbacks> callbacks,
     int64_t transaction_id,
     const base::WeakPtr<WebIDBCursorImpl>& cursor,
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner,
     scoped_refptr<base::SingleThreadTaskRunner> callback_runner)
     : callbacks_(std::move(callbacks)),
       transaction_id_(transaction_id),
       cursor_(cursor),
-      io_runner_(std::move(io_runner)),
       callback_runner_(std::move(callback_runner)) {
   IndexedDBDispatcher::ThreadSpecificInstance()->RegisterMojoOwnedCallbacks(
       this);
@@ -282,8 +278,8 @@
     blink::WebIDBDataLoss data_loss,
     const std::string& data_loss_message,
     const content::IndexedDBDatabaseMetadata& metadata) {
-  WebIDBDatabase* database = new WebIDBDatabaseImpl(
-      std::move(database_info), io_runner_, callback_runner_);
+  WebIDBDatabase* database =
+      new WebIDBDatabaseImpl(std::move(database_info), callback_runner_);
   WebIDBMetadata web_metadata;
   ConvertDatabaseMetadata(metadata, &web_metadata);
   callbacks_->OnUpgradeNeeded(old_version, database, web_metadata, data_loss,
@@ -296,8 +292,8 @@
     const content::IndexedDBDatabaseMetadata& metadata) {
   WebIDBDatabase* database = nullptr;
   if (database_info.is_valid()) {
-    database = new WebIDBDatabaseImpl(std::move(database_info), io_runner_,
-                                      callback_runner_);
+    database =
+        new WebIDBDatabaseImpl(std::move(database_info), callback_runner_);
   }
 
   WebIDBMetadata web_metadata;
@@ -312,7 +308,7 @@
     const IndexedDBKey& primary_key,
     indexed_db::mojom::ValuePtr value) {
   WebIDBCursorImpl* cursor = new WebIDBCursorImpl(
-      std::move(cursor_info), transaction_id_, io_runner_, callback_runner_);
+      std::move(cursor_info), transaction_id_, callback_runner_);
   callbacks_->OnSuccess(cursor, WebIDBKeyBuilder::Build(key),
                         WebIDBKeyBuilder::Build(primary_key),
                         ConvertValue(value));
diff --git a/content/renderer/indexed_db/indexed_db_callbacks_impl.h b/content/renderer/indexed_db/indexed_db_callbacks_impl.h
index f60f5862..1f41f85 100644
--- a/content/renderer/indexed_db/indexed_db_callbacks_impl.h
+++ b/content/renderer/indexed_db/indexed_db_callbacks_impl.h
@@ -32,7 +32,6 @@
     InternalState(std::unique_ptr<blink::WebIDBCallbacks> callbacks,
                   int64_t transaction_id,
                   const base::WeakPtr<WebIDBCursorImpl>& cursor,
-                  scoped_refptr<base::SingleThreadTaskRunner> io_runner,
                   scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
     ~InternalState();
 
@@ -66,7 +65,6 @@
     std::unique_ptr<blink::WebIDBCallbacks> callbacks_;
     int64_t transaction_id_;
     base::WeakPtr<WebIDBCursorImpl> cursor_;
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner_;
     scoped_refptr<base::SingleThreadTaskRunner> callback_runner_;
 
     DISALLOW_COPY_AND_ASSIGN(InternalState);
@@ -79,7 +77,6 @@
       std::unique_ptr<blink::WebIDBCallbacks> callbacks,
       int64_t transaction_id,
       const base::WeakPtr<WebIDBCursorImpl>& cursor,
-      scoped_refptr<base::SingleThreadTaskRunner> io_runner,
       scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
   ~IndexedDBCallbacksImpl() override;
 
diff --git a/content/renderer/indexed_db/webidbcursor_impl.cc b/content/renderer/indexed_db/webidbcursor_impl.cc
index 797d4d8e..0ee145e 100644
--- a/content/renderer/indexed_db/webidbcursor_impl.cc
+++ b/content/renderer/indexed_db/webidbcursor_impl.cc
@@ -26,50 +26,19 @@
 
 namespace content {
 
-class WebIDBCursorImpl::IOThreadHelper {
- public:
-  explicit IOThreadHelper(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
-  ~IOThreadHelper();
-
-  void Bind(CursorAssociatedPtrInfo cursor_info);
-  void Advance(uint32_t count,
-               std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void Continue(const IndexedDBKey& key,
-                const IndexedDBKey& primary_key,
-                std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void Prefetch(int32_t count,
-                std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void PrefetchReset(int32_t used_prefetches, int32_t unused_prefetches);
-
- private:
-  CallbacksAssociatedPtrInfo GetCallbacksProxy(
-      std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-
-  indexed_db::mojom::CursorAssociatedPtr cursor_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(IOThreadHelper);
-};
-
 WebIDBCursorImpl::WebIDBCursorImpl(
     indexed_db::mojom::CursorAssociatedPtrInfo cursor_info,
     int64_t transaction_id,
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner,
     scoped_refptr<base::SingleThreadTaskRunner> callback_runner)
     : transaction_id_(transaction_id),
-      helper_(new IOThreadHelper(io_runner)),
-      io_runner_(std::move(io_runner)),
       callback_runner_(std::move(callback_runner)),
+      cursor_(std::move(cursor_info)),
       continue_count_(0),
       used_prefetches_(0),
       pending_onsuccess_callbacks_(0),
       prefetch_amount_(kMinPrefetchAmount),
       weak_factory_(this) {
   IndexedDBDispatcher::ThreadSpecificInstance()->RegisterCursor(this);
-  io_runner_->PostTask(FROM_HERE, base::BindOnce(&IOThreadHelper::Bind,
-                                                 base::Unretained(helper_),
-                                                 std::move(cursor_info)));
 }
 
 WebIDBCursorImpl::~WebIDBCursorImpl() {
@@ -78,7 +47,6 @@
   // this object. But, if that ever changed, then we'd need to invalidate
   // any such pointers.
   IndexedDBDispatcher::ThreadSpecificInstance()->UnregisterCursor(this);
-  io_runner_->DeleteSoon(FROM_HERE, helper_);
 }
 
 void WebIDBCursorImpl::Advance(unsigned long count,
@@ -96,11 +64,8 @@
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
       std::move(callbacks), transaction_id_, weak_factory_.GetWeakPtr(),
-      io_runner_, callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::Advance, base::Unretained(helper_), count,
-                     std::move(callbacks_impl)));
+      callback_runner_);
+  cursor_->Advance(count, GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBCursorImpl::Continue(WebIDBKeyView key,
@@ -125,11 +90,9 @@
 
       auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
           std::move(callbacks), transaction_id_, weak_factory_.GetWeakPtr(),
-          io_runner_, callback_runner_);
-      io_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&IOThreadHelper::Prefetch, base::Unretained(helper_),
-                         prefetch_amount_, std::move(callbacks_impl)));
+          callback_runner_);
+      cursor_->Prefetch(prefetch_amount_,
+                        GetCallbacksProxy(std::move(callbacks_impl)));
 
       // Increase prefetch_amount_ exponentially.
       prefetch_amount_ *= 2;
@@ -149,13 +112,10 @@
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
       std::move(callbacks), transaction_id_, weak_factory_.GetWeakPtr(),
-      io_runner_, callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::Continue, base::Unretained(helper_),
-                     IndexedDBKeyBuilder::Build(key),
-                     IndexedDBKeyBuilder::Build(primary_key),
-                     std::move(callbacks_impl)));
+      callback_runner_);
+  cursor_->Continue(IndexedDBKeyBuilder::Build(key),
+                    IndexedDBKeyBuilder::Build(primary_key),
+                    GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBCursorImpl::PostSuccessHandlerCallback() {
@@ -239,10 +199,7 @@
   }
 
   // Reset the back-end cursor.
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::PrefetchReset, base::Unretained(helper_),
-                     used_prefetches_, prefetch_keys_.size()));
+  cursor_->PrefetchReset(used_prefetches_, prefetch_keys_.size());
 
   // Reset the prefetch cache.
   prefetch_keys_.clear();
@@ -252,43 +209,7 @@
   pending_onsuccess_callbacks_ = 0;
 }
 
-WebIDBCursorImpl::IOThreadHelper::IOThreadHelper(
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : task_runner_(std::move(task_runner)) {}
-
-WebIDBCursorImpl::IOThreadHelper::~IOThreadHelper() {}
-
-void WebIDBCursorImpl::IOThreadHelper::Bind(
-    CursorAssociatedPtrInfo cursor_info) {
-  cursor_.Bind(std::move(cursor_info), task_runner_);
-}
-
-void WebIDBCursorImpl::IOThreadHelper::Advance(
-    uint32_t count,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  cursor_->Advance(count, GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBCursorImpl::IOThreadHelper::Continue(
-    const IndexedDBKey& key,
-    const IndexedDBKey& primary_key,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  cursor_->Continue(key, primary_key, GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBCursorImpl::IOThreadHelper::Prefetch(
-    int32_t count,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  cursor_->Prefetch(count, GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBCursorImpl::IOThreadHelper::PrefetchReset(
-    int32_t used_prefetches,
-    int32_t unused_prefetches) {
-  cursor_->PrefetchReset(used_prefetches, unused_prefetches);
-}
-
-CallbacksAssociatedPtrInfo WebIDBCursorImpl::IOThreadHelper::GetCallbacksProxy(
+CallbacksAssociatedPtrInfo WebIDBCursorImpl::GetCallbacksProxy(
     std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
   CallbacksAssociatedPtrInfo ptr_info;
   auto request = mojo::MakeRequest(&ptr_info);
diff --git a/content/renderer/indexed_db/webidbcursor_impl.h b/content/renderer/indexed_db/webidbcursor_impl.h
index 3a6778e..ff277dc5 100644
--- a/content/renderer/indexed_db/webidbcursor_impl.h
+++ b/content/renderer/indexed_db/webidbcursor_impl.h
@@ -27,11 +27,12 @@
 
 namespace content {
 
+class IndexedDBCallbacksImpl;
+
 class CONTENT_EXPORT WebIDBCursorImpl : public blink::WebIDBCursor {
  public:
   WebIDBCursorImpl(indexed_db::mojom::CursorAssociatedPtrInfo cursor,
                    int64_t transaction_id,
-                   scoped_refptr<base::SingleThreadTaskRunner> io_runner,
                    scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
   ~WebIDBCursorImpl() override;
 
@@ -54,14 +55,15 @@
   int64_t transaction_id() const { return transaction_id_; }
 
  private:
+  indexed_db::mojom::CallbacksAssociatedPtrInfo GetCallbacksProxy(
+      std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
+
   FRIEND_TEST_ALL_PREFIXES(IndexedDBDispatcherTest, CursorReset);
   FRIEND_TEST_ALL_PREFIXES(IndexedDBDispatcherTest, CursorTransactionId);
   FRIEND_TEST_ALL_PREFIXES(WebIDBCursorImplTest, AdvancePrefetchTest);
   FRIEND_TEST_ALL_PREFIXES(WebIDBCursorImplTest, PrefetchReset);
   FRIEND_TEST_ALL_PREFIXES(WebIDBCursorImplTest, PrefetchTest);
 
-  class IOThreadHelper;
-
   enum { kInvalidCursorId = -1 };
   enum { kPrefetchContinueThreshold = 2 };
   enum { kMinPrefetchAmount = 5 };
@@ -69,9 +71,8 @@
 
   int64_t transaction_id_;
 
-  IOThreadHelper* helper_;
-  scoped_refptr<base::SingleThreadTaskRunner> io_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> callback_runner_;
+  indexed_db::mojom::CursorAssociatedPtr cursor_;
 
   // Prefetch cache.
   base::circular_deque<IndexedDBKey> prefetch_keys_;
diff --git a/content/renderer/indexed_db/webidbcursor_impl_unittest.cc b/content/renderer/indexed_db/webidbcursor_impl_unittest.cc
index eec2bbac..071d1300 100644
--- a/content/renderer/indexed_db/webidbcursor_impl_unittest.cc
+++ b/content/renderer/indexed_db/webidbcursor_impl_unittest.cc
@@ -125,7 +125,6 @@
         mojo::MakeRequestAssociatedWithDedicatedPipe(&ptr));
     cursor_ = std::make_unique<WebIDBCursorImpl>(
         ptr.PassInterface(), 1,
-        blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
         blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   }
 
diff --git a/content/renderer/indexed_db/webidbdatabase_impl.cc b/content/renderer/indexed_db/webidbdatabase_impl.cc
index 711dc4a..e0e036e 100644
--- a/content/renderer/indexed_db/webidbdatabase_impl.cc
+++ b/content/renderer/indexed_db/webidbdatabase_impl.cc
@@ -62,175 +62,52 @@
 
 }  // namespace
 
-class WebIDBDatabaseImpl::IOThreadHelper {
- public:
-  explicit IOThreadHelper(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
-  ~IOThreadHelper();
-
-  void Bind(DatabaseAssociatedPtrInfo database_info);
-  void CreateObjectStore(int64_t transaction_id,
-                         int64_t object_store_id,
-                         const base::string16& name,
-                         const IndexedDBKeyPath& key_path,
-                         bool auto_increment);
-  void DeleteObjectStore(int64_t transaction_id, int64_t object_store_id);
-  void RenameObjectStore(int64_t transaction_id,
-                         int64_t object_store_id,
-                         const base::string16& new_name);
-  void CreateTransaction(int64_t transaction_id,
-                         const std::vector<int64_t>& object_store_ids,
-                         blink::WebIDBTransactionMode mode);
-  void Close();
-  void VersionChangeIgnored();
-  void AddObserver(int64_t transaction_id,
-                   int32_t observer_id,
-                   bool include_transaction,
-                   bool no_records,
-                   bool values,
-                   uint16_t operation_types);
-  void RemoveObservers(const std::vector<int32_t>& observers);
-  void Get(int64_t transaction_id,
-           int64_t object_store_id,
-           int64_t index_id,
-           const IndexedDBKeyRange& key_range,
-           bool key_only,
-           std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void GetAll(int64_t transaction_id,
-              int64_t object_store_id,
-              int64_t index_id,
-              const IndexedDBKeyRange& key_range,
-              int64_t max_count,
-              bool key_only,
-              std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void Put(int64_t transaction_id,
-           int64_t object_store_id,
-           indexed_db::mojom::ValuePtr value,
-           const IndexedDBKey& key,
-           blink::WebIDBPutMode mode,
-           std::unique_ptr<IndexedDBCallbacksImpl> callbacks,
-           const std::vector<content::IndexedDBIndexKeys>& index_keys);
-  void SetIndexKeys(int64_t transaction_id,
-                    int64_t object_store_id,
-                    const IndexedDBKey& primary_key,
-                    const std::vector<content::IndexedDBIndexKeys>& index_keys);
-  void SetIndexesReady(int64_t transaction_id,
-                       int64_t object_store_id,
-                       const std::vector<int64_t>& index_ids);
-  void OpenCursor(int64_t transaction_id,
-                  int64_t object_store_id,
-                  int64_t index_id,
-                  const IndexedDBKeyRange& key_range,
-                  blink::WebIDBCursorDirection direction,
-                  bool key_only,
-                  blink::WebIDBTaskType task_type,
-                  std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void Count(int64_t transaction_id,
-             int64_t object_store_id,
-             int64_t index_id,
-             const IndexedDBKeyRange& key_range,
-             std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void DeleteRange(int64_t transaction_id,
-                   int64_t object_store_id,
-                   const IndexedDBKeyRange& key_range,
-                   std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void Clear(int64_t transaction_id,
-             int64_t object_store_id,
-             std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-  void CreateIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id,
-                   const base::string16& name,
-                   const IndexedDBKeyPath& key_path,
-                   bool unique,
-                   bool multi_entry);
-  void DeleteIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id);
-  void RenameIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id,
-                   const base::string16& new_name);
-  void Abort(int64_t transaction_id);
-  void Commit(int64_t transaction_id);
-
- private:
-  CallbacksAssociatedPtrInfo GetCallbacksProxy(
-      std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
-
-  indexed_db::mojom::DatabaseAssociatedPtr database_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-
-  DISALLOW_COPY_AND_ASSIGN(IOThreadHelper);
-};
-
 WebIDBDatabaseImpl::WebIDBDatabaseImpl(
     DatabaseAssociatedPtrInfo database_info,
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner,
     scoped_refptr<base::SingleThreadTaskRunner> callback_runner)
-    : helper_(new IOThreadHelper(io_runner)),
-      io_runner_(std::move(io_runner)),
-      callback_runner_(std::move(callback_runner)) {
-  io_runner_->PostTask(FROM_HERE, base::BindOnce(&IOThreadHelper::Bind,
-                                                 base::Unretained(helper_),
-                                                 std::move(database_info)));
-}
+    : callback_runner_(std::move(callback_runner)),
+      database_(std::move(database_info)) {}
 
-WebIDBDatabaseImpl::~WebIDBDatabaseImpl() {
-  io_runner_->DeleteSoon(FROM_HERE, helper_);
-}
+WebIDBDatabaseImpl::~WebIDBDatabaseImpl() = default;
 
 void WebIDBDatabaseImpl::CreateObjectStore(long long transaction_id,
                                            long long object_store_id,
                                            const WebString& name,
                                            const WebIDBKeyPath& key_path,
                                            bool auto_increment) {
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::CreateObjectStore,
-                     base::Unretained(helper_), transaction_id, object_store_id,
-                     name.Utf16(), IndexedDBKeyPathBuilder::Build(key_path),
-                     auto_increment));
+  database_->CreateObjectStore(transaction_id, object_store_id, name.Utf16(),
+                               IndexedDBKeyPathBuilder::Build(key_path),
+                               auto_increment);
 }
 
 void WebIDBDatabaseImpl::DeleteObjectStore(long long transaction_id,
                                            long long object_store_id) {
-  io_runner_->PostTask(FROM_HERE,
-                       base::BindOnce(&IOThreadHelper::DeleteObjectStore,
-                                      base::Unretained(helper_), transaction_id,
-                                      object_store_id));
+  database_->DeleteObjectStore(transaction_id, object_store_id);
 }
 
 void WebIDBDatabaseImpl::RenameObjectStore(long long transaction_id,
                                            long long object_store_id,
                                            const blink::WebString& new_name) {
-  io_runner_->PostTask(FROM_HERE,
-                       base::BindOnce(&IOThreadHelper::RenameObjectStore,
-                                      base::Unretained(helper_), transaction_id,
-                                      object_store_id, new_name.Utf16()));
+  database_->RenameObjectStore(transaction_id, object_store_id,
+                               new_name.Utf16());
 }
 
 void WebIDBDatabaseImpl::CreateTransaction(
     long long transaction_id,
     const WebVector<long long>& object_store_ids,
     blink::WebIDBTransactionMode mode) {
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::CreateTransaction,
-                                base::Unretained(helper_), transaction_id,
-                                std::vector<int64_t>(object_store_ids.begin(),
-                                                     object_store_ids.end()),
-                                mode));
+  database_->CreateTransaction(
+      transaction_id,
+      std::vector<int64_t>(object_store_ids.begin(), object_store_ids.end()),
+      mode);
 }
 
 void WebIDBDatabaseImpl::Close() {
-  io_runner_->PostTask(FROM_HERE, base::BindOnce(&IOThreadHelper::Close,
-                                                 base::Unretained(helper_)));
+  database_->Close();
 }
 
 void WebIDBDatabaseImpl::VersionChangeIgnored() {
-  io_runner_->PostTask(FROM_HERE,
-                       base::BindOnce(&IOThreadHelper::VersionChangeIgnored,
-                                      base::Unretained(helper_)));
+  database_->VersionChangeIgnored();
 }
 
 void WebIDBDatabaseImpl::AddObserver(
@@ -242,11 +119,8 @@
     const std::bitset<blink::kWebIDBOperationTypeCount>& operation_types) {
   static_assert(blink::kWebIDBOperationTypeCount < sizeof(uint16_t) * CHAR_BIT,
                 "WebIDBOperationType Count exceeds size of uint16_t");
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::AddObserver, base::Unretained(helper_),
-                     transaction_id, observer_id, include_transaction,
-                     no_records, values, operation_types.to_ulong()));
+  database_->AddObserver(transaction_id, observer_id, include_transaction,
+                         no_records, values, operation_types.to_ulong());
 }
 
 void WebIDBDatabaseImpl::RemoveObservers(
@@ -254,11 +128,7 @@
   std::vector<int32_t> remove_observer_ids(
       observer_ids_to_remove.Data(),
       observer_ids_to_remove.Data() + observer_ids_to_remove.size());
-
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::RemoveObservers,
-                     base::Unretained(helper_), remove_observer_ids));
+  database_->RemoveObservers(remove_observer_ids);
 }
 
 void WebIDBDatabaseImpl::Get(long long transaction_id,
@@ -271,13 +141,10 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::Get, base::Unretained(helper_),
-                                transaction_id, object_store_id, index_id,
-                                IndexedDBKeyRangeBuilder::Build(key_range),
-                                key_only, std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->Get(transaction_id, object_store_id, index_id,
+                 IndexedDBKeyRangeBuilder::Build(key_range), key_only,
+                 GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::GetAll(long long transaction_id,
@@ -291,14 +158,10 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::GetAll, base::Unretained(helper_),
-                     transaction_id, object_store_id, index_id,
-                     IndexedDBKeyRangeBuilder::Build(key_range), max_count,
-                     key_only, std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->GetAll(transaction_id, object_store_id, index_id,
+                    IndexedDBKeyRangeBuilder::Build(key_range), key_only,
+                    max_count, GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::Put(long long transaction_id,
@@ -353,14 +216,10 @@
   }
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::Put, base::Unretained(helper_),
-                     transaction_id, object_store_id, std::move(mojo_value),
-                     key, put_mode, std::move(callbacks_impl),
-                     ConvertWebIndexKeys(index_ids, index_keys)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->Put(transaction_id, object_store_id, std::move(mojo_value), key,
+                 put_mode, ConvertWebIndexKeys(index_ids, index_keys),
+                 GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::SetIndexKeys(
@@ -369,12 +228,9 @@
     WebIDBKeyView primary_key,
     const WebVector<long long>& index_ids,
     const WebVector<WebIndexKeys>& index_keys) {
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::SetIndexKeys, base::Unretained(helper_),
-                     transaction_id, object_store_id,
-                     IndexedDBKeyBuilder::Build(primary_key),
-                     ConvertWebIndexKeys(index_ids, index_keys)));
+  database_->SetIndexKeys(transaction_id, object_store_id,
+                          IndexedDBKeyBuilder::Build(primary_key),
+                          ConvertWebIndexKeys(index_ids, index_keys));
 }
 
 void WebIDBDatabaseImpl::SetIndexesReady(
@@ -383,10 +239,8 @@
     const WebVector<long long>& web_index_ids) {
   std::vector<int64_t> index_ids(web_index_ids.Data(),
                                  web_index_ids.Data() + web_index_ids.size());
-  io_runner_->PostTask(FROM_HERE,
-                       base::BindOnce(&IOThreadHelper::SetIndexesReady,
-                                      base::Unretained(helper_), transaction_id,
-                                      object_store_id, std::move(index_ids)));
+  database_->SetIndexesReady(transaction_id, object_store_id,
+                             std::move(index_ids));
 }
 
 void WebIDBDatabaseImpl::OpenCursor(long long transaction_id,
@@ -401,14 +255,11 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::OpenCursor, base::Unretained(helper_),
-                     transaction_id, object_store_id, index_id,
-                     IndexedDBKeyRangeBuilder::Build(key_range), direction,
-                     key_only, task_type, std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->OpenCursor(transaction_id, object_store_id, index_id,
+                        IndexedDBKeyRangeBuilder::Build(key_range), direction,
+                        key_only, task_type,
+                        GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::Count(long long transaction_id,
@@ -420,14 +271,10 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::Count, base::Unretained(helper_),
-                     transaction_id, object_store_id, index_id,
-                     IndexedDBKeyRangeBuilder::Build(key_range),
-                     std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->Count(transaction_id, object_store_id, index_id,
+                   IndexedDBKeyRangeBuilder::Build(key_range),
+                   GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::Delete(long long transaction_id,
@@ -438,14 +285,10 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::DeleteRange, base::Unretained(helper_),
-                     transaction_id, object_store_id,
-                     IndexedDBKeyRangeBuilder::Build(primary_key),
-                     std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->DeleteRange(transaction_id, object_store_id,
+                         IndexedDBKeyRangeBuilder::Build(primary_key),
+                         GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::DeleteRange(long long transaction_id,
@@ -456,14 +299,10 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::DeleteRange, base::Unretained(helper_),
-                     transaction_id, object_store_id,
-                     IndexedDBKeyRangeBuilder::Build(key_range),
-                     std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->DeleteRange(transaction_id, object_store_id,
+                         IndexedDBKeyRangeBuilder::Build(key_range),
+                         GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::Clear(long long transaction_id,
@@ -473,12 +312,9 @@
       transaction_id, nullptr);
 
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      callback_runner_);
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::Clear,
-                                base::Unretained(helper_), transaction_id,
-                                object_store_id, std::move(callbacks_impl)));
+      base::WrapUnique(callbacks), transaction_id, nullptr, callback_runner_);
+  database_->Clear(transaction_id, object_store_id,
+                   GetCallbacksProxy(std::move(callbacks_impl)));
 }
 
 void WebIDBDatabaseImpl::CreateIndex(long long transaction_id,
@@ -488,238 +324,34 @@
                                      const WebIDBKeyPath& key_path,
                                      bool unique,
                                      bool multi_entry) {
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::CreateIndex, base::Unretained(helper_),
-                     transaction_id, object_store_id, index_id, name.Utf16(),
-                     IndexedDBKeyPathBuilder::Build(key_path), unique,
-                     multi_entry));
+  database_->CreateIndex(transaction_id, object_store_id, index_id,
+                         name.Utf16(), IndexedDBKeyPathBuilder::Build(key_path),
+                         unique, multi_entry);
 }
 
 void WebIDBDatabaseImpl::DeleteIndex(long long transaction_id,
                                      long long object_store_id,
                                      long long index_id) {
-  io_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&IOThreadHelper::DeleteIndex, base::Unretained(helper_),
-                     transaction_id, object_store_id, index_id));
+  database_->DeleteIndex(transaction_id, object_store_id, index_id);
 }
 
 void WebIDBDatabaseImpl::RenameIndex(long long transaction_id,
                                      long long object_store_id,
                                      long long index_id,
                                      const WebString& new_name) {
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::RenameIndex,
-                                base::Unretained(helper_), transaction_id,
-                                object_store_id, index_id, new_name.Utf16()));
+  database_->RenameIndex(transaction_id, object_store_id, index_id,
+                         new_name.Utf16());
 }
 
 void WebIDBDatabaseImpl::Abort(long long transaction_id) {
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::Abort,
-                                base::Unretained(helper_), transaction_id));
-}
-
-void WebIDBDatabaseImpl::Commit(long long transaction_id) {
-  io_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IOThreadHelper::Commit,
-                                base::Unretained(helper_), transaction_id));
-}
-
-WebIDBDatabaseImpl::IOThreadHelper::IOThreadHelper(
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : task_runner_(std::move(task_runner)) {}
-
-WebIDBDatabaseImpl::IOThreadHelper::~IOThreadHelper() {}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Bind(
-    DatabaseAssociatedPtrInfo database_info) {
-  database_.Bind(std::move(database_info), task_runner_);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::CreateObjectStore(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const base::string16& name,
-    const IndexedDBKeyPath& key_path,
-    bool auto_increment) {
-  database_->CreateObjectStore(transaction_id, object_store_id, name, key_path,
-                               auto_increment);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::DeleteObjectStore(
-    int64_t transaction_id,
-    int64_t object_store_id) {
-  database_->DeleteObjectStore(transaction_id, object_store_id);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::RenameObjectStore(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const base::string16& new_name) {
-  database_->RenameObjectStore(transaction_id, object_store_id, new_name);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::CreateTransaction(
-    int64_t transaction_id,
-    const std::vector<int64_t>& object_store_ids,
-    blink::WebIDBTransactionMode mode) {
-  database_->CreateTransaction(transaction_id, object_store_ids, mode);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Close() {
-  database_->Close();
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::VersionChangeIgnored() {
-  database_->VersionChangeIgnored();
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::AddObserver(int64_t transaction_id,
-                                                     int32_t observer_id,
-                                                     bool include_transaction,
-                                                     bool no_records,
-                                                     bool values,
-                                                     uint16_t operation_types) {
-  database_->AddObserver(transaction_id, observer_id, include_transaction,
-                         no_records, values, operation_types);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::RemoveObservers(
-    const std::vector<int32_t>& observers) {
-  database_->RemoveObservers(observers);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Get(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const IndexedDBKeyRange& key_range,
-    bool key_only,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->Get(transaction_id, object_store_id, index_id, key_range, key_only,
-                 GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::GetAll(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const IndexedDBKeyRange& key_range,
-    int64_t max_count,
-    bool key_only,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->GetAll(transaction_id, object_store_id, index_id, key_range,
-                    key_only, max_count,
-                    GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Put(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    indexed_db::mojom::ValuePtr value,
-    const IndexedDBKey& key,
-    blink::WebIDBPutMode mode,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks,
-    const std::vector<content::IndexedDBIndexKeys>& index_keys) {
-  database_->Put(transaction_id, object_store_id, std::move(value), key, mode,
-                 index_keys, GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::SetIndexKeys(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const IndexedDBKey& primary_key,
-    const std::vector<content::IndexedDBIndexKeys>& index_keys) {
-  database_->SetIndexKeys(transaction_id, object_store_id, primary_key,
-                          index_keys);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::SetIndexesReady(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const std::vector<int64_t>& index_ids) {
-  database_->SetIndexesReady(transaction_id, object_store_id, index_ids);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::OpenCursor(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const IndexedDBKeyRange& key_range,
-    blink::WebIDBCursorDirection direction,
-    bool key_only,
-    blink::WebIDBTaskType task_type,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->OpenCursor(transaction_id, object_store_id, index_id, key_range,
-                        direction, key_only, task_type,
-                        GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Count(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const IndexedDBKeyRange& key_range,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->Count(transaction_id, object_store_id, index_id, key_range,
-                   GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::DeleteRange(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const IndexedDBKeyRange& key_range,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->DeleteRange(transaction_id, object_store_id, key_range,
-                         GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Clear(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
-  database_->Clear(transaction_id, object_store_id,
-                   GetCallbacksProxy(std::move(callbacks)));
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::CreateIndex(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const base::string16& name,
-    const IndexedDBKeyPath& key_path,
-    bool unique,
-    bool multi_entry) {
-  database_->CreateIndex(transaction_id, object_store_id, index_id, name,
-                         key_path, unique, multi_entry);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::DeleteIndex(int64_t transaction_id,
-                                                     int64_t object_store_id,
-                                                     int64_t index_id) {
-  database_->DeleteIndex(transaction_id, object_store_id, index_id);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::RenameIndex(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const base::string16& new_name) {
-  database_->RenameIndex(transaction_id, object_store_id, index_id, new_name);
-}
-
-void WebIDBDatabaseImpl::IOThreadHelper::Abort(int64_t transaction_id) {
   database_->Abort(transaction_id);
 }
 
-void WebIDBDatabaseImpl::IOThreadHelper::Commit(int64_t transaction_id) {
+void WebIDBDatabaseImpl::Commit(long long transaction_id) {
   database_->Commit(transaction_id);
 }
 
-CallbacksAssociatedPtrInfo
-WebIDBDatabaseImpl::IOThreadHelper::GetCallbacksProxy(
+CallbacksAssociatedPtrInfo WebIDBDatabaseImpl::GetCallbacksProxy(
     std::unique_ptr<IndexedDBCallbacksImpl> callbacks) {
   CallbacksAssociatedPtrInfo ptr_info;
   auto request = mojo::MakeRequest(&ptr_info);
diff --git a/content/renderer/indexed_db/webidbdatabase_impl.h b/content/renderer/indexed_db/webidbdatabase_impl.h
index dfe7649..d208d00e 100644
--- a/content/renderer/indexed_db/webidbdatabase_impl.h
+++ b/content/renderer/indexed_db/webidbdatabase_impl.h
@@ -26,11 +26,12 @@
 
 namespace content {
 
+class IndexedDBCallbacksImpl;
+
 class CONTENT_EXPORT WebIDBDatabaseImpl : public blink::WebIDBDatabase {
  public:
   WebIDBDatabaseImpl(
       indexed_db::mojom::DatabaseAssociatedPtrInfo database,
-      scoped_refptr<base::SingleThreadTaskRunner> io_runner,
       scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
   ~WebIDBDatabaseImpl() override;
 
@@ -134,21 +135,21 @@
   void Commit(long long transaction_id) override;
 
  private:
+  indexed_db::mojom::CallbacksAssociatedPtrInfo GetCallbacksProxy(
+      std::unique_ptr<IndexedDBCallbacksImpl> callbacks);
+
   FRIEND_TEST_ALL_PREFIXES(WebIDBDatabaseImplTest, ValueSizeTest);
   FRIEND_TEST_ALL_PREFIXES(WebIDBDatabaseImplTest, KeyAndValueSizeTest);
 
-  class IOThreadHelper;
-
   // Maximum size (in bytes) of value/key pair allowed for put requests. Any
   // requests larger than this size will be rejected.
   // Used by unit tests to exercise behavior without allocating huge chunks
   // of memory.
   size_t max_put_value_size_ = kMaxIDBMessageSizeInBytes;
 
-  IOThreadHelper* helper_;
   std::set<int32_t> observer_ids_;
-  scoped_refptr<base::SingleThreadTaskRunner> io_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> callback_runner_;
+  indexed_db::mojom::DatabaseAssociatedPtr database_;
 };
 
 }  // namespace content
diff --git a/content/renderer/indexed_db/webidbdatabase_impl_unittest.cc b/content/renderer/indexed_db/webidbdatabase_impl_unittest.cc
index 31b31f6b..491da74 100644
--- a/content/renderer/indexed_db/webidbdatabase_impl_unittest.cc
+++ b/content/renderer/indexed_db/webidbdatabase_impl_unittest.cc
@@ -60,8 +60,7 @@
   EXPECT_CALL(callbacks, OnError(_)).Times(1);
 
   WebIDBDatabaseImpl database_impl(
-      nullptr, blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
-      blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+      nullptr, blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   database_impl.max_put_value_size_ = kMaxValueSizeForTesting;
   const WebIDBKey idb_key = WebIDBKey::CreateNumber(0);
   database_impl.Put(transaction_id, object_store_id, value, web_blob_info,
@@ -89,8 +88,7 @@
   EXPECT_CALL(callbacks, OnError(_)).Times(1);
 
   WebIDBDatabaseImpl database_impl(
-      nullptr, blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
-      blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+      nullptr, blink::scheduler::GetSingleThreadTaskRunnerForTesting());
   database_impl.max_put_value_size_ = kMaxValueSizeForTesting;
   database_impl.Put(transaction_id, object_store_id, value, web_blob_info,
                     key.View(), blink::kWebIDBPutModeAddOrUpdate, &callbacks,
diff --git a/content/renderer/indexed_db/webidbfactory_impl.cc b/content/renderer/indexed_db/webidbfactory_impl.cc
index 0ba7d15..221045a 100644
--- a/content/renderer/indexed_db/webidbfactory_impl.cc
+++ b/content/renderer/indexed_db/webidbfactory_impl.cc
@@ -25,12 +25,8 @@
 
 namespace content {
 
-WebIDBFactoryImpl::WebIDBFactoryImpl(
-    FactoryPtrInfo factory_info,
-    scoped_refptr<base::SingleThreadTaskRunner> io_runner)
-    : io_runner_(std::move(io_runner)) {
-  factory_.Bind(std::move(factory_info));
-}
+WebIDBFactoryImpl::WebIDBFactoryImpl(FactoryPtrInfo factory_info)
+    : factory_(std::move(factory_info)) {}
 
 WebIDBFactoryImpl::~WebIDBFactoryImpl() = default;
 
@@ -40,7 +36,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
       base::WrapUnique(callbacks), IndexedDBCallbacksImpl::kNoTransaction,
-      nullptr, io_runner_, std::move(task_runner));
+      nullptr, std::move(task_runner));
   factory_->GetDatabaseNames(GetCallbacksProxy(std::move(callbacks_impl)),
                              url::Origin(origin));
 }
@@ -54,8 +50,7 @@
     const WebSecurityOrigin& origin,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
-      base::WrapUnique(callbacks), transaction_id, nullptr, io_runner_,
-      task_runner);
+      base::WrapUnique(callbacks), transaction_id, nullptr, task_runner);
   auto database_callbacks_impl =
       std::make_unique<IndexedDBDatabaseCallbacksImpl>(
           base::WrapUnique(database_callbacks), std::move(task_runner));
@@ -72,7 +67,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   auto callbacks_impl = std::make_unique<IndexedDBCallbacksImpl>(
       base::WrapUnique(callbacks), IndexedDBCallbacksImpl::kNoTransaction,
-      nullptr, io_runner_, std::move(task_runner));
+      nullptr, std::move(task_runner));
   factory_->DeleteDatabase(GetCallbacksProxy(std::move(callbacks_impl)),
                            url::Origin(origin), name.Utf16(), force_close);
 }
diff --git a/content/renderer/indexed_db/webidbfactory_impl.h b/content/renderer/indexed_db/webidbfactory_impl.h
index d4b06c0..f19655df 100644
--- a/content/renderer/indexed_db/webidbfactory_impl.h
+++ b/content/renderer/indexed_db/webidbfactory_impl.h
@@ -23,8 +23,7 @@
 
 class WebIDBFactoryImpl : public blink::WebIDBFactory {
  public:
-  WebIDBFactoryImpl(indexed_db::mojom::FactoryPtrInfo factory_info,
-                    scoped_refptr<base::SingleThreadTaskRunner> io_runner);
+  explicit WebIDBFactoryImpl(indexed_db::mojom::FactoryPtrInfo factory_info);
   ~WebIDBFactoryImpl() override;
 
   // See WebIDBFactory.h for documentation on these functions.
@@ -53,7 +52,6 @@
   GetDatabaseCallbacksProxy(
       std::unique_ptr<IndexedDBDatabaseCallbacksImpl> callbacks);
 
-  scoped_refptr<base::SingleThreadTaskRunner> io_runner_;
   indexed_db::mojom::FactoryPtr factory_;
 };
 
diff --git a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
index a68c3d48..bc622cf 100644
--- a/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
+++ b/content/renderer/media/stream/webmediaplayer_ms_unittest.cc
@@ -1110,7 +1110,8 @@
 }
 
 // Tests that GpuMemoryBufferVideoFramePool is called in the expected sequence.
-TEST_F(WebMediaPlayerMSTest, CreateHardwareFrames) {
+// TODO(https://crbug.com/831327): Flaky under load / CPU scheduling delays.
+TEST_F(WebMediaPlayerMSTest, DISABLED_CreateHardwareFrames) {
   MockMediaStreamVideoRenderer* provider = LoadAndGetFrameProvider(true);
   SetGpuMemoryBufferVideoForTesting();
 
@@ -1145,7 +1146,9 @@
 // cover that, see HiddenPlayerTests for specifics.
 #if !defined(OS_ANDROID)
 // Tests that GpuMemoryBufferVideoFramePool is not called when page is hidden.
-TEST_F(WebMediaPlayerMSTest, StopsCreatingHardwareFramesWhenHiddenOrClosed) {
+// TODO(https://crbug.com/831327): Flaky under load / CPU scheduling delays.
+TEST_F(WebMediaPlayerMSTest,
+       DISABLED_StopsCreatingHardwareFramesWhenHiddenOrClosed) {
   MockMediaStreamVideoRenderer* provider = LoadAndGetFrameProvider(true);
   SetGpuMemoryBufferVideoForTesting();
 
diff --git a/content/renderer/pepper/url_request_info_util.cc b/content/renderer/pepper/url_request_info_util.cc
index f5b4028..5108a428 100644
--- a/content/renderer/pepper/url_request_info_util.cc
+++ b/content/renderer/pepper/url_request_info_util.cc
@@ -9,7 +9,7 @@
 
 #include "base/logging.h"
 #include "base/strings/string_util.h"
-#include "content/common/fileapi/file_system_messages.h"
+#include "content/public/common/service_names.mojom.h"
 #include "content/renderer/loader/request_extra_data.h"
 #include "content/renderer/pepper/host_globals.h"
 #include "content/renderer/pepper/pepper_file_ref_renderer_host.h"
@@ -24,6 +24,8 @@
 #include "ppapi/shared_impl/url_request_info_data.h"
 #include "ppapi/shared_impl/var.h"
 #include "ppapi/thunk/enter.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
 #include "third_party/blink/public/platform/file_path_conversion.h"
 #include "third_party/blink/public/platform/web_data.h"
 #include "third_party/blink/public/platform/web_feature.mojom.h"
@@ -49,6 +51,13 @@
 
 namespace {
 
+blink::mojom::FileSystemManagerPtr GetFileSystemManager() {
+  blink::mojom::FileSystemManagerPtr file_system_manager_ptr;
+  ChildThreadImpl::current()->GetConnector()->BindInterface(
+      mojom::kBrowserServiceName, mojo::MakeRequest(&file_system_manager_ptr));
+  return file_system_manager_ptr;
+}
+
 // Appends the file ref given the Resource pointer associated with it to the
 // given HTTP body, returning true on success.
 bool AppendFileRefToBody(PP_Instance instance,
@@ -78,9 +87,8 @@
     case PP_FILESYSTEMTYPE_LOCALPERSISTENT:
       // TODO(kinuko): remove this sync IPC when we fully support
       // AppendURLRange for FileSystem URL.
-      RenderThreadImpl::current()->Send(
-          new FileSystemHostMsg_SyncGetPlatformPath(
-              file_ref_host->GetFileSystemURL(), &platform_path));
+      GetFileSystemManager()->GetPlatformPath(file_ref_host->GetFileSystemURL(),
+                                              &platform_path);
       break;
     case PP_FILESYSTEMTYPE_EXTERNAL:
       platform_path = file_ref_host->GetExternalFilePath();
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 31dae684..98bc450f 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1562,12 +1562,6 @@
   return user_agent_;
 }
 
-bool RenderThreadImpl::OnMessageReceived(const IPC::Message& msg) {
-  if (file_system_dispatcher_->OnMessageReceived(msg))
-    return true;
-  return ChildThreadImpl::OnMessageReceived(msg);
-}
-
 void RenderThreadImpl::OnAssociatedInterfaceRequest(
     const std::string& name,
     mojo::ScopedInterfaceEndpointHandle handle) {
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index f69aed6..60995a2 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -228,7 +228,6 @@
   blink::WebString GetUserAgent() const override;
 
   // IPC::Listener implementation via ChildThreadImpl:
-  bool OnMessageReceived(const IPC::Message& msg) override;
   void OnAssociatedInterfaceRequest(
       const std::string& name,
       mojo::ScopedInterfaceEndpointHandle handle) override;
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 2e5d8a2f..4ab795b9 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -460,7 +460,8 @@
 
 void RendererBlinkPlatformImpl::FetchCachedCode(
     const blink::WebURL& url,
-    base::OnceCallback<void(const std::vector<uint8_t>&)> callback) {
+    base::OnceCallback<void(base::Time, const std::vector<uint8_t>&)>
+        callback) {
   RenderThreadImpl::current()->render_message_filter()->FetchCachedCode(
       url, std::move(callback));
 }
@@ -565,16 +566,11 @@
 
 std::unique_ptr<blink::WebIDBFactory>
 RendererBlinkPlatformImpl::CreateIdbFactory() {
-  // If running in a test context, RenderThread may not exist on init, which
-  // would lead to |io_runner_| being null.
-  if (!io_runner_)
-    return nullptr;
   indexed_db::mojom::FactoryPtrInfo web_idb_factory_host_info;
   GetInterfaceProvider()->GetInterface(
       mojo::MakeRequest(&web_idb_factory_host_info));
   return std::make_unique<WebIDBFactoryImpl>(
-      std::move(web_idb_factory_host_info),
-      io_runner_);
+      std::move(web_idb_factory_host_info));
 }
 
 //------------------------------------------------------------------------------
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 3fb1af2..d3ae4a5b 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -85,7 +85,8 @@
                      size_t) override;
   void FetchCachedCode(
       const blink::WebURL&,
-      base::OnceCallback<void(const std::vector<uint8_t>&)>) override;
+      base::OnceCallback<void(base::Time, const std::vector<uint8_t>&)>)
+      override;
   void CacheMetadataInCacheStorage(
       const blink::WebURL&,
       base::Time,
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 8f23aff..74fa0762 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -23,7 +23,6 @@
 #include "content/common/service_worker/service_worker_messages.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/common/content_features.h"
-#include "content/public/common/push_event_payload.h"
 #include "content/public/common/referrer.h"
 #include "content/public/renderer/content_renderer_client.h"
 #include "content/public/renderer/document_state.h"
@@ -1839,7 +1838,7 @@
 }
 
 void ServiceWorkerContextClient::DispatchPushEvent(
-    const PushEventPayload& payload,
+    const base::Optional<std::string>& payload,
     DispatchPushEventCallback callback) {
   int request_id = context_->timeout_timer->StartEventWithCustomTimeout(
       CreateAbortCallback(&context_->push_event_callbacks),
@@ -1853,8 +1852,8 @@
 
   // Only set data to be a valid string if the payload had decrypted data.
   blink::WebString data;
-  if (!payload.is_null)
-    data = blink::WebString::FromUTF8(payload.data);
+  if (payload)
+    data = blink::WebString::FromUTF8(*payload);
   proxy_->DispatchPushEvent(request_id, data);
 }
 
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index b48aa9a..89d162fe 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -55,7 +55,6 @@
 namespace content {
 
 struct PlatformNotificationData;
-struct PushEventPayload;
 class EmbeddedWorkerInstanceClientImpl;
 class ServiceWorkerNetworkProvider;
 class ServiceWorkerProviderContext;
@@ -318,7 +317,7 @@
       const std::string& notification_id,
       const PlatformNotificationData& notification_data,
       DispatchNotificationCloseEventCallback callback) override;
-  void DispatchPushEvent(const PushEventPayload& payload,
+  void DispatchPushEvent(const base::Optional<std::string>& payload,
                          DispatchPushEventCallback callback) override;
   void DispatchSyncEvent(const std::string& tag,
                          bool last_chance,
diff --git a/content/shell/browser/shell_application_mac.mm b/content/shell/browser/shell_application_mac.mm
index 58d8773c..89896675 100644
--- a/content/shell/browser/shell_application_mac.mm
+++ b/content/shell/browser/shell_application_mac.mm
@@ -5,12 +5,20 @@
 #include "content/shell/browser/shell_application_mac.h"
 
 #include "base/auto_reset.h"
+#include "base/observer_list.h"
+#include "content/public/browser/native_event_processor_mac.h"
+#include "content/public/browser/native_event_processor_observer_mac.h"
 #include "content/public/common/url_constants.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_browser_context.h"
 #include "content/shell/browser/shell_content_browser_client.h"
 #include "url/gurl.h"
 
+@interface ShellCrApplication ()<NativeEventProcessor> {
+  base::ObserverList<content::NativeEventProcessorObserver> observers_;
+}
+@end
+
 @implementation ShellCrApplication
 
 - (BOOL)isHandlingSendEvent {
@@ -19,6 +27,9 @@
 
 - (void)sendEvent:(NSEvent*)event {
   base::AutoReset<BOOL> scoper(&handlingSendEvent_, YES);
+
+  content::ScopedNotifyNativeEventProcessorObserver scopedObserverNotifier(
+      &observers_, event);
   [super sendEvent:event];
 }
 
@@ -35,4 +46,14 @@
                                   gfx::Size());
 }
 
+- (void)addNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer {
+  observers_.AddObserver(observer);
+}
+
+- (void)removeNativeEventProcessorObserver:
+    (content::NativeEventProcessorObserver*)observer {
+  observers_.RemoveObserver(observer);
+}
+
 @end
diff --git a/content/shell/test_runner/app_banner_service.cc b/content/shell/test_runner/app_banner_service.cc
index 3af64140..8a23432 100644
--- a/content/shell/test_runner/app_banner_service.cc
+++ b/content/shell/test_runner/app_banner_service.cc
@@ -31,11 +31,12 @@
   binding_.Bind(mojo::MakeRequest(&proxy));
   controller_->BannerPromptRequest(
       std::move(proxy), mojo::MakeRequest(&event_), platforms,
+      true /* require_gesture */,
       base::BindOnce(&AppBannerService::OnBannerPromptReply,
                      base::Unretained(this), std::move(callback)));
 }
 
-void AppBannerService::DisplayAppBanner(bool user_gesture) { /* do nothing */
+void AppBannerService::DisplayAppBanner() { /* do nothing */
 }
 
 void AppBannerService::OnBannerPromptReply(
diff --git a/content/shell/test_runner/app_banner_service.h b/content/shell/test_runner/app_banner_service.h
index 58500fd..d52125b 100644
--- a/content/shell/test_runner/app_banner_service.h
+++ b/content/shell/test_runner/app_banner_service.h
@@ -30,7 +30,7 @@
                                base::OnceCallback<void(bool)> callback);
 
   // blink::mojom::AppBannerService overrides.
-  void DisplayAppBanner(bool user_gesture) override;
+  void DisplayAppBanner() override;
 
  private:
   void OnBannerPromptReply(base::OnceCallback<void(bool)> callback,
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 2699557d..e987cd2 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -823,6 +823,7 @@
     "../browser/renderer_host/render_widget_host_view_child_frame_browsertest.cc",
     "../browser/renderer_host/render_widget_host_view_mac_browsertest.mm",
     "../browser/resource_loading_browsertest.cc",
+    "../browser/scheduler/responsiveness/native_event_observer_browsertest.mm",
     "../browser/screen_orientation/screen_orientation_browsertest.cc",
     "../browser/security_exploit_browsertest.cc",
     "../browser/service_manager/service_manager_context_browsertest.cc",
@@ -1325,7 +1326,6 @@
     "../browser/download/save_package_unittest.cc",
     "../browser/fileapi/browser_file_system_helper_unittest.cc",
     "../browser/fileapi/file_system_operation_runner_unittest.cc",
-    "../browser/fileapi/fileapi_message_filter_unittest.cc",
     "../browser/frame_host/ancestor_throttle_unittest.cc",
     "../browser/frame_host/frame_service_base_unittest.cc",
     "../browser/frame_host/frame_tree_node_blame_context_unittest.cc",
@@ -1922,6 +1922,7 @@
       "../browser/renderer_host/pepper/pepper_file_system_browser_host_unittest.cc",
       "../browser/renderer_host/pepper/pepper_gamepad_host_unittest.cc",
       "../browser/renderer_host/pepper/pepper_printing_host_unittest.cc",
+      "../browser/renderer_host/pepper/pepper_proxy_lookup_helper_unittest.cc",
       "../browser/renderer_host/pepper/quota_reservation_unittest.cc",
       "../renderer/media/pepper/pepper_to_video_track_adapter_unittest.cc",
       "../renderer/media/pepper/video_track_to_pepper_adapter_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index ef010573..3913d83 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -118,3 +118,9 @@
     self.Fail('Pixel_CanvasLowLatencyWebGL', ['android', 'nvidia'], bug=868596)
     self.Fail('Pixel_OffscreenCanvasWebGLPaintAfterResize',
               ['android', 'nvidia'], bug=868596)
+
+    # Failing on FYI Nexus 5: crbug.com/870023
+    self.Fail('Pixel_WebGLGreenTriangle_NoAA_Alpha',
+        ['android', ('qualcomm', 'Adreno (TM) 330')], bug=870023)
+    self.Fail('Pixel_WebGLGreenTriangle_NoAA_NoAlpha',
+        ['android', ('qualcomm', 'Adreno (TM) 330')], bug=870023)
diff --git a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
index 68ac8dc..49c7ac6 100644
--- a/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
+++ b/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm
@@ -159,7 +159,7 @@
   EXPECT_FALSE(advertisement_);
   EXPECT_FALSE(registration_error_);
 
-  // Change the adapter state to CBPeripheralManagerStateUnsupported, which
+  // Change the adapter state to CBPeripheralManagerStatePoweredOff, which
   // causes the registration to fail.
   peripheral_manager_state_ = CBPeripheralManagerStatePoweredOff;
   advertisement_manager_.OnPeripheralManagerStateChanged();
diff --git a/device/fido/ctap_get_assertion_request.cc b/device/fido/ctap_get_assertion_request.cc
index bcb44dc4..ac7a828 100644
--- a/device/fido/ctap_get_assertion_request.cc
+++ b/device/fido/ctap_get_assertion_request.cc
@@ -160,6 +160,13 @@
   return *this;
 }
 
+bool CtapGetAssertionRequest::CheckResponseRpIdHash(
+    const std::array<uint8_t, kRpIdHashLength>& response_rp_id_hash) {
+  return response_rp_id_hash == fido_parsing_utils::CreateSHA256Hash(rp_id_) ||
+         (alternative_application_parameter_ &&
+          response_rp_id_hash == *alternative_application_parameter_);
+}
+
 base::Optional<CtapGetAssertionRequest> ParseCtapGetAssertionRequest(
     base::span<const uint8_t> request_bytes) {
   const auto& cbor_request = cbor::CBORReader::Read(request_bytes);
diff --git a/device/fido/ctap_get_assertion_request.h b/device/fido/ctap_get_assertion_request.h
index beda6ca..6bdeef0 100644
--- a/device/fido/ctap_get_assertion_request.h
+++ b/device/fido/ctap_get_assertion_request.h
@@ -52,6 +52,11 @@
       base::span<const uint8_t, kRpIdHashLength>
           alternative_application_parameter);
 
+  // Return true if the given RP ID hash from a response is valid for this
+  // request.
+  bool CheckResponseRpIdHash(
+      const std::array<uint8_t, kRpIdHashLength>& response_rp_id_hash);
+
   const std::string& rp_id() const { return rp_id_; }
   const std::array<uint8_t, kClientDataHashLength>& client_data_hash() const {
     return client_data_hash_;
diff --git a/device/fido/fido_request_handler_base.cc b/device/fido/fido_request_handler_base.cc
index f8a34347..8dd8463 100644
--- a/device/fido/fido_request_handler_base.cc
+++ b/device/fido/fido_request_handler_base.cc
@@ -140,11 +140,11 @@
   active_authenticators_.emplace(authenticator->GetId(),
                                  std::move(authenticator));
 
-  if (!ShouldDeferRequestDispatchToUi(*authenticator.get()))
+  if (!ShouldDeferRequestDispatchToUi(*authenticator_ptr))
     DispatchRequest(authenticator_ptr);
 
   if (observer_)
-    observer_->FidoAuthenticatorAdded(*authenticator);
+    observer_->FidoAuthenticatorAdded(*authenticator_ptr);
 }
 
 }  // namespace device
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index b1ff520..074e04a 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -178,7 +178,7 @@
     return;
   }
 
-  if (!response || !response->CheckRpIdHash(request_.rp_id()) ||
+  if (!response || !request_.CheckResponseRpIdHash(response->GetRpIdHash()) ||
       !CheckResponseCredentialIdMatchesRequestAllowList(*authenticator,
                                                         request_, *response) ||
       !CheckRequirementsOnResponseUserEntity(request_, *response)) {
diff --git a/device/fido/make_credential_handler_unittest.cc b/device/fido/make_credential_handler_unittest.cc
index 2421f3e..b536f6f3 100644
--- a/device/fido/make_credential_handler_unittest.cc
+++ b/device/fido/make_credential_handler_unittest.cc
@@ -20,6 +20,7 @@
 #include "device/fido/make_credential_request_handler.h"
 #include "device/fido/mock_fido_device.h"
 #include "device/fido/test_callback_receiver.h"
+#include "device/fido/virtual_ctap2_device.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -297,4 +298,48 @@
   EXPECT_FALSE(callback().was_called());
 }
 
+// Tests that only authenticators with resident key support will successfully
+// process MakeCredential request when the relying party requires using resident
+// keys in AuthenicatorSelectionCriteria.
+TEST_F(FidoMakeCredentialHandlerTest,
+       SuccessfulMakeCredentialWithResidentKeyOption) {
+  auto device = std::make_unique<VirtualCtap2Device>();
+  AuthenticatorSupportedOptions option;
+  option.SetSupportsResidentKey(true);
+  device->SetAuthenticatorSupportedOptions(std::move(option));
+
+  auto request_handler =
+      CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
+          AuthenticatorSelectionCriteria(
+              AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
+              true /* require_resident_key */,
+              UserVerificationRequirement::kPreferred));
+
+  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->AddDevice(std::move(device));
+
+  scoped_task_environment_.FastForwardUntilNoTasksRemain();
+  callback().WaitForCallback();
+  EXPECT_EQ(FidoReturnCode::kSuccess, callback().status());
+}
+
+// Tests that MakeCredential request fails when asking to use resident keys with
+// authenticators that do not support resident key.
+TEST_F(FidoMakeCredentialHandlerTest,
+       MakeCredentialFailsForIncompatibleResidentKeyOption) {
+  auto device = std::make_unique<VirtualCtap2Device>();
+  auto request_handler =
+      CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
+          AuthenticatorSelectionCriteria(
+              AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
+              true /* require_resident_key */,
+              UserVerificationRequirement::kPreferred));
+
+  discovery()->WaitForCallToStartAndSimulateSuccess();
+  discovery()->AddDevice(std::move(device));
+
+  scoped_task_environment_.FastForwardUntilNoTasksRemain();
+  EXPECT_FALSE(callback().was_called());
+}
+
 }  // namespace device
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index a8b40ce..133fe01 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "device/fido/authenticator_make_credential_response.h"
 #include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_parsing_utils.h"
 #include "device/fido/make_credential_task.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -72,6 +73,8 @@
       !options.supports_resident_key()) {
     return false;
   }
+  request->SetResidentKeySupported(
+      authenticator_selection_criteria.require_resident_key());
 
   const auto& user_verification_requirement =
       authenticator_selection_criteria.user_verification_requirement();
@@ -112,7 +115,10 @@
     return;
   }
 
-  if (!response || !response->CheckRpIdHash(request_parameter_.rp().rp_id())) {
+  const auto rp_id_hash =
+      fido_parsing_utils::CreateSHA256Hash(request_parameter_.rp().rp_id());
+
+  if (!response || response->GetRpIdHash() != rp_id_hash) {
     OnAuthenticatorResponse(
         authenticator, CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
     return;
diff --git a/device/fido/response_data.cc b/device/fido/response_data.cc
index 994ed97..9a6312b5 100644
--- a/device/fido/response_data.cc
+++ b/device/fido/response_data.cc
@@ -32,9 +32,4 @@
   return id;
 }
 
-bool ResponseData::CheckRpIdHash(const std::string& rp_id) const {
-  return base::make_span(GetRpIdHash()) ==
-         base::make_span(fido_parsing_utils::CreateSHA256Hash(rp_id));
-}
-
 }  // namespace device
diff --git a/device/fido/response_data.h b/device/fido/response_data.h
index ad5082c..0d9728c7 100644
--- a/device/fido/response_data.h
+++ b/device/fido/response_data.h
@@ -28,11 +28,6 @@
 
   std::string GetId() const;
 
-  // Checks that the SHA256 hash of the relying party id obtained from the
-  // request parameter matches the application parameter returned from the
-  // authenticator.
-  bool CheckRpIdHash(const std::string& rp_id) const;
-
   const std::vector<uint8_t>& raw_credential_id() const {
     return raw_credential_id_;
   }
diff --git a/device/gamepad/public/cpp/gamepad_features.cc b/device/gamepad/public/cpp/gamepad_features.cc
index 75dfa02..86fd54d 100644
--- a/device/gamepad/public/cpp/gamepad_features.cc
+++ b/device/gamepad/public/cpp/gamepad_features.cc
@@ -33,11 +33,32 @@
 
 }  // namespace
 
+// Enables gamepadbuttondown, gamepadbuttonup, gamepadbuttonchange,
+// gamepadaxismove non-standard gamepad events.
+const base::Feature kEnableGamepadButtonAxisEvents{
+    "EnableGamepadButtonAxisEvents", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Overrides the gamepad polling interval.
 const base::Feature kGamepadPollingInterval{"GamepadPollingInterval",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
 const char kGamepadPollingIntervalParamKey[] = "interval-ms";
 
+bool AreGamepadButtonAxisEventsEnabled() {
+  // Check if button and axis events are enabled by a field trial.
+  if (base::FeatureList::IsEnabled(kEnableGamepadButtonAxisEvents))
+    return true;
+
+  // Check if button and axis events are enabled by a command-line flag.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line &&
+      command_line->HasSwitch(switches::kEnableGamepadButtonAxisEvents)) {
+    return true;
+  }
+
+  return false;
+}
+
 size_t GetGamepadPollingInterval() {
   size_t polling_interval = kPollingIntervalMillisecondsMax;
 
diff --git a/device/gamepad/public/cpp/gamepad_features.h b/device/gamepad/public/cpp/gamepad_features.h
index 462c501e..e58cc7f 100644
--- a/device/gamepad/public/cpp/gamepad_features.h
+++ b/device/gamepad/public/cpp/gamepad_features.h
@@ -9,9 +9,11 @@
 
 namespace features {
 
+extern const base::Feature kEnableGamepadButtonAxisEvents;
 extern const base::Feature kGamepadPollingInterval;
 extern const char kGamepadPollingIntervalParamKey[];
 
+bool AreGamepadButtonAxisEventsEnabled();
 size_t GetGamepadPollingInterval();
 
 }  // namespace features
diff --git a/device/gamepad/public/cpp/gamepad_switches.cc b/device/gamepad/public/cpp/gamepad_switches.cc
index 1dd28a07..9185c772 100644
--- a/device/gamepad/public/cpp/gamepad_switches.cc
+++ b/device/gamepad/public/cpp/gamepad_switches.cc
@@ -6,6 +6,11 @@
 
 namespace switches {
 
+// Enables gamepadbuttondown, gamepadbuttonup, gamepadbuttonchange,
+// gamepadaxismove non-standard gamepad events.
+const char kEnableGamepadButtonAxisEvents[] =
+    "enable-gamepad-button-axis-events";
+
 // Overrides the gamepad polling interval. Decreasing the interval improves
 // input latency of buttons and axes but may negatively affect performance due
 // to more CPU time spent in the input polling thread.
diff --git a/device/gamepad/public/cpp/gamepad_switches.h b/device/gamepad/public/cpp/gamepad_switches.h
index 18460ec..bbbbb18 100644
--- a/device/gamepad/public/cpp/gamepad_switches.h
+++ b/device/gamepad/public/cpp/gamepad_switches.h
@@ -9,6 +9,7 @@
 
 // All switches in alphabetical order. The switches should be documented
 // alongside the definition of their values in the .cc file.
+extern const char kEnableGamepadButtonAxisEvents[];
 extern const char kGamepadPollingInterval[];
 
 }  // namespace switches
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 9a11ad0d..e23544d 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -113,6 +113,8 @@
       ]
 
       sources += [
+        "isolated_gamepad_data_fetcher.cc",
+        "isolated_gamepad_data_fetcher.h",
         "windows/d3d11_texture_helper.cc",
         "windows/d3d11_texture_helper.h",
         "windows/flip_pixel_shader.h",
@@ -132,8 +134,8 @@
         "oculus/oculus_device.h",
         "oculus/oculus_device_provider.cc",
         "oculus/oculus_device_provider.h",
-        "oculus/oculus_gamepad_data_fetcher.cc",
-        "oculus/oculus_gamepad_data_fetcher.h",
+        "oculus/oculus_gamepad_helper.cc",
+        "oculus/oculus_gamepad_helper.h",
         "oculus/oculus_render_loop.cc",
         "oculus/oculus_render_loop.h",
         "oculus/oculus_type_converters.cc",
diff --git a/device/vr/isolated_gamepad_data_fetcher.cc b/device/vr/isolated_gamepad_data_fetcher.cc
new file mode 100644
index 0000000..22d15dd
--- /dev/null
+++ b/device/vr/isolated_gamepad_data_fetcher.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 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 "device/vr/isolated_gamepad_data_fetcher.h"
+#include "device/vr/vr_device.h"
+
+namespace device {
+
+IsolatedGamepadDataFetcher::Factory::Factory(
+    VRDeviceId display_id,
+    device::mojom::IsolatedXRGamepadProviderFactoryPtr factory)
+    : display_id_(display_id), factory_(std::move(factory)) {}
+
+IsolatedGamepadDataFetcher::Factory::~Factory() {}
+
+std::unique_ptr<GamepadDataFetcher>
+IsolatedGamepadDataFetcher::Factory::CreateDataFetcher() {
+  device::mojom::IsolatedXRGamepadProviderPtr provider;
+  factory_->GetIsolatedXRGamepadProvider(mojo::MakeRequest(&provider));
+  return std::make_unique<IsolatedGamepadDataFetcher>(display_id_,
+                                                      std::move(provider));
+}
+
+GamepadSource IsolatedGamepadDataFetcher::Factory::source() {
+  return (display_id_ == VRDeviceId::OPENVR_DEVICE_ID) ? GAMEPAD_SOURCE_OPENVR
+                                                       : GAMEPAD_SOURCE_OCULUS;
+}
+
+IsolatedGamepadDataFetcher::IsolatedGamepadDataFetcher(
+    VRDeviceId display_id,
+    device::mojom::IsolatedXRGamepadProviderPtr provider)
+    : display_id_(display_id) {
+  // We bind provider_ on the poling thread, but we're created on the main UI
+  // thread.
+  provider_info_ = provider.PassInterface();
+}
+
+IsolatedGamepadDataFetcher::~IsolatedGamepadDataFetcher() = default;
+
+GamepadSource IsolatedGamepadDataFetcher::source() {
+  return (display_id_ == VRDeviceId::OPENVR_DEVICE_ID) ? GAMEPAD_SOURCE_OPENVR
+                                                       : GAMEPAD_SOURCE_OCULUS;
+}
+
+void IsolatedGamepadDataFetcher::OnDataUpdated(
+    device::mojom::XRGamepadDataPtr data) {
+  data_ = std::move(data);
+  have_outstanding_request_ = false;
+}
+
+GamepadPose GamepadPoseFromXRPose(device::mojom::VRPose* pose) {
+  GamepadPose ret = {};
+  ret.not_null = pose;
+  if (!pose) {
+    return ret;
+  }
+
+  if (pose->position) {
+    ret.position.not_null = true;
+    ret.position.x = (*pose->position)[0];
+    ret.position.y = (*pose->position)[1];
+    ret.position.z = (*pose->position)[2];
+  }
+
+  if (pose->orientation) {
+    ret.orientation.not_null = true;
+    ret.orientation.x = (*pose->orientation)[0];
+    ret.orientation.y = (*pose->orientation)[1];
+    ret.orientation.z = (*pose->orientation)[2];
+    ret.orientation.w = (*pose->orientation)[3];
+  }
+
+  if (pose->angularVelocity) {
+    ret.angular_velocity.not_null = true;
+    ret.angular_velocity.x = (*pose->angularVelocity)[0];
+    ret.angular_velocity.y = (*pose->angularVelocity)[1];
+    ret.angular_velocity.z = (*pose->angularVelocity)[2];
+  }
+
+  if (pose->linearVelocity) {
+    ret.linear_velocity.not_null = true;
+    ret.linear_velocity.x = (*pose->linearVelocity)[0];
+    ret.linear_velocity.y = (*pose->linearVelocity)[1];
+    ret.linear_velocity.z = (*pose->linearVelocity)[2];
+  }
+
+  if (pose->angularAcceleration) {
+    ret.angular_acceleration.not_null = true;
+    ret.angular_acceleration.x = (*pose->angularAcceleration)[0];
+    ret.angular_acceleration.y = (*pose->angularAcceleration)[1];
+    ret.angular_acceleration.z = (*pose->angularAcceleration)[2];
+  }
+
+  if (pose->linearAcceleration) {
+    ret.linear_acceleration.not_null = true;
+    ret.linear_acceleration.x = (*pose->linearAcceleration)[0];
+    ret.linear_acceleration.y = (*pose->linearAcceleration)[1];
+    ret.linear_acceleration.z = (*pose->linearAcceleration)[2];
+  }
+
+  return ret;
+}
+
+void IsolatedGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) {
+  if (!provider_ && provider_info_) {
+    provider_.Bind(std::move(provider_info_));
+  }
+
+  // If we don't have a provider, we can't give out data.
+  if (!provider_) {
+    return;
+  }
+
+  // Request new data.
+  if (!have_outstanding_request_) {
+    have_outstanding_request_ = true;
+    provider_->RequestUpdate(base::BindOnce(
+        &IsolatedGamepadDataFetcher::OnDataUpdated, base::Unretained(this)));
+  }
+
+  // If we have no data to give out, nothing to do.
+  if (!data_) {
+    return;
+  }
+
+  // Keep track of gamepads that go missing.
+  std::set<unsigned int> seen_gamepads;
+
+  // Give out data_, but we need to translate it.
+  for (size_t i = 0; i < data_->gamepads.size(); ++i) {
+    auto& source = data_->gamepads[i];
+    PadState* state = GetPadState(source->controller_id);
+    if (!state)
+      continue;
+    Gamepad& dest = state->data;
+    dest.connected = true;
+
+    seen_gamepads.insert(source->controller_id);
+    dest.timestamp = CurrentTimeInMicroseconds();
+    dest.pose = GamepadPoseFromXRPose(source->pose.get());
+    dest.pose.has_position = source->can_provide_position;
+    dest.pose.has_orientation = source->can_provide_orientation;
+    dest.hand = source->hand == device::mojom::XRHandedness::LEFT
+                    ? GamepadHand::kLeft
+                    : source->hand == device::mojom::XRHandedness::RIGHT
+                          ? GamepadHand::kRight
+                          : GamepadHand::kNone;
+    dest.display_id = static_cast<unsigned int>(display_id_);
+    dest.is_xr = true;
+
+    dest.axes_length = source->axes.size();
+    for (size_t j = 0; j < source->axes.size(); ++j) {
+      dest.axes[j] = source->axes[j];
+    }
+
+    dest.buttons_length = source->buttons.size();
+    for (size_t j = 0; j < source->buttons.size(); ++j) {
+      dest.buttons[j] =
+          GamepadButton(source->buttons[j]->pressed,
+                        source->buttons[j]->touched, source->buttons[j]->value);
+    }
+
+    // Gamepad extensions refer to display_id, corresponding to the WebVR
+    // VRDisplay's dipslayId property.
+    // As WebVR is deprecated, and we only hand out a maximum of one "display"
+    // per runtime, we use the VRDeviceId now to associate the controller with
+    // a headset.  This doesn't change behavior, but the device/display naming
+    // could be confusing here.
+    if (display_id_ == VRDeviceId::OPENVR_DEVICE_ID) {
+      swprintf(dest.id, Gamepad::kIdLengthCap, L"OpenVR Gamepad");
+    } else {
+      if (dest.hand == GamepadHand::kLeft) {
+        swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Touch (Left)");
+      } else if (dest.hand == GamepadHand::kRight) {
+        swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Touch (Right)");
+      } else {
+        swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Remote");
+      }
+    }
+
+    dest.mapping[0] = 0;
+  }
+
+  // Remove any gamepads that aren't connected.
+  for (auto id : active_gamepads_) {
+    if (seen_gamepads.find(id) == seen_gamepads.end()) {
+      PadState* state = GetPadState(id);
+      if (state) {
+        state->data.connected = false;
+      }
+    }
+  }
+  active_gamepads_ = std::move(seen_gamepads);
+}
+
+void IsolatedGamepadDataFetcher::PauseHint(bool paused) {}
+
+void IsolatedGamepadDataFetcher::OnAddedToProvider() {}
+
+}  // namespace device
diff --git a/device/vr/isolated_gamepad_data_fetcher.h b/device/vr/isolated_gamepad_data_fetcher.h
new file mode 100644
index 0000000..8958b383
--- /dev/null
+++ b/device/vr/isolated_gamepad_data_fetcher.h
@@ -0,0 +1,56 @@
+// Copyright 2018 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 DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_
+#define DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_
+
+#include "device/gamepad/gamepad_data_fetcher.h"
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
+#include "device/vr/vr_device.h"
+
+namespace device {
+
+class IsolatedGamepadDataFetcher : public GamepadDataFetcher {
+ public:
+  class Factory : public GamepadDataFetcherFactory {
+   public:
+    Factory(VRDeviceId display_id,
+            device::mojom::IsolatedXRGamepadProviderFactoryPtr factory);
+    ~Factory() override;
+    std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override;
+    GamepadSource source() override;
+
+   private:
+    VRDeviceId display_id_;
+    device::mojom::IsolatedXRGamepadProviderFactoryPtr factory_;
+  };
+
+  IsolatedGamepadDataFetcher(
+      VRDeviceId display_id,
+      device::mojom::IsolatedXRGamepadProviderPtr provider);
+  ~IsolatedGamepadDataFetcher() override;
+
+  GamepadSource source() override;
+
+  void GetGamepadData(bool devices_changed_hint) override;
+  void PauseHint(bool paused) override;
+  void OnAddedToProvider() override;
+
+  void OnDataUpdated(device::mojom::XRGamepadDataPtr data);
+
+ private:
+  VRDeviceId display_id_;
+  bool have_outstanding_request_ = false;
+  std::set<unsigned int> active_gamepads_;
+  device::mojom::XRGamepadDataPtr data_;
+  device::mojom::IsolatedXRGamepadProviderPtr
+      provider_;  // Bound on the polling thread.
+  device::mojom::IsolatedXRGamepadProviderPtrInfo
+      provider_info_;  // Received on the UI thread, bound when polled.
+
+  DISALLOW_COPY_AND_ASSIGN(IsolatedGamepadDataFetcher);
+};
+
+}  // namespace device
+#endif  // DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_
diff --git a/device/vr/oculus/oculus_device.cc b/device/vr/oculus/oculus_device.cc
index adccd7e..7b4e386 100644
--- a/device/vr/oculus/oculus_device.cc
+++ b/device/vr/oculus/oculus_device.cc
@@ -87,6 +87,7 @@
     : VRDeviceBase(VRDeviceId::OCULUS_DEVICE_ID),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       exclusive_controller_binding_(this),
+      gamepad_provider_factory_binding_(this),
       weak_ptr_factory_(this) {
   StartOvrSession();
   if (!session_) {
@@ -97,22 +98,22 @@
 
   render_loop_ = std::make_unique<OculusRenderLoop>(
       base::BindRepeating(&OculusDevice::OnPresentationEnded,
-                          weak_ptr_factory_.GetWeakPtr()),
-      base::BindRepeating(&OculusDevice::OnControllerUpdated,
                           weak_ptr_factory_.GetWeakPtr()));
 }
 
-OculusDevice::~OculusDevice() {
-  StopOvrSession();
+mojom::IsolatedXRGamepadProviderFactoryPtr OculusDevice::BindGamepadFactory() {
+  mojom::IsolatedXRGamepadProviderFactoryPtr ret;
+  gamepad_provider_factory_binding_.Bind(mojo::MakeRequest(&ret));
+  return ret;
+}
 
+OculusDevice::~OculusDevice() {
   // Wait for the render loop to stop before completing destruction. This will
   // ensure that bindings are closed on the correct thread.
   if (render_loop_ && render_loop_->IsRunning())
     render_loop_->Stop();
 
-  device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
-      device::GAMEPAD_SOURCE_OCULUS);
-  data_fetcher_ = nullptr;
+  StopOvrSession();
 }
 
 void OculusDevice::RequestSession(
@@ -125,13 +126,23 @@
 
   StopOvrSession();
 
-  if (!render_loop_->IsRunning())
+  if (!render_loop_->IsRunning()) {
     render_loop_->Start();
 
-  if (!render_loop_->IsRunning()) {
-    std::move(callback).Run(nullptr, nullptr);
-    StartOvrSession();
-    return;
+    if (!render_loop_->IsRunning()) {
+      std::move(callback).Run(nullptr, nullptr);
+      StartOvrSession();
+      return;
+    }
+
+    // If we have a pending gamepad provider request when starting the render
+    // loop, post the request over to the render loop to be bound.
+    if (provider_request_) {
+      render_loop_->task_runner()->PostTask(
+          FROM_HERE, base::BindOnce(&OculusRenderLoop::RequestGamepadProvider,
+                                    render_loop_->GetWeakPtr(),
+                                    std::move(provider_request_)));
+    }
   }
 
   auto on_request_present_result =
@@ -168,23 +179,6 @@
                      base::Unretained(this)));
 
   std::move(callback).Run(std::move(session), std::move(session_controller));
-
-  if (!oculus_gamepad_factory_) {
-    oculus_gamepad_factory_ =
-        new OculusGamepadDataFetcher::Factory(GetId(), this);
-    GamepadDataFetcherManager::GetInstance()->AddFactory(
-        oculus_gamepad_factory_);
-  }
-}
-
-void OculusDevice::OnControllerUpdated(ovrInputState input,
-                                       ovrInputState remote,
-                                       ovrTrackingState tracking,
-                                       bool has_touch,
-                                       bool has_remote) {
-  if (data_fetcher_)
-    data_fetcher_->UpdateGamepadData(
-        {input, remote, tracking, has_touch, has_remote});
 }
 
 // XRSessionController
@@ -243,8 +237,20 @@
   std::move(callback).Run(std::move(frame_data));
 }
 
-void OculusDevice::RegisterDataFetcher(OculusGamepadDataFetcher* data_fetcher) {
-  data_fetcher_ = data_fetcher;
+void OculusDevice::GetIsolatedXRGamepadProvider(
+    mojom::IsolatedXRGamepadProviderRequest provider_request) {
+  // We bind the provider_request on the render loop thread, so gamepad data is
+  // updated at the rendering rate.
+  // If we haven't started the render loop yet, postpone binding the request
+  // until we do.
+  if (render_loop_->IsRunning()) {
+    render_loop_->task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&OculusRenderLoop::RequestGamepadProvider,
+                                  render_loop_->GetWeakPtr(),
+                                  std::move(provider_request)));
+  } else {
+    provider_request_ = std::move(provider_request);
+  }
 }
 
 }  // namespace device
diff --git a/device/vr/oculus/oculus_device.h b/device/vr/oculus/oculus_device.h
index 9002ac33..911362e 100644
--- a/device/vr/oculus/oculus_device.h
+++ b/device/vr/oculus/oculus_device.h
@@ -9,7 +9,6 @@
 
 #include "base/macros.h"
 #include "base/single_thread_task_runner.h"
-#include "device/vr/oculus/oculus_gamepad_data_fetcher.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device_base.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -21,7 +20,7 @@
 
 class OculusDevice : public VRDeviceBase,
                      public mojom::XRSessionController,
-                     public OculusGamepadDataProvider {
+                     public mojom::IsolatedXRGamepadProviderFactory {
  public:
   explicit OculusDevice();
   ~OculusDevice() override;
@@ -38,33 +37,31 @@
 
   bool IsInitialized() { return !!session_; }
 
+  mojom::IsolatedXRGamepadProviderFactoryPtr BindGamepadFactory();
+
  private:
   // XRSessionController
   void SetFrameDataRestricted(bool restricted) override;
 
   void OnPresentingControllerMojoConnectionError();
 
-  // OculusGamepadDataProvider
-  void RegisterDataFetcher(OculusGamepadDataFetcher*) override;
+  // mojom::IsolatedXRGamepadProviderFactory
+  void GetIsolatedXRGamepadProvider(
+      mojom::IsolatedXRGamepadProviderRequest provider_request) override;
 
   void OnPresentationEnded();
   void StartOvrSession();
   void StopOvrSession();
 
-  void OnControllerUpdated(ovrInputState input,
-                           ovrInputState remote,
-                           ovrTrackingState tracking,
-                           bool has_touch,
-                           bool has_remote);
-
   std::unique_ptr<OculusRenderLoop> render_loop_;
-  OculusGamepadDataFetcher* data_fetcher_ = nullptr;
   mojom::VRDisplayInfoPtr display_info_;
   ovrSession session_ = nullptr;
-  OculusGamepadDataFetcher::Factory* oculus_gamepad_factory_ = nullptr;
   scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
 
   mojo::Binding<mojom::XRSessionController> exclusive_controller_binding_;
+  mojo::Binding<mojom::IsolatedXRGamepadProviderFactory>
+      gamepad_provider_factory_binding_;
+  mojom::IsolatedXRGamepadProviderRequest provider_request_;
 
   base::WeakPtrFactory<OculusDevice> weak_ptr_factory_;
 
diff --git a/device/vr/oculus/oculus_device_provider.cc b/device/vr/oculus/oculus_device_provider.cc
index cda8a0a8..0b8fa24f 100644
--- a/device/vr/oculus/oculus_device_provider.cc
+++ b/device/vr/oculus/oculus_device_provider.cc
@@ -5,8 +5,8 @@
 #include "device/vr/oculus/oculus_device_provider.h"
 
 #include "device/gamepad/gamepad_data_fetcher_manager.h"
+#include "device/vr/isolated_gamepad_data_fetcher.h"
 #include "device/vr/oculus/oculus_device.h"
-#include "device/vr/oculus/oculus_gamepad_data_fetcher.h"
 #include "third_party/libovr/src/Include/OVR_CAPI.h"
 
 namespace device {
@@ -14,6 +14,10 @@
 OculusVRDeviceProvider::OculusVRDeviceProvider() : initialized_(false) {}
 
 OculusVRDeviceProvider::~OculusVRDeviceProvider() {
+  // Removing gamepad factory corresponding to VRDeviceId::OCULUS_DEVICE_ID,
+  // added during initialization.
+  device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory(
+      device::GAMEPAD_SOURCE_OCULUS);
 }
 
 void OculusVRDeviceProvider::Initialize(
@@ -23,9 +27,17 @@
     base::RepeatingCallback<void(unsigned int)> remove_device_callback,
     base::OnceClosure initialization_complete) {
   CreateDevice();
-  if (device_)
-    add_device_callback.Run(device_->GetId(), device_->GetVRDisplayInfo(),
-                            device_->BindXRRuntimePtr());
+  if (device_) {
+    add_device_callback.Run(
+        static_cast<unsigned int>(VRDeviceId::OCULUS_DEVICE_ID),
+        device_->GetVRDisplayInfo(), device_->BindXRRuntimePtr());
+
+    // Removed in our destructor, as VRDeviceId::OCULUS_DEVICE_ID corresponds to
+    // device::GAMEPAD_SOURCE_OCULUS.
+    GamepadDataFetcherManager::GetInstance()->AddFactory(
+        new IsolatedGamepadDataFetcher::Factory(VRDeviceId::OCULUS_DEVICE_ID,
+                                                device_->BindGamepadFactory()));
+  }
   initialized_ = true;
   std::move(initialization_complete).Run();
 }
diff --git a/device/vr/oculus/oculus_gamepad_data_fetcher.cc b/device/vr/oculus/oculus_gamepad_data_fetcher.cc
deleted file mode 100644
index aec6d3c..0000000
--- a/device/vr/oculus/oculus_gamepad_data_fetcher.cc
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/oculus/oculus_gamepad_data_fetcher.h"
-
-#include <memory>
-
-#include "base/logging.h"
-#include "base/strings/utf_string_conversions.h"
-#include "device/gamepad/public/cpp/gamepads.h"
-#include "third_party/libovr/src/Include/OVR_CAPI.h"
-#include "ui/gfx/transform.h"
-#include "ui/gfx/transform_util.h"
-
-namespace device {
-
-namespace {
-
-float ApplyTriggerDeadzone(float value) {
-  // Trigger value should be between 0 and 1.  We apply a deadzone for small
-  // values so a loose controller still reports a value of 0 when not in use.
-  float kTriggerDeadzone = 0.01f;
-
-  return (value < kTriggerDeadzone) ? 0 : value;
-}
-
-void SetGamepadButton(Gamepad* pad,
-                      const ovrInputState& input_state,
-                      ovrButton button_id) {
-  bool pressed = (input_state.Buttons & button_id) != 0;
-  bool touched = (input_state.Touches & button_id) != 0;
-  pad->buttons[pad->buttons_length].pressed = pressed;
-  pad->buttons[pad->buttons_length].touched = touched;
-  pad->buttons[pad->buttons_length].value = pressed ? 1.0f : 0.0f;
-  pad->buttons_length++;
-}
-
-void SetGamepadTouchTrigger(Gamepad* pad,
-                            const ovrInputState& input_state,
-                            ovrTouch touch_id,
-                            float value) {
-  bool touched = (input_state.Touches & touch_id) != 0;
-  value = ApplyTriggerDeadzone(value);
-  pad->buttons[pad->buttons_length].pressed = value != 0;
-  pad->buttons[pad->buttons_length].touched = touched;
-  pad->buttons[pad->buttons_length].value = value;
-  pad->buttons_length++;
-}
-
-void SetGamepadTrigger(Gamepad* pad, float value) {
-  value = ApplyTriggerDeadzone(value);
-  pad->buttons[pad->buttons_length].pressed = value != 0;
-  pad->buttons[pad->buttons_length].touched = value != 0;
-  pad->buttons[pad->buttons_length].value = value;
-  pad->buttons_length++;
-}
-
-void SetGamepadTouch(Gamepad* pad,
-                     const ovrInputState& input_state,
-                     ovrTouch touch_id) {
-  bool touched = (input_state.Touches & touch_id) != 0;
-  pad->buttons[pad->buttons_length].pressed = false;
-  pad->buttons[pad->buttons_length].touched = touched;
-  pad->buttons[pad->buttons_length].value = 0.0f;
-  pad->buttons_length++;
-}
-
-void SetTouchData(PadState* state,
-                  const ovrPoseStatef& pose,
-                  const ovrInputState& input_state,
-                  ovrHandType hand,
-                  unsigned int display_id) {
-  if (!state)
-    return;
-  Gamepad& pad = state->data;
-  if (!state->is_initialized) {
-    state->is_initialized = true;
-    pad.connected = true;
-    pad.is_xr = true;
-    pad.pose.not_null = true;
-    pad.pose.has_orientation = true;
-    pad.pose.has_position = true;
-    pad.display_id = display_id;
-    switch (hand) {
-      case ovrHand_Left:
-        swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Touch (Left)");
-        pad.hand = GamepadHand::kLeft;
-        break;
-      case ovrHand_Right:
-        swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Touch (Right)");
-        pad.hand = GamepadHand::kRight;
-        break;
-      default:
-        NOTREACHED();
-        return;
-    }
-  }
-  pad.timestamp = OculusGamepadDataFetcher::CurrentTimeInMicroseconds();
-  pad.axes_length = 0;
-  pad.buttons_length = 0;
-  pad.axes[pad.axes_length++] = input_state.Thumbstick[hand].x;
-  pad.axes[pad.axes_length++] = -input_state.Thumbstick[hand].y;
-
-  // This gamepad layout is the defacto standard, but can be adjusted for WebXR.
-  switch (hand) {
-    case ovrHand_Left:
-      SetGamepadButton(&pad, input_state, ovrButton_LThumb);
-      break;
-    case ovrHand_Right:
-      SetGamepadButton(&pad, input_state, ovrButton_RThumb);
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-  SetGamepadTouchTrigger(
-      &pad, input_state,
-      hand == ovrHand_Left ? ovrTouch_LIndexTrigger : ovrTouch_RIndexTrigger,
-      input_state.IndexTrigger[hand]);
-  SetGamepadTrigger(&pad, input_state.HandTrigger[hand]);
-  switch (hand) {
-    case ovrHand_Left:
-      SetGamepadButton(&pad, input_state, ovrButton_X);
-      SetGamepadButton(&pad, input_state, ovrButton_Y);
-      SetGamepadTouch(&pad, input_state, ovrTouch_LThumbRest);
-      break;
-    case ovrHand_Right:
-      SetGamepadButton(&pad, input_state, ovrButton_A);
-      SetGamepadButton(&pad, input_state, ovrButton_B);
-      SetGamepadTouch(&pad, input_state, ovrTouch_RThumbRest);
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-
-  pad.pose.orientation.not_null = true;
-  pad.pose.orientation.x = pose.ThePose.Orientation.x;
-  pad.pose.orientation.y = pose.ThePose.Orientation.y;
-  pad.pose.orientation.z = pose.ThePose.Orientation.z;
-  pad.pose.orientation.w = pose.ThePose.Orientation.w;
-
-  pad.pose.position.not_null = true;
-  pad.pose.position.x = pose.ThePose.Position.x;
-  pad.pose.position.y = pose.ThePose.Position.y;
-  pad.pose.position.z = pose.ThePose.Position.z;
-
-  pad.pose.angular_velocity.not_null = true;
-  pad.pose.angular_velocity.x = pose.AngularVelocity.x;
-  pad.pose.angular_velocity.y = pose.AngularVelocity.y;
-  pad.pose.angular_velocity.z = pose.AngularVelocity.z;
-
-  pad.pose.linear_velocity.not_null = true;
-  pad.pose.linear_velocity.x = pose.LinearVelocity.x;
-  pad.pose.linear_velocity.y = pose.LinearVelocity.y;
-  pad.pose.linear_velocity.z = pose.LinearVelocity.z;
-
-  pad.pose.angular_acceleration.not_null = true;
-  pad.pose.angular_acceleration.x = pose.AngularAcceleration.x;
-  pad.pose.angular_acceleration.y = pose.AngularAcceleration.y;
-  pad.pose.angular_acceleration.z = pose.AngularAcceleration.z;
-
-  pad.pose.linear_acceleration.not_null = true;
-  pad.pose.linear_acceleration.x = pose.LinearAcceleration.x;
-  pad.pose.linear_acceleration.y = pose.LinearAcceleration.y;
-  pad.pose.linear_acceleration.z = pose.LinearAcceleration.z;
-}
-
-}  // namespace
-
-OculusGamepadDataFetcher::Factory::Factory(unsigned int display_id,
-                                           OculusGamepadDataProvider* provider)
-    : display_id_(display_id), provider_(provider) {
-  DVLOG(1) << __FUNCTION__ << "=" << this;
-}
-
-OculusGamepadDataFetcher::Factory::~Factory() {
-  DVLOG(1) << __FUNCTION__ << "=" << this;
-}
-
-std::unique_ptr<GamepadDataFetcher>
-OculusGamepadDataFetcher::Factory::CreateDataFetcher() {
-  return std::make_unique<OculusGamepadDataFetcher>(display_id_, provider_);
-}
-
-GamepadSource OculusGamepadDataFetcher::Factory::source() {
-  return GAMEPAD_SOURCE_OCULUS;
-}
-
-OculusGamepadDataFetcher::OculusGamepadDataFetcher(
-    unsigned int display_id,
-    OculusGamepadDataProvider* provider)
-    : display_id_(display_id), weak_ptr_factory_(this) {
-  DVLOG(1) << __FUNCTION__ << "=" << this;
-
-  // Register for updates.
-  provider->RegisterDataFetcher(this);
-}
-
-OculusGamepadDataFetcher::~OculusGamepadDataFetcher() {
-  DVLOG(1) << __FUNCTION__ << "=" << this;
-}
-
-GamepadSource OculusGamepadDataFetcher::source() {
-  return GAMEPAD_SOURCE_OCULUS;
-}
-
-void OculusGamepadDataFetcher::OnAddedToProvider() {}
-
-void OculusGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) {
-  base::AutoLock lock(lock_);
-  if (data_.have_input_touch) {
-    SetTouchData(GetPadState(ovrControllerType_LTouch),
-                 data_.tracking.HandPoses[ovrHand_Left], data_.input_touch,
-                 ovrHand_Left, display_id_);
-    SetTouchData(GetPadState(ovrControllerType_RTouch),
-                 data_.tracking.HandPoses[ovrHand_Right], data_.input_touch,
-                 ovrHand_Right, display_id_);
-  }
-
-  if (data_.have_input_remote) {
-    PadState* state = GetPadState(ovrControllerType_Remote);
-    if (state) {
-      Gamepad& pad = state->data;
-      if (!state->is_initialized) {
-        state->is_initialized = true;
-        swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Remote");
-        pad.connected = true;
-        pad.is_xr = true;
-        pad.display_id = display_id_;
-      }
-      pad.timestamp = CurrentTimeInMicroseconds();
-      pad.axes_length = 0;
-      pad.buttons_length = 0;
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Enter);
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Back);
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Up);
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Down);
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Left);
-      SetGamepadButton(&pad, data_.input_remote, ovrButton_Right);
-    }
-  }
-}
-
-void OculusGamepadDataFetcher::UpdateGamepadData(OculusInputData data) {
-  base::AutoLock lock(lock_);
-  data_ = data;
-}
-
-void OculusGamepadDataFetcher::PauseHint(bool paused) {}
-
-}  // namespace device
diff --git a/device/vr/oculus/oculus_gamepad_data_fetcher.h b/device/vr/oculus/oculus_gamepad_data_fetcher.h
deleted file mode 100644
index e3430e6..0000000
--- a/device/vr/oculus/oculus_gamepad_data_fetcher.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_
-#define DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_
-
-#include "base/synchronization/lock.h"
-#include "device/gamepad/gamepad_data_fetcher.h"
-#include "third_party/libovr/src/Include/OVR_CAPI.h"
-
-namespace device {
-
-class OculusGamepadDataFetcher;
-
-class OculusGamepadDataProvider {
- public:
-  virtual void RegisterDataFetcher(OculusGamepadDataFetcher*) = 0;
-};
-
-struct OculusInputData {
-  ovrInputState input_touch = {};
-  ovrInputState input_remote = {};
-  ovrTrackingState tracking = {};
-  bool have_input_touch = false;
-  bool have_input_remote = false;
-};
-
-class OculusGamepadDataFetcher : public GamepadDataFetcher {
- public:
-  class Factory : public GamepadDataFetcherFactory {
-   public:
-    Factory(unsigned int display_id, OculusGamepadDataProvider* provider);
-    ~Factory() override;
-    std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override;
-    GamepadSource source() override;
-
-   private:
-    unsigned int display_id_;
-    OculusGamepadDataProvider* provider_;
-  };
-
-  OculusGamepadDataFetcher(unsigned int display_id,
-                           OculusGamepadDataProvider* provider);
-  ~OculusGamepadDataFetcher() override;
-
-  GamepadSource source() override;
-
-  void GetGamepadData(bool devices_changed_hint) override;
-  void PauseHint(bool paused) override;
-  void OnAddedToProvider() override;
-
-  void UpdateGamepadData(OculusInputData data);  // Called on UI thread.
-
- private:
-  unsigned int display_id_;
-
-  // Protects access to data_, which is read/written on different threads.
-  base::Lock lock_;
-
-  // have_input_* are initialized to false, so we won't try to read uninialized
-  // data from this if we read data_ before UpdateGamepadData is called.
-  OculusInputData data_;
-
-  base::WeakPtrFactory<OculusGamepadDataFetcher> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(OculusGamepadDataFetcher);
-};
-
-}  // namespace device
-
-#endif  // DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_
diff --git a/device/vr/oculus/oculus_gamepad_helper.cc b/device/vr/oculus/oculus_gamepad_helper.cc
new file mode 100644
index 0000000..a4a1520f
--- /dev/null
+++ b/device/vr/oculus/oculus_gamepad_helper.cc
@@ -0,0 +1,210 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/oculus/oculus_gamepad_helper.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "device/gamepad/public/cpp/gamepads.h"
+#include "device/vr/vr_device.h"
+#include "third_party/libovr/src/Include/OVR_CAPI.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+namespace device {
+
+namespace {
+
+enum GamepadIndex : unsigned int {
+  kLeftTouchController = 0x0,
+  kRightTouchController = 0x1,
+  kRemote = 0x2,
+};
+
+float ApplyTriggerDeadzone(float value) {
+  // Trigger value should be between 0 and 1.  We apply a deadzone for small
+  // values so a loose controller still reports a value of 0 when not in use.
+  float kTriggerDeadzone = 0.01f;
+
+  return (value < kTriggerDeadzone) ? 0 : value;
+}
+
+mojom::XRGamepadButtonPtr GetGamepadButton(const ovrInputState& input_state,
+                                           ovrButton button_id) {
+  bool pressed = (input_state.Buttons & button_id) != 0;
+  bool touched = (input_state.Touches & button_id) != 0;
+
+  auto button = mojom::XRGamepadButton::New();
+  button->pressed = pressed;
+  button->touched = touched;
+  button->value = pressed ? 1.0f : 0.0f;
+
+  return button;
+}
+
+mojom::XRGamepadButtonPtr GetGamepadTouchTrigger(
+    const ovrInputState& input_state,
+    ovrTouch touch_id,
+    float value) {
+  bool touched = (input_state.Touches & touch_id) != 0;
+  value = ApplyTriggerDeadzone(value);
+
+  auto button = mojom::XRGamepadButton::New();
+  button->pressed = value != 0;
+  button->touched = touched;
+  button->value = value;
+
+  return button;
+}
+
+mojom::XRGamepadButtonPtr GetGamepadTrigger(float value) {
+  value = ApplyTriggerDeadzone(value);
+  auto button = mojom::XRGamepadButton::New();
+  button->pressed = value != 0;
+  button->touched = value != 0;
+  button->value = value;
+
+  return button;
+}
+
+mojom::XRGamepadButtonPtr GetGamepadTouch(const ovrInputState& input_state,
+                                          ovrTouch touch_id) {
+  bool touched = (input_state.Touches & touch_id) != 0;
+
+  auto button = mojom::XRGamepadButton::New();
+  button->pressed = false;
+  button->touched = touched;
+  button->value = 0.0f;
+
+  return button;
+}
+
+void AddTouchData(mojom::XRGamepadDataPtr& data,
+                  const ovrTrackingState& tracking,
+                  const ovrInputState& input_state,
+                  ovrHandType hand) {
+  auto gamepad = mojom::XRGamepad::New();
+  // This gamepad layout is the defacto standard, but can be adjusted for WebXR.
+  switch (hand) {
+    case ovrHand_Left:
+      gamepad->hand = device::mojom::XRHandedness::LEFT;
+      gamepad->controller_id = kLeftTouchController;
+      gamepad->buttons.push_back(
+          GetGamepadButton(input_state, ovrButton_LThumb));
+      break;
+    case ovrHand_Right:
+      gamepad->hand = device::mojom::XRHandedness::RIGHT;
+      gamepad->controller_id = kRightTouchController;
+      gamepad->buttons.push_back(
+          GetGamepadButton(input_state, ovrButton_RThumb));
+      break;
+    default:
+      NOTREACHED();
+      return;
+  }
+
+  gamepad->axes.push_back(input_state.Thumbstick[hand].x);
+  gamepad->axes.push_back(-input_state.Thumbstick[hand].y);
+
+  gamepad->buttons.push_back(GetGamepadTouchTrigger(
+      input_state,
+      hand == ovrHand_Left ? ovrTouch_LIndexTrigger : ovrTouch_RIndexTrigger,
+      input_state.IndexTrigger[hand]));
+  gamepad->buttons.push_back(GetGamepadTrigger(input_state.HandTrigger[hand]));
+
+  switch (hand) {
+    case ovrHand_Left:
+      gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_X));
+      gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_Y));
+      gamepad->buttons.push_back(
+          GetGamepadTouch(input_state, ovrTouch_LThumbRest));
+      break;
+    case ovrHand_Right:
+      gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_A));
+      gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_B));
+      gamepad->buttons.push_back(
+          GetGamepadTouch(input_state, ovrTouch_RThumbRest));
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  auto dst_pose = mojom::VRPose::New();
+  const ovrPoseStatef& src_pose = tracking.HandPoses[hand];
+
+  dst_pose->orientation = std::vector<float>(
+      {src_pose.ThePose.Orientation.x, src_pose.ThePose.Orientation.y,
+       src_pose.ThePose.Orientation.z, src_pose.ThePose.Orientation.w});
+
+  dst_pose->position = std::vector<float>({src_pose.ThePose.Position.x,
+                                           src_pose.ThePose.Position.y,
+                                           src_pose.ThePose.Position.z});
+
+  dst_pose->angularVelocity = std::vector<float>({src_pose.AngularVelocity.x,
+                                                  src_pose.AngularVelocity.y,
+                                                  src_pose.AngularVelocity.z});
+
+  dst_pose->linearVelocity =
+      std::vector<float>({src_pose.LinearVelocity.x, src_pose.LinearVelocity.y,
+                          src_pose.LinearVelocity.z});
+
+  dst_pose->angularAcceleration = std::vector<float>(
+      {src_pose.AngularAcceleration.x, src_pose.AngularAcceleration.y,
+       src_pose.AngularAcceleration.z});
+
+  dst_pose->linearAcceleration = std::vector<float>(
+      {src_pose.LinearAcceleration.x, src_pose.LinearAcceleration.y,
+       src_pose.LinearAcceleration.z});
+
+  gamepad->pose = std::move(dst_pose);
+  gamepad->can_provide_position = true;
+  gamepad->can_provide_orientation = true;
+  data->gamepads.push_back(std::move(gamepad));
+}
+
+void AddRemoteData(mojom::XRGamepadDataPtr& data,
+                   const ovrInputState& input_remote) {
+  auto remote = mojom::XRGamepad::New();
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Enter));
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Back));
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Up));
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Down));
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Left));
+  remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Right));
+  remote->controller_id = kRemote;
+  remote->hand = device::mojom::XRHandedness::NONE;
+  data->gamepads.push_back(std::move(remote));
+}
+
+}  // namespace
+
+mojom::XRGamepadDataPtr OculusGamepadHelper::GetGamepadData(
+    ovrSession session) {
+  ovrInputState input_touch;
+  bool have_touch = OVR_SUCCESS(
+      ovr_GetInputState(session, ovrControllerType_Touch, &input_touch));
+
+  ovrInputState input_remote;
+  bool have_remote = OVR_SUCCESS(
+      ovr_GetInputState(session, ovrControllerType_Remote, &input_remote));
+
+  ovrTrackingState tracking = ovr_GetTrackingState(session, 0, false);
+
+  mojom::XRGamepadDataPtr data = mojom::XRGamepadData::New();
+
+  if (have_touch) {
+    AddTouchData(data, tracking, input_touch, ovrHand_Left);
+    AddTouchData(data, tracking, input_touch, ovrHand_Right);
+  }
+
+  if (have_remote)
+    AddRemoteData(data, input_remote);
+
+  return data;
+}
+
+}  // namespace device
diff --git a/device/vr/oculus/oculus_gamepad_helper.h b/device/vr/oculus/oculus_gamepad_helper.h
new file mode 100644
index 0000000..0e4f690
--- /dev/null
+++ b/device/vr/oculus/oculus_gamepad_helper.h
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_
+#define DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_
+
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
+#include "third_party/libovr/src/Include/OVR_CAPI.h"
+
+namespace device {
+
+class OculusGamepadHelper {
+ public:
+  static mojom::XRGamepadDataPtr GetGamepadData(ovrSession session);
+};
+
+}  // namespace device
+
+#endif  // DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_
diff --git a/device/vr/oculus/oculus_render_loop.cc b/device/vr/oculus/oculus_render_loop.cc
index f914b14..4d17fc9 100644
--- a/device/vr/oculus/oculus_render_loop.cc
+++ b/device/vr/oculus/oculus_render_loop.cc
@@ -4,6 +4,7 @@
 
 #include "device/vr/oculus/oculus_render_loop.h"
 
+#include "device/vr/oculus/oculus_gamepad_helper.h"
 #include "device/vr/oculus/oculus_type_converters.h"
 #include "third_party/libovr/src/Include/Extras/OVR_Math.h"
 #include "third_party/libovr/src/Include/OVR_CAPI.h"
@@ -42,16 +43,13 @@
 }  // namespace
 
 OculusRenderLoop::OculusRenderLoop(
-    base::RepeatingCallback<void()> on_presentation_ended,
-    base::RepeatingCallback<
-        void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)>
-        on_controller_updated)
+    base::RepeatingCallback<void()> on_presentation_ended)
     : base::Thread("OculusRenderLoop"),
       main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       presentation_binding_(this),
       frame_data_binding_(this),
       on_presentation_ended_(on_presentation_ended),
-      on_controller_updated_(on_controller_updated),
+      gamepad_provider_(this),
       weak_ptr_factory_(this) {
   DCHECK(main_thread_task_runner_);
 }
@@ -72,6 +70,7 @@
   StopOvrSession();
   presentation_binding_.Close();
   frame_data_binding_.Close();
+  gamepad_provider_.Close();
 }
 
 void OculusRenderLoop::SubmitFrameMissing(int16_t frame_index,
@@ -363,6 +362,14 @@
   std::move(callback).Run(std::move(frame_data));
 }
 
+void OculusRenderLoop::RequestGamepadProvider(
+    mojom::IsolatedXRGamepadProviderRequest request) {
+  gamepad_provider_.Close();
+  // We just close the binding, so the other side won't expect callbacks.
+  gamepad_callback_.Reset();
+  gamepad_provider_.Bind(std::move(request));
+}
+
 std::vector<mojom::XRInputSourceStatePtr> OculusRenderLoop::GetInputState(
     const ovrTrackingState& tracking_state) {
   std::vector<mojom::XRInputSourceStatePtr> input_states;
@@ -416,28 +423,18 @@
 }
 
 void OculusRenderLoop::UpdateControllerState() {
-  if (!session_) {
-    ovrInputState input = {};
-    ovrTrackingState tracking = {};
-    main_thread_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(on_controller_updated_, input, input,
-                                  tracking, false, false));
+  if (!gamepad_callback_) {
+    // Nobody is listening to updates, so bail early.
+    return;
   }
 
-  ovrInputState input_touch;
-  bool have_touch = OVR_SUCCESS(
-      ovr_GetInputState(session_, ovrControllerType_Touch, &input_touch));
+  if (!session_) {
+    std::move(gamepad_callback_).Run(nullptr);
+    return;
+  }
 
-  ovrInputState input_remote;
-  bool have_remote = OVR_SUCCESS(
-      ovr_GetInputState(session_, ovrControllerType_Remote, &input_remote));
-
-  ovrTrackingState tracking = ovr_GetTrackingState(session_, 0, false);
-
-  main_thread_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(on_controller_updated_, input_touch, input_remote,
-                     tracking, have_touch, have_remote));
+  std::move(gamepad_callback_)
+      .Run(OculusGamepadHelper::GetGamepadData(session_));
 }
 
 device::mojom::XRInputSourceStatePtr OculusRenderLoop::GetTouchData(
@@ -498,4 +495,22 @@
   return state;
 }
 
+void OculusRenderLoop::RequestUpdate(
+    mojom::IsolatedXRGamepadProvider::RequestUpdateCallback callback) {
+  DCHECK(!gamepad_callback_);
+  if (gamepad_callback_) {
+    std::move(gamepad_callback_).Run(nullptr);
+  }
+
+  // If we aren't presenting, reply now saying that we have no controllers.
+  if (!is_presenting_) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  // Otherwise, save the callback to resolve next time we update (typically on
+  // vsync).
+  gamepad_callback_ = std::move(callback);
+}
+
 }  // namespace device
diff --git a/device/vr/oculus/oculus_render_loop.h b/device/vr/oculus/oculus_render_loop.h
index 26cfdd57..0f241ed 100644
--- a/device/vr/oculus/oculus_render_loop.h
+++ b/device/vr/oculus/oculus_render_loop.h
@@ -25,16 +25,13 @@
 
 class OculusRenderLoop : public base::Thread,
                          mojom::XRPresentationProvider,
-                         mojom::XRFrameDataProvider {
+                         mojom::XRFrameDataProvider,
+                         mojom::IsolatedXRGamepadProvider {
  public:
   using RequestSessionCallback =
       base::OnceCallback<void(bool result, mojom::XRSessionPtr)>;
 
-  OculusRenderLoop(
-      base::RepeatingCallback<void()> on_presentation_ended,
-      base::RepeatingCallback<
-          void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)>
-          on_controller_updated);
+  OculusRenderLoop(base::RepeatingCallback<void()> on_presentation_ended);
   ~OculusRenderLoop() override;
 
   void RequestSession(mojom::XRDeviceRuntimeSessionOptionsPtr options,
@@ -42,6 +39,10 @@
   void ExitPresent();
   base::WeakPtr<OculusRenderLoop> GetWeakPtr();
 
+  // IsolatedXRGamepadProvider
+  void RequestUpdate(mojom::IsolatedXRGamepadProvider::RequestUpdateCallback
+                         callback) override;
+
   // XRPresentationProvider overrides:
   void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
   void SubmitFrame(int16_t frame_index,
@@ -59,6 +60,10 @@
   void GetFrameData(
       mojom::XRFrameDataProvider::GetFrameDataCallback callback) override;
 
+  // Bind a gamepad provider on the render loop thread, so we can provide
+  // updates with the latest poses used for rendering.
+  void RequestGamepadProvider(mojom::IsolatedXRGamepadProviderRequest request);
+
  private:
   // base::Thread overrides:
   void Init() override;
@@ -90,6 +95,8 @@
   base::OnceCallback<void()> delayed_get_frame_data_callback_;
   bool has_outstanding_frame_ = false;
 
+  mojom::IsolatedXRGamepadProvider::RequestUpdateCallback gamepad_callback_;
+
   long long ovr_frame_index_ = 0;
   int16_t next_frame_id_ = 0;
   bool is_presenting_ = false;
@@ -107,9 +114,8 @@
   mojo::Binding<mojom::XRFrameDataProvider> frame_data_binding_;
   bool primary_input_pressed[kMaxOculusRenderLoopInputId];
   base::RepeatingCallback<void()> on_presentation_ended_;
-  base::RepeatingCallback<
-      void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)>
-      on_controller_updated_;
+
+  mojo::Binding<mojom::IsolatedXRGamepadProvider> gamepad_provider_;
 
   base::WeakPtrFactory<OculusRenderLoop> weak_ptr_factory_;
 
diff --git a/device/vr/public/mojom/BUILD.gn b/device/vr/public/mojom/BUILD.gn
index 06cc4f23..a66e485 100644
--- a/device/vr/public/mojom/BUILD.gn
+++ b/device/vr/public/mojom/BUILD.gn
@@ -14,6 +14,7 @@
   ]
 
   public_deps = [
+    "//device/gamepad/public/mojom",
     "//gpu/ipc/common:interfaces",
     "//mojo/public/mojom/base",
     "//ui/display/mojo:interfaces",
diff --git a/device/vr/public/mojom/README.md b/device/vr/public/mojom/README.md
index dab1c92..f2dff80 100644
--- a/device/vr/public/mojom/README.md
+++ b/device/vr/public/mojom/README.md
@@ -73,3 +73,11 @@
 
 XRRuntimeEventListener - Lives in the browser process.  Exposes runtime events
 to the browser.
+
+# Browser <-> XRInput interfaces (defined in isolated_xr_service.mojom)
+IsolatedXRGamepadProvider and IsolatedXRGamepadProviderFactory - Live in the
+XRInput process, and allow GamepadDataFetchers living in the browser process
+to expose data from gamepads that cannot be queried from the browser process.
+
+The XRInput process may be the browser process or a separate process depending
+on the platform.
diff --git a/device/vr/public/mojom/isolated_xr_service.mojom b/device/vr/public/mojom/isolated_xr_service.mojom
index 83320de..9bf2e9e 100644
--- a/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/device/vr/public/mojom/isolated_xr_service.mojom
@@ -73,3 +73,62 @@
 
   SetListeningForActivate(bool listen_for_activation);
 };
+
+// Represents the state of a single button or trigger.
+struct XRGamepadButton {
+  bool pressed; // Is the button currently pressed from its default position?
+  bool touched; // Is the user in contact with a button (always true if pressed)
+  double value; // How far pressed is it, from 0 to 1?
+};
+
+// Represents the state of a single controller.
+struct XRGamepad {
+  bool can_provide_orientation; // Is the controller capable of orientation?
+  bool can_provide_position; // Is the controller capable of position?
+  array<double> axes;
+  array<XRGamepadButton> buttons;
+
+  // The position/orientation of a controller, and its velocity and acceleration
+  // if available.  Members inside this may be null if not available currently.
+  VRPose? pose;
+
+  // Left/Right handed controller, or none if unknown.
+  XRHandedness hand;
+
+  // A unique (per device_id) id that allows controllers to be tracked between
+  // updates.  Useful to identify controllers as they are added/removed.
+  uint32 controller_id;
+};
+
+// Represents the state of a set of controllers driven by some runtime API.
+struct XRGamepadData {
+  array<XRGamepad> gamepads;
+};
+
+// OpenVR and Oculus APIs can't run in the browser process, but the gamepad
+// polling happens there.  This interface allows gamepad polling to request
+// data from out-of-process gamepad providers, at the cost of some extra IPC
+// latency.  IsolatedXRGamepadProvider is currently implemented in the XRDevice
+// process, and consumed by the gamepad polling thread in the browser process.
+// It will move to live in a separate XRInput process in the future.
+interface IsolatedXRGamepadProvider {
+  // Consumers should not call RequestUpdate until the previous request returns
+  // to avoid queuing up extra requests if polling and rendering are happening
+  // at different rates.  If called while an outstanding request is queued, it
+  // returns immediately with null data.
+  // Returned data is null if we aren't currently getting data from the runtime.
+  RequestUpdate() => (XRGamepadData? data);
+};
+
+// Gamepad providers may come and go as pages request or stop requesting gamepad
+// data.  IsolatedXRGamepadProviderFactory allows GamepadDataFetchers to acquire
+// new IsolatedXRGamepadProviders when needed.
+// IsolatedXRGamepadProvider is consumed in the browser process.  It is
+// currently implemented in the XRDevice process, but will move to a separate
+// XRInput process.
+interface IsolatedXRGamepadProviderFactory {
+  // Get the IsolatedXRGamepadProvider for a specific XR runtime API (Oculus, or
+  // OpenVR, which are currently the only two that are hosted outside of the
+  // browser process).
+  GetIsolatedXRGamepadProvider(IsolatedXRGamepadProvider& provider);
+};
diff --git a/docs/README.md b/docs/README.md
index 92931779..7a36442 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -258,6 +258,8 @@
     coverage data with the EMMA tool.
 *   [Android BuildConfig files](../build/android/docs/build_config.md) -
     What are .build_config files and how they are used.
+*   [Android App Bundles](../build/android/docs/android_app_bundles.md) -
+    How to build Android app bundles for Chrome.
 
 ### Misc iOS-Specific Docs
 *   [Continuous Build and Test Infrastructure for Chromium for iOS](ios/infra.md)
diff --git a/docs/mojo_guide.md b/docs/mojo_guide.md
index ba38d18..c5ec846 100644
--- a/docs/mojo_guide.md
+++ b/docs/mojo_guide.md
@@ -15,7 +15,7 @@
 A **mojom** file describes **interfaces** which describe strongly typed message
 structures, similar to proto files.
 
-Given a **mojom interface** and a **message pipe**, the two **message pipes**
+Given a **mojom interface** and a **message pipe**, the two **endpoints**
 can be given the labels **InterfacePtr** and **Binding**. This now describes a
 strongly typed **message pipe** which transports messages described by the
 **mojom interface**. The **InterfacePtr** is the **endpoint** which "sends"
diff --git a/extensions/browser/app_window/app_delegate.h b/extensions/browser/app_window/app_delegate.h
index 2503d8c48..161b438d 100644
--- a/extensions/browser/app_window/app_delegate.h
+++ b/extensions/browser/app_window/app_delegate.h
@@ -25,6 +25,10 @@
 class Size;
 }
 
+namespace viz {
+class SurfaceId;
+}
+
 namespace extensions {
 
 class Extension;
@@ -89,6 +93,17 @@
   // a chance to handle the focus change.
   // Return whether focus has been handled.
   virtual bool TakeFocus(content::WebContents* web_contents, bool reverse) = 0;
+
+  // Notifies the Picture-in-Picture controller that there is a new player
+  // entering Picture-in-Picture.
+  // Returns the size of the Picture-in-Picture window.
+  virtual gfx::Size EnterPictureInPicture(content::WebContents* web_contents,
+                                          const viz::SurfaceId& surface_id,
+                                          const gfx::Size& natural_size) = 0;
+
+  // Updates the Picture-in-Picture controller with a signal that
+  // Picture-in-Picture mode has ended.
+  virtual void ExitPictureInPicture() = 0;
 };
 
 }  // namespace extensions
diff --git a/extensions/browser/app_window/app_window.cc b/extensions/browser/app_window/app_window.cc
index 089d386..41b56b5 100644
--- a/extensions/browser/app_window/app_window.cc
+++ b/extensions/browser/app_window/app_window.cc
@@ -438,6 +438,16 @@
   return app_delegate_->TakeFocus(source, reverse);
 }
 
+gfx::Size AppWindow::EnterPictureInPicture(const viz::SurfaceId& surface_id,
+                                           const gfx::Size& natural_size) {
+  return app_delegate_->EnterPictureInPicture(web_contents(), surface_id,
+                                              natural_size);
+}
+
+void AppWindow::ExitPictureInPicture() {
+  app_delegate_->ExitPictureInPicture();
+}
+
 bool AppWindow::OnMessageReceived(const IPC::Message& message,
                                   content::RenderFrameHost* render_frame_host) {
   bool handled = true;
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h
index 2b900665..c759c5b1 100644
--- a/extensions/browser/app_window/app_window.h
+++ b/extensions/browser/app_window/app_window.h
@@ -444,6 +444,9 @@
       content::RenderFrameHost* frame,
       const content::BluetoothChooser::EventHandler& event_handler) override;
   bool TakeFocus(content::WebContents* source, bool reverse) override;
+  gfx::Size EnterPictureInPicture(const viz::SurfaceId& surface_id,
+                                  const gfx::Size& natural_size) override;
+  void ExitPictureInPicture() override;
 
   // content::WebContentsObserver implementation.
   bool OnMessageReceived(const IPC::Message& message,
diff --git a/extensions/common/api/networking_onc.idl b/extensions/common/api/networking_onc.idl
index 7eafa46..04eeeaa 100644
--- a/extensions/common/api/networking_onc.idl
+++ b/extensions/common/api/networking_onc.idl
@@ -612,6 +612,8 @@
     DOMString? Security;
     // The network signal strength.
     long? SignalStrength;
+    // The tethering state associated with the connection.
+    DOMString? TetheringState;
   };
 
   dictionary ManagedWiFiProperties {
@@ -637,6 +639,8 @@
     ManagedDOMString Security;
     // See $(ref:WiFiProperties.SignalStrength).
     long? SignalStrength;
+    // See $(ref:WiFiProperties.TetheringState).
+    DOMString? TetheringState;
   };
 
   dictionary WiFiStateProperties {
@@ -652,6 +656,8 @@
     long? SignalStrength;
     // See $(ref:WiFiProperties.SSID).
     DOMString? SSID;
+    // See $(ref:WiFiProperties.TetheringState).
+    DOMString? TetheringState;
   };
 
   dictionary WiMAXProperties {
diff --git a/extensions/common/api/networking_private.idl b/extensions/common/api/networking_private.idl
index 19bfb3f8..414e084 100644
--- a/extensions/common/api/networking_private.idl
+++ b/extensions/common/api/networking_private.idl
@@ -654,6 +654,7 @@
     DOMString? SSID;
     DOMString? Security;
     long? SignalStrength;
+    DOMString? TetheringState;
   };
 
   dictionary ManagedWiFiProperties {
@@ -671,6 +672,7 @@
     ManagedDOMString? SSID;
     ManagedDOMString Security;
     long? SignalStrength;
+    DOMString? TetheringState;
   };
 
   dictionary WiFiStateProperties {
@@ -680,6 +682,7 @@
     DOMString Security;
     long? SignalStrength;
     DOMString? SSID;
+    DOMString? TetheringState;
   };
 
   dictionary WiMAXProperties {
diff --git a/extensions/renderer/app_window_custom_bindings.cc b/extensions/renderer/app_window_custom_bindings.cc
index 7db62aa..1ce274a9 100644
--- a/extensions/renderer/app_window_custom_bindings.cc
+++ b/extensions/renderer/app_window_custom_bindings.cc
@@ -44,8 +44,8 @@
   if (!args[0]->IsInt32() || !args[1]->IsBoolean())
     return;
 
-  int frame_id = args[0]->Int32Value();
-  bool notify_browser = args[1]->BooleanValue();
+  int frame_id = args[0].As<v8::Int32>()->Value();
+  bool notify_browser = args[1].As<v8::Boolean>()->Value();
 
   if (frame_id == MSG_ROUTING_NONE)
     return;
@@ -83,7 +83,7 @@
     return;
   }
 
-  int frame_id = args[0]->Int32Value();
+  int frame_id = args[0].As<v8::Int32>()->Value();
   content::RenderFrame* app_frame =
       content::RenderFrame::FromRoutingID(frame_id);
   if (!app_frame) {
diff --git a/extensions/renderer/bindings/argument_spec_unittest.cc b/extensions/renderer/bindings/argument_spec_unittest.cc
index ed2451d..71e3f61 100644
--- a/extensions/renderer/bindings/argument_spec_unittest.cc
+++ b/extensions/renderer/bindings/argument_spec_unittest.cc
@@ -858,13 +858,13 @@
     ArgumentSpec spec(ArgumentType::INTEGER);
     ExpectSuccess(spec, "1", base::BindOnce([](v8::Local<v8::Value> value) {
                     ASSERT_TRUE(value->IsInt32());
-                    EXPECT_EQ(1, value->Int32Value());
+                    EXPECT_EQ(1, value.As<v8::Int32>()->Value());
                   }));
     // The conversion should handle the -0 value (which is considered an
     // integer but stored in v8 has a number) by converting it to a 0 integer.
     ExpectSuccess(spec, "-0", base::BindOnce([](v8::Local<v8::Value> value) {
                     ASSERT_TRUE(value->IsInt32());
-                    EXPECT_EQ(0, value->Int32Value());
+                    EXPECT_EQ(0, value.As<v8::Int32>()->Value());
                   }));
   }
 
diff --git a/extensions/renderer/blob_native_handler.cc b/extensions/renderer/blob_native_handler.cc
index 2e440da..0a0e5e8 100644
--- a/extensions/renderer/blob_native_handler.cc
+++ b/extensions/renderer/blob_native_handler.cc
@@ -48,7 +48,7 @@
   std::string type(*v8::String::Utf8Value(isolate, args[1]));
   blink::WebBlob blob = blink::WebBlob::CreateFromUUID(
       blink::WebString::FromUTF8(uuid), blink::WebString::FromUTF8(type),
-      args[2]->Int32Value());
+      args[2].As<v8::Int32>()->Value());
   args.GetReturnValue().Set(
       blob.ToV8Value(context()->v8_context()->Global(), isolate));
 }
diff --git a/extensions/renderer/event_bindings.cc b/extensions/renderer/event_bindings.cc
index 003cc230..8d75bc61 100644
--- a/extensions/renderer/event_bindings.cc
+++ b/extensions/renderer/event_bindings.cc
@@ -141,7 +141,7 @@
   CHECK(args[0]->IsString());
   CHECK(args[1]->IsBoolean());
   AttachEvent(*v8::String::Utf8Value(args.GetIsolate(), args[0]),
-              args[1]->BooleanValue());
+              args[1].As<v8::Boolean>()->Value());
 }
 
 void EventBindings::AttachEvent(const std::string& event_name,
@@ -180,8 +180,8 @@
   CHECK(args[0]->IsString());
   CHECK(args[1]->IsBoolean());
   CHECK(args[2]->IsBoolean());
-  bool was_manual = args[1]->BooleanValue();
-  bool supports_lazy_listeners = args[2]->BooleanValue();
+  bool was_manual = args[1].As<v8::Boolean>()->Value();
+  bool supports_lazy_listeners = args[2].As<v8::Boolean>()->Value();
   DetachEvent(*v8::String::Utf8Value(args.GetIsolate(), args[0]),
               was_manual && supports_lazy_listeners);
 }
@@ -236,7 +236,7 @@
     filter = base::DictionaryValue::From(std::move(filter_value));
   }
 
-  bool supports_lazy_listeners = args[2]->BooleanValue();
+  bool supports_lazy_listeners = args[2].As<v8::Boolean>()->Value();
 
   EventBookkeeper* bookkeeper = EventBookkeeper::Get();
   DCHECK(bookkeeper);
@@ -271,9 +271,9 @@
   CHECK(args[0]->IsInt32());
   CHECK(args[1]->IsBoolean());
   CHECK(args[2]->IsBoolean());
-  bool was_manual = args[1]->BooleanValue();
-  bool supports_lazy_listeners = args[2]->BooleanValue();
-  DetachFilteredEvent(args[0]->Int32Value(),
+  bool was_manual = args[1].As<v8::Boolean>()->Value();
+  bool supports_lazy_listeners = args[2].As<v8::Boolean>()->Value();
+  DetachFilteredEvent(args[0].As<v8::Int32>()->Value(),
                       was_manual && supports_lazy_listeners);
 }
 
diff --git a/extensions/renderer/file_system_natives.cc b/extensions/renderer/file_system_natives.cc
index 5b896ff..f4a7cd8 100644
--- a/extensions/renderer/file_system_natives.cc
+++ b/extensions/renderer/file_system_natives.cc
@@ -92,8 +92,9 @@
 
   CHECK(args[4]->IsBoolean());
   blink::WebDOMFileSystem::EntryType entry_type =
-      args[4]->BooleanValue() ? blink::WebDOMFileSystem::kEntryTypeDirectory
-                              : blink::WebDOMFileSystem::kEntryTypeFile;
+      args[4].As<v8::Boolean>()->Value()
+          ? blink::WebDOMFileSystem::kEntryTypeDirectory
+          : blink::WebDOMFileSystem::kEntryTypeFile;
 
   blink::WebLocalFrame* webframe =
       blink::WebLocalFrame::FrameForContext(context()->v8_context());
diff --git a/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc b/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
index 96c87766..2d1cdea 100644
--- a/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
+++ b/extensions/renderer/guest_view/guest_view_internal_custom_bindings.cc
@@ -156,7 +156,7 @@
   // Optional Callback Function.
   CHECK(args.Length() < 4 || args[3]->IsFunction());
 
-  int element_instance_id = args[0]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
   // An element instance ID uniquely identifies a GuestViewContainer.
   auto* guest_view_container =
       guest_view::GuestViewContainer::FromID(element_instance_id);
@@ -168,7 +168,7 @@
   // Retain a weak pointer so we can easily test if the container goes away.
   auto weak_ptr = guest_view_container->GetWeakPtr();
 
-  int guest_instance_id = args[1]->Int32Value();
+  int guest_instance_id = args[1].As<v8::Int32>()->Value();
 
   std::unique_ptr<base::DictionaryValue> params = base::DictionaryValue::From(
       content::V8ValueConverter::Create()->FromV8Value(
@@ -206,7 +206,7 @@
   // Optional Callback Function.
   CHECK(args.Length() < 2 || args[1]->IsFunction());
 
-  int element_instance_id = args[0]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
   // An element instance ID uniquely identifies a GuestViewContainer.
   auto* guest_view_container =
       guest_view::GuestViewContainer::FromID(element_instance_id);
@@ -244,8 +244,8 @@
   CHECK(args.Length() <= num_required_params ||
         args[num_required_params]->IsFunction());
 
-  int element_instance_id = args[0]->Int32Value();
-  int guest_instance_id = args[1]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
+  int guest_instance_id = args[1].As<v8::Int32>()->Value();
 
   // Get the WebLocalFrame before (possibly) executing any user-space JS while
   // getting the |params|. We track the status of the RenderFrame via an
@@ -309,7 +309,7 @@
   if (!args[0]->IsInt32())
     return;
 
-  int element_instance_id = args[0]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
   auto* guest_view_container =
       guest_view::GuestViewContainer::FromID(element_instance_id);
   if (!guest_view_container)
@@ -334,7 +334,7 @@
   if (!args[0]->IsInt32())
     return;
 
-  int view_id = args[0]->Int32Value();
+  int view_id = args[0].As<v8::Int32>()->Value();
   if (view_id == MSG_ROUTING_NONE)
     return;
 
@@ -355,7 +355,7 @@
   CHECK(args.Length() == 1);
   // The view ID.
   CHECK(args[0]->IsInt32());
-  int view_id = args[0]->Int32Value();
+  int view_id = args[0].As<v8::Int32>()->Value();
 
   ViewMap& view_map = weak_view_map.Get();
   auto map_entry = view_map.find(view_id);
@@ -376,7 +376,7 @@
   // Callback function.
   CHECK(args[1]->IsFunction());
 
-  int element_instance_id = args[0]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
   // An element instance ID uniquely identifies a GuestViewContainer within a
   // RenderView.
   auto* guest_view_container =
@@ -399,7 +399,7 @@
   // Callback function.
   CHECK(args[1]->IsFunction());
 
-  int element_instance_id = args[0]->Int32Value();
+  int element_instance_id = args[0].As<v8::Int32>()->Value();
   // An element instance ID uniquely identifies a ExtensionsGuestViewContainer
   // within a RenderView.
   auto* guest_view_container =
@@ -427,7 +427,7 @@
   // A reference to the view object is stored in |weak_view_map| using its view
   // ID as the key. The reference is made weak so that it will not extend the
   // lifetime of the object.
-  int view_instance_id = args[0]->Int32Value();
+  int view_instance_id = args[0].As<v8::Int32>()->Value();
   auto* object =
       new v8::Global<v8::Object>(args.GetIsolate(), args[1].As<v8::Object>());
   weak_view_map.Get().insert(std::make_pair(view_instance_id, object));
diff --git a/extensions/renderer/logging_native_handler.cc b/extensions/renderer/logging_native_handler.cc
index d312677..72938859 100644
--- a/extensions/renderer/logging_native_handler.cc
+++ b/extensions/renderer/logging_native_handler.cc
@@ -66,7 +66,8 @@
     bool* check_value,
     std::string* error_message) {
   CHECK_LE(args.Length(), 2);
-  *check_value = args[0]->BooleanValue();
+  *check_value =
+      args[0]->BooleanValue(context()->v8_context()).FromMaybe(false);
   if (args.Length() == 2) {
     *error_message = "Error: " + std::string(*v8::String::Utf8Value(
                                      args.GetIsolate(), args[1]));
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
index 70955e1..bf5ff720 100644
--- a/extensions/renderer/messaging_bindings.cc
+++ b/extensions/renderer/messaging_bindings.cc
@@ -196,8 +196,7 @@
   info.source_url = context()->url();
   std::string channel_name = *v8::String::Utf8Value(isolate, args[1]);
   // TODO(devlin): Why is this not part of info?
-  bool include_tls_channel_id =
-      args.Length() > 2 ? args[2]->BooleanValue() : false;
+  bool include_tls_channel_id = args[2].As<v8::Boolean>()->Value();
 
   {
     SCOPED_UMA_HISTOGRAM_TIMER(
@@ -264,8 +263,8 @@
   ports_[js_id] = std::make_unique<ExtensionPort>(context(), port_id, js_id);
 
   ExtensionMsg_TabTargetConnectionInfo info;
-  info.tab_id = args[0]->Int32Value();
-  info.frame_id = args[1]->Int32Value();
+  info.tab_id = args[0].As<v8::Int32>()->Value();
+  info.frame_id = args[1].As<v8::Int32>()->Value();
   // TODO(devlin): Why is this not part of info?
   v8::Isolate* isolate = args.GetIsolate();
   std::string extension_id = *v8::String::Utf8Value(isolate, args[2]);
diff --git a/extensions/renderer/messaging_util.cc b/extensions/renderer/messaging_util.cc
index 77d7ad3c..b22dcfd 100644
--- a/extensions/renderer/messaging_util.cc
+++ b/extensions/renderer/messaging_util.cc
@@ -136,10 +136,12 @@
 }
 
 int ExtractIntegerId(v8::Local<v8::Value> value) {
+  if (value->IsInt32())
+    return value.As<v8::Int32>()->Value();
+
   // Account for -0, which is a valid integer, but is stored as a number in v8.
-  DCHECK(value->IsNumber() &&
-         (value->IsInt32() || value.As<v8::Number>()->Value() == 0.0));
-  return value->Int32Value();
+  DCHECK(value->IsNumber() && value.As<v8::Number>()->Value() == 0.0);
+  return 0;
 }
 
 MessageOptions ParseMessageOptions(v8::Local<v8::Context> context,
@@ -173,7 +175,7 @@
     if (!v8_include_tls_channel_id->IsUndefined()) {
       DCHECK(v8_include_tls_channel_id->IsBoolean());
       options.include_tls_channel_id =
-          v8_include_tls_channel_id->BooleanValue();
+          v8_include_tls_channel_id.As<v8::Boolean>()->Value();
     }
   }
 
diff --git a/extensions/renderer/render_frame_observer_natives.cc b/extensions/renderer/render_frame_observer_natives.cc
index 7139a74..7eb7dee0 100644
--- a/extensions/renderer/render_frame_observer_natives.cc
+++ b/extensions/renderer/render_frame_observer_natives.cc
@@ -74,7 +74,7 @@
   CHECK(args[0]->IsInt32());
   CHECK(args[1]->IsFunction());
 
-  int frame_id = args[0]->Int32Value();
+  int frame_id = args[0].As<v8::Int32>()->Value();
 
   content::RenderFrame* frame = content::RenderFrame::FromRoutingID(frame_id);
   if (!frame) {
diff --git a/extensions/renderer/runtime_custom_bindings.cc b/extensions/renderer/runtime_custom_bindings.cc
index f4e510b..816e23e 100644
--- a/extensions/renderer/runtime_custom_bindings.cc
+++ b/extensions/renderer/runtime_custom_bindings.cc
@@ -51,8 +51,8 @@
 
   // |browser_window_id| == extension_misc::kUnknownWindowId means getting
   // all views for the current extension.
-  int browser_window_id = args[0]->Int32Value();
-  int tab_id = args[1]->Int32Value();
+  int browser_window_id = args[0].As<v8::Int32>()->Value();
+  int tab_id = args[1].As<v8::Int32>()->Value();
 
   std::string view_type_string =
       base::ToUpperASCII(*v8::String::Utf8Value(args.GetIsolate(), args[2]));
@@ -67,7 +67,6 @@
   if (extension_id.empty())
     return;
 
-  v8::Local<v8::Context> v8_context = args.GetIsolate()->GetCurrentContext();
   // We ignore iframes here. (Returning subframes can cause broken behavior by
   // treating an app window's iframe as its main frame, and maybe other
   // nastiness).
@@ -75,7 +74,8 @@
   // can be reasons to want to access just a frame - especially with isolated
   // extension frames in web pages.
   v8::Local<v8::Array> v8_views = ExtensionFrameHelper::GetV8MainFrames(
-      v8_context, extension_id, browser_window_id, tab_id, view_type);
+      context()->v8_context(), extension_id, browser_window_id, tab_id,
+      view_type);
 
   args.GetReturnValue().Set(v8_views);
 }
diff --git a/extensions/renderer/send_request_natives.cc b/extensions/renderer/send_request_natives.cc
index 6093db9..1e39f93 100644
--- a/extensions/renderer/send_request_natives.cc
+++ b/extensions/renderer/send_request_natives.cc
@@ -34,9 +34,12 @@
   base::ElapsedTimer timer;
   CHECK_EQ(5, args.Length());
   std::string name = *v8::String::Utf8Value(args.GetIsolate(), args[0]);
-  bool has_callback = args[2]->BooleanValue();
-  bool for_io_thread = args[3]->BooleanValue();
-  bool preserve_null_in_objects = args[4]->BooleanValue();
+  bool has_callback =
+      args[2]->BooleanValue(context()->v8_context()).FromMaybe(false);
+  bool for_io_thread =
+      args[3]->BooleanValue(context()->v8_context()).FromMaybe(false);
+  bool preserve_null_in_objects =
+      args[4]->BooleanValue(context()->v8_context()).FromMaybe(false);
 
   int request_id = request_sender_->GetNextRequestId();
   args.GetReturnValue().Set(static_cast<int32_t>(request_id));
diff --git a/extensions/renderer/set_icon_natives.cc b/extensions/renderer/set_icon_natives.cc
index 11e554d8..bc29330 100644
--- a/extensions/renderer/set_icon_natives.cc
+++ b/extensions/renderer/set_icon_natives.cc
@@ -41,14 +41,17 @@
 bool SetIconNatives::ConvertImageDataToBitmapValue(
     const v8::Local<v8::Object> image_data,
     v8::Local<v8::Value>* image_data_bitmap) {
-  v8::Isolate* isolate = context()->v8_context()->GetIsolate();
+  v8::Local<v8::Context> v8_context = context()->v8_context();
+  v8::Isolate* isolate = v8_context->GetIsolate();
   v8::Local<v8::Object> data =
       image_data->Get(v8::String::NewFromUtf8(isolate, "data"))
           ->ToObject(isolate);
-  int width =
-      image_data->Get(v8::String::NewFromUtf8(isolate, "width"))->Int32Value();
-  int height =
-      image_data->Get(v8::String::NewFromUtf8(isolate, "height"))->Int32Value();
+  int width = image_data->Get(v8::String::NewFromUtf8(isolate, "width"))
+                  ->Int32Value(v8_context)
+                  .FromMaybe(0);
+  int height = image_data->Get(v8::String::NewFromUtf8(isolate, "height"))
+                   ->Int32Value(v8_context)
+                   .FromMaybe(0);
 
   if (width <= 0 || height <= 0) {
     isolate->ThrowException(v8::Exception::Error(
@@ -65,8 +68,9 @@
     return false;
   }
 
-  int data_length =
-      data->Get(v8::String::NewFromUtf8(isolate, "length"))->Int32Value();
+  int data_length = data->Get(v8::String::NewFromUtf8(isolate, "length"))
+                        ->Int32Value(v8_context)
+                        .FromMaybe(0);
   if (data_length != 4 * width * height) {
     isolate->ThrowException(
         v8::Exception::Error(v8::String::NewFromUtf8(isolate, kInvalidData)));
@@ -84,15 +88,27 @@
   uint32_t* pixels = bitmap.getAddr32(0, 0);
   for (int t = 0; t < width * height; t++) {
     // |data| is RGBA, pixels is ARGB.
-    pixels[t] = SkPreMultiplyColor(
-        ((data->Get(v8::Integer::New(isolate, 4 * t + 3))->Int32Value() & 0xFF)
-         << 24) |
-        ((data->Get(v8::Integer::New(isolate, 4 * t + 0))->Int32Value() & 0xFF)
-         << 16) |
-        ((data->Get(v8::Integer::New(isolate, 4 * t + 1))->Int32Value() & 0xFF)
-         << 8) |
-        ((data->Get(v8::Integer::New(isolate, 4 * t + 2))->Int32Value() & 0xFF)
-         << 0));
+    pixels[t] =
+        SkPreMultiplyColor(((data->Get(v8::Integer::New(isolate, 4 * t + 3))
+                                 ->Int32Value(v8_context)
+                                 .FromMaybe(0) &
+                             0xFF)
+                            << 24) |
+                           ((data->Get(v8::Integer::New(isolate, 4 * t + 0))
+                                 ->Int32Value(v8_context)
+                                 .FromMaybe(0) &
+                             0xFF)
+                            << 16) |
+                           ((data->Get(v8::Integer::New(isolate, 4 * t + 1))
+                                 ->Int32Value(v8_context)
+                                 .FromMaybe(0) &
+                             0xFF)
+                            << 8) |
+                           ((data->Get(v8::Integer::New(isolate, 4 * t + 2))
+                                 ->Int32Value(v8_context)
+                                 .FromMaybe(0) &
+                             0xFF)
+                            << 0));
   }
 
   // Construct the Value object.
diff --git a/extensions/shell/browser/shell_app_delegate.cc b/extensions/shell/browser/shell_app_delegate.cc
index 7945feb..b6261e4f 100644
--- a/extensions/shell/browser/shell_app_delegate.cc
+++ b/extensions/shell/browser/shell_app_delegate.cc
@@ -106,4 +106,16 @@
   return false;
 }
 
+gfx::Size ShellAppDelegate::EnterPictureInPicture(
+    content::WebContents* web_contents,
+    const viz::SurfaceId& surface_id,
+    const gfx::Size& natural_size) {
+  NOTREACHED();
+  return gfx::Size();
+}
+
+void ShellAppDelegate::ExitPictureInPicture() {
+  NOTREACHED();
+}
+
 }  // namespace extensions
diff --git a/extensions/shell/browser/shell_app_delegate.h b/extensions/shell/browser/shell_app_delegate.h
index 2f4857c..4be3c8a 100644
--- a/extensions/shell/browser/shell_app_delegate.h
+++ b/extensions/shell/browser/shell_app_delegate.h
@@ -52,6 +52,10 @@
   void OnHide() override {}
   void OnShow() override {}
   bool TakeFocus(content::WebContents* web_contents, bool reverse) override;
+  gfx::Size EnterPictureInPicture(content::WebContents* web_contents,
+                                  const viz::SurfaceId& surface_id,
+                                  const gfx::Size& natural_size) override;
+  void ExitPictureInPicture() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ShellAppDelegate);
diff --git a/gpu/command_buffer/service/abstract_texture.h b/gpu/command_buffer/service/abstract_texture.h
index 08d9eaa..0f2a757 100644
--- a/gpu/command_buffer/service/abstract_texture.h
+++ b/gpu/command_buffer/service/abstract_texture.h
@@ -77,12 +77,6 @@
   // The context must be current.
   virtual void BindImage(gl::GLImage* image, bool client_managed) = 0;
 
-  // Unbind and release any image bound to this texture, and return it to an
-  // uncleared state.
-  //
-  // The context must be current.
-  virtual void ReleaseImage() = 0;
-
   // Return the image, if any.
   virtual gl::GLImage* GetImage() const = 0;
 
diff --git a/gpu/command_buffer/service/gl_context_virtual.cc b/gpu/command_buffer/service/gl_context_virtual.cc
index 05980b0..86674b4 100644
--- a/gpu/command_buffer/service/gl_context_virtual.cc
+++ b/gpu/command_buffer/service/gl_context_virtual.cc
@@ -110,6 +110,10 @@
 void GLContextVirtual::BackpressureFenceWait(uint64_t fence) {
   shared_context_->BackpressureFenceWait(fence);
 }
+
+void GLContextVirtual::FlushForDebugging() {
+  shared_context_->FlushForDebugging();
+}
 #endif
 
 GLContextVirtual::~GLContextVirtual() {
diff --git a/gpu/command_buffer/service/gl_context_virtual.h b/gpu/command_buffer/service/gl_context_virtual.h
index f0156ead..bdca6091 100644
--- a/gpu/command_buffer/service/gl_context_virtual.h
+++ b/gpu/command_buffer/service/gl_context_virtual.h
@@ -50,6 +50,7 @@
 #if defined(OS_MACOSX)
   uint64_t BackpressureFenceCreate() override;
   void BackpressureFenceWait(uint64_t fence) override;
+  void FlushForDebugging() override;
 #endif
 
  protected:
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 3d451369..afd2203 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -24,6 +24,7 @@
 #include "base/containers/queue.h"
 #include "base/containers/span.h"
 #include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/ranges.h"
@@ -5617,6 +5618,14 @@
   int process_pos = 0;
   unsigned int command = 0;
 
+// Instrumentation for https://crbug.com/863817. Keep a list of all commands
+// that are flushed, to see what calls may be the culprit.
+#if defined(OS_MACOSX)
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "mac_gl_commands_in_flush", base::debug::CrashKeySize::Size256);
+  std::string crash_info;
+#endif
+
   while (process_pos < num_entries && result == error::kNoError &&
          commands_to_process_--) {
     const unsigned int size = cmd_data->value_header.size;
@@ -5632,6 +5641,19 @@
       break;
     }
 
+// Continued instrumentation for https://crbug.com/863817. If we run out of
+// buffer space then flush.
+#if defined(OS_MACOSX)
+    std::string command_name = GetCommandName(command);
+    if (!crash_info.empty() && crash_info.size() + command_name.size() > 256) {
+      TRACE_EVENT0("gpu", "Egregious Flush");
+      context_->FlushForDebugging();
+      crash_info = "";
+    }
+    crash_info += command_name + ",";
+    base::debug::SetCrashKeyString(crash_key, crash_info);
+#endif
+
     if (DebugImpl && log_commands()) {
       LOG(ERROR) << "[" << logger_.GetLogPrefix() << "]"
                  << "cmd: " << GetCommandName(command);
@@ -5690,6 +5712,15 @@
     }
   }
 
+// Continued instrumentation for https://crbug.com/863817
+#if defined(OS_MACOSX)
+  {
+    TRACE_EVENT0("gpu", "Egregious Flush");
+    context_->FlushForDebugging();
+    base::debug::ClearCrashKeyString(crash_key);
+  }
+#endif
+
   *entries_processed = process_pos;
 
   if (error::IsError(result)) {
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
index 0ac95e9..fe72f2c 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest.cc
@@ -325,7 +325,7 @@
   EXPECT_EQ(texture->GetLevelImage(target, 0), image.get());
 
   // Unbinding should make it not renderable.
-  abstract_texture->ReleaseImage();
+  abstract_texture->BindImage(nullptr, false);
   EXPECT_EQ(texture->SafeToRenderFrom(), false);
   EXPECT_EQ(abstract_texture->GetImage(), nullptr);
 
diff --git a/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc b/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc
index 35463a8..4171fb8 100644
--- a/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc
+++ b/gpu/command_buffer/service/passthrough_abstract_texture_impl.cc
@@ -51,8 +51,23 @@
   if (!texture_passthrough_)
     return;
 
-  texture_passthrough_->set_is_bind_pending(!client_managed);
-  texture_passthrough_->SetLevelImage(texture_passthrough_->target(), 0, image);
+  const GLuint target = texture_passthrough_->target();
+  const GLuint level = 0;
+
+  // If there is a decoder-managed image bound, release it.
+  if (decoder_managed_image_) {
+    gl::GLImage* current_image =
+        texture_passthrough_->GetLevelImage(target, level);
+    // TODO(sandersd): This isn't correct if CopyTexImage() was used.
+    bool is_bound = !texture_passthrough_->is_bind_pending();
+    if (current_image && is_bound)
+      current_image->ReleaseTexImage(target);
+  }
+
+  // Configure the new image.
+  decoder_managed_image_ = image && !client_managed;
+  texture_passthrough_->set_is_bind_pending(decoder_managed_image_);
+  texture_passthrough_->SetLevelImage(target, level, image);
 }
 
 void PassthroughAbstractTextureImpl::BindStreamTextureImage(
@@ -61,19 +76,6 @@
   NOTREACHED();
 }
 
-void PassthroughAbstractTextureImpl::ReleaseImage() {
-  if (!texture_passthrough_)
-    return;
-
-  texture_passthrough_->set_is_bind_pending(false);
-  const GLuint target = texture_passthrough_->target();
-  const GLuint level = 0;
-  if (gl::GLImage* image = GetImage()) {
-    image->ReleaseTexImage(target);
-    texture_passthrough_->SetLevelImage(target, level, nullptr);
-  }
-}
-
 gl::GLImage* PassthroughAbstractTextureImpl::GetImage() const {
   if (!texture_passthrough_)
     return nullptr;
diff --git a/gpu/command_buffer/service/passthrough_abstract_texture_impl.h b/gpu/command_buffer/service/passthrough_abstract_texture_impl.h
index 18deaf7..a4ef6c54 100644
--- a/gpu/command_buffer/service/passthrough_abstract_texture_impl.h
+++ b/gpu/command_buffer/service/passthrough_abstract_texture_impl.h
@@ -33,7 +33,6 @@
   void BindImage(gl::GLImage* image, bool client_managed) override;
   void BindStreamTextureImage(GLStreamTextureImage* image,
                               GLuint service_id) override;
-  void ReleaseImage() override;
   gl::GLImage* GetImage() const override;
   void SetCleared() override;
   void SetCleanupCallback(CleanupCallback cb) override;
@@ -43,6 +42,7 @@
 
  private:
   scoped_refptr<TexturePassthrough> texture_passthrough_;
+  bool decoder_managed_image_ = false;
   gl::GLApi* gl_api_;
   GLES2DecoderPassthroughImpl* decoder_;
   CleanupCallback cleanup_cb_;
diff --git a/gpu/command_buffer/service/validating_abstract_texture_impl.cc b/gpu/command_buffer/service/validating_abstract_texture_impl.cc
index 82098979..7c400f5 100644
--- a/gpu/command_buffer/service/validating_abstract_texture_impl.cc
+++ b/gpu/command_buffer/service/validating_abstract_texture_impl.cc
@@ -52,26 +52,37 @@
 
 void ValidatingAbstractTextureImpl::BindImage(gl::GLImage* image,
                                               bool client_managed) {
-  DCHECK(image);
-
   if (!texture_ref_)
     return;
 
+  const GLuint target = texture_ref_->texture()->target();
   const GLint level = 0;
 
-  Texture::ImageState state = client_managed ? Texture::ImageState::BOUND
-                                             : Texture::ImageState::UNBOUND;
+  // If there is a decoder-managed image bound, release it.
+  if (decoder_managed_image_) {
+    Texture::ImageState image_state;
+    gl::GLImage* current_image =
+        texture_ref_->texture()->GetLevelImage(target, 0, &image_state);
+    if (current_image && image_state == Texture::BOUND)
+      current_image->ReleaseTexImage(target);
+  }
 
-  GetTextureManager()->SetLevelImage(texture_ref_.get(),
-                                     texture_ref_->texture()->target(), level,
-                                     image, state);
-  SetCleared();
+  // Configure the new image.
+  decoder_managed_image_ = image && !client_managed;
+  Texture::ImageState state = image && client_managed
+                                  ? Texture::ImageState::BOUND
+                                  : Texture::ImageState::UNBOUND;
+  GetTextureManager()->SetLevelImage(texture_ref_.get(), target, level, image,
+                                     state);
+  GetTextureManager()->SetLevelCleared(texture_ref_.get(), target, level,
+                                       image);
 }
 
 void ValidatingAbstractTextureImpl::BindStreamTextureImage(
     GLStreamTextureImage* image,
     GLuint service_id) {
   DCHECK(image);
+  DCHECK(!decoder_managed_image_);
 
   if (!texture_ref_)
     return;
@@ -86,31 +97,6 @@
   SetCleared();
 }
 
-void ValidatingAbstractTextureImpl::ReleaseImage() {
-  if (!texture_ref_)
-    return;
-
-  GLuint target = texture_ref_->texture()->target();
-  Texture::ImageState image_state;
-  gl::GLImage* image =
-      texture_ref_->texture()->GetLevelImage(target, 0, &image_state);
-  if (!image)
-    return;
-
-  // TODO(liberato): Suppress errors here?
-  if (image_state == Texture::BOUND)
-    image->ReleaseTexImage(target);
-
-  GetTextureManager()->SetLevelImage(texture_ref_.get(), target, 0, nullptr,
-                                     Texture::UNBOUND);
-
-  // Mark the texture as uncleared, in case it's only cleared because of the
-  // image binding.
-  const GLint level = 0;
-  GetTextureManager()->SetLevelCleared(texture_ref_.get(), target, level,
-                                       false);
-}
-
 gl::GLImage* ValidatingAbstractTextureImpl::GetImage() const {
   if (!texture_ref_)
     return nullptr;
diff --git a/gpu/command_buffer/service/validating_abstract_texture_impl.h b/gpu/command_buffer/service/validating_abstract_texture_impl.h
index 3dc7b75e2..9fec136 100644
--- a/gpu/command_buffer/service/validating_abstract_texture_impl.h
+++ b/gpu/command_buffer/service/validating_abstract_texture_impl.h
@@ -38,7 +38,6 @@
   void BindStreamTextureImage(GLStreamTextureImage* image,
                               GLuint service_id) override;
   void BindImage(gl::GLImage* image, bool client_managed) override;
-  void ReleaseImage() override;
   gl::GLImage* GetImage() const override;
   void SetCleared() override;
   void SetCleanupCallback(CleanupCallback cb) override;
@@ -58,6 +57,7 @@
   void SetLevelInfo();
 
   scoped_refptr<TextureRef> texture_ref_;
+  bool decoder_managed_image_ = false;
 
   DecoderContext* decoder_context_ = nullptr;
   DestructionCB destruction_cb_;
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index d285253..5de37e6 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -392,9 +392,9 @@
 builder_mixins {
   name: "xcode-10-caches"
   caches: {
-    # Cache for Xcode 10.0 beta 4 (build version 10l213o) needed for iOS builds.
-    name: "xcode_ios_10l213o"
-    path: "xcode_ios_10l213o.app"
+    # Cache for Xcode 10.0 beta 5 (build version 10l221o) needed for iOS builds.
+    name: "xcode_ios_10l221o"
+    path: "xcode_ios_10l221o.app"
   }
 }
 
@@ -2351,7 +2351,7 @@
     }
     builders {
       mixins: "android-try"
-      mixins: "goma-j150"
+      mixins: "goma-j300"
       name: "android-marshmallow-arm64-rel"
       dimensions: "os:Ubuntu-14.04"
     }
@@ -2384,7 +2384,7 @@
     }
     builders {
       mixins: "android-try"
-      mixins: "goma-j150"
+      mixins: "goma-j300"
       name: "android_clang_dbg_recipe"
       dimensions: "os:Ubuntu-14.04"
     }
diff --git a/infra/config/global/tricium-prod.cfg b/infra/config/global/tricium-prod.cfg
new file mode 100644
index 0000000..5e0b78c7
--- /dev/null
+++ b/infra/config/global/tricium-prod.cfg
@@ -0,0 +1,29 @@
+# Schema for this config file: ProjectConfig in
+# luci-config.appspot.com/schemas/projects:tricium-prod.cfg
+
+acls {
+  role: REQUESTER
+  group: "tricium-chromium-requesters"
+}
+
+selections {
+  function: "GitFileIsolator"
+  platform: UBUNTU
+}
+
+selections {
+  function: "SpellChecker"
+  platform: UBUNTU
+}
+
+repos {
+  gerrit_project {
+    host: "chromium-review.googlesource.com"
+    project: "chromium/src"
+    git_url: "https://chromium.googlesource.com/chromium/src"
+  }
+  disable_reporting: true
+}
+
+service_account: "tricium-prod@appspot.gserviceaccount.com"
+swarming_service_account: "swarming@tricium-prod.iam.gserviceaccount.com"
diff --git a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json b/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
index 21a53103..bb093ac 100644
--- a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
@@ -1,8 +1,7 @@
 {
   "comments": [
     "Run tests on iOS12beta track on 64-bit iOS 12 simulators.",
-    "Note: Xcode 10 requires a minimum of OSX 10.13.4 so .5 is specified for",
-    "'host os'."
+    "Note: Xcode 10 requires OSX 10.13.4 hence 'host os'"
   ],
   "xcode build version": "9C40b",
   "gn_args": [
@@ -20,91 +19,91 @@
       "include": "common_tests.json",
       "device type": "iPhone X",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone X",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone 6 Plus",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_cq_tests.json",
       "device type": "iPhone 5s",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_cq_tests.json",
       "device type": "iPad Pro",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_tests.json",
       "device type": "iPhone X",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_tests.json",
       "device type": "iPhone 6 Plus",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_tests.json",
       "device type": "iPad Air",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "eg_tests.json",
       "device type": "iPhone 5s",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 6s Plus",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone X",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPhone 5s",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     },
     {
       "include": "screen_size_dependent_tests.json",
       "device type": "iPad Air 2",
       "os": "12.0",
-      "xcode build version": "10l213o",
+      "xcode build version": "10L221o",
       "host os": "Mac-10.13.5"
     }
   ]
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-device.json b/ios/build/bots/chromium.fyi/ios12-sdk-device.json
index 05b679a8..31f04da0 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-device.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-device.json
@@ -4,7 +4,7 @@
     "Build is performed with gn+ninja.",
     "If modified, please change chromium.fyi/ios-device-goma-canary-clobber.json too."
   ],
-  "xcode build version": "10l213o",
+  "xcode build version": "10L221o",
   "gn_args": [
     "additional_target_cpus=[ \"arm64\" ]",
     "goma_dir=\"$(goma_dir)\"",
diff --git a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
index aaa3a367..4d2b5eb0 100644
--- a/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios12-sdk-simulator.json
@@ -2,9 +2,9 @@
   "comments": [
     "Runs tests on 64-bit iOS 10.3 and 11.4 and 12.0 tests",
     "on iPad, iPhone, @3x, and @2x on main and CQ ios12-beta-simulator.",
-    "Note: Xcode 10.3 requires Mac OS 10.13.X or higher, hence 'host os'."
+    "Note: Xcode 10 requires Mac OS 10.13.4 or higher, hence 'host os'."
   ],
-  "xcode build version": "10l213o",
+  "xcode build version": "10L221o",
   "gn_args": [
     "additional_target_cpus=[\"x86\"]",
     "goma_dir=\"$(goma_dir)\"",
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 7be4ae14..920aec2 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -168,6 +168,9 @@
       <message name="IDS_IOS_CONSENT_BUMP_PERSONALIZATION_MESSAGE" desc="Title for the Consent Bump personalization menu, presented when the user wants more informations and control over the sync personalization. [iOS only]">
         The settings that control sync, personalization, and other Google services in Chromium have changed. This may affect your current settings.
       </message>
+      <message name="IDS_IOS_CONSENT_BUMP_TURN_ON_TEXT" desc="Text for the Consent Bump personalization option to turn on the sync. [iOS only]">
+        You can customize this anytime in Chromium Settings
+      </message>
       <message name="IDS_IOS_DISCONNECT_DIALOG_TITLE" desc="The title of the disconnect dialog [Length: 30em].">
         Sign out of Chromium?
       </message>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index eed4773..82cc54d 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -168,6 +168,9 @@
       <message name="IDS_IOS_CONSENT_BUMP_PERSONALIZATION_MESSAGE" desc="Title for the Consent Bump personalization menu, presented when the user wants more informations and control over the sync personalization [iOS only]">
         The settings that control sync, personalization, and other Google services in Chrome have changed. This may affect your current settings.
       </message>
+      <message name="IDS_IOS_CONSENT_BUMP_TURN_ON_TEXT" desc="Text for the Consent Bump personalization option to turn on the sync. [iOS only]">
+        You can customize this anytime in Chrome Settings
+      </message>
       <message name="IDS_IOS_DISCONNECT_DIALOG_TITLE" desc="The title of the disconnect dialog [Length: 30em].">
         Sign out of Chrome?
       </message>
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index dede6b08..a576d6a 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -528,9 +528,21 @@
       <message name="IDS_IOS_CLEAR_SAVED_PASSWORDS" desc="Label for the option in settings to clear saved passwords. In titlecase. [Length: 20em] [iOS only]">
         Saved Passwords
       </message>
+      <message name="IDS_IOS_CONSENT_BUMP_NO_CHANGE_TEXT" desc="Text for the Consent Bump personalization option to not make any change to the existing sync options. [iOS only]">
+        The data sync to Google and the features you use will not change
+      </message>
+      <message name="IDS_IOS_CONSENT_BUMP_NO_CHANGE_TITLE" desc="Title for the Consent Bump personalization option to not make any change to the existing sync options. [iOS only]">
+        Make no changes
+      </message>
       <message name="IDS_IOS_CONSENT_BUMP_PERSONALIZATION_TITLE" desc="Title for the Consent Bump personalization menu, presented when the user wants more informations and control over the sync personalization. [iOS only]">
         Control Sync, Personalization, and More
       </message>
+      <message name="IDS_IOS_CONSENT_BUMP_REVIEW_TITLE" desc="Title for the Consent Bump personalization option to review the existing settings once the current screen is dismissed. [iOS only]">
+        Review your settings on the next screen
+      </message>
+      <message name="IDS_IOS_CONSENT_BUMP_TURN_ON_TITLE" desc="Title for the Consent Bump personalization option to turn on the sync. [iOS only]">
+        Turn on sync, personalization, and other Google services
+      </message>
       <message name="IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST" desc="The iOS menu item for adding a link to the reading list.">
         Read Later
       </message>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_cs.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_cs.xtb
index 334d5d1..fb3e4ab 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_cs.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_cs.xtb
@@ -22,6 +22,7 @@
 <translation id="3605252743693911722">Chcete-li mít své záložky, hesla a další obsah na všech zařízeních, přihlaste se do prohlížeče Chromium.</translation>
 <translation id="3805899903892079518">Chromium nemá přístup k vašim fotkám nebo videím. Povolte přístup v systému iOS v Nastavení &gt; Soukromí &gt; Fotky.</translation>
 <translation id="4024541897090868497">Chcete-li karty synchronizovat do všech svých zařízení, přihlaste se do prohlížeče Chromium.</translation>
+<translation id="4157467675761413638">Tip pro Chromium. Další možnosti karet zobrazíte podržením tlačítka Zobrazit karty, které najdete v dolní nebo horní části obrazovky.</translation>
 <translation id="4241912885070669028">Odhlašujete se z účtu, který je spravován doménou <ph name="SIGNOUT_MANAGED_DOMAIN" />. Touto akcí svá data prohlížeče Chromium smažete z tohoto zařízení, ve vašem účtu Google však zůstanou.</translation>
 <translation id="4272892696084633551">Pomoci s vylepšováním funkcí a výkonu prohlížeče Chromium</translation>
 <translation id="4555020257205549924">Pokud je tato funkce zapnutá, bude Chromium nabízet překlad stránek v jiných jazycích pomocí Překladače Google.<ph name="BEGIN_LINK" />Další informace<ph name="END_LINK" /></translation>
@@ -44,6 +45,7 @@
 <translation id="7357211569052832586">Vybraná data byla z prohlížeče Chromium a synchronizovaných zařízení odstraněna. Na stránce history.google.com mohou být k dispozici další druhy historie prohlížení zaznamenané ve vašem účtu Google, například vyhledávací dotazy a aktivita z ostatních služeb Google.</translation>
 <translation id="7400689562045506105">Používejte Chromium všude</translation>
 <translation id="7674213385180944843">Otevřete Nastavení &gt; Soukromí &gt; Kamera &gt; Chromium a zapněte fotoaparát.</translation>
+<translation id="7746854981345936341">Tip pro Chromium. Některá tlačítka, například Zpět, Vpřed a Hledat, jsou teď v dolní části obrazovky.</translation>
 <translation id="786327964234957808">Přepínáte účet pro synchronizaci z <ph name="USER_EMAIL1" /> na <ph name="USER_EMAIL2" />. Vaše existující data prohlížeče Chromium jsou spravována doménou <ph name="DOMAIN" />. Touto akcí svá data smažete z tohoto zařízení, v účtu <ph name="USER_EMAIL1" /> však vaše data zůstanou.</translation>
 <translation id="8175055321229419309">Tip: <ph name="BEGIN_LINK" />Přesuňte si prohlížeč Chromium do doku<ph name="END_LINK" /></translation>
 <translation id="8252885722420466166">Služby Google mohou v prohlížeči Chromium díky údajům o poloze fungovat lépe.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_en-GB.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_en-GB.xtb
index 40d2a3f5..fad5da7b 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_en-GB.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_en-GB.xtb
@@ -23,6 +23,7 @@
 <translation id="3605252743693911722">Sign in to Chromium to get your bookmarks, passwords and more on all your devices.</translation>
 <translation id="3805899903892079518">Chromium does not have access to your photos or videos. Enable access in iOS Settings &gt; Privacy &gt; Photos.</translation>
 <translation id="4024541897090868497">To get your tabs on all your devices, sign in to Chromium.</translation>
+<translation id="4157467675761413638">Chromium tip. For more tab options, press and hold the Show Tabs button in the toolbar, which is at the bottom or top of your screen.</translation>
 <translation id="4241912885070669028">You are signing out of an account managed by <ph name="SIGNOUT_MANAGED_DOMAIN" />. This will delete your Chromium data from this device, but your data will remain in your Google account.</translation>
 <translation id="4272892696084633551">Help improve Chromium's features and performance</translation>
 <translation id="4555020257205549924">When this feature is turned on, Chromium will offer to translate pages written in other languages using Google Translate. <ph name="BEGIN_LINK" />Find out more<ph name="END_LINK" /></translation>
@@ -45,6 +46,7 @@
 <translation id="7357211569052832586">The selected data has been removed from Chromium and synced devices. Your Google account may have other forms of browsing history such as searches and activity from other Google services at history.google.com.</translation>
 <translation id="7400689562045506105">Use Chromium Everywhere</translation>
 <translation id="7674213385180944843">Open Settings &gt; Privacy &gt; Camera &gt; Chromium and turn on the camera.</translation>
+<translation id="7746854981345936341">Chromium tip. Some buttons are now at the bottom of your screen, such as Back, Forward and Search.</translation>
 <translation id="786327964234957808">You are switching sync accounts from <ph name="USER_EMAIL1" /> to <ph name="USER_EMAIL2" />. Your existing Chromium data is managed by <ph name="DOMAIN" />. This will delete your data from this device, but your data will remain in <ph name="USER_EMAIL1" />.</translation>
 <translation id="8175055321229419309">Tip: <ph name="BEGIN_LINK" />Move Chromium to your dock<ph name="END_LINK" /></translation>
 <translation id="8252885722420466166">Get a better Google experience in Chromium based on your location.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb
index 767b3f86..3d7a9ed 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb
@@ -23,6 +23,7 @@
 <translation id="3605252743693911722">Prisijunkite prie „Chromium“, kad galėtumėte naudoti žymes, slaptažodžius ir kitus nustatymus visuose įrenginiuose.</translation>
 <translation id="3805899903892079518">„Chromium“ nepasiekia nuotraukų ar vaizdo įr. Įgalinkite prieigą apsil. „iOS“ nustatymai“ &gt; „Privatumas“ &gt; „Nuotraukos“.</translation>
 <translation id="4024541897090868497">Prisijunkite prie „Chromium“, kad pasiektumėte skirtukus visuose įrenginiuose.</translation>
+<translation id="4157467675761413638">„Chromium“ patarimas. Jei reikia daugiau skirtukų parinkčių, paspauskite ir palaikykite mygtuką „Rodyti skirtukus“ įrankių juostoje, kuri yra ekrano apačioje arba viršuje.</translation>
 <translation id="4241912885070669028">Atsijungiate nuo paskyros, kurią valdo <ph name="SIGNOUT_MANAGED_DOMAIN" />. „Chromium“ duomenys bus ištrinti iš šio įrenginio, bet liks „Google“ paskyroje.</translation>
 <translation id="4272892696084633551">Padėti tobulinti „Chromium“ funkcijas ir našumą</translation>
 <translation id="4555020257205549924">Kai ši funkcija bus įjungta, „Chromium“ siūlys versti kitomis kalbomis parašytus puslapius naudojant „Google“ vertėją. <ph name="BEGIN_LINK" />Sužinokite daugiau<ph name="END_LINK" /></translation>
@@ -45,6 +46,7 @@
 <translation id="7357211569052832586">Pasirinkti duomenys pašalinti iš „Chromium“ ir sinchronizuojamų įrenginių. Adresu history.google.com gali būti pateikta kitų formų jūsų „Google“ paskyros istorija, pvz., paieškos ir veikla kitose „Google“ paslaugose.</translation>
 <translation id="7400689562045506105">„Chromium“ naudojimas visuose įrenginiuose</translation>
 <translation id="7674213385180944843">Atidarykite „Nustatymai“ &gt; „Privatumas“ &gt; „Fotoaparatas“ &gt; „Chromium“ ir įjunkite fotoaparatą.</translation>
+<translation id="7746854981345936341">„Chromium“ patarimas. Kai kurie mygtukai dabar yra ekrano apačioje, pvz., mygtukai „Atgal“, „Pirmyn“ ir „Ieškoti“.</translation>
 <translation id="786327964234957808">Perjungiate sinchronizuojamas paskyras iš <ph name="USER_EMAIL1" /> į <ph name="USER_EMAIL2" />. Esamus „Chromium“ duomenis tvarko <ph name="DOMAIN" />. Tai atlikus bus ištrinti duomenys iš šio įrenginio, bet duomenys liks <ph name="USER_EMAIL1" />.</translation>
 <translation id="8175055321229419309">Patarimas: <ph name="BEGIN_LINK" />perkelkite „Chromium“ į doką<ph name="END_LINK" /></translation>
 <translation id="8252885722420466166">Patogiau naudokitės „Google“ paslaugomis naudodami pagal vietovę pritaikytą „Chromium“.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_mr.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_mr.xtb
index cc515fa..496d3d6 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_mr.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_mr.xtb
@@ -23,6 +23,7 @@
 <translation id="3605252743693911722">तुमच्या सर्व डिव्हाइसवर तुमचे बुकमार्क, पासवर्ड आणि अधिक मिळवण्यासाठी Chromiumमध्ये साइन इन करा.</translation>
 <translation id="3805899903892079518">Chromium ला आपल्या फोटो किंवा व्हिडिओंमध्ये प्रवेश नाही. iOS सेटिंग्ज&gt; गोपनीयता&gt; फोटो मध्ये प्रवेश सक्षम करा.</translation>
 <translation id="4024541897090868497">तुमच्या सर्व डीव्हाइसवर तुमचे टॅब मिळवण्यासाठी Chromium मध्ये साइन इन करा.</translation>
+<translation id="4157467675761413638">Chromium टीप. अधिक टॅब पर्यायांसाठी, टूलबारवरील टॅब बटण दाखवा दाबा आणि धरून ठेवा, जे तुमच्या स्क्रीनच्या खाली किंवा सर्वात वर असते.</translation>
 <translation id="4241912885070669028"><ph name="SIGNOUT_MANAGED_DOMAIN" /> द्वारे व्यवस्थापित केलेल्या खात्यामधून आपण साइन आउट करीत आहात. हे आपला Chromium डेटा या डिव्हाइस वरून हटवेल परंतु आपला डेटा आपल्या Google खात्यामध्ये असेल.</translation>
 <translation id="4272892696084633551">Chromium च्या वैशिष्ट्ये आणि परफॉर्मंसमध्ये सुधारणा करण्यात मदत करा</translation>
 <translation id="4555020257205549924">हे वैशिष्ट्य चालू केल्यावर, Chromium Google भाषांतर वापरून अन्य भाषामध्ये लिहिलेल्या पृष्ठांचा भाषांतर ऑफर करेल. <ph name="BEGIN_LINK" />अधिक जाणून घ्या<ph name="END_LINK" /></translation>
@@ -45,6 +46,7 @@
 <translation id="7357211569052832586">निवडलेला डेटा Chromium आणि संकालित केलेल्या डिव्हाइस मधून काढला गेला आहे. आपल्या Google खात्यामध्ये history.google.com येथे Google च्या इतर सेवांमधील शोध आणि क्रियाकलाप यासारख्या ब्राउझिंग इतिहासाची इतर स्वरूपे असू शकतात.</translation>
 <translation id="7400689562045506105">Chromium सर्वत्र वापरा</translation>
 <translation id="7674213385180944843">सेटिंग्ज &gt; गोपनीयता &gt; कॅमेरा &gt; Chromium उघडा आणि कॅमेरा चालू करा.</translation>
+<translation id="7746854981345936341">Chromium टीप. काही बटणे आता तुमच्या स्क्रीनच्या तळाशी आहेत, जसे की मागे, पुढे आणि शोध.</translation>
 <translation id="786327964234957808">आपण संकालित केलेली खाती <ph name="USER_EMAIL1" /> वरून <ph name="USER_EMAIL2" /> वर स्विच करीत आहात. आपला विद्यमान Chromium डेटा <ph name="DOMAIN" /> द्वारे व्यवस्थापित केला आहे. हे आपला डेटा या डिव्हाइस वरून हटवेल परंतु आपला डेटा <ph name="USER_EMAIL1" /> मध्ये असेल.</translation>
 <translation id="8175055321229419309">टीप: <ph name="BEGIN_LINK" />Chromium आपल्या डॉकमध्ये हलवा<ph name="END_LINK" /></translation>
 <translation id="8252885722420466166">आपल्या स्थानावर आधारित Chromium मध्ये उत्कृष्ट Google अनुभव मिळवा.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_sk.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_sk.xtb
index 6ee3316..c57ae31e7 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_sk.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_sk.xtb
@@ -23,6 +23,7 @@
 <translation id="3605252743693911722">Prihláste sa do prehliadača Chromium a získajte svoje záložky, heslá a ďalšie nastavenia na všetkých zariadeniach.</translation>
 <translation id="3805899903892079518">Chromium nemá prístup k vašim fotkám ani videám. Prístup povoľte v časti Nastavenia systému iOS &gt; Súkromie &gt; Fotky.</translation>
 <translation id="4024541897090868497">Ak chcete mať karty na všetkých zariadeniach, prihláste sa do prehliadača Chromium.</translation>
+<translation id="4157467675761413638">Tip pre Chromium: ďalšie možnosti kariet získate pridržaním tlačidla Zobraziť karty na paneli s nástrojmi, ktorý sa nachádza v dolnej alebo hornej časti obrazovky.</translation>
 <translation id="4241912885070669028">Odhlasujete sa z účtu spravovaného doménou <ph name="SIGNOUT_MANAGED_DOMAIN" />. Touto akciou odstránite údaje prehliadača Chromium z príslušného zariadenia, avšak zostanú naďalej vo vašom účte Google.</translation>
 <translation id="4272892696084633551">Pomôžte vylepšiť funkcie a výkon prehliadača Chromium</translation>
 <translation id="4555020257205549924">Po zapnutí tejto funkcie bude prehliadač Chromium ponúkať preloženie stránok napísaných v inom jazyku pomocou Prekladača Google. <ph name="BEGIN_LINK" />Ďalšie informácie<ph name="END_LINK" /></translation>
@@ -45,6 +46,7 @@
 <translation id="7357211569052832586">Vybraté údaje boli odstránené z prehliadača Chromium a synchronizovaných zariadení. Váš účet Google môže mať na adrese history.google.com ďalšie formy histórie prehliadania, ako napríklad vyhľadávania a aktivity v iných službách Google.</translation>
 <translation id="7400689562045506105">Používajte Chromium kdekoľvek</translation>
 <translation id="7674213385180944843">Otvorte Nastavenia &gt; Súkromie &gt; Fotoaparát &gt; Chromium a zapnite fotoaparát.</translation>
+<translation id="7746854981345936341">Tip pre Chromium: niektoré tlačidlá sa teraz nachádzajú v dolnej časti obrazovky, napríklad Dopredu, Späť a Hľadať.</translation>
 <translation id="786327964234957808">Účet na synchronizáciu <ph name="USER_EMAIL1" /> prepínate na <ph name="USER_EMAIL2" />. Vaše existujúce údaje prehliadača Chromium spravuje doména <ph name="DOMAIN" />. Touto akciou odstránite údaje z vášho zariadenia, avšak zostanú naďalej v účte <ph name="USER_EMAIL1" />.</translation>
 <translation id="8175055321229419309">Tip: <ph name="BEGIN_LINK" />Presuňte Chromium do doku<ph name="END_LINK" /></translation>
 <translation id="8252885722420466166">Získajte lepšie prostredie Google v prehliadači Chromium na základe svojej polohy.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_te.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_te.xtb
index 0cc55f9..9645a98 100644
--- a/ios/chrome/app/strings/resources/ios_chromium_strings_te.xtb
+++ b/ios/chrome/app/strings/resources/ios_chromium_strings_te.xtb
@@ -24,6 +24,7 @@
 <translation id="3805899903892079518">Chromium మీ ఫోటోలు లేదా వీడియోలకు ప్రాప్యతను కలిగి లేదు. iOS సెట్టింగ్‌లు &gt; గోప్యత &gt; ఫోటోల్లో ప్రాప్యతను ప్రారంభించండి.</translation>
 <translation id="4024541897090868497">మీ అన్ని పరికరాల్లో మీ ట్యాబ్‌లను పొందడం కోసం, Chromiumకి సైన్ ఇన్ చేయండి.</translation>
 <translation id="4241912885070669028">మీరు <ph name="SIGNOUT_MANAGED_DOMAIN" /> నిర్వహణలో ఉన్న ఖాతా నుండి సైన్ అవుట్ చేస్తున్నారు. దీని వలన మీ Chromium డేటా ఈ పరికరం నుండి తొలగించబడుతుంది, కానీ మీ డేటా మీ Google ఖాతాలో అలాగే ఉంటుంది.</translation>
+<translation id="4272892696084633551">Chromium ఫీచర్‌లు మరియు పనితీరును మెరుగుపరచడంలో సహాయపడండి</translation>
 <translation id="4555020257205549924">ఈ లక్షణం ప్రారంభించినప్పుడు, Chromium ఇతర భాషల్లో వ్రాసిన పేజీలకు Google అనువాదం ఉపయోగించి అనువాదాన్ని ఆఫర్ చేస్తుంది. <ph name="BEGIN_LINK" />మరింత తెలుసుకోండి<ph name="END_LINK" /></translation>
 <translation id="4787850887676698916">మీరు మీ ఇతర పరికరాల్లోని Chromiumలో తెరిచిన ట్యాబ్‌లు ఇక్కడ చూపబడతాయి.</translation>
 <translation id="495292094137889840">Chromium QR స్కానర్ వినియోగం ప్రారంభించండి</translation>
@@ -33,6 +34,7 @@
 <translation id="5945387852661427312">మీరు <ph name="DOMAIN" /> నిర్వహణలో ఉన్న ఖాతా నుండి సైన్ ఇన్ చేస్తున్నారు మరియు దీని నిర్వాహకునికి మీ Chromium డేటాపై నియంత్రణను అందిస్తున్నారు. మీ డేటా శాశ్వతంగా ఈ ఖాతాకు అనుబంధించబడుతుంది. Chromium నుండి సైన్ అవుట్ చేయడం వలన ఈ పరికరం నుండి మీ డేటా తొలగించబడుతుంది, కానీ ఇది మీ Google ఖాతాలో అలాగే నిల్వ చేయబడి ఉంటుంది.</translation>
 <translation id="6068866989048414399">Chromium సేవా నిబంధనలు</translation>
 <translation id="6268381023930128611">Chromium నుండి సైన్ అవుట్ చేయాలా?</translation>
+<translation id="641451971369018375">బ్రౌజింగ్ మరియు Chromiumను మెరుగుపరచడానికి Googleను సంప్రదిస్తుంది</translation>
 <translation id="6424492062988593837">Chrome ఇప్పుడే మెరుగుపరచబడింది! క్రొత్త సంస్కరణ అందుబాటులో ఉంది.</translation>
 <translation id="6604711459180487467">మీ స్థానం ఆధారంగా Chromiumలో మెరుగైన అనుభవాన్ని పొందండి.</translation>
 <translation id="6626296268883197964">ఈ యాప్‌ని ఉపయోగించడం ద్వారా, మీరు Chromium <ph name="BEGIN_LINK_TOS" />సేవా నిబంధనలు<ph name="END_LINK_TOS" /> మరియు <ph name="BEGIN_LINK_PRIVACY" />గోప్యతా ప్రకటన<ph name="END_LINK_PRIVACY" />ని అంగీకరిస్తున్నారు.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_am.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_am.xtb
index 7f2c6855..11ff73f 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_am.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_am.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">የChrome ባህሪያት እና አፈጻጸም እንዲሻሻል ያግዙ</translation>
 <translation id="4523886039239821078">አንዳንድ ተጨማሪዎች Chrome እንዲበላሽ ያደርጋሉ። እባክዎ የሚከተለውን ያራግፉ፦</translation>
 <translation id="4615174829807303908">በ<ph name="SIGNOUT_MANAGED_DOMAIN" /> ከሚተዳደር መለያ ዘግተው እየወጡ ነው። ይሄ የChrome ውሂብዎን ከዚህ መሣሪያ ይሰርዘዋል፣ ነገር ግን ውሂብዎ አሁንም በእርስዎ የGoogle መለያ ውስጥ እንዳለ ይቆያል።</translation>
+<translation id="5240817131241497236">በChrome ውስጥ ስምረትን፣ ግላዊነት ማላበስን እና ሌሎች የGoogle አገልግሎቶችን የሚቆጣጠሩ ቅንብሮች ተቀይረዋል። ይህ በአሁኑ ቅንብሮችዎ ላይ ተጽዕኖ ሊኖረው ይችላል።</translation>
 <translation id="5389212809648216794">ሌላ መተግበሪያ ካሜራዎን እየተጠቀመበት ስለሆነ Google Chrome ካሜራዎን መጠቀም አይችልም</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />ቅንብሮች<ph name="END_LINK" />ን በማንኛውም ጊዜ ማበጀት ይችላሉ። Chromeን እና እንደ ትርጉም፣ ፍለጋ እና ማስታወቂያዎች ያሉ ሌሎች የGoogle አገልግሎቶችን ግላዊነት ለማላበስ Google እርስዎ በሚጎበኟቸው ጣቢያዎች ላይ ያለ ይዘትን እና የአሰሳ እንቅስቃሴ ሊጠቀም ይችላል።</translation>
 <translation id="5639704535586432836">ቅንብሮች &gt; ግላዊነት &gt; ካሜራ &gt; Google Chromeን ይክፈቱና ካሜራውን ያብሩ።</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ar.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ar.xtb
index cf68e40..719e463 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ar.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ar.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">‏المساعدة في تحسين ميزات Chrome وأدائه</translation>
 <translation id="4523886039239821078">‏تتسبب بعض الإضافات في تعطل Chrome. يُرجى إزالتها:</translation>
 <translation id="4615174829807303908">‏يتم تسجيل خروجك من حساب تتم إدارته من خلال <ph name="SIGNOUT_MANAGED_DOMAIN" />. سيؤدي ذلك إلى حذف بيانات Chrome من هذا الجهاز، ولكن ستظل البيانات في حسابك في Google.</translation>
+<translation id="5240817131241497236">‏تم تغيير الإعدادات التي تتحكَّم في المزامنة والتخصيص وخدمات Google الأخرى في Chrome. قد يؤثر هذا في إعداداتك الحالية.</translation>
 <translation id="5389212809648216794">‏يتعذر على Google Chrome استخدام الكاميرا نظرًا لأنها قيد الاستخدام من قِبل تطبيق آخر</translation>
 <translation id="5489543008378040943">‏يمكن تخصيص <ph name="BEGIN_LINK" />الإعدادات<ph name="END_LINK" /> في أي وقت. قد تستخدم Google محتوى المواقع التي تزورها وتفاعلات المتصفّح ونشاطه لتخصيص Chrome وخدمات Google، مثل "ترجمة" و"بحث" والإعلانات.</translation>
 <translation id="5639704535586432836">‏افتح الإعدادات &gt; الخصوصية &gt; الكاميرا &gt; Google Chrome وشغِّل الكاميرا.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_bg.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_bg.xtb
index a9dab5e..ed5880b 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_bg.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_bg.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Помощ за подобряването на функциите и ефективността на Chrome</translation>
 <translation id="4523886039239821078">Някои добавки водят до срив на Chrome. Моля, деинсталирайте:</translation>
 <translation id="4615174829807303908">Излизате от профил, управляван от <ph name="SIGNOUT_MANAGED_DOMAIN" />. Данните ви в Chrome ще се изтрият от това устройство, но ще останат в профила ви в Google.</translation>
+<translation id="5240817131241497236">Настройките за контрол върху синхронизирането, персонализирането и други услуги на Google в Chrome се промениха. Това може да повлияе на настоящите ви настройки.</translation>
 <translation id="5389212809648216794">Google Chrome не може да използва камерата ви, защото е заета от друго приложение</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Настройките<ph name="END_LINK" /> могат да бъдат персонализирани по всяко време. Може да използваме съдържанието на посещаваните от вас сайтове, активността в браузъра и взаимодействията с него, за да персонализираме Chrome и услуги на Google, като Преводач, Търсене и рекламите.</translation>
 <translation id="5639704535586432836">Отворете „Settings“ &gt; „Privacy“ &gt; „Camera“ &gt; „Google Chrome“ и включете камерата.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_bn.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_bn.xtb
index 50999bc..6654b37 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_bn.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_bn.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome-এর বৈশিষ্ট্য এবং পারফরম্যান্স উন্নত করে তুলতে সাহায্য করুন</translation>
 <translation id="4523886039239821078">কিছু অ্যাড-অনের কারণে Chrome ক্র্যাশ করতে পারে৷ দয়া করে আনইনস্টল করুন:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> পরিচালিত একটি অ্যাকাউন্ট থেকে আপনি প্রস্থান করুন করছেন৷ এটি এই ডিভাইস থেকে আপনার Chrome ডেটা মুছে ফেলবে, কিন্তু আপনার Google অ্যাকাউন্টে ডেটা থেকে যাবে৷</translation>
+<translation id="5240817131241497236">Chrome-এর মধ্যে যে সেটিংসের মাধ্যমে  সিঙ্ক, ব্যাক্তিগতকরণ এবং অন্যান্য Google পরিষেবাগুলি নিয়ন্ত্রিত হয় সেটিতে পরিবর্তন করা হয়েছে।  এর ফলে আপনার বর্তমান সেটিংস প্রভাবিত হতে পারে।</translation>
 <translation id="5389212809648216794">Google Chrome আপনার ক্যামেরা ব্যবহার করতে পারছে না কারণ এটি অন্য একটি অ্যাপ্লিকেশান দ্বারা ব্যবহার করা হচ্ছে</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />সেটিংস<ph name="END_LINK" /> যেকোনও সময় কাস্টমাইজ করা যায়। Google, আপনার দেখা সাইটগুলির কন্টেন্ট, ব্রাউজারের ইন্টার‍্যাকশন এবং অ্যাক্টিভিটি ব্যবহার করে Chrome এবং অন্যান্য Google পরিষেবা যেমন অনুবাদ, সার্চ ও বিজ্ঞাপনগুলি ব্যক্তিগতকৃত করতে পারে।</translation>
 <translation id="5639704535586432836">সেটিংস &gt; গোপনীয়তা &gt; ক্যামেরা &gt; Google Chrome খুলুন এবং ক্যামেরা চালু করুন।</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ca.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ca.xtb
index e09e6ce..5c4c37a 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ca.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ca.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Ajuda a millorar les funcions i el rendiment de Chrome</translation>
 <translation id="4523886039239821078">Alguns complements fan que Chrome es bloquegi. Desinstal·leu-los.</translation>
 <translation id="4615174829807303908">Esteu tancant la sessió d'un compte gestionat per <ph name="SIGNOUT_MANAGED_DOMAIN" />. Se suprimiran les vostres dades de Chrome d'aquest dispositiu, però continuaran al vostre compte de Google.</translation>
+<translation id="5240817131241497236">La configuració que controla la sincronització, la personalització i altres serveis de Google a Chrome ha canviat. Això pot afectar la teva configuració actual.</translation>
 <translation id="5389212809648216794">Google Chrome no pot utilitzar la càmera perquè l'està utilitzant una altra aplicació</translation>
 <translation id="5489543008378040943">La <ph name="BEGIN_LINK" />configuració<ph name="END_LINK" /> es pot personalitzar en qualsevol moment. Google pot utilitzar contingut dels llocs web que visites, així com l'activitat i les interaccions al navegador per personalitzar Chrome i els serveis de Google, com ara el Traductor, la Cerca i els anuncis.</translation>
 <translation id="5639704535586432836">Obre Configuració &gt; Privacitat &gt; Càmera &gt; Google Chrome i activa la càmera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_cs.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_cs.xtb
index 5dcf77b..3553b6e 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_cs.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_cs.xtb
@@ -32,11 +32,13 @@
 <translation id="447252321002412580">Pomoci s vylepšováním funkcí a výkonu prohlížeče Chrome</translation>
 <translation id="4523886039239821078">Některé doplňky způsobují selhání prohlížeče Chrome. Prosím odinstalujte:</translation>
 <translation id="4615174829807303908">Odhlašujete se z účtu, který je spravován doménou <ph name="SIGNOUT_MANAGED_DOMAIN" />. Touto akcí smažete svá data prohlížeče Chrome z tohoto zařízení, ve vašem účtu Google však vaše data zůstanou.</translation>
+<translation id="5240817131241497236">Nastavení, která v Chromu ovládají synchronizaci, personalizaci a další služby Google, se změnila. Může to mít dopad na vaše aktuální nastavení.</translation>
 <translation id="5389212809648216794">Google Chrome nemůže použít fotoaparát, protože jej právě používá jiná aplikace</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Nastavení<ph name="END_LINK" /> lze kdykoliv upravit. Google může používat obsah na webech, které navštěvujete, interakce s prohlížečem a aktivity k personalizaci prohlížeče Chrome a služeb Google, jako jsou Překladač, Vyhledávání a reklamy.</translation>
 <translation id="5639704535586432836">Otevřete Nastavení &gt; Soukromí &gt; Fotoaparát &gt; Google Chrome a zapněte fotoaparát.</translation>
 <translation id="5642200033778930880">Google Chrome nemůže používat fotoaparát v režimu Split View</translation>
 <translation id="5703130498371792817">Jste s prohlížečem Chrome spokojeni? <ph name="BEGIN_LINK" />Ohodnoťte tuto aplikaci<ph name="END_LINK" />.</translation>
+<translation id="5854621639439811139">Tip pro Chrome. Některá tlačítka, například Zpět, Vpřed a Hledat, jsou teď v dolní části obrazovky.</translation>
 <translation id="6036420186814142909">Google Chrome obsahuje funkce, které pomáhají spravovat přenosy dat z internetu a umožňují zrychlit načítání webových stránek.
 <ph name="BEGIN_LINK" />Další informace<ph name="END_LINK" /></translation>
 <translation id="6573431926118603307">Zde se objeví karty, které jste otevřeli v Chromu ve svých ostatních zařízeních.</translation>
@@ -55,4 +57,5 @@
 <translation id="8459495907675268833">Vybraná data byla z Chromu a synchronizovaných zařízení odstraněna. Na stránce history.google.com mohou být k dispozici další druhy historie prohlížení zaznamenané ve vašem účtu Google, například vyhledávací dotazy a aktivita z ostatních služeb Google.</translation>
 <translation id="8540666473246803645">Google Chrome</translation>
 <translation id="8606668294522778825">Prohlížeč Google Chrome vám může usnadnit prohlížení pomocí webových služeb. Tyto služby můžete případně zakázat. <ph name="BEGIN_LINK" />Další informace<ph name="END_LINK" /></translation>
+<translation id="96145293669295453">Tip pro Chrome. Další možnosti karet zobrazíte podržením tlačítka Zobrazit karty, které najdete v dolní nebo horní části obrazovky.</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_da.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_da.xtb
index 5e97d1ad..8466352 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_da.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_da.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Vær med til at forbedre Chromes funktioner og ydeevne</translation>
 <translation id="4523886039239821078">Nogle tilføjelser får Chrome til at gå ned. Prøv at afinstallere:</translation>
 <translation id="4615174829807303908">Du er ved at logge ud af en konto, der administreres af <ph name="SIGNOUT_MANAGED_DOMAIN" />. Denne handling sletter dine Chrome-data fra denne enhed, men dine data forbliver på din Google-konto.</translation>
+<translation id="5240817131241497236">De indstillinger, der styrer synkronisering, tilpasning og andre Google-tjenester i Chrome, er blevet ændret. Dette kan påvirke dine aktuelle indstillinger.</translation>
 <translation id="5389212809648216794">Google Chrome kan ikke bruge dit kamera, da det bruges af en anden app</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Indstillinger<ph name="END_LINK" /> kan tilpasses når som helst. Google kan bruge indholdet på de websites, du besøger, samt browserinteraktioner og -aktivitet til at tilpasse Chrome og Google-tjenester såsom Oversæt, Søgning og annoncer.</translation>
 <translation id="5639704535586432836">Åbn Indstillinger &gt; Privatliv &gt; Kamera &gt; Google Chrome, og slå kameraet til.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_de.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_de.xtb
index f9983cb..6a238f3c4 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_de.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_de.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Helfen, die Funktionen und die Leistung von Chrome zu verbessern</translation>
 <translation id="4523886039239821078">Einige Add-ons führen zum Ausfall von Chrome. Bitte deinstallieren Sie:</translation>
 <translation id="4615174829807303908">Sie melden sich von einem Konto ab, das von <ph name="SIGNOUT_MANAGED_DOMAIN" /> verwaltet wird. Dadurch werden Ihre Chrome-Daten von diesem Gerät gelöscht, bleiben jedoch in Ihrem Google-Konto erhalten.</translation>
+<translation id="5240817131241497236">Die Einstellungen für die Synchronisierung, Personalisierung und andere Google-Dienste in Chrome wurden geändert. Dadurch könnten sich auch Ihre aktuellen Einstellungen geändert haben.</translation>
 <translation id="5389212809648216794">Google Chrome kann nicht auf die Kamera zugreifen, da diese gerade von einer anderen App verwendet wird</translation>
 <translation id="5489543008378040943">Die <ph name="BEGIN_LINK" />Einstellungen<ph name="END_LINK" /> können jederzeit angepasst werden. Google kann die Inhalte der von Ihnen besuchten Websites, Browserinteraktionen und Browseraktivitäten nutzen, um Chrome und andere Google-Dienste wie Übersetzer, die Google-Suche und Anzeigen zu personalisieren.</translation>
 <translation id="5639704535586432836">Öffnen Sie "Einstellungen" &gt; "Datenschutz" &gt; "Kamera" &gt; "Google Chrome" und aktivieren Sie die Kamera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_el.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_el.xtb
index eab71f9..a696e16ed 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_el.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_el.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Συμβάλλετε στη βελτίωση των λειτουργιών και της απόδοσης του Chrome</translation>
 <translation id="4523886039239821078">Ορισμένα πρόσθετα προκαλούν σφάλματα στο Chrome. Aπεγκαταστήστε τα.</translation>
 <translation id="4615174829807303908">Πρόκειται να αποσυνδεθείτε από έναν λογαριασμό του οποίου η διαχείριση γίνεται από <ph name="SIGNOUT_MANAGED_DOMAIN" />. Αυτό θα διαγράψει τα δεδομένα σας Chrome από αυτήν τη συσκευή, αλλά θα διατηρηθούν στο Λογαριασμό σας Google.</translation>
+<translation id="5240817131241497236">Οι ρυθμίσεις που ελέγχουν τον συγχρονισμό, την εξατομίκευση και άλλες υπηρεσίες Google στο Chrome άλλαξαν. Αυτό μπορεί να επηρεάσει τις τρέχουσες ρυθμίσεις σας.</translation>
 <translation id="5389212809648216794">Το Google Chrome δεν μπορεί να χρησιμοποιήσει την κάμερά σας επειδή χρησιμοποιείται από άλλη εφαρμογή.</translation>
 <translation id="5489543008378040943">Μπορείτε να προσαρμόσετε τις <ph name="BEGIN_LINK" />Ρυθμίσεις<ph name="END_LINK" /> ανά πάσα στιγμή. Η Google μπορεί να χρησιμοποιεί το περιεχόμενο σε ιστοτόπους που επισκέπτεστε, αλληλεπιδράσεις με το πρόγραμμα περιήγησης και τη δραστηριότητά σας για να προσαρμόσει το Chrome και υπηρεσίες Google όπως η Μετάφραση, η Αναζήτηση και οι διαφημίσεις.</translation>
 <translation id="5639704535586432836">Ανοίξτε το μενού Ρυθμίσεις &gt; Απόρρητο &gt; Κάμερα &gt; Google Chrome και ενεργοποιήστε την κάμερα.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_en-GB.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_en-GB.xtb
index 1df51d5..f51f6a9 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_en-GB.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_en-GB.xtb
@@ -32,11 +32,13 @@
 <translation id="447252321002412580">Help improve Chrome's features and performance</translation>
 <translation id="4523886039239821078">Some add-ons cause Chrome to crash. Please uninstall:</translation>
 <translation id="4615174829807303908">You are signing out of an account managed by <ph name="SIGNOUT_MANAGED_DOMAIN" />. This will delete your Chrome data from this device, but your data will remain in your Google account.</translation>
+<translation id="5240817131241497236">The settings that control sync, personalisation and other Google services in Chrome have changed. This may affect your current settings.</translation>
 <translation id="5389212809648216794">Google Chrome can't use your camera because it's in use by another application</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Settings<ph name="END_LINK" /> can be customised at any time. Google may use content on sites that you visit, as well as browsing activity and interactions, to personalise Chrome and Google services such as Translate, Search and ads.</translation>
 <translation id="5639704535586432836">Open Settings &gt; Privacy &gt; Camera &gt; Google Chrome and turn on the camera.</translation>
 <translation id="5642200033778930880">Google Chrome can't use your camera in Split View mode</translation>
 <translation id="5703130498371792817">Enjoying Chrome? <ph name="BEGIN_LINK" />Rate this app<ph name="END_LINK" /></translation>
+<translation id="5854621639439811139">Chrome tip. Some buttons are now at the bottom of your screen, such as Back, Forward and Search.</translation>
 <translation id="6036420186814142909">Google Chrome has features that help you manage your internet data and how quickly you're able to load web pages.
 <ph name="BEGIN_LINK" />Learn more<ph name="END_LINK" /></translation>
 <translation id="6573431926118603307">Tabs that you've opened in Chrome on your other devices will appear here.</translation>
@@ -55,4 +57,5 @@
 <translation id="8459495907675268833">The selected data has been removed from Chrome and synced devices. Your Google Account may have other forms of browsing history like searches and activity from other Google services at history.google.com.</translation>
 <translation id="8540666473246803645">Google Chrome</translation>
 <translation id="8606668294522778825">Google Chrome may use web services to improve your browsing experience. You may optionally disable these services. <ph name="BEGIN_LINK" />Learn more<ph name="END_LINK" /></translation>
+<translation id="96145293669295453">Chrome tip. For more tab options, press and hold the Show Tabs button in the toolbar, which is at the bottom or top of your screen.</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_es-419.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_es-419.xtb
index 5fd5bd91..c53648d9 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_es-419.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_es-419.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Ayudar a mejorar las funciones y el rendimiento de Chrome</translation>
 <translation id="4523886039239821078">Algunos complementos causan bloqueos en Chrome. Desinstala:</translation>
 <translation id="4615174829807303908">Estás saliendo de una cuenta administrada por <ph name="SIGNOUT_MANAGED_DOMAIN" />. Esta acción borrará tus datos de Chrome en este dispositivo, pero permanecerán en tu cuenta de Google.</translation>
+<translation id="5240817131241497236">Se cambiaron las opciones de configuración que controlan la sincronización, la personalización y otros servicios de Google en Chrome. Es posible que tu configuración actual se vea afectada.</translation>
 <translation id="5389212809648216794">Google Chrome no puede utilizar tu cámara porque la está usando otra aplicación</translation>
 <translation id="5489543008378040943">Se puede modificar la <ph name="BEGIN_LINK" />Configuración<ph name="END_LINK" /> en cualquier momento. Es posible que usemos contenido de los sitios que visitas (así como tus interacciones y actividad de navegación) a fin de personalizar Chrome y otros servicios de Google, como el Traductor, la Búsqueda y los anuncios.</translation>
 <translation id="5639704535586432836">Abre Configuración &gt; Privacidad &gt; Cámara &gt; Google Chrome y activa la cámara.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_es.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_es.xtb
index 22b79a3..810ac29 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_es.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_es.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Ayudar a mejorar las funciones y el rendimiento de Chrome</translation>
 <translation id="4523886039239821078">Algunos complementos hacen que Chrome falle. Desinstálalos.</translation>
 <translation id="4615174829807303908">Estás cerrando sesión en una cuenta gestionada por <ph name="SIGNOUT_MANAGED_DOMAIN" />. Se van a eliminar tus datos de Chrome de este dispositivo, pero van a permanecer en tu cuenta de Google.</translation>
+<translation id="5240817131241497236">Las opciones que controlan la sincronización, la personalización y otros servicios de Google en Chrome han cambiado. Es posible que esto afecte a tu configuración actual.</translation>
 <translation id="5389212809648216794">Google Chrome no puede utilizar la cámara porque la está usando otra aplicación</translation>
 <translation id="5489543008378040943">Puedes personalizar la <ph name="BEGIN_LINK" />configuración<ph name="END_LINK" /> en cualquier momento. Google puede utilizar el contenido de los sitios web que visites y la actividad e interacciones de navegación para personalizar Chrome y otros servicios de Google, como el Traductor, la Búsqueda y los anuncios.</translation>
 <translation id="5639704535586432836">Abre Ajustes &gt; Privacidad &gt; Cámara &gt; Google Chrome y activa la cámara.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_et.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_et.xtb
index ccf0f36..14bbeb4 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_et.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_et.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Aidake täiustada Chrome'i funktsioone ja toimivust</translation>
 <translation id="4523886039239821078">Mõned pistikprogrammid põhjustavad Chrome'i kokkujooksmise. Desinstallige need:</translation>
 <translation id="4615174829807303908">Logite välja kontolt, mida haldab <ph name="SIGNOUT_MANAGED_DOMAIN" />. Selle tulemusel kustutatakse seadmes olevad Chrome'i andmed, kuid need jäävad alles teie Google'i kontole.</translation>
+<translation id="5240817131241497236">Sünkroonimist, isikupärastamist ja muid Chrome'is saadaolevaid Google'i teenuseid juhtivad seaded on muutunud. See võib teie praegusi seadeid mõjutada.</translation>
 <translation id="5389212809648216794">Google Chrome ei saa kaamerat kasutada, kuna seda kasutab muu rakendus</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Seadeid<ph name="END_LINK" /> saab igal ajal kohandada. Google võib kasutada teie külastatavate saitide sisu, brauseri interaktsioone ja tegevusi Chrome'i ja Google'i teenuste, näiteks tõlke, otsingu ja reklaamide täiustamiseks.</translation>
 <translation id="5639704535586432836">Tehke valikud Seaded &gt; Privaatsus &gt; Kaamera &gt; Google Chrome ja lülitage kaamera sisse.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fa.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fa.xtb
index 7463c78e..4c3aed3 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fa.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fa.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">‏کمک به بهبود ویژگی‌ها و عملکرد Chrome</translation>
 <translation id="4523886039239821078">‏برخی از افزونه‌ها باعث می‌شوند Chrome از کار بیفتد. لطفاً آنها را حذف نصب کنید:</translation>
 <translation id="4615174829807303908">‏هم‌اکنون از حسابی که توسط <ph name="SIGNOUT_MANAGED_DOMAIN" /> مدیریت می‌شود، خارج می‌شوید. با این کار داده‌های Chrome شما از این دستگاه حذف می‌شود اما همچنان در حساب Google شما باقی می‌ماند.</translation>
+<translation id="5240817131241497236">‏تنظیماتی که همگام‌سازی، شخصی‌سازی و سرویس‌های دیگر Google در Chrome را کنترل می‌کند تغییر کرده است. این تغییر ممکن است روی تنظیمات فعلی تأثیر گذارد.</translation>
 <translation id="5389212809648216794">‏Google Chrome نمی‌تواند از دوربین استفاده کند زیرا برنامه دیگری از آن استفاده می‌کند</translation>
 <translation id="5489543008378040943">‏هرزمان بخواهید می‌توانید <ph name="BEGIN_LINK" />تنظیمات<ph name="END_LINK" /> را سفارشی کنید. ممکن است Google از محتوای موجود در سایت‌هایی که بازدید می‌کنید و تعاملات و فعالیت مرورگر برای شخصی‌سازی Chrome و سرویس‌های Google (مانند «مترجم»، «جستجو» و آگهی‌ها) استفاده کند.</translation>
 <translation id="5639704535586432836">‏Settings (تنظیمات) &gt; Privacy (حریم خصوصی) &gt; Camera (دوربین) &gt; Google Chrome را باز کنید و دوربین را روشن کنید.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fi.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fi.xtb
index 6321ff5..01db1bb 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fi.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fi.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Auta parantamaan Chromen ominaisuuksia ja suorituskykyä</translation>
 <translation id="4523886039239821078">Jokin laajennus kaataa Chromen. Kokeile poistaa seuraavat:</translation>
 <translation id="4615174829807303908">Olet kirjautumassa ulos verkkotunnuksen <ph name="SIGNOUT_MANAGED_DOMAIN" /> hallinnoimalta tililtä. Chrome-tietosi poistetaan tältä laitteelta, mutta tietosi säilyvät Google-tililläsi.</translation>
+<translation id="5240817131241497236">Synkronointia, personointia ja muita Googlen palveluita Chromessa hallitsevat asetukset ovat muuttuneet. Tämä voi vaikuttaa nykyisiin asetuksiisi.</translation>
 <translation id="5389212809648216794">Google Chrome ei voi käyttää kameraa, koska se on toisen sovelluksen käytössä.</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Asetuksia<ph name="END_LINK" /> voi muuttaa milloin tahansa. Google saattaa käyttää selaustietojasi, käyttämiesi sivustojen sisältöä ja muita selaintoimintoja Chromen, Kääntäjän, Haun, mainosten ja muiden Google-palveluiden personointiin.</translation>
 <translation id="5639704535586432836">Avaa Asetukset &gt; Tietosuoja &gt; Kamera &gt; Google Chrome ja ota kamera käyttöön.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fil.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fil.xtb
index 97f70ba..ccc78db 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fil.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fil.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Tumulong sa pagpapahusay sa mga feature at performance ng Chrome</translation>
 <translation id="4523886039239821078">Nagiging dahilan ang ilang add-on ng pag-crash ng Chrome. Paki-uninstall.</translation>
 <translation id="4615174829807303908">Nagsa-sign out ka sa account na pinamamahalaan ng <ph name="SIGNOUT_MANAGED_DOMAIN" />. Ide-delete nito ang data mo sa Chrome sa device na ito, ngunit mananatili ang data mo sa iyong Google account.</translation>
+<translation id="5240817131241497236">Ang mga setting na nabago ng pagkontrol sa pag-sync, pag-personalize, at iba pang serbisyo ng Google sa Chrome. Maaari itong makaapekto sa iyong mga kasalukuyang setting.</translation>
 <translation id="5389212809648216794">Hindi magamit ng Google Chrome ang iyong camera ngayon dahil ginagamit ito sa isa pang application</translation>
 <translation id="5489543008378040943">Maaaring i-customize ang <ph name="BEGIN_LINK" />Mga Setting<ph name="END_LINK" /> anumang oras. Maaaring gamitin ng Google ang content sa mga site na binibisita mo, mga pakikipag-ugnayan at aktibidad sa browser para i-personalize ang Chrome at ang mga serbisyo ng Google tulad ng Translate, Search at mga ad.</translation>
 <translation id="5639704535586432836">Buksan ang Settings &gt; Privacy &gt; Camera &gt; Google Chrome at i-on ang camera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fr.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fr.xtb
index c86b257..5555c8f7 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_fr.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_fr.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Contribuer à l'amélioration des fonctionnalités et des performances de Chrome</translation>
 <translation id="4523886039239821078">Certains modules complémentaires entraînent le plantage de Chrome. Veuillez les désinstaller.</translation>
 <translation id="4615174829807303908">Vous vous déconnectez d'un compte géré par <ph name="SIGNOUT_MANAGED_DOMAIN" />. Cette opération entraînera la suppression de vos données Chrome de cet appareil, mais celles-ci seront conservées dans votre compte Google.</translation>
+<translation id="5240817131241497236">Les paramètres relatifs à la synchronisation, à la personnalisation et à d'autres services Google dans Chrome ont été modifiés, ce qui peut avoir une incidence sur votre configuration actuelle.</translation>
 <translation id="5389212809648216794">Google Chrome ne peut pas utiliser l'appareil photo, car celui-ci est utilisé par une autre application</translation>
 <translation id="5489543008378040943">Nous pouvons nous appuyer sur le contenu des sites que vous consultez, ainsi que sur vos activités de navigation et vos interactions avec le navigateur pour personnaliser Chrome, les annonces qui vous sont proposées et d'autres de nos services comme Google Traduction et la recherche Google. Sachez toutefois que vous pouvez personnaliser ces <ph name="BEGIN_LINK" />paramètres<ph name="END_LINK" /> à tout moment.</translation>
 <translation id="5639704535586432836">Ouvrez Paramètres &gt; Confidentialité &gt; Appareil photo &gt; Google Chrome, puis activez l'appareil photo.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_gu.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_gu.xtb
index a0ce6d2..7792c9b5 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_gu.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_gu.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chromeની સુવિધાઓ અને પ્રદર્શનને સુધારવામાં સહાય કરો</translation>
 <translation id="4523886039239821078">કેટલાંક એડ-ઓન્સને કારણે Chrome ક્રેશ થાય છે. કૃપા કરી આને અનઇન્સ્ટોલ કરો:</translation>
 <translation id="4615174829807303908">તમે <ph name="SIGNOUT_MANAGED_DOMAIN" /> દ્વારા સંચાલિત એકાઉન્ટમાંથી સાઇન આઉટ કરી રહ્યાં છો. આ તમારા Chrome ડેટાને આ ઉપકરણમાંથી કાઢી નાખશે, પરંતુ તમારો ડેટા તમારા Google એકાઉન્ટમાં રહેશે.</translation>
+<translation id="5240817131241497236">Chromeમાં સિંક, વૈયક્તિકરણ અને અન્ય Google સેવાઓ નિયંત્રિત કરતી સેટિંગ બદલવામાં આવી છે. તેની તમારી વર્તમાન સેટિંગ પર અસર થઈ શકે છે.</translation>
 <translation id="5389212809648216794">Google Chrome, તમારા કૅમેરાનો ઉપયોગ કરી શકતું નથી કારણ કે તે બીજી ઍપ્લિકેશન દ્વારા ઉપયોગમાં છે</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />સેટિંગ<ph name="END_LINK" />ને કોઈપણ સમયે કસ્ટમાઇઝ કરી શકાય છે. Google તમે મુલાકાત લીધેલ સાઇટના કન્ટેન્ટનો તથા બ્રાઉઝિંગ ક્રિયાપ્રતિક્રિયા અને પ્રવૃત્તિનો ઉપયોગ Chrome અને Google સેવાઓ જેમ કે અનુવાદ, શોધ અને જાહેરાતોને વ્યક્તિગત બનાવવા માટે કરી શકે છે.</translation>
 <translation id="5639704535586432836">સેટિંગ્સ &gt; ગોપનીયતા &gt; કૅમેરો &gt; Google Chrome ખોલો અને કૅમેરો ચાલુ કરો.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hi.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hi.xtb
index fb293359..bf119b89 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hi.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hi.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome के फ़ीचर और परफ़ॉर्मेंस को बेहतर बनाने में सहायता करें</translation>
 <translation id="4523886039239821078">कुछ ऐड-ऑन के कारण Chrome क्रैश हो जाता है. कृपया उन्हें अनइंस्टॉल करें:</translation>
 <translation id="4615174829807303908">आप <ph name="SIGNOUT_MANAGED_DOMAIN" /> द्वारा प्रबंधित खाते से प्रस्थान कर रहे हैं. इससे आपका Chrome डेटा इस डिवाइस से हट जाएगा, लेकिन आपका डेटा आपके Google खाते में बना रहेगा.</translation>
+<translation id="5240817131241497236">Chrome में 'सिंक करें', 'मनमुताबिक बनाएं' और दूसरी 'Google सेवाओं' को नियंत्रित करने वाली सेटिंग बदल गई हैं. आपकी मौजूदा सेटिंग पर इसका असर पड़ सकता है.</translation>
 <translation id="5389212809648216794">Google Chrome आपके कैमरे का उपयोग नहीं कर सकता क्योंकि दूसरा ऐप्लिकेशन उसका उपयोग कर रहा है</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />सेटिंग<ph name="END_LINK" /> को कभी भी अपनी पसंद के मुताबिक बनाया जा सकता है. Chrome और अनुवाद, सर्च जैसी Google सेवाओं के साथ ही विज्ञापनों को आपकी पसंद के मुताबिक आपको दिखाने के लिए Google उन साइटों की सामग्री जिन पर आप गए हैं, ब्राउज़र इंटरैक्शन और गतिविधि का इस्तेमाल कर सकता है.</translation>
 <translation id="5639704535586432836">सेटिंग &gt; निजता &gt; कैमरा &gt; Google Chrome खोलें और कैमरा चालू करें.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hr.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hr.xtb
index 88ff2230..0a721fb 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hr.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hr.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Pomozite poboljšati Chromeove značajke i izvedbu</translation>
 <translation id="4523886039239821078">Neki programski dodaci uzrokuju rušenje preglednika Chrome. Deinstalirajte:</translation>
 <translation id="4615174829807303908">Odjavljujete se s računa kojim upravlja <ph name="SIGNOUT_MANAGED_DOMAIN" />. Vaši podaci na Chromeu izbrisat će se s ovog uređaja, no ostat će na vašem Google računu.</translation>
+<translation id="5240817131241497236">Promijenjene su postavke koje upravljaju sinkronizacijom, prilagodbom i drugim Googleovim uslugama u Chromeu. To može utjecati na vaše trenutačne postavke.</translation>
 <translation id="5389212809648216794">Google Chrome ne može upotrebljavati vaš fotoaparat jer ga upotrebljava neka druga aplikacija</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Postavke<ph name="END_LINK" /> se mogu prilagoditi u bilo kojem trenutku. Google može upotrebljavati sadržaj na web-lokacijama koje posjećujete, vaše interakcije u pregledniku i aktivnost pregledavanja kako bi prilagodio Chrome i Googleove usluge kao što su Prevoditelj, Pretraživanje i oglasi.</translation>
 <translation id="5639704535586432836">Otvorite Postavke &gt; Privatnost &gt; Fotoaparat &gt; Google Chrome i uključite fotoaparat.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb
index 044912c..3870d48 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Segítség a Chrome funkcióinak és teljesítményének javítása érdekében</translation>
 <translation id="4523886039239821078">Bizonyos bővítmények a Chrome összeomlását eredményezik. Távolítsa el a következő(ke)t:</translation>
 <translation id="4615174829807303908">Kijelentkezik egy <ph name="SIGNOUT_MANAGED_DOMAIN" /> által felügyelt fiókból. Ezzel törli az eszközön található Chrome-adatokat, amelyek azonban Google-fiókjában megmaradnak.</translation>
+<translation id="5240817131241497236">Megváltoztak a szinkronizálást, a személyre szabást és a Chrome más Google-szolgáltatásait vezérlő beállítások. Ez befolyásolhatja az aktuális beállításokat.</translation>
 <translation id="5389212809648216794">A Google Chrome nem tudja használni a kamerát, mivel egy másik alkalmazás használja</translation>
 <translation id="5489543008378040943">A <ph name="BEGIN_LINK" />Beállításokat<ph name="END_LINK" /> bármikor személyre szabhatja. A Google felhasználhatja az Ön által felkeresett webhelyeken lévő tartalmakat, a böngészési interakciókat és tevékenységeket a Chrome és az egyéb Google-szolgáltatások (például a Fordító, a Keresés és a megjelenő hirdetések) személyre szabásához.</translation>
 <translation id="5639704535586432836">Nyissa meg a Beállítások &gt; Adatvédelem &gt; Kamera &gt; Google Chrome elemet, és kapcsolja be a kamerát.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_id.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_id.xtb
index d8e68c65f..551fe391 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_id.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_id.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Bantu sempurnakan fitur dan performa Chrome</translation>
 <translation id="4523886039239821078">Beberapa pengaya (pengaya) menyebabkan Chrome mogok. Uninstal:</translation>
 <translation id="4615174829807303908">Anda keluar dari akun yang dikelola oleh <ph name="SIGNOUT_MANAGED_DOMAIN" />. Tindakan ini akan menghapus data Chrome dari perangkat ini, namun data ini akan tetap tersedia di Akun Google.</translation>
+<translation id="5240817131241497236">Setelan yang mengontrol sinkronisasi, personalisasi, dan layanan Google lainnya di Chrome telah berubah. Ini dapat memengaruhi setelan saat ini.</translation>
 <translation id="5389212809648216794">Google Chrome tidak dapat menggunakan kamera karena sedang digunakan aplikasi lain</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Setelan<ph name="END_LINK" /> dapat disesuaikan kapan saja. Google dapat menggunakan konten di situs yang Anda buka, interaksi dan aktivitas browser untuk mempersonalisasi Chrome dan layanan Google seperti Terjemahan, Penelusuran, dan iklan.</translation>
 <translation id="5639704535586432836">Buka Setelan &gt; Privasi &gt; Kamera &gt; Google Chrome, lalu aktifkan kamera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_it.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_it.xtb
index af410c5..3ba51fa 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_it.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_it.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Contribuisci a migliorare le funzioni e le prestazioni di Chrome</translation>
 <translation id="4523886039239821078">Alcuni componenti aggiuntivi provocano l'arresto anomalo di Chrome. Disinstallali.</translation>
 <translation id="4615174829807303908">Stai per uscire da un account gestito da <ph name="SIGNOUT_MANAGED_DOMAIN" />. I dati di Chrome verranno eliminati da questo dispositivo, ma rimarranno memorizzati nel tuo account Google.</translation>
+<translation id="5240817131241497236">Le impostazioni che controllano sincronizzazione, personalizzazione e altri servizi Google in Chrome sono cambiate. Questo cambiamento potrebbe incidere sulle impostazioni attuali.</translation>
 <translation id="5389212809648216794">Google Chrome non può utilizzare la videocamera perché è attualmente utilizzata da un'altra applicazione</translation>
 <translation id="5489543008378040943">Le <ph name="BEGIN_LINK" />impostazioni<ph name="END_LINK" /> possono essere personalizzate in qualsiasi momento. Google potrebbe usare i contenuti dei siti che visiti, le attività e le interazioni con il browser per personalizzare Chrome e altri servizi Google come Traduttore, Ricerca e gli annunci.</translation>
 <translation id="5639704535586432836">Apri impostazioni &gt; Privacy &gt; Fotocamera &gt; Google Chrome e attiva la videocamera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_iw.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_iw.xtb
index 59dd907..e83802e 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_iw.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_iw.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">‏עזרה בשיפור התכונות והביצועים של Chrome</translation>
 <translation id="4523886039239821078">‏תוספים מסוימים גורמים ל-Chrome לקרוס. הסר את ההתקנה שלהם:</translation>
 <translation id="4615174829807303908">‏אתה יוצא מחשבון המנוהל על-ידי <ph name="SIGNOUT_MANAGED_DOMAIN" />. פעולה זו תמחק את הנתונים שלך ב-Chrome מהמכשיר הזה, אבל הם יישארו בחשבון Google שלך.</translation>
+<translation id="5240817131241497236">‏ההגדרות ששולטות בסינכרון, בהתאמה אישית ובשירותים אחרים של Google ב-Chrome השתנו. השינוי עשוי להשפיע על ההגדרות הנוכחיות שלך.</translation>
 <translation id="5389212809648216794">‏Google Chrome לא יכול להשתמש במצלמה כי אפליקציה אחרת משתמשת בה</translation>
 <translation id="5489543008378040943">‏אפשר תמיד להתאים אישית את <ph name="BEGIN_LINK" />ההגדרות<ph name="END_LINK" />. Google עשויה להשתמש בתוכן מאתרים שאליהם נכנסת, באינטראקציות בדפדפן ובפעילות גלישה כדי להתאים אישית את Chrome ואת שירותי Google, כמו Translate, החיפוש ומודעות.</translation>
 <translation id="5639704535586432836">‏עבור אל 'הגדרות' &gt; 'פרטיות' &gt; 'מצלמה' &gt; Google Chrome והפעל את המצלמה.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ja.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ja.xtb
index 49dc147..36e9930 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ja.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ja.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome の機能と動作の改善に協力する</translation>
 <translation id="4523886039239821078">アドオンが原因でChromeがクラッシュしました。次をアンインストールしてください:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> で管理されているアカウントからログアウトします。Chrome データはこのデバイスから削除されますが、Google アカウントで保持されます。</translation>
+<translation id="5240817131241497236">Chrome で同期、カスタマイズ、その他の Google サービスを管理する設定を変更しました。これにより、現在の設定に影響が生じる可能性があります。</translation>
 <translation id="5389212809648216794">カメラは別のアプリが使用中のため、Google Chrome では使用できません</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />設定<ph name="END_LINK" />はいつでも変更できます。Google は、Chrome やその他の Google サービス(翻訳、検索、広告など)をカスタマイズする目的で、ユーザーがアクセスしたサイトのコンテンツ、ユーザーの閲覧行動や操作を使用することがあります。</translation>
 <translation id="5639704535586432836">[設定] &gt; [プライバシー] &gt; [カメラ] &gt; [Google Chrome] の順に選択し、カメラをオンにします。</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_kn.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_kn.xtb
index 04c92eb..177ae04 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_kn.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_kn.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome ನ ವೈಶಿಷ್ಟ್ಯಗಳು ಹಾಗೂ ಕೆಲಸ ನಿರ್ವಹಣೆಯನ್ನು ಸುಧಾರಿಸಲು ಸಹಾಯ ಮಾಡಿ</translation>
 <translation id="4523886039239821078">ಕೆಲವು ಆಡ್-ಆನ್‌ಗಳು Chrome ಕ್ರ್ಯಾಶ್ ಆಗಲು ಕಾರಣವಾಗಿವೆ. ದಯವಿಟ್ಟು ಇದನ್ನು ಅಸ್ಥಾಪನೆಗೊಳಿಸಿ:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> ಮೂಲಕ ನಿರ್ವಹಿಸಲಾದ ಖಾತೆಯಿಂದ ನೀವು ಸೈನ್ ಔಟ್ ಮಾಡುತ್ತಿರುವಿರಿ. ಇದು ಈ ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ Chrome ಡೇಟಾವನ್ನು ಅಳಿಸುತ್ತದೆ, ಆದರೆ ನಿಮ್ಮ ಡೇಟಾ ನಿಮ್ಮ Google ಖಾತೆಯಲ್ಲಿಯೇ ಇರುತ್ತದೆ.</translation>
+<translation id="5240817131241497236">Chrome ನಲ್ಲಿ ಸಿಂಕ್, ವೈಯಕ್ತೀಕರಣ ಮತ್ತು ಇತರ Google ಸೇವೆಗಳನ್ನು ನಿಯಂತ್ರಿಸುವ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಬದಲಾಗಿವೆ. ಇದು ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರಬಹುದು.</translation>
 <translation id="5389212809648216794">ನಿಮ್ಮ ಕ್ಯಾಮರಾವನ್ನು ಬೇರೆ ಅಪ್ಲಿಕೇಶನ್ ಮೂಲಕ ಬಳಸುತ್ತಿರುವ ಕಾರಣ Google Chrome ಗೆ ಅದನ್ನು ಬಳಸಲು ಸಾಧ್ಯವಿಲ್ಲ</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />ಸೆಟ್ಟಿಂಗ್‍ಗಳನ್ನು<ph name="END_LINK" /> ಯಾವ ಸಮಯದಲ್ಲಾದರೂ ಕಸ್ಟಮೈಸ್‌ ಮಾಡಬಹುದು. ಅನುವಾದ, ಹುಡುಕಾಟ ಮತ್ತು ಜಾಹೀರಾತುಗಳಂತಹ Google ಸೇವೆಗಳನ್ನು ಮತ್ತು Chrome ಅನ್ನು ವೈಯಕ್ತೀಕರಿಸಲು ನೀವು ಭೇಟಿ ನೀಡುವ ಸೈಟ್‌ಗಳಲ್ಲಿನ ವಿಷಯ, ಬ್ರೌಸರ್‌ನೊಂದಿಗಿನ ಸಂವಾದಗಳು ಹಾಗೂ ಚಟುವಟಿಕೆಗಳನ್ನು Google ಬಳಸಬಹುದು.</translation>
 <translation id="5639704535586432836">ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಗೌಪ್ಯತೆ &gt; ಕ್ಯಾಮರಾ &gt; Google Chrome ತೆರೆಯಿರಿ ಮತ್ತು ಕ್ಯಾಮರಾ ಆನ್ ಮಾಡಿ.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ko.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ko.xtb
index 1ba2d2d..076823b 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ko.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ko.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome의 기능 및 성능 개선에 참여</translation>
 <translation id="4523886039239821078">일부 부가기능이 Chrome을 비정상 종료시킵니다. 다음 항목을 제거하세요.</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" />에서 관리하는 계정에서 로그아웃합니다. 이렇게 하면 내 Chrome 데이터가 이 기기에서 삭제되지만 Google 계정에는 그대로 유지됩니다.</translation>
+<translation id="5240817131241497236">Chrome에서 동기화, 맞춤설정 및 기타 Google 서비스를 관리하는 설정이 변경되었습니다. 이로 인해 현재 설정이 영향을 받을 수 있습니다.</translation>
 <translation id="5389212809648216794">다른 애플리케이션에서 카메라를 사용하고 있으므로 Chrome에서 카메라를 사용할 수 없습니다.</translation>
 <translation id="5489543008378040943">언제든지 <ph name="BEGIN_LINK" />설정<ph name="END_LINK" />을 변경할 수 있습니다. Google에서 Chrome 및 번역, 검색과 같은 기타 Google 서비스, 광고를 맞춤설정하기 위해 사용자가 방문한 사이트의 콘텐츠, 탐색 활동, 상호작용 기록을 사용할 수 있습니다.</translation>
 <translation id="5639704535586432836">설정 &gt; 개인정보 보호 &gt; 카메라 &gt; Chrome을 열고 카메라를 사용 설정하세요.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb
index 3df738d..8512cc4 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb
@@ -32,11 +32,13 @@
 <translation id="447252321002412580">Padėti tobulinti „Chrome“ funkcijas ir našumą</translation>
 <translation id="4523886039239821078">Dėl tam tikrų priedų „Chrome“ užstringa . Pašalinkite:</translation>
 <translation id="4615174829807303908">Atsijungiate nuo paskyros, kurią valdo <ph name="SIGNOUT_MANAGED_DOMAIN" />. „Chrome“ duomenys bus ištrinti iš šio įrenginio, bet liks „Google“ paskyroje.</translation>
+<translation id="5240817131241497236">Pasikeitė sinchronizavimo, suasmeninimo ir kitų „Google“ paslaugų, naudojamų naršyklėje „Chrome“, valdymo nustatymai. Tai gali turėti įtakos jūsų dabartiniams nustatymams.</translation>
 <translation id="5389212809648216794">„Google Chrome“ negali naudoti fotoaparato, nes jį naudoja kita programa</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Nustatymus<ph name="END_LINK" /> galima tinkinti bet kuriuo metu. „Google“ gali naudoti jūsų lankomų svetainių turinį, naršyklės sąveikas ir veiklą suasmenindama „Chrome“ ir kitas „Google“ paslaugas, pvz., Vertėją, Paiešką ir skelbimus.</translation>
 <translation id="5639704535586432836">Atidarykite „Nustatymai“ &gt; „Privatumas“ &gt; „Fotoaparatas“ &gt; „Google Chrome“ ir įjunkite fotoaparatą.</translation>
 <translation id="5642200033778930880">„Google Chrome“ negali naudoti fotoaparato padalyto rodinio režimu</translation>
 <translation id="5703130498371792817">Mėgaujatės „Chrome“? <ph name="BEGIN_LINK" />Įvertinkite šią programą<ph name="END_LINK" /></translation>
+<translation id="5854621639439811139">„Chrome“ patarimas. Kai kurie mygtukai dabar yra ekrano apačioje, pvz., mygtukai „Atgal“, „Pirmyn“ ir „Ieškoti“.</translation>
 <translation id="6036420186814142909">„Google Chrome“ yra funkcijų, kurias naudojant lengva tvarkyti internetinius duomenis ir tinklalapių įkėlimo spartą.
 <ph name="BEGIN_LINK" />Sužinokite daugiau<ph name="END_LINK" /></translation>
 <translation id="6573431926118603307">Čia bus rodomi kituose įrenginiuose atidaryti „Chrome“ skirtukai.</translation>
@@ -55,4 +57,5 @@
 <translation id="8459495907675268833">Pasirinkti duomenys pašalinti iš „Chrome“ ir sinchronizuojamų įrenginių. Adresu history.google.com gali būti pateikta kitų formų jūsų „Google“ paskyros istorija, pvz., paieškos ir veikla kitose „Google“ paslaugose.</translation>
 <translation id="8540666473246803645">Google Chrome</translation>
 <translation id="8606668294522778825">„Google Chrome“ gali naudoti žiniatinklio paslaugas naršymo patirčiai pagerinti. Galite pasirinktinai išjungti šias paslaugas. <ph name="BEGIN_LINK" />Sužinokite daugiau<ph name="END_LINK" /></translation>
+<translation id="96145293669295453">„Chrome“ patarimas. Jei reikia daugiau skirtukų parinkčių, paspauskite ir palaikykite mygtuką „Rodyti skirtukus“ įrankių juostoje, kuri yra ekrano apačioje arba viršuje.</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lv.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lv.xtb
index 713c3b8e..a576fc1af 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lv.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lv.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Palīdzēt uzlabot Chrome funkcijas un veiktspēju</translation>
 <translation id="4523886039239821078">Daži papildinājumi var izraisīt pārlūka Chrome avarēšanu. Lūdzu, atinstalējiet šos papildinājumus:</translation>
 <translation id="4615174829807303908">Jūs izrakstāties no konta, kas tiek pārvaldīts domēnā <ph name="SIGNOUT_MANAGED_DOMAIN" />. Izrakstoties Chrome dati tiks dzēsti no šīs ierīces, taču tie paliks jūsu Google kontā.</translation>
+<translation id="5240817131241497236">Ir mainīti sinhronizācijas, personalizācijas un citu Google pakalpojumu pārvaldības iestatījumi pārlūkā Chrome. Tas var ietekmēt jūsu pašreizējos iestatījumus.</translation>
 <translation id="5389212809648216794">Google Chrome pašlaik nevar izmantot kameru, jo to izmanto cita lietojumprogramma.</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Iestatījumus<ph name="END_LINK" /> var pielāgot jebkurā laikā. Google var izmantot jūsu apmeklēto vietņu saturu, pārlūkā veiktās mijiedarbības un darbības, lai personalizētu pārlūku Chrome un tādus Google pakalpojumus kā Tulkotājs, Meklēšana un reklāmas.</translation>
 <translation id="5639704535586432836">Atveriet sadaļu Iestatījumi &gt; Konfidencialitāte &gt; Kamera &gt; Google Chrome un ieslēdziet kameru.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ml.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ml.xtb
index ae318e4..273612a 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ml.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ml.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome-ന്റെ ഫീച്ചറുകളും പ്രകടനവും മെച്ചപ്പെടുത്താൻ സഹായിക്കുക</translation>
 <translation id="4523886039239821078">ചില ആഡ്-ഓണുകൾ Chrome ക്രാഷാകുന്നതിന് ഇടയാക്കുന്നു. ഇനിപ്പറയുന്നവ അൺഇൻസ്റ്റാളുചെയ്യുക:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> മാനേജുചെയ്യുന്ന ഒരു അക്കൗണ്ടിൽ നിന്നും നിങ്ങൾ സൈൻ ഔട്ട് ചെയ്യുകയാണ്. ഇത് ഈ ഉപകരണത്തിൽ നിന്ന് നിങ്ങളുടെ Chrome വിവരങ്ങൾ ഇല്ലാതാക്കുമെങ്കിലും, Google അക്കൗണ്ടിൽ തുടർന്നും അവ ഉണ്ടായിരിക്കുന്നതാണ്.</translation>
+<translation id="5240817131241497236">Chrome-ൽ സമന്വയം, വ്യക്തിപരമാക്കൽ, മറ്റ് Google സേവനങ്ങൾ എന്നിവ നിയന്ത്രിക്കുന്ന ക്രമീകരണം മാറിയിട്ടുണ്ട്. ഇത് നിങ്ങളുടെ ക്രമീകരണത്തെ ബാധിച്ചേക്കാം.</translation>
 <translation id="5389212809648216794">മറ്റൊരു അപ്ലിക്കേഷൻ നിങ്ങളുടെ ക്യാമറ ഉപയോഗിക്കുന്നതിനാൽ Google Chrome-ന് അത്‌ ഉപയോഗിക്കാനാവില്ല</translation>
 <translation id="5489543008378040943">ഏത് സമയത്തും <ph name="END_LINK" />ക്രമീകരണം<ph name="BEGIN_LINK" /> ഇഷ്‌ടാനുസൃതമാക്കാം. Chrome, Translate, തിരയൽ, പരസ്യങ്ങൾ പോലുള്ള Google സേവനങ്ങൾ വ്യക്തിപരമാക്കാൻ നിങ്ങൾ സന്ദർശിക്കുന്ന സൈറ്റുകളിലെ ഉള്ളടക്കം, ബ്രൗസർ ഇടപെടലുകൾ, ആക്‌റ്റിവിറ്റി എന്നിവ Google ഉപയോഗിച്ചേക്കാം.</translation>
 <translation id="5639704535586432836">ക്രമീകരണം &gt; സ്വകാര്യത &gt; ക്യാമറ &gt; Google Chrome തുറന്ന് ക്യാമറ ഓണാക്കുക.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_mr.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_mr.xtb
index 4dd7a7d1..27382be3 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_mr.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_mr.xtb
@@ -32,11 +32,13 @@
 <translation id="447252321002412580">Chrome ची वैशिष्ट्ये आणि परफॉर्मन्स सुधारण्यात मदत करा</translation>
 <translation id="4523886039239821078">Chrome क्रॅश होण्याचे कारण काही अॅड-ऑन आहेत. कृपया ती अनइंस्टॉल करा:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> द्वारे व्यवस्थापित केलेल्या खात्यामधून आपण साइन आउट करीत आहात. हे आपला Chrome डेटा या डिव्हाइस वरून हटवेल परंतु आपला डेटा आपल्या Google खात्यामध्ये असेल.</translation>
+<translation id="5240817131241497236">Chrome मध्ये सिंक, पर्सनलायझेशन आणि इतर Google सेवा नियंत्रित करणारी सेटिंग्ज बदलली आहेत. याचा तुमच्या सद्य सेटिंग्जवर परिणाम होऊ शकतो.</translation>
 <translation id="5389212809648216794">Google Chrome आपला कॅमेरा वापरू शकत नाही कारण दुसरा अॅप्लिकेशन सध्या तो वापरत आहे.</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />सेटिंग्ज<ph name="END_LINK" /> कधीही कस्टमाइझ केली जाऊ शकतात. Chrome आणि भाषांतर, शोध आणि जाहिरातींसारख्या Google सेवा पर्सनलाइझ करण्यासाठी Google तुम्ही भेट देत असलेल्या साइटवरील आशय, ब्राउझर परस्परसंवाद आणि अॅक्टिव्हिटी वापरू शकते.</translation>
 <translation id="5639704535586432836">सेटिंग्ज &gt; गोपनीयता &gt; कॅमेरा &gt; Google Chrome उघडा आणि कॅमेरा चालू करा.</translation>
 <translation id="5642200033778930880">विभाजित दृश्य मोडमध्ये Google Chrome आपला कॅमेरा वापरू शकत नाही</translation>
 <translation id="5703130498371792817">Chrome चा आनंद घेत आहात? <ph name="BEGIN_LINK" />या अॅपला रेट करा<ph name="END_LINK" /></translation>
+<translation id="5854621639439811139">Chrome टीप. काही बटणे आता तुमच्या स्क्रीनच्या तळाशी आहेत, जसे की मागे, पुढे आणि शोध.</translation>
 <translation id="6036420186814142909">Google Chrome कडे अशी वैशिष्ट्ये आहेत जी आपल्याला आपला इंटरनेट डेटा आणि आपण किती द्रुतपणे वेबपृष्ठे लोड करण्यात सक्षम होते हे व्यवस्थापित करण्यात मदत करतात.
 <ph name="BEGIN_LINK" />अधिक जाणून घ्या<ph name="END_LINK" /></translation>
 <translation id="6573431926118603307">आपल्या अन्य डिव्हाइसेसवर आपण Chrome मध्ये उघडलेले टॅब येथे दिसतील.</translation>
@@ -56,4 +58,5 @@
 <translation id="8459495907675268833">निवडलेला डेटा Chrome आणि संकालित केलेल्या डिव्हाइसेस मधून काढला गेला आहे. आपल्या Google खात्यामध्ये history.google.com येथे Google च्या इतर सेवांमधील शोध आणि क्रियाकलाप यासारख्या ब्राउझिंग इतिहासाची इतर स्वरूपे असू शकतात.</translation>
 <translation id="8540666473246803645">Google Chrome</translation>
 <translation id="8606668294522778825">Google Chrome आपला ब्राउझिंग अनुभव सुधारण्यासाठी वेब सेवा वापरू शकते. आपण या सेवा वैकल्पिकपणे अक्षम करू शकता. <ph name="BEGIN_LINK" />अधिक जाणून घ्या<ph name="END_LINK" /></translation>
+<translation id="96145293669295453">Chrome टीप. अधिक टॅब पर्यायांसाठी, टूलबारवरील टॅब दाखवा बटण दाबा आणि धरून ठेवा, जे तुमच्या स्क्रीनच्या खाली किंवा सर्वात वर असते.</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb
index 0ebfda2..0b97f23f 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Bantu meningkatkan ciri dan prestasi Chrome</translation>
 <translation id="4523886039239821078">Beberapa tambahan menyebabkan Chrome ranap. Sila nyahpasang:</translation>
 <translation id="4615174829807303908">Anda log keluar daripada akaun yang diurus oleh <ph name="SIGNOUT_MANAGED_DOMAIN" />. Tindakan ini akan memadamkan data Chrome anda daripada peranti ini, tetapi data anda akan kekal dalam akaun Google anda.</translation>
+<translation id="5240817131241497236">Tetapan yang mengawal penyegerakan, pemperibadian dan perkhidmatan Google yang lain dalam Chrome telah berubah. Perubahan ini mungkin mempengaruhi tetapan semasa anda.</translation>
 <translation id="5389212809648216794">Google Chrome tidak dapat menggunakan kamera anda kerana kamera sedang digunakan oleh aplikasi lain</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Tetapan<ph name="END_LINK" /> boleh disesuaikan pada bila-bila masa. Google boleh menggunakan kandungan di tapak yang anda lawati, interaksi dan aktiviti penyemak imbas untuk memperibadikan Chrome dan Perkhidmatan Google seperti Terjemahan, Carian dan iklan.</translation>
 <translation id="5639704535586432836">Buka Tetapan &gt; Privasi &gt; Kamera &gt; Google Chrome dan hidupkan kamera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_nl.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_nl.xtb
index ed298378..cae3599 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_nl.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_nl.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Help de functies en prestaties van Chrome verbeteren</translation>
 <translation id="4523886039239821078">Sommige add-ons kunnen ervoor zorgen dat Chrome crasht. Verwijder de volgende add-ons:</translation>
 <translation id="4615174829807303908">Je logt uit van een account dat wordt beheerd door <ph name="SIGNOUT_MANAGED_DOMAIN" />. Hierdoor worden je Chrome-gegevens verwijderd van dit apparaat. Je gegevens blijven echter opgeslagen in je Google-account.</translation>
+<translation id="5240817131241497236">De instellingen waarmee je synchronisatie, personalisatie en andere Google-services in Chrome beheert, zijn gewijzigd. Dit is mogelijk van invloed op je huidige instellingen.</translation>
 <translation id="5389212809648216794">Google Chrome kan je camera niet gebruiken omdat deze wordt gebruikt door een andere app</translation>
 <translation id="5489543008378040943">De <ph name="BEGIN_LINK" />instellingen<ph name="END_LINK" /> kunnen op elk gewenst moment worden aangepast. Google kan content op sites die je bezoekt en je browseractiviteit en interacties gebruiken om Chrome en Google-services (zoals Translate, Zoeken en advertenties) te personaliseren.</translation>
 <translation id="5639704535586432836">Open Instellingen &gt; Privacy &gt; Camera &gt; Google Chrome en zet de camera aan.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_no.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_no.xtb
index 7113a9bb..99c13ff 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_no.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_no.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Hjelp til med å forbedre funksjonene og ytelsen til Chrome</translation>
 <translation id="4523886039239821078">Noen tillegg fører til at Chrome krasjer. Avinstaller:</translation>
 <translation id="4615174829807303908">Du logger av en konto som administreres av <ph name="SIGNOUT_MANAGED_DOMAIN" />. Dette sletter Chrome-dataene dine fra denne enheten, men dataene blir værende i Google-kontoen din.</translation>
+<translation id="5240817131241497236">Innstillingene som kontrollerer synkronisering, personlig tilpasning og andre Google-tjenester i Chrome, er blitt endret. Dette kan påvirke de nåværende innstillingene dine.</translation>
 <translation id="5389212809648216794">Google Chrome kan ikke bruke kameraet fordi det brukes av en annen app</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Innstillinger<ph name="END_LINK" /> kan tilpasses når som helst. Google kan bruke innhold på nettsteder du besøker, nettleserinteraksjoner og aktiviteter for å gi Chrome og Google-tjenester (som Oversetter, Søk og annonser) et personlig preg.</translation>
 <translation id="5639704535586432836">Åpne Innstillinger &gt; Personvern &gt; Kamera &gt; Google Chrome, og slå på kameraet.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pl.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pl.xtb
index 96e33e6a..cef02aa 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pl.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pl.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Pomóż w ulepszaniu funkcji i działania Chrome</translation>
 <translation id="4523886039239821078">Niektóre dodatki mogą powodować awarie Chrome. Odinstaluj je.</translation>
 <translation id="4615174829807303908">Wylogowujesz się z konta, którym zarządza <ph name="SIGNOUT_MANAGED_DOMAIN" />. Spowoduje to usunięcie danych Chrome z tego urządzenia, ale pozostaną one na koncie Google.</translation>
+<translation id="5240817131241497236">Zostały zmienione ustawienia synchronizacji, personalizacji i innych usług Google w Chrome. Może to wpłynąć na Twoje bieżące ustawienia.</translation>
 <translation id="5389212809648216794">Google Chrome nie może użyć aparatu, bo używa go już inna aplikacja</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Ustawienia<ph name="END_LINK" /> możesz zmienić w dowolnym momencie. Google może używać zawartości niektórych odwiedzanych przez Ciebie stron oraz informacji o Twojej aktywności i interakcjach w przeglądarce, by personalizować Chrome i inne usługi Google takie jak Tłumacz, wyszukiwarka czy reklamy.</translation>
 <translation id="5639704535586432836">Wybierz Ustawienia &gt; Prywatność &gt; Aparat &gt; Google Chrome i włącz aparat.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-BR.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-BR.xtb
index 79332564..bc3abe2 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-BR.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-BR.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Ajude a melhorar os recursos e o desempenho do Chrome</translation>
 <translation id="4523886039239821078">Alguns complementos causam a falha do Google Chrome. Desinstale-os.</translation>
 <translation id="4615174829807303908">Você está saindo de uma conta gerenciada por <ph name="SIGNOUT_MANAGED_DOMAIN" />. Os dados do Chrome serão excluídos desse dispositivo, mas permanecerão na sua Conta do Google.</translation>
+<translation id="5240817131241497236">As configurações que controlam sincronização, personalização e outros serviços do Google no Chrome foram alteradas. Isso pode afetar suas configurações atuais.</translation>
 <translation id="5389212809648216794">O Google Chrome não pode usar a câmera porque ela está sendo usada por outro app</translation>
 <translation id="5489543008378040943">As <ph name="BEGIN_LINK" />configurações<ph name="END_LINK" /> podem ser personalizadas a qualquer momento. O Google pode usar o conteúdo dos sites que você visita, interações do navegador e atividades para personalizar o Chrome e os serviços do Google, como o Tradutor, a Pesquisa e os anúncios.</translation>
 <translation id="5639704535586432836">Abra Ajustes &gt; Privacidade &gt; Câmera &gt; Google Chrome e ative a câmera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-PT.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-PT.xtb
index f9c03f3..ce3259e 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-PT.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_pt-PT.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Ajudar a melhorar as funcionalidades e o desempenho do Chrome</translation>
 <translation id="4523886039239821078">Alguns suplementos provocam falhas no sistema do Chrome. Desinstale:</translation>
 <translation id="4615174829807303908">Está a terminar sessão numa conta gerida por <ph name="SIGNOUT_MANAGED_DOMAIN" />. Esta ação elimina os seus dados do Chrome deste dispositivo, embora permaneçam na Conta Google.</translation>
+<translation id="5240817131241497236">As definições que controlam a sincronização, a personalização e outros serviços Google no Chrome foram alteradas. Isto pode afetar as suas definições atuais.</translation>
 <translation id="5389212809648216794">O Google Chrome não pode utilizar a câmara porque está a ser utilizada por outra aplicação</translation>
 <translation id="5489543008378040943">Pode personalizar as <ph name="BEGIN_LINK" />Definições<ph name="END_LINK" /> a qualquer momento. A Google pode utilizar conteúdos nos sites que visita, assim como as interações e a atividade com o navegador, para personalizar o Chrome e outros serviços Google, como o Tradutor, a Pesquisa e os anúncios.</translation>
 <translation id="5639704535586432836">Abra Definições &gt; Privacidade &gt; Câmara &gt; Google Chrome e ative a câmara.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ro.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ro.xtb
index 1ed952d..21262617 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ro.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ro.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Contribuie la îmbunătățirea funcțiilor și performanței Chrome</translation>
 <translation id="4523886039239821078">Unele suplimente determină blocarea browserului Chrome. Dezinstalează:</translation>
 <translation id="4615174829807303908">Te deconectezi de la un cont gestionat de <ph name="SIGNOUT_MANAGED_DOMAIN" />. Astfel, se vor șterge datele Chrome de pe acest dispozitiv, dar datele vor rămâne în Contul Google.</translation>
+<translation id="5240817131241497236">Setările care controlează sincronizarea, personalizarea și alte servicii Google din Chrome s-au modificat. Această acțiune poate afecta setările curente.</translation>
 <translation id="5389212809648216794">Google Chrome nu poate folosi camera foto, deoarece este folosită de o altă aplicație</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Setările<ph name="END_LINK" /> pot fi personalizate oricând. Google poate folosi conținutul de pe site-urile pe care le accesezi, interacțiunile cu browserul și activitatea pentru a personaliza Chrome și servicii Google precum Traducere, Căutare și anunțuri.</translation>
 <translation id="5639704535586432836">Deschide Setări &gt; Confidențialitate &gt; Cameră foto &gt; Google Chrome și activează camera foto.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ru.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ru.xtb
index eff67f5e..0800f00 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ru.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ru.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Помогать повышать производительность Chrome и улучшать функции</translation>
 <translation id="4523886039239821078">Некоторые дополнения вызывают сбой Chrome. Необходимо удалить:</translation>
 <translation id="4615174829807303908">Вы выходите из аккаунта, которым управляет администратор домена <ph name="SIGNOUT_MANAGED_DOMAIN" />. Все данные Chrome, хранящиеся на этом устройстве, будут удалены, но останутся в вашем аккаунте Google.</translation>
+<translation id="5240817131241497236">В Chrome изменились настройки синхронизации, персонализации и других сервисов Google. Это может повлиять на текущие настройки.</translation>
 <translation id="5389212809648216794">Google Chrome не может получить доступ к камере, поскольку она используется другим приложением.</translation>
 <translation id="5489543008378040943">Для персонализации рекламы, Chrome и таких сервисов Google, как Переводчик и Поиск, могут использоваться данные с сайтов, которые вы посещаете, а также история просмотров и действий в браузере. Вы можете изменить <ph name="BEGIN_LINK" />настройки<ph name="END_LINK" /> в любой момент.</translation>
 <translation id="5639704535586432836">Откройте "Настройки &gt; Конфиденциальность &gt; Камера &gt; Chrome" и включите камеру.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sk.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sk.xtb
index 285e6cd..4905bd4 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sk.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sk.xtb
@@ -32,11 +32,13 @@
 <translation id="447252321002412580">Pomáhať s vylepšovaním funkcií a výkonu Chromu</translation>
 <translation id="4523886039239821078">Niektoré doplnky spôsobujú zlyhanie prehliadača Chrome. Odinštalujte ich.</translation>
 <translation id="4615174829807303908">Odhlasujete sa z účtu spravovaného doménou <ph name="SIGNOUT_MANAGED_DOMAIN" />. Týmto odstránite údaje prehliadača Chrome z príslušného zariadenia, avšak zostanú naďalej vo vašom účte Google.</translation>
+<translation id="5240817131241497236">Nastavenia, ktoré riadia synchronizáciu, prispôsobenie a ďalšie služby v Chrome, boli zmenené. Môže to ovplyvniť vaše aktuálne nastavenia.</translation>
 <translation id="5389212809648216794">Google Chrome nemôže použiť váš fotoaparát, pretože ho práve používa iná aplikácia</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Nastavenia<ph name="END_LINK" /> si môžete kedykoľvek prispôsobiť. Google môže použiť obsah na weboch, ktoré navštívite, interakcie a aktivitu v prehliadači na prispôsobenie Chromu a služieb Googlu, ako sú Prekladač, Vyhľadávanie a reklamy.</translation>
 <translation id="5639704535586432836">Otvorte Nastavenia &gt; Súkromie &gt; Fotoaparát &gt; Google Chrome a zapnite fotoaparát.</translation>
 <translation id="5642200033778930880">Google Chrome nemôže použiť váš fotoaparát v režime Rozdelené zobrazenie</translation>
 <translation id="5703130498371792817">Páči sa vám Chrome? <ph name="BEGIN_LINK" />Ohodnoťte túto aplikáciu.<ph name="END_LINK" /></translation>
+<translation id="5854621639439811139">Tip pre Chrome: niektoré tlačidlá sa teraz nachádzajú v dolnej časti obrazovky, napríklad Dopredu, Späť a Hľadať.</translation>
 <translation id="6036420186814142909">Prehliadač Google Chrome obsahuje funkcie, ktoré vám pomôžu spravovať objem internetových dát a rýchlosť načítania webových stránok.
 <ph name="BEGIN_LINK" />Ďalšie informácie<ph name="END_LINK" /></translation>
 <translation id="6573431926118603307">Tu sa zobrazia karty, ktoré ste otvorili v Chrome na iných zariadeniach.</translation>
@@ -55,4 +57,5 @@
 <translation id="8459495907675268833">Vybraté údaje boli odstránené z Chromu a synchronizovaných zariadení. Váš účet Google môže mať na adrese history.google.com ďalšie formy histórie prehliadania, ako napríklad vyhľadávania a aktivity v iných službách Google.</translation>
 <translation id="8540666473246803645">Google Chrome</translation>
 <translation id="8606668294522778825">Google Chrome vám môže uľahčiť prehliadanie pomocou webových služieb. Vybrané webové služby môžete podľa potreby vypnúť. <ph name="BEGIN_LINK" />Ďalšie informácie<ph name="END_LINK" /></translation>
+<translation id="96145293669295453">Tip pre Chrome: ďalšie možnosti kariet získate pridržaním tlačidla Zobraziť karty na paneli s nástrojmi, ktorý sa nachádza v dolnej alebo hornej časti obrazovky.</translation>
 </translationbundle>
\ No newline at end of file
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sl.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sl.xtb
index f6f17306..c93c073 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sl.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sl.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Pomagajte izboljšati funkcije in delovanje Chroma</translation>
 <translation id="4523886039239821078">Nekateri dodatki povzročajo zrušitve Chroma. Odstranite jih:</translation>
 <translation id="4615174829807303908">Odjavili se boste iz računa, ki ga upravlja <ph name="SIGNOUT_MANAGED_DOMAIN" />. S tem boste iz te naprave izbrisali podatke v Chromu, vendar bodo vaši podatki še vedno na voljo v Google Računu.</translation>
+<translation id="5240817131241497236">Nastavitve, ki nadzirajo sinhronizacijo, prilagajanje in druge Googlove storitve v Chromu, so se spremenile. To lahko vpliva na vaše trenutne nastavitve.</translation>
 <translation id="5389212809648216794">Google Chrome ne more uporabljati fotoaparata, ker ga uporablja druga aplikacija</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Nastavitve<ph name="END_LINK" /> je mogoče kadar koli prilagoditi. Google lahko vsebino na spletnih mestih, ki jih obiščete, interakcije z brskalnikom in dejavnost v brskalniku uporabi za prilagajanje Chroma in Googlovih storitev, kot so Prevajalnik, Iskanje Google in oglasi.</translation>
 <translation id="5639704535586432836">Odprite Settings &gt; Privacy &gt; Camera &gt; Google Chrome in vklopite fotoaparat.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sr.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sr.xtb
index 83b2014..55f27350 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sr.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sr.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Помозите нам да побољшамо Chrome-ове функције и учинак</translation>
 <translation id="4523886039239821078">Неки програмски додаци изазивају отказивање Chrome-а. Деинсталирајте:</translation>
 <translation id="4615174829807303908">Одјављујете се са налога којим управља <ph name="SIGNOUT_MANAGED_DOMAIN" />. То ће избрисати Chrome податке са овог уређаја, али ће подаци остати на Google налогу.</translation>
+<translation id="5240817131241497236">Променила су се подешавања која контролишу синхронизацију, персонализацију и друге Google услуге у Chrome-у. То може да утиче на тренутна подешавања.</translation>
 <translation id="5389212809648216794">Google Chrome не може да користи камеру зато што је већ користи друга апликација</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Подешавања<ph name="END_LINK" /> увек можете да прилагодите. Google може да користи садржај на сајтовима које посећујете, интеракције са прегледачима и активности у њима да би персонализовао Chrome и Google услуге као што су Преводилац, Претрага и огласи.</translation>
 <translation id="5639704535586432836">Отворите Подешавања &gt; Приватност &gt; Камера &gt; Google Chrome и укључите камеру.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sv.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sv.xtb
index 8ab7ed1..ecfee56f 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sv.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sv.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Bidra till att förbättra Chromes funktioner och prestanda</translation>
 <translation id="4523886039239821078">Några tillägg har fått Chrome att krascha. Avinstallera följande:</translation>
 <translation id="4615174829807303908">Du håller på att logga ut från ett konto som hanteras av <ph name="SIGNOUT_MANAGED_DOMAIN" />. Åtgärden raderar din data i Chrome från den här enheten, men den finns kvar på Google-kontot.</translation>
+<translation id="5240817131241497236">Inställningarna som styr synkronisering, anpassning och andra tjänster från Google i Chrome har ändrats. Detta kan påverka dina nuvarande inställningar.</translation>
 <translation id="5389212809648216794">Google Chrome kan inte använda kameran eftersom den används av en annan app</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Inställningarna<ph name="END_LINK" /> kan anpassas när som helst. Google kan anpassa Chrome och tjänster från Google som Översätt, Sök och annonser utifrån innehållet på webbplatser du besöker, vad du interagerar med i webbläsaren och annan aktivitet.</translation>
 <translation id="5639704535586432836">Öppna Inställningar &gt; Sekretess &gt; Kamera &gt; Google Chrome och aktivera kameran.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sw.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sw.xtb
index 67d1ee2..0636b2e 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_sw.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_sw.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Tusaidie tuboreshe utendaji na vipengele vya Chrome</translation>
 <translation id="4523886039239821078">Baadhi ya programu jalizi husababisha Chrome iache kufanya kazi. Tafadhali sanidua:</translation>
 <translation id="4615174829807303908">Unaondoka kwenye akaunti inayodhibitiwa na <ph name="SIGNOUT_MANAGED_DOMAIN" />. Hatua hii itafuta data yako ya Chrome kwenye kifaa hiki, lakini data yako itasalia katika akaunti yako ya Google.</translation>
+<translation id="5240817131241497236">Mipangilio inayodhibiti usawazishaji, mapendeleo na huduma nyingine za Google katika Chrome imebadilishwa. Huenda hali hii ikaathiri mipangilio yako ya sasa.</translation>
 <translation id="5389212809648216794">Google Chrome imeshindwa kutumia kamera yako kwa sababu inatumiwa na programu nyingine</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Mipangilio<ph name="END_LINK" /> inaweza kubadilishwa wakati wowote. Google inaweza kutumia maudhui kwenye tovuti unazotembelea, matumizi ya kivinjari na shughuli ili kuweka mapendeleo kwenye huduma za Chrome and Google kama vile Tafsiri, Tafuta na Google na matangazo</translation>
 <translation id="5639704535586432836">Fungua Mipangilio &gt; Faragha &gt; Kamera &gt; Google Chrome na uwashe kamera.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ta.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ta.xtb
index ea7e335..e1d9f77f 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ta.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ta.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome இன் அம்சங்களையும் செயல்திறனையும் மேம்படுத்த உதவுக</translation>
 <translation id="4523886039239821078">சில செருகு நிரல்களினால் Chrome சிதைவுக்குட்படுகிறது. பின்வருபவற்றை நிறுவல் நீக்கம் செய்யவும்:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> நிர்வகிக்கும் கணக்கிலிருந்து வெளியேறுகிறீர்கள். இவ்வாறு செய்வதால், இந்தச் சாதனத்திலிருந்து Chrome தரவு நீக்கப்படும், எனினும் உங்கள் Google கணக்கில் தரவு தொடர்ந்து இருக்கும்.</translation>
+<translation id="5240817131241497236">Chromeமில் ஒத்திசைவு, தனிப்பயனாக்கம் மற்றும் பிற Google சேவைகளைக் கட்டுப்படுத்தும் அமைப்புகள் மாற்றப்பட்டுள்ளன. இது உங்கள் தற்போதைய அமைப்புகளைப் பாதிக்கலாம்.</translation>
 <translation id="5389212809648216794">கேமராவை மற்றொரு பயன்பாடு உபயோகிப்பதால், Google Chrome ஆல் அதைப் பயன்படுத்த முடியாது</translation>
 <translation id="5489543008378040943">எப்போது வேண்டுமானாலும் <ph name="BEGIN_LINK" />அமைப்புகளைத்<ph name="END_LINK" /> தனிப்பயனாக்கலாம். Chrome, மொழியாக்கம், தேடல், விளம்பரங்கள் போன்ற Google சேவைகளைத் தனிப்பயனாக்க, நீங்கள் பார்வையிடும் தளங்களில் உள்ள உள்ளடக்கம், உலாவி உரையாடல்கள், மேற்கொள்ளும் செயல்பாடு ஆகியவற்றை Google பயன்படுத்தக்கூடும்.</translation>
 <translation id="5639704535586432836">அமைப்புகள் &gt; தனியுரிமை &gt; கேமரா &gt; Google Chrome என்பதைத் திறந்து, கேமராவை இயக்கவும்.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_te.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_te.xtb
index 1f527aa..e29210e 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_te.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_te.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome ఫీచర్‌లు మరియు పనితీరును మెరుగుపరచడంలో సహాయపడండి</translation>
 <translation id="4523886039239821078">కొన్ని యాడ్-ఆన్‌లు Chrome క్రాష్ అయ్యేలా చేసాయి. దయచేసి వీటిని అన్‌ఇన్‌స్టాల్ చేయండి:</translation>
 <translation id="4615174829807303908">మీరు <ph name="SIGNOUT_MANAGED_DOMAIN" /> నిర్వహణలో ఉన్న ఖాతా నుండి సైన్ అవుట్ చేస్తున్నారు. దీని వలన మీ Chrome డేటా ఈ పరికరం నుండి తొలగించబడుతుంది, కానీ మీ డేటా మీ Google ఖాతాలో అలాగే ఉంటుంది.</translation>
+<translation id="5240817131241497236">Chromeలో సమకాలీకరణ, వ్యక్తిగతీకరణ మరియు ఇతర Google సేవలను నియంత్రించే సెట్టింగ్‌లు మార్చబడ్డాయి. ఇది మీ ప్రస్తుత సెట్టింగ్‌లను ప్రభావితం చేయవచ్చు.</translation>
 <translation id="5389212809648216794">మీ కెమెరాను మరొక అనువర్తనం ఉపయోగిస్తున్నందున Google Chrome దాన్ని ఉపయోగించలేదు</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />సెట్టింగ్‌లను<ph name="END_LINK" /> ఎప్పుడైనా అనుకూలీకరించవచ్చు. Google మీరు సందర్శించే సైట్‌ల్లోని కంటెంట్, బ్రౌజర్ పరస్పర చర్యలు మరియు కార్యకలాపం ఉపయోగించడం ద్వారా Chromeని మరియు అనువాదం, శోధన మరియు ప్రకటనలు వంటి Google సేవలను వ్యక్తిగతీకరించవచ్చు.</translation>
 <translation id="5639704535586432836">సెట్టింగ్‌లు &gt; గోప్యత &gt; కెమెరా &gt; Google Chromeని తెరిచి, కెమెరాను ఆన్ చేయండి.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_th.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_th.xtb
index ed76abb2..0797f211 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_th.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_th.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">ช่วยปรับปรุงฟีเจอร์และประสิทธิภาพของ Chrome</translation>
 <translation id="4523886039239821078">ส่วนเสริมบางรายการทำให้ Chrome ขัดข้อง โปรดถอนการติดตั้ง</translation>
 <translation id="4615174829807303908">คุณกำลังออกจากระบบบัญชีที่จัดการโดย <ph name="SIGNOUT_MANAGED_DOMAIN" /> การออกจากระบบจะลบข้อมูล Chrome ของคุณออกจากอุปกรณ์เครื่องนี้ แต่ข้อมูลจะยังคงอยู่ในบัญชี Google</translation>
+<translation id="5240817131241497236">การตั้งค่าที่ควบคุมการซิงค์ การปรับเปลี่ยนในแบบของคุณ และบริการอื่นๆ ของ Google ใน Chrome มีการเปลี่ยนแปลง ซึ่งอาจส่งผลต่อการตั้งค่าปัจจุบันของคุณ</translation>
 <translation id="5389212809648216794">Google Chrome ไม่สามารถใช้กล้องถ่ายรูปเนื่องจากแอปพลิเคชันอื่นใช้งานอยู่</translation>
 <translation id="5489543008378040943">คุณปรับแต่ง<ph name="BEGIN_LINK" />การตั้งค่า<ph name="END_LINK" />ได้ทุกเมื่อ Google อาจใช้เนื้อหาในเว็บไซต์ที่คุณเข้าชม รวมถึงการโต้ตอบและกิจกรรมในเบราว์เซอร์เพื่อปรับ Chrome และบริการของ Google อย่างเช่น แปลภาษา, Search และโฆษณาให้เหมาะกับคุณ</translation>
 <translation id="5639704535586432836">เปิดการตั้งค่า &gt; ความเป็นส่วนตัว &gt; กล้อง &gt; Google Chrome เพื่อเปิดกล้องถ่ายรูป</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_tr.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_tr.xtb
index 624f82f..535b66bc 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_tr.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_tr.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Chrome'un özelliklerini ve performansını iyileştirmeye yardımcı olun</translation>
 <translation id="4523886039239821078">Bazı eklentiler Chrome'un kilitlenmesine neden oluyor. Lütfen şunların yüklemelerini kaldırın:</translation>
 <translation id="4615174829807303908"><ph name="SIGNOUT_MANAGED_DOMAIN" /> tarafından yönetilen bir hesabın oturumunu kapatıyorsunuz. Bu işlemle Chrome verileriniz bu cihazdan silinir, ancak Google hesabınızda kalmaya devam eder.</translation>
+<translation id="5240817131241497236">Chrome'da senkronizasyon, kişiselleştirme ve diğer Google hizmetlerini kontrol eden ayarlar değişti. Bu durum geçerli ayarlarınızı etkileyebilir.</translation>
 <translation id="5389212809648216794">Kameranız başka bir uygulama tarafından kullanıldığından Google Chrome, kameranızı kullanamıyor.</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Ayarlar<ph name="END_LINK" />'ı istediğiniz zaman özelleştirebilirsiniz. Google; Chrome'u ve Çeviri, Arama, reklamlar gibi diğer Google hizmetlerini kişiselleştirmek için ziyaret ettiğiniz sitelerdeki içeriği, tarayıcı etkileşimlerini ve etkinlikleri kullanabilir.</translation>
 <translation id="5639704535586432836">Ayarlar &gt; Gizlilik &gt; Kamera &gt; Google Chrome'a gidin ve kamerayı açın.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_uk.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_uk.xtb
index 4812a92a..a104f55d 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_uk.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_uk.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Допоможіть покращити функції й ефективність Chrome</translation>
 <translation id="4523886039239821078">Деякі додатки спричиняють аварійне завершення роботи Chrome. Видаліть такі додатки:</translation>
 <translation id="4615174829807303908">Ви виходите з облікового запису, зареєстрованого в домені <ph name="SIGNOUT_MANAGED_DOMAIN" />. Дані Chrome буде видалено з цього пристрою, але вони залишаться у вашому обліковому записі Google.</translation>
+<translation id="5240817131241497236">Змінилися налаштування, які керують синхронізацією, персоналізацією й іншими службами Google у Chrome. Це може вплинути на поточні налаштування.</translation>
 <translation id="5389212809648216794">Зараз Google Chrome не може використовувати камеру, оскільки її використовує інший додаток</translation>
 <translation id="5489543008378040943"><ph name="BEGIN_LINK" />Налаштування<ph name="END_LINK" /> можна будь-коли змінювати. Google може використовувати вміст відвіданих сайтів, дані веб-перегляду й інші дії, щоб персоналізувати Chrome та інші сервіси Google, як-от Перекладач, Пошук і оголошення.</translation>
 <translation id="5639704535586432836">Відкрийте меню "Параметри" &gt; "Приватність" &gt; "Камера" &gt; "Google Chrome" і ввімкніть камеру.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_vi.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_vi.xtb
index 3571bd2..eab015c 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_vi.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_vi.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">Giúp cải thiện hiệu suất cũng như các tính năng của Chrome</translation>
 <translation id="4523886039239821078">Một số tiện ích bổ sung khiến Chrome trục trặc. Hãy gỡ cài đặt:</translation>
 <translation id="4615174829807303908">Bạn đang đăng xuất khỏi tài khoản do <ph name="SIGNOUT_MANAGED_DOMAIN" /> quản lý. Thao tác này sẽ xóa dữ liệu Chrome khỏi thiết bị này nhưng dữ liệu đó sẽ vẫn còn trong tài khoản Google của bạn.</translation>
+<translation id="5240817131241497236">Các tùy chọn cài đặt kiểm soát tính năng đồng bộ hóa, cá nhân hóa và các dịch vụ khác của Google trong Chrome đã thay đổi. Điều này có thể ảnh hưởng đến các tùy chọn cài đặt hiện tại của bạn.</translation>
 <translation id="5389212809648216794">Google Chrome không thể sử dụng máy ảnh của bạn do một ứng dụng khác đang dùng máy ảnh</translation>
 <translation id="5489543008378040943">Bạn có thể tùy chỉnh mục <ph name="BEGIN_LINK" />Cài đặt<ph name="END_LINK" /> bất cứ lúc nào. Google có thể sử dụng nội dung trên các trang web bạn truy cập, hoạt động và các tương tác trên trình duyệt để cá nhân hóa Chrome và các dịch vụ của Google, chẳng hạn như Dịch, Tìm kiếm và quảng cáo.</translation>
 <translation id="5639704535586432836">Mở Cài đặt &gt; Bảo mật &gt; Máy ảnh &gt; Google Chrome và bật máy ảnh.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-CN.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-CN.xtb
index af92a4c..fea0a2e8 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-CN.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-CN.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">帮助我们改进 Chrome 的功能和性能</translation>
 <translation id="4523886039239821078">部分插件导致 Chrome 崩溃,请卸载这些插件:</translation>
 <translation id="4615174829807303908">您正要退出由 <ph name="SIGNOUT_MANAGED_DOMAIN" /> 管理的帐号。退出后,您的 Chrome 数据将从这台设备上删除,但仍会保留在您的 Google 帐号中。</translation>
+<translation id="5240817131241497236">Chrome 中控制同步、个性化和其他 Google 服务的设置已发生更改。这可能会影响您的当前设置。</translation>
 <translation id="5389212809648216794">由于另一个应用正在使用您的相机,因此 Google Chrome 无法使用</translation>
 <translation id="5489543008378040943">您随时可以自定义<ph name="BEGIN_LINK" />设置<ph name="END_LINK" />。Google 可能会根据您访问的网站上的内容、在浏览器中的互动行为和活动,为您提供个性化的 Chrome 体验和其他 Google 服务体验(例如翻译、搜索和广告)。</translation>
 <translation id="5639704535586432836">打开“设置”&gt;“隐私”&gt;“相机”&gt;“Google Chrome”,然后开启相机。</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-TW.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-TW.xtb
index 376c9bc..85099467 100644
--- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-TW.xtb
+++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_zh-TW.xtb
@@ -32,6 +32,7 @@
 <translation id="447252321002412580">協助改善 Chrome 的功能與效能</translation>
 <translation id="4523886039239821078">部分外掛程式造成 Chrome 當機,請解除安裝這些外掛程式:</translation>
 <translation id="4615174829807303908">您即將登出由 <ph name="SIGNOUT_MANAGED_DOMAIN" /> 所管理的帳戶。系統會將您的 Chrome 資料從這個裝置上刪除,但繼續保留在您的 Google 帳戶中。</translation>
+<translation id="5240817131241497236">Chrome 的同步處理、個人化功能和其他 Google 服務控制設定已變更。這可能影響你目前的設定。</translation>
 <translation id="5389212809648216794">另一個應用程式正在使用你的相機,因此 Google Chrome 無法使用</translation>
 <translation id="5489543008378040943">你隨時可以調整<ph name="BEGIN_LINK" />設定<ph name="END_LINK" />。Google 可能會根據你所造訪網站的內容、瀏覽器互動行為和瀏覽活動,提供個人化的 Chrome 體驗和 Google 服務體驗,例如翻譯、搜尋和廣告。</translation>
 <translation id="5639704535586432836">請前往 [設定] &gt; [隱私權] &gt; [相機] &gt; [Google Chrome],並開啟相機。</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_nl.xtb b/ios/chrome/app/strings/resources/ios_strings_nl.xtb
index 92be600b..56ae3a5 100644
--- a/ios/chrome/app/strings/resources/ios_strings_nl.xtb
+++ b/ios/chrome/app/strings/resources/ios_strings_nl.xtb
@@ -39,7 +39,7 @@
 <translation id="152234381334907219">Nooit opgeslagen</translation>
 <translation id="1540800554400757039">Adres 1</translation>
 <translation id="1545749641540134597">QR-code scannen</translation>
-<translation id="1558704936695638228">Druk hierop en houd dit vast voor meer opties voor tabbladen</translation>
+<translation id="1558704936695638228">Tik hierop en houd vast voor meer opties voor tabbladen</translation>
 <translation id="1580783302095112590">E-mail verzonden.</translation>
 <translation id="1582732959743469162">Hiermee wordt de voortgang van de huidige download gestopt.</translation>
 <translation id="1605658421715042784">Afbeelding kopiëren</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_ta.xtb b/ios/chrome/app/strings/resources/ios_strings_ta.xtb
index 513c1315..ad12662 100644
--- a/ios/chrome/app/strings/resources/ios_strings_ta.xtb
+++ b/ios/chrome/app/strings/resources/ios_strings_ta.xtb
@@ -23,7 +23,7 @@
 <translation id="127138278192656016">ஒத்திசைவையும் அனைத்துச் சேவைகளையும் பயன்படுத்து</translation>
 <translation id="1272079795634619415">நிறுத்து</translation>
 <translation id="1323735185997015385">நீக்கு</translation>
-<translation id="132390688737681464">சேமி &amp; முகவரிகளைத் தானாக நிரப்பு</translation>
+<translation id="132390688737681464">முகவரிகளைச் சேமித்துத் தானாக நிரப்பு</translation>
 <translation id="132683371494960526">மூலக் கோப்புறையை மாற்ற, இருமுறை தட்டவும்.</translation>
 <translation id="1340643665687018190">மூடு மெனு</translation>
 <translation id="1375321115329958930">சேமிக்கப்பட்ட கடவுச்சொற்கள்</translation>
@@ -240,7 +240,7 @@
 <translation id="5062321486222145940">Google இயக்ககத்தை நிறுவு</translation>
 <translation id="5083464117946352670">கோப்பின் அளவைத் தீர்மானிக்க முடியவில்லை.</translation>
 <translation id="5094827893301452931">டுவீட் நிறைவுபெற்றது.</translation>
-<translation id="5123982333065001601">சேமி &amp; கிரெடிட் கார்டுத் தகவலைத் தானாக நிரப்பு</translation>
+<translation id="5123982333065001601">கிரெடிட் கார்டு தகவலைச் சேமித்துத் தானாக நிரப்பு</translation>
 <translation id="5173593619615111996">மறைநிலைத் தாவல்களை மூடு</translation>
 <translation id="5181140330217080051">பதிவிறக்குகிறது</translation>
 <translation id="5186185447130319458">தனிப்பட்டது</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_te.xtb b/ios/chrome/app/strings/resources/ios_strings_te.xtb
index 564b99b..5e37648 100644
--- a/ios/chrome/app/strings/resources/ios_strings_te.xtb
+++ b/ios/chrome/app/strings/resources/ios_strings_te.xtb
@@ -40,6 +40,7 @@
 <translation id="1545749641540134597">QR కోడ్‌ని స్కాన్ చేయండి</translation>
 <translation id="1580783302095112590">మెయిల్ పంపబడింది.</translation>
 <translation id="1582732959743469162">ఇది మీ ప్రస్తుత డౌన్‌లోడ్ యొక్క మొత్తం ప్రోగ్రెస్‌ని ఆపివేస్తుంది.</translation>
+<translation id="1605658421715042784">చిత్రాన్ని కాపీ చేయి</translation>
 <translation id="1612730193129642006">ట్యాబ్ గ్రిడ్‌ను చూపించండి</translation>
 <translation id="1644574205037202324">చరిత్ర</translation>
 <translation id="1646446875146297738">‘ట్రాక్ చేయవద్దు’ ప్రారంభించడం వలన మీ బ్రౌజింగ్ ట్రాఫిక్‌తో ఒక అభ్యర్థన చేర్చబడుతుంది. ప్రభావం ఏదైనా అభ్యర్థనకు వెబ్‌సైట్ ప్రతిస్పందించిందా లేదా మరియు అభ్యర్థన ఎలా వ్యాఖ్యానించబడింది అనేవాటిపై ఆధారపడి ఉంటుంది.
@@ -57,6 +58,7 @@
 <translation id="1809939268435598390">ఫోల్డర్‌ను తొలగించు</translation>
 <translation id="1813414402673211292">బ్రౌజింగ్‌డేటాను క్లియర్ చెయ్యి</translation>
 <translation id="1820259098641718022">పఠన జాబితాకు జోడించబడింది</translation>
+<translation id="1821253160463689938">మీ ప్రాధాన్యతలను గుర్తుంచుకోవడానికి కుక్కీలను ఉపయోగిస్తుంది, మీరు ఆ పేజీలను సందర్శించకపోయినా కూడా అది అమలవుతుంది</translation>
 <translation id="1876721852596493031">వినియోగ డేటాను పంపు</translation>
 <translation id="1886928167269928266">ప్రారంభ సమయం</translation>
 <translation id="1911619930368729126">Google డిస్క్‌కి అప్‌. చే.</translation>
@@ -70,6 +72,7 @@
 <translation id="209018056901015185">డెస్క్‌టాప్ సైట్‌ను అభ్యర్థించు</translation>
 <translation id="2103075008456228677">history.google.com తెరువు</translation>
 <translation id="2116625576999540962"><ph name="NUMBER_OF_SELECTED_BOOKMARKS" /> అంశాలు తరలించబడ్డాయి</translation>
+<translation id="2120297377148151361">కార్యకలాపం మరియు పరస్పర చర్యలు</translation>
 <translation id="213900355088104901">ప్రైవేట్‌గా బ్రౌజ్ చేయాలంటే, అజ్ఞాత ట్యాబ్‌ని తెరవండి</translation>
 <translation id="2149973817440762519">బుక్‌మార్క్‌ను సవరించు</translation>
 <translation id="2207590065820824892">చదవాల్సిన జాబితాలో <ph name="UNREAD_COUNT" /> కథనాలు ఉన్నాయి.</translation>
@@ -86,6 +89,7 @@
 <translation id="2386793615875593361">1 ఎంచుకోబడింది</translation>
 <translation id="2435457462613246316">పాస్‌వర్డ్‌ను చూపించు</translation>
 <translation id="2481538920734869610">ఖాతాను జోడించు</translation>
+<translation id="2523184218357549926">మీరు సందర్శించే పేజీల URLను Googleకి పంపుతుంది</translation>
 <translation id="2523363575747517183">ఈ వెబ్‌సైట్ మరో అప్లికేషన్‌ను తెరవడానికి పలుసార్లు ప్రయత్నిస్తోంది.</translation>
 <translation id="2529021024822217800">అన్నీ తెరువు</translation>
 <translation id="2572712655377361602">ఒక పరికర విధానం మీ ఫోటోలకు ప్రాప్యతను బ్లాక్ చేసింది</translation>
@@ -115,6 +119,7 @@
 <translation id="2898963176829412617">కొత్త ఫోల్డర్…</translation>
 <translation id="2903493209154104877">చిరునామాలు</translation>
 <translation id="2909437209446960244">ఇటీవలి ట్యాబ్‌లు</translation>
+<translation id="2916171785467530738">స్వయంపూర్తి శోధనలు మరియు URLలు</translation>
 <translation id="291754862089661335">QR కోడ్‌ను లేదా బార్‌కోడ్‌ను ఈ ఫ్రేమ్‌లో ఉంచండి</translation>
 <translation id="2921219216347069551">పేజీ భాగస్వామ్యం సాధ్యపడలేదు</translation>
 <translation id="2923448633003185837">పేస్ట్ చేసి, ముందుకు వెళ్ళండి</translation>
@@ -220,10 +225,12 @@
 <translation id="4860895144060829044">కాల్ చేయండి</translation>
 <translation id="4875622588773761625">మీరు ఈ సైట్ కోసం <ph name="PASSWORD_MANAGER_BRAND" /> మీ పాస్‌వర్డ్‌ను నవీకరించాలని కోరుకుంటున్నారా?</translation>
 <translation id="4881695831933465202">తెరువు</translation>
+<translation id="4882831918239250449">శోధన, ప్రకటనలు మరియు మరిన్నింటిని వ్యక్తిగతీకరించడానికి మీ బ్రౌజింగ్ చరిత్ర ఎలా ఉపయోగించబడుతుందో నియంత్రించండి</translation>
 <translation id="4904877109095351937">చదివినట్లు గుర్తు పెట్టు</translation>
 <translation id="4930268273022498155">ఇప్పటికే ఉన్న డేటాను తొలగించండి. మీరు <ph name="USER_EMAIL1" />కి తిరిగి వెళ్లడం ద్వారా దాన్ని తిరిగి పొందవచ్చు.</translation>
 <translation id="4941089862236492464">క్షమించండి, మీ అంశాన్ని భాగస్వామ్యం చేయడంలో సమస్య ఉంది.</translation>
 <translation id="4979397965658815378">మీ బుక్‌మార్క్‌లు, పాస్‌వర్డ్‌లు, చరిత్ర మరియు ఇతర సెట్టింగ్‌లను మీ అన్ని పరికరాల్లో పొందడానికి మీ Google ఖాతాతో సైన్ ఇన్ చేయండి</translation>
+<translation id="5004416275253351869">Google కార్యకలాపం నియంత్రణలు</translation>
 <translation id="5005498671520578047">పాస్‌వర్డ్ కాపీచేయడం</translation>
 <translation id="5010803260590204777">వెబ్‌ను ప్రైవేట్‌గా బ్రౌజ్ చేయడానికి అజ్ఞాత ట్యాబ్‌ను తెరవండి.</translation>
 <translation id="5011684439661633295">హాయ్, <ph name="FULL_ACCOUNT_NAME" /></translation>
@@ -250,6 +257,7 @@
 <translation id="5548760955356983418">ఈ పరికరంలో వెబ్‌సైట్‌ను బ్రౌజ్ చేయడాన్ని ప్రారంభించి, ఆ తర్వాత దాన్ని మీ Macలో సులభంగా కొనసాగించడానికి హ్యాండ్‌ఆఫ్ మిమ్మల్ని అనుమతిస్తుంది. ప్రస్తుతం తెరిచిన వెబ్‌సైట్ మీ Mac డాక్‌లో కనిపిస్తుంది.
 
 హ్యాండ్‌ఆఫ్‌ను తప్పనిసరిగా సాధారణ సెట్టింగ్‌ల విభాగంలో కూడా ప్రారంభించాలి మరియు మీ పరికరాలు తప్పనిసరిగా ఒకే iCloud ఖాతాను ఉపయోగించాలి.</translation>
+<translation id="5554368826343982379">సమకాలీకరణ మరియు వ్యక్తిగతీకరణ</translation>
 <translation id="5556459405103347317">రీలోడ్</translation>
 <translation id="5619279135193775234">మెయిల్</translation>
 <translation id="5626245204502895507">ఫైల్‌ను ఈ సమయంలో డౌన్‌‌లోడ్ చేయడం సాధ్యపడలేదు.</translation>
@@ -341,6 +349,7 @@
 <translation id="6979158407327259162">Google డిస్క్</translation>
 <translation id="7004499039102548441">ఇటీవలి ట్యాబ్‌లు</translation>
 <translation id="7006788746334555276">కంటెంట్ సెట్టింగ్‌లు</translation>
+<translation id="7017968314960951695">వ్యక్తిగతీకరణ కోసం మీరు సందర్శించే సైట్‌లలోని కంటెంట్ మరియు బ్రౌజర్ కార్యకలాపం మరియు పరస్పర చర్యలను ఉపయోగిస్తుంది.</translation>
 <translation id="7029809446516969842">పాస్‌వర్డ్‌లు</translation>
 <translation id="7031882061095297553">వీటికి సమకాలీకరించండి</translation>
 <translation id="7053983685419859001">నిరోధించు</translation>
@@ -383,6 +392,7 @@
 <translation id="7769602470925380267">అంగీకరిస్తున్నాను, సైన్ అవుట్ చేయి</translation>
 <translation id="7772032839648071052">పాస్‌ఫ్రేజ్‌ని నిర్ధారించండి</translation>
 <translation id="780301667611848630">వద్దు , ధన్యవాదాలు</translation>
+<translation id="7830720446622801252">వ్యక్తిగతీకరించబడని సేవలు</translation>
 <translation id="7856733331829174190">డౌన్‌లోడ్ చేయడం సాధ్యపడలేదు</translation>
 <translation id="7859704718976024901">బ్రౌజింగ్ చరిత్ర</translation>
 <translation id="7918293828610777738">మీ చదవాల్సిన జాబితా ఆఫ్‌లైన్‌లో అందుబాటులో ఉంది. మీ చదవాల్సిన జాబితాకు పేజీని జోడించడానికి, <ph name="SHARE_OPENING_ICON" />ని నొక్కి తర్వాత <ph name="READ_LATER_TEXT" /> ఎంపికను నొక్కండి.</translation>
@@ -402,6 +412,7 @@
 <translation id="8076014560081431679">సేవ్ చేసిన సైట్ సెట్టింగ్‌లు తొలగించబడవు మరియు మీ బ్రౌజింగ్ అలవాట్లను ప్రదర్శించవచ్చు. <ph name="BEGIN_LINK" />మరింత తెలుసుకోండి<ph name="END_LINK" /></translation>
 <translation id="8080028325999236607">అన్ని ట్యాబ్‌లను మూసివేయి</translation>
 <translation id="8114753159095730575">ఫైల్ డౌన్‌లోడ్ అందుబాటులో ఉంది. ఎంపికలు స్క్రీన్ దిగువ భాగంలో అందుబాటులో ఉంటాయి.</translation>
+<translation id="8160722851663543621">చిరునామా పట్టీ మరియు శోధన పెట్టెలోని శోధనలను మరియు కొన్ని కుక్కీలను మీ డిఫాల్ట్ శోధన ఇంజిన్‌కు పంపుతుంది</translation>
 <translation id="8178325540017397816">మీరు ఎంచుకున్న అంశాలు తీసివేయబడతాయి</translation>
 <translation id="8205564605687841303">రద్దు చేయి</translation>
 <translation id="8225985093977202398">కాష్ చిత్రాలు, ఫైల్‌లు</translation>
@@ -413,6 +424,7 @@
 <translation id="842017693807136194">దీనితో సైన్ ఇన్ చేయబడింది</translation>
 <translation id="8428045167754449968">నగరం / పట్టణం</translation>
 <translation id="8428213095426709021">సెట్టింగ్‌లు</translation>
+<translation id="8438566539970814960">శోధనలు మరియు బ్రౌజింగ్‌ను మెరుగుపరచండి</translation>
 <translation id="8458397775385147834">1 అంశం తొలగించబడింది</translation>
 <translation id="8487700953926739672">ఆఫ్‌లైన్‌లో అందుబాటు</translation>
 <translation id="8503813439785031346">యూజర్‌పేరు</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_vi.xtb b/ios/chrome/app/strings/resources/ios_strings_vi.xtb
index 9af3048..046c817f 100644
--- a/ios/chrome/app/strings/resources/ios_strings_vi.xtb
+++ b/ios/chrome/app/strings/resources/ios_strings_vi.xtb
@@ -39,7 +39,7 @@
 <translation id="152234381334907219">Không bao giờ lưu</translation>
 <translation id="1540800554400757039">Địa chỉ 1</translation>
 <translation id="1545749641540134597">Quét mã QR</translation>
-<translation id="1558704936695638228">Nhấn và giữ để có thêm các tùy chọn tab</translation>
+<translation id="1558704936695638228">Nhấn và giữ để có thêm các tùy chọn về tab</translation>
 <translation id="1580783302095112590">Thư đã được gửi.</translation>
 <translation id="1582732959743469162">Thao tác này sẽ ngừng tất cả hoạt động tải xuống đang diễn ra.</translation>
 <translation id="1605658421715042784">Sao chép hình ảnh</translation>
@@ -240,7 +240,7 @@
 <translation id="5062321486222145940">Cài đặt Google Drive</translation>
 <translation id="5083464117946352670">Không thể xác định kích thước tệp.</translation>
 <translation id="5094827893301452931">Đã đăng lên Twitter.</translation>
-<translation id="5123982333065001601">Lưu và điền thẻ tín dụng</translation>
+<translation id="5123982333065001601">Lưu và điền thông tin thẻ tín dụng</translation>
 <translation id="5173593619615111996">Đóng tất cả các tab ẩn danh</translation>
 <translation id="5181140330217080051">Đang tải xuống</translation>
 <translation id="5186185447130319458">Riêng tư</translation>
diff --git a/ios/chrome/browser/BUILD.gn b/ios/chrome/browser/BUILD.gn
index e71ef21..a906447c 100644
--- a/ios/chrome/browser/BUILD.gn
+++ b/ios/chrome/browser/BUILD.gn
@@ -116,6 +116,7 @@
     "//components/webdata_services",
     "//google_apis",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/app_launcher:feature_flags",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/browsing_data:feature_flags",
     "//ios/chrome/browser/download",
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index 3aa1219..cafbec8b 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -41,6 +41,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/unified_consent/feature.h"
+#include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #include "ios/chrome/browser/browsing_data/browsing_data_features.h"
 #include "ios/chrome/browser/chrome_switches.h"
 #include "ios/chrome/browser/drag_and_drop/drag_and_drop_flag.h"
@@ -296,6 +297,10 @@
     {"unified-consent", flag_descriptions::kUnifiedConsentName,
      flag_descriptions::kUnifiedConsentDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(unified_consent::kUnifiedConsent)},
+    {"force-unified-consent-bump",
+     flag_descriptions::kForceUnifiedConsentBumpName,
+     flag_descriptions::kForceUnifiedConsentBumpDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(unified_consent::kForceUnifiedConsentBump)},
     {"autofill-dynamic-forms", flag_descriptions::kAutofillDynamicFormsName,
      flag_descriptions::kAutofillDynamicFormsDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(autofill::features::kAutofillDynamicForms)},
@@ -371,6 +376,9 @@
      flag_descriptions::kNewPasswordFormParsingName,
      flag_descriptions::kNewPasswordFormParsingDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(password_manager::features::kNewPasswordFormParsing)},
+    {"app-launcher-refresh", flag_descriptions::kAppLauncherRefreshName,
+     flag_descriptions::kAppLauncherRefreshDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(kAppLauncherRefresh)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/app_launcher/BUILD.gn b/ios/chrome/browser/app_launcher/BUILD.gn
index 8d7ae5c5..96c7b5fe 100644
--- a/ios/chrome/browser/app_launcher/BUILD.gn
+++ b/ios/chrome/browser/app_launcher/BUILD.gn
@@ -16,6 +16,7 @@
     "app_launching_state.mm",
   ]
   deps = [
+    ":feature_flags",
     "//base",
     "//components/reading_list/core:core",
     "//ios/chrome/browser",
@@ -26,6 +27,17 @@
   ]
 }
 
+source_set("feature_flags") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "app_launcher_flags.h",
+    "app_launcher_flags.mm",
+  ]
+  deps = [
+    "//base",
+  ]
+}
+
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
@@ -36,9 +48,15 @@
   ]
   deps = [
     ":app_launcher",
+    ":feature_flags",
     "//base",
     "//base/test:test_support",
+    "//components/reading_list/core:core",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/reading_list",
+    "//ios/chrome/browser/tabs",
+    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web:web_internal",
     "//ios/web/public/test/fakes",
     "//testing/gtest",
diff --git a/ios/chrome/browser/app_launcher/app_launcher_flags.h b/ios/chrome/browser/app_launcher/app_launcher_flags.h
new file mode 100644
index 0000000..388b65ba
--- /dev/null
+++ b/ios/chrome/browser/app_launcher/app_launcher_flags.h
@@ -0,0 +1,13 @@
+// Copyright 2018 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_APP_LAUNCHER_APP_LAUNCHER_FLAGS_H_
+#define IOS_CHROME_BROWSER_APP_LAUNCHER_APP_LAUNCHER_FLAGS_H_
+
+#include "base/feature_list.h"
+
+// Feature flag to enable the new app launcher logic.
+extern const base::Feature kAppLauncherRefresh;
+
+#endif  // IOS_CHROME_BROWSER_APP_LAUNCHER_APP_LAUNCHER_FLAGS_H_
diff --git a/ios/chrome/browser/app_launcher/app_launcher_flags.mm b/ios/chrome/browser/app_launcher/app_launcher_flags.mm
new file mode 100644
index 0000000..3521d9f
--- /dev/null
+++ b/ios/chrome/browser/app_launcher/app_launcher_flags.mm
@@ -0,0 +1,12 @@
+// Copyright 2018 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 "ios/chrome/browser/app_launcher/app_launcher_flags.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+const base::Feature kAppLauncherRefresh{"AppLauncherRefresh",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.h b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.h
index cf666f2..0a0a531 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.h
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.h
@@ -28,6 +28,10 @@
                                 AppLauncherAbuseDetector* abuse_detector,
                                 id<AppLauncherTabHelperDelegate> delegate);
 
+  // Returns true, if the |url| has a scheme for an external application
+  // (eg. twitter:// , calshow://).
+  static bool IsAppUrl(const GURL& url);
+
   // Requests to open the application with |url|.
   // The method checks if the application for |url| has been opened repeatedly
   // by the |source_page_url| page in a short time frame, in that case a prompt
@@ -39,7 +43,7 @@
   // method returns NO.
   bool RequestToLaunchApp(const GURL& url,
                           const GURL& source_page_url,
-                          bool link_tapped);
+                          bool link_transition);
 
   // web::WebStatePolicyDecider implementation
   bool ShouldAllowRequest(
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
index 8f3695ba..61aaca5 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
@@ -11,6 +11,7 @@
 #import "base/strings/sys_string_conversions.h"
 #include "components/reading_list/core/reading_list_model.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_abuse_detector.h"
+#include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h"
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
@@ -95,9 +96,16 @@
 
 AppLauncherTabHelper::~AppLauncherTabHelper() = default;
 
+// static
+bool AppLauncherTabHelper::IsAppUrl(const GURL& url) {
+  return !(web::UrlHasWebScheme(url) ||
+           web::GetWebClient()->IsAppSpecificURL(url) ||
+           url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme));
+}
+
 bool AppLauncherTabHelper::RequestToLaunchApp(const GURL& url,
                                               const GURL& source_page_url,
-                                              bool link_tapped) {
+                                              bool link_transition) {
   // Don't open external application if chrome is not active.
   if ([[UIApplication sharedApplication] applicationState] !=
       UIApplicationStateActive) {
@@ -120,7 +128,7 @@
     case ExternalAppLaunchPolicyAllow: {
       return [delegate_ appLauncherTabHelper:this
                             launchAppWithURL:url
-                                  linkTapped:link_tapped];
+                              linkTransition:link_transition];
     }
     case ExternalAppLaunchPolicyPrompt: {
       is_prompt_active_ = true;
@@ -138,7 +146,7 @@
               // is no need to check for |link_tapped|.
               [delegate_ appLauncherTabHelper:weak_this.get()
                              launchAppWithURL:copied_url
-                                   linkTapped:YES];
+                               linkTransition:YES];
             } else {
               // TODO(crbug.com/674649): Once non modal dialogs are implemented,
               // update this to always prompt instead of blocking the app.
@@ -156,10 +164,7 @@
     NSURLRequest* request,
     const web::WebStatePolicyDecider::RequestInfo& request_info) {
   GURL request_url = net::GURLWithNSURL(request.URL);
-  if (web::UrlHasWebScheme(request_url) ||
-      web::GetWebClient()->IsAppSpecificURL(request_url) ||
-      request_url.SchemeIs(url::kFileScheme) ||
-      request_url.SchemeIs(url::kAboutScheme)) {
+  if (!IsAppUrl(request_url)) {
     // This URL can be handled by the WebState and doesn't require App launcher
     // handling.
     return true;
@@ -185,7 +190,7 @@
 
   Tab* tab = LegacyTabHelper::GetTabForWebState(web_state_);
 
-  // If this is a Universal 2nd Factory (U2F) call, the origin needs to be
+  // If this is a Universal 2nd Factor (U2F) call, the origin needs to be
   // checked to make sure it's secure and then update the |request_url| with
   // the generated x-callback GURL based on x-callback-url specs.
   if (request_url.SchemeIs("u2f")) {
@@ -199,6 +204,23 @@
   const GURL& source_url = request_info.source_url;
   bool is_link_transition = ui::PageTransitionTypeIncludingQualifiersIs(
       request_info.transition_type, ui::PAGE_TRANSITION_LINK);
+
+  if (base::FeatureList::IsEnabled(kAppLauncherRefresh)) {
+    if (!is_link_transition && source_url.is_valid()) {
+      // At this stage the navigation will be canceled in all cases. If this
+      // was a redirection, the |source_url| may not have been reported to
+      // ReadingListWebStateObserver. Report it to mark as read if needed.
+      ReadingListModel* model =
+          ReadingListModelFactory::GetForBrowserState(tab.browserState);
+      if (model && model->loaded())
+        model->SetReadStatus(source_url, true);
+    }
+    if (IsValidAppUrl(request_url)) {
+      RequestToLaunchApp(request_url, source_url, is_link_transition);
+    }
+    return false;
+  }
+
   if (IsValidAppUrl(request_url) &&
       RequestToLaunchApp(request_url, source_url, is_link_transition)) {
     // Clears pending navigation history after successfully launching the
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h
index 8461f12..b2f3989 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h
@@ -14,12 +14,12 @@
 @protocol AppLauncherTabHelperDelegate
 
 // Launches application that has |URL| if possible (optionally after confirming
-// via dialog in case the user didn't interact using |linkTapped| or if the
-// application is facetime). Returns NO if there is no such application
-// available.
+// via dialog). Returns NO if there is no such application available.
+// TODO(crbug.com/850760): Change this method return to void, once the new
+// AppLauncherRefresh logic is always enabled.
 - (BOOL)appLauncherTabHelper:(AppLauncherTabHelper*)tabHelper
             launchAppWithURL:(const GURL&)URL
-                  linkTapped:(BOOL)linkTapped;
+              linkTransition:(BOOL)linkTransition;
 
 // Alerts the user that there have been repeated attempts to launch
 // the application. |completionHandler| is called with the user's
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
index 3e8474b..b300c56 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
@@ -7,9 +7,19 @@
 #include <memory>
 
 #include "base/compiler_specific.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/time/default_clock.h"
+#include "components/reading_list/core/reading_list_entry.h"
+#include "components/reading_list/core/reading_list_model_impl.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_abuse_detector.h"
+#include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h"
+#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/chrome_url_util.h"
+#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
+#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
+#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/public/test/fakes/test_navigation_manager.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -40,13 +50,15 @@
 @synthesize countOfAppsLaunched = _countOfAppsLaunched;
 @synthesize countOfAlertsShown = _countOfAlertsShown;
 @synthesize simulateUserAcceptingPrompt = _simulateUserAcceptingPrompt;
+
 - (BOOL)appLauncherTabHelper:(AppLauncherTabHelper*)tabHelper
             launchAppWithURL:(const GURL&)URL
-                  linkTapped:(BOOL)linkTapped {
+              linkTransition:(BOOL)linkTransition {
   self.countOfAppsLaunched++;
   self.lastLaunchedAppURL = URL;
   return YES;
 }
+
 - (void)appLauncherTabHelper:(AppLauncherTabHelper*)tabHelper
     showAlertOfRepeatedLaunchesWithCompletionHandler:
         (ProceduralBlockWithBool)completionHandler {
@@ -81,6 +93,16 @@
   DISALLOW_COPY_AND_ASSIGN(FakeNavigationManager);
 };
 
+std::unique_ptr<KeyedService> BuildReadingListModel(
+    web::BrowserState* context) {
+  ios::ChromeBrowserState* browser_state =
+      ios::ChromeBrowserState::FromBrowserState(context);
+  std::unique_ptr<ReadingListModelImpl> reading_list_model(
+      new ReadingListModelImpl(nullptr, browser_state->GetPrefs(),
+                               base::DefaultClock::GetInstance()));
+  return reading_list_model;
+}
+
 // Test fixture for AppLauncherTabHelper class.
 class AppLauncherTabHelperTest : public PlatformTest {
  protected:
@@ -107,9 +129,58 @@
                                            request_info);
   }
 
+  // Initialize reading list model and its required tab helpers.
+  void InitializeReadingListModel() {
+    TestChromeBrowserState::Builder test_cbs_builder;
+    chrome_browser_state_ = test_cbs_builder.Build();
+    web_state_.SetBrowserState(chrome_browser_state_.get());
+    ReadingListModelFactory::GetInstance()->SetTestingFactoryAndUse(
+        chrome_browser_state_.get(), &BuildReadingListModel);
+    TabIdTabHelper::CreateForWebState(&web_state_);
+    LegacyTabHelper::CreateForWebState(&web_state_);
+    is_reading_list_initialized_ = true;
+  }
+
+  // Returns true if the |expected_read_status| matches the read status for any
+  // non empty source URL based on the transition type and the app policy.
+  bool TestReadingListUpdate(bool is_app_blocked,
+                             bool is_link_transition,
+                             bool expected_read_status) {
+    // Make sure reading list model is initialized.
+    if (!is_reading_list_initialized_)
+      InitializeReadingListModel();
+
+    GURL test_source_url("http://google.com");
+    ReadingListModel* model = ReadingListModelFactory::GetForBrowserState(
+        chrome_browser_state_.get());
+    EXPECT_TRUE(model->DeleteAllEntries());
+    model->AddEntry(test_source_url, "unread",
+                    reading_list::ADDED_VIA_CURRENT_APP);
+    abuse_detector_.policy = is_app_blocked ? ExternalAppLaunchPolicyBlock
+                                            : ExternalAppLaunchPolicyAllow;
+    ui::PageTransition transition_type =
+        is_link_transition
+            ? ui::PageTransition::PAGE_TRANSITION_LINK
+            : ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT;
+
+    NSURL* url = [NSURL
+        URLWithString:@"itms-apps://itunes.apple.com/us/app/appname/id123"];
+    web::WebStatePolicyDecider::RequestInfo request_info(
+        transition_type, test_source_url,
+        /*target_frame_is_main=*/true, /*has_user_gesture=*/true);
+    EXPECT_FALSE(tab_helper_->ShouldAllowRequest(
+        [NSURLRequest requestWithURL:url], request_info));
+
+    const ReadingListEntry* entry = model->GetEntryByURL(test_source_url);
+    return entry->IsRead() == expected_read_status;
+  }
+
+  base::test::ScopedTaskEnvironment scoped_task_environment;
   web::TestWebState web_state_;
+  std::unique_ptr<TestChromeBrowserState> chrome_browser_state_ = nil;
   FakeAppLauncherAbuseDetector* abuse_detector_ = nil;
   FakeAppLauncherTabHelperDelegate* delegate_ = nil;
+  bool is_reading_list_initialized_ = false;
   AppLauncherTabHelper* tab_helper_;
 };
 
@@ -201,7 +272,7 @@
 
 // Tests that invalid Urls are completely blocked.
 TEST_F(AppLauncherTabHelperTest, InvalidUrls) {
-  EXPECT_FALSE(TestShouldAllowRequest(@"",
+  EXPECT_FALSE(TestShouldAllowRequest(/*url_string=*/@"",
                                       /*target_frame_is_main=*/true,
                                       /*has_user_gesture=*/false));
   EXPECT_FALSE(TestShouldAllowRequest(@"invalid",
@@ -240,7 +311,62 @@
   EXPECT_FALSE(TestShouldAllowRequest(url,
                                       /*target_frame_is_main=*/true,
                                       /*has_user_gesture=*/false));
-
   EXPECT_EQ(1U, delegate_.countOfAppsLaunched);
 }
+
+// Tests that ShouldAllowRequest updates the reading list correctly, when there
+// is a valid app URL to be launches successfully.
+// TODO(crbug.com/850760): Remove this test, once the new AppLauncherRefresh
+// logic is always enabled.
+TEST_F(AppLauncherTabHelperTest, UpdatingTheReadingList) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kAppLauncherRefresh);
+  // Reading list isn't expected to be updated if there was no app launch.
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/true,
+                                    /*is_link_transition*/ false,
+                                    /*expected_read_status*/ false));
+  EXPECT_EQ(0U, delegate_.countOfAppsLaunched);
+
+  // Reading list to be updated when app launch is successful.
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/false,
+                                    /*is_link_transition*/ false,
+                                    /*expected_read_status*/ true));
+  EXPECT_EQ(1U, delegate_.countOfAppsLaunched);
+
+  // Transition type doesn't affect the reading list status
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/false,
+                                    /*is_link_transition*/ true,
+                                    /*expected_read_status*/ true));
+  EXPECT_EQ(2U, delegate_.countOfAppsLaunched);
+}
+
+// Tests that ShouldAllowRequest updates the reading list correctly for non-link
+// transitions regardless of the app launching success when AppLauncherRefresh
+// flag is enabled.
+TEST_F(AppLauncherTabHelperTest, UpdatingTheReadingListWithAppLauncherRefresh) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kAppLauncherRefresh);
+  // Update reading list if the transition is not a link transition.
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/true,
+                                    /*is_link_transition*/ false,
+                                    /*expected_read_status*/ true));
+  EXPECT_EQ(0U, delegate_.countOfAppsLaunched);
+
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/false,
+                                    /*is_link_transition*/ false,
+                                    /*expected_read_status*/ true));
+  EXPECT_EQ(1U, delegate_.countOfAppsLaunched);
+
+  // Don't update reading list if the transition is a link transition.
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/true,
+                                    /*is_link_transition*/ true,
+                                    /*expected_read_status*/ false));
+  EXPECT_EQ(1U, delegate_.countOfAppsLaunched);
+
+  EXPECT_TRUE(TestReadingListUpdate(/*is_app_blocked=*/false,
+                                    /*is_link_transition*/ true,
+                                    /*expected_read_status*/ false));
+  EXPECT_EQ(2U, delegate_.countOfAppsLaunched);
+}
+
 }  // namespace
diff --git a/ios/chrome/browser/autofill/manual_fill/BUILD.gn b/ios/chrome/browser/autofill/manual_fill/BUILD.gn
index 12f6da7f..33264bd81 100644
--- a/ios/chrome/browser/autofill/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/autofill/manual_fill/BUILD.gn
@@ -6,8 +6,6 @@
 
 source_set("manual_fill") {
   sources = [
-    "accessory_provider.h",
-    "accessory_provider.mm",
     "passwords_fetcher.h",
     "passwords_fetcher.mm",
   ]
@@ -19,7 +17,6 @@
     "//ios/chrome/browser/autofill:autofill_shared",
     "//ios/chrome/browser/browser_state:browser_state",
     "//ios/chrome/browser/passwords:passwords",
-    "//ios/chrome/browser/ui/autofill/manual_fill",
   ]
   libs = [ "UIKit.framework" ]
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/ios/chrome/browser/autofill/manual_fill/accessory_provider.h b/ios/chrome/browser/autofill/manual_fill/accessory_provider.h
deleted file mode 100644
index ab9106b..0000000
--- a/ios/chrome/browser/autofill/manual_fill/accessory_provider.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_ACCESSORY_PROVIDER_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_ACCESSORY_PROVIDER_H_
-
-#import "ios/chrome/browser/autofill/form_input_accessory_view_provider.h"
-
-// Returns a default keyboard accessory view with entry points to manual
-// fallbacks for form filling.
-@interface ManualFillAccessoryProvider
-    : NSObject<FormInputAccessoryViewProvider>
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_ACCESSORY_PROVIDER_H_
diff --git a/ios/chrome/browser/autofill/manual_fill/accessory_provider.mm b/ios/chrome/browser/autofill/manual_fill/accessory_provider.mm
deleted file mode 100644
index 02cce4e3..0000000
--- a/ios/chrome/browser/autofill/manual_fill/accessory_provider.mm
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/accessory_provider.h"
-
-#include "base/feature_list.h"
-#import "base/mac/foundation_util.h"
-#include "components/autofill/core/common/autofill_features.h"
-#import "ios/chrome/browser/ui/autofill/manual_fill/keyboard_accessory_view.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface ManualFillAccessoryProvider ()
-
-// The default accesory view to return in the update block.
-@property(nonatomic, readonly) ManualFillKeyboardAccessoryView* accessoryView;
-
-// Callback to update the accessory view.
-@property(nonatomic, copy)
-    AccessoryViewReadyCompletion accessoryViewUpdateBlock;
-
-@end
-
-@implementation ManualFillAccessoryProvider
-
-@synthesize accessoryViewDelegate = _accessoryViewDelegate;
-@synthesize accessoryView = _accessoryView;
-@synthesize accessoryViewUpdateBlock = _accessoryViewUpdateBlock;
-
-#pragma mark - FormInputAccessoryViewProvider
-
-- (void)retrieveAccessoryViewForForm:(const web::FormActivityParams&)params
-                            webState:(web::WebState*)webState
-            accessoryViewUpdateBlock:
-                (AccessoryViewReadyCompletion)accessoryViewUpdateBlock {
-  DCHECK(accessoryViewUpdateBlock);
-  BOOL isManualFillEnabled =
-      base::FeatureList::IsEnabled(autofill::features::kAutofillManualFallback);
-  if (!isManualFillEnabled) {
-    accessoryViewUpdateBlock(nil, self);
-    return;
-  }
-  accessoryViewUpdateBlock(self.accessoryView, self);
-  self.accessoryViewUpdateBlock = accessoryViewUpdateBlock;
-}
-
-- (void)inputAccessoryViewControllerDidReset:
-    (FormInputAccessoryViewController*)controller {
-  self.accessoryViewUpdateBlock = nil;
-}
-
-- (void)resizeAccessoryView {
-  DCHECK(_accessoryViewUpdateBlock);
-  self.accessoryViewUpdateBlock(self.accessoryView, self);
-}
-
-#pragma mark - Getters
-
-- (ManualFillKeyboardAccessoryView*)accessoryView {
-  if (!_accessoryView) {
-    _accessoryView =
-        [[ManualFillKeyboardAccessoryView alloc] initWithDelegate:nil];
-  }
-  return _accessoryView;
-}
-
-@end
diff --git a/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.h b/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.h
deleted file mode 100644
index f01ad35..0000000
--- a/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_INPUT_ASSISTANT_MANUAL_FILL_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_INPUT_ASSISTANT_MANUAL_FILL_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h"
-
-// This class allows the user to manual fill data by adding input assistant
-// items  to the first responder. Which then uses pop overs to show the
-// available options. Currently the `inputAssistantItem` property is only
-// available on iPads.
-@interface InputAssistantManualFillViewController : ManualFillViewController
-@end
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_INPUT_ASSISTANT_MANUAL_FILL_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.mm b/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.mm
deleted file mode 100644
index 55cdbf0..0000000
--- a/ios/chrome/browser/autofill/manual_fill/input_assistant_manual_fill_view_controller.mm
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/input_assistant_manual_fill_view_controller.h"
-
-#import <WebKit/WebKit.h>
-
-#import "ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface InputAssistantManualfillViewController ()
-
-/// The view controller presented or nil if none.
-@property(nonatomic, weak) UIViewController* presentedPickerViewController;
-
-@end
-
-@implementation InputAssistantManualfillViewController
-
-@synthesize presentedPickerViewController = _presentedPickerViewController;
-
-#pragma mark - Life Cycle
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-
-  NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter addObserver:self
-                    selector:@selector(handleKeyboardWillChangeFrame:)
-                        name:UIKeyboardWillChangeFrameNotification
-                      object:nil];
-}
-
-#pragma mark - Keyboard Notifications
-
-- (void)handleKeyboardWillChangeFrame:(NSNotification*)notification {
-  // TODO:(javierrobles) state why this needs to be in this notification.
-  [self overrideAccessories];
-}
-
-#pragma mark - ManualFillContentDelegate
-
-- (void)userDidPickContent:(NSString*)content {
-  UIViewController* presentingViewController =
-      self.presentedPickerViewController.presentingViewController;
-  [presentingViewController dismissViewControllerAnimated:YES completion:nil];
-  [self fillLastSelectedFieldWithString:content];
-
-// This code jump by to the next field by invoking a method on an Apple's owned
-// bar button.
-// TODO:(javierrobles) use the action instead of NSSelectorFromString.
-// TODO:(javierrobles) add safety.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
-  [[self.lastFirstResponder inputAssistantItem]
-          .trailingBarButtonGroups[1]
-          .barButtonItems.firstObject.target
-      performSelector:NSSelectorFromString(@"_nextTapped:")
-           withObject:nil];
-#pragma clang diagnostic pop
-}
-
-#pragma mark Private Helpers
-
-// Sets the accessory items of the keyboard to the custom options we want to
-// show to the user. In this case an icon for passwords, an icon for addresses
-// and a last one for credit cards.
-- (void)overrideAccessories {
-  UIImage* keyImage = [UIImage imageNamed:@"ic_vpn_key"];
-  UIBarButtonItem* itemOne =
-      [[UIBarButtonItem alloc] initWithImage:keyImage
-                                       style:UIBarButtonItemStylePlain
-                                      target:self
-                                      action:@selector(presentCredentials:)];
-
-  UIImage* accountImage = [UIImage imageNamed:@"ic_account_circle"];
-  UIBarButtonItem* itemTwo =
-      [[UIBarButtonItem alloc] initWithImage:accountImage
-                                       style:UIBarButtonItemStylePlain
-                                      target:self
-                                      action:@selector(presentAddresses:)];
-
-  UIImage* creditCardImage = [UIImage imageNamed:@"ic_credit_card"];
-  UIBarButtonItem* itemThree =
-      [[UIBarButtonItem alloc] initWithImage:creditCardImage
-                                       style:UIBarButtonItemStylePlain
-                                      target:self
-                                      action:@selector(presentCards:)];
-
-  UIBarButtonItem* itemChoose =
-      [[UIBarButtonItem alloc] initWithTitle:@"Manual Fill"
-                                       style:UIBarButtonItemStylePlain
-                                      target:nil
-                                      action:nil];
-
-  UIBarButtonItemGroup* group = [[UIBarButtonItemGroup alloc]
-      initWithBarButtonItems:@[ itemOne, itemTwo, itemThree ]
-          representativeItem:itemChoose];
-
-  UITextInputAssistantItem* item =
-      [manual_fill::GetFirstResponderSubview(self.view) inputAssistantItem];
-  item.leadingBarButtonGroups = @[ group ];
-}
-
-// Presents the options for the manual fill as a popover.
-//
-// @param sender The item requesting the pop over, used for positioning.
-- (void)presentPopOverForSender:(UIBarButtonItem*)sender {
-  [self updateActiveFieldID];
-  self.lastFirstResponder = manual_fill::GetFirstResponderSubview(self.view);
-
-  // TODO:(javierrobles) Test this on iOS 10.
-  // TODO:(javierrobles) Support / dismiss on rotation.
-  UIViewController* presenter;
-  UIView* buttonView;
-  id view = [sender valueForKey:@"view"];
-  if ([view isKindOfClass:[UIView class]]) {
-    buttonView = view;
-    presenter = buttonView.window.rootViewController;
-    while (presenter.childViewControllers.count) {
-      presenter = presenter.childViewControllers.firstObject;
-    }
-  }
-  if (!presenter) {
-    // TODO:(javierrobles) log an error.
-    return;
-  }
-
-  // TODO:(javierrobles) support addresses and credit cards.
-  PasswordPickerViewController* passwordPickerViewController =
-      [[PasswordPickerViewController alloc] initWithDelegate:self];
-  passwordPickerViewController.modalPresentationStyle =
-      UIModalPresentationPopover;
-
-  [presenter presentViewController:passwordPickerViewController
-                          animated:YES
-                        completion:nil];
-
-  UIPopoverPresentationController* presentationController =
-      passwordPickerViewController.popoverPresentationController;
-  presentationController.permittedArrowDirections =
-      UIPopoverArrowDirectionDown | UIPopoverArrowDirectionUp;
-  presentationController.sourceView = buttonView;
-  presentationController.sourceRect = buttonView.bounds;
-  self.presentedPickerViewController = passwordPickerViewController;
-}
-
-// Called when the user presses the "key" button. Causing a list of credentials
-// to be shown.
-//
-// @param sender The bar button item pressed.
-- (void)presentCredentials:(UIBarButtonItem*)sender {
-  [self presentPopOverForSender:sender];
-}
-
-// Called when the user presses the "account" button. Causing a list of
-// addresses to be shown.
-//
-// @param sender The bar button item pressed.
-- (void)presentAddresses:(UIBarButtonItem*)sender {
-  [self presentPopOverForSender:sender];
-}
-
-// Called when the user presses the "card" button. Causing a list of credit
-// cards to be shown.
-//
-// @param sender The bar button item pressed.
-- (void)presentCards:(UIBarButtonItem*)sender {
-  [self presentPopOverForSender:sender];
-}
-
-@end
diff --git a/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.h b/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.h
deleted file mode 100644
index ac95ea57..0000000
--- a/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_KEYBOARD_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_KEYBOARD_VIEW_CONTROLLER_H_
-
-#import "ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h"
-
-#import "ios/chrome/browser/ui/autofill/manual_fill/keyboard_accessory_view.h"
-
-// Subclass of `ManualFillViewController` with the code that is specific for
-// devices with no undocked keyboard.
-@interface ManualFillKeyboardViewController
-    : ManualFillViewController<KeyboardAccessoryViewDelegate>
-@end
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_KEYBOARD_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.mm b/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.mm
deleted file mode 100644
index a643156..0000000
--- a/ios/chrome/browser/autofill/manual_fill/keyboard_view_controller.mm
+++ /dev/null
@@ -1,282 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/keyboard_view_controller.h"
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.h"
-#import "ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.h"
-#import "ios/chrome/common/ui_util/constraints_ui_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-// Finds the first responder in the shared app and returns its input accessory
-// view.
-//
-// @return The `inputAccessoryView` of the first responder or nil if it was not
-//         found.
-UIView* GetKeyboardAccessoryView() {
-  for (UIWindow* window in [[UIApplication sharedApplication] windows]) {
-    UIView* firstResponder = manual_fill::GetFirstResponderSubview(window);
-    if (firstResponder) {
-      return firstResponder.inputAccessoryView;
-    }
-  }
-  return nil;
-}
-
-}  // namespace
-
-@interface ManualFillKeyboardViewController ()
-
-// A strong reference to `inputAccessoryView` used in this class to jump
-// between the web view fields.
-@property(nonatomic) UIView* lastAccessoryInputView;
-
-// This state boolean is used to determine if the keyboard input accessory view
-// needs to be hidden when the keyboard animates out.
-@property(nonatomic) BOOL shouldShowManualFillView;
-
-// The manual fill view, this is what is shown behind the keyboard.
-@property(nonatomic) KeyboardBackgroundView* manualFillView;
-
-// TODO:(javierrobles) explore always hidding the host view instead of this.
-// The keyboard accessory view, this is added above the WKWebView's.
-@property(nonatomic) KeyboardAccessoryView* keyboardAccessoryView;
-
-// This is a reference to the superview's superview of the input accessory view
-// used to hide it when the keyboard dismisses to prevent input bar from
-// appearing two times on screen.
-@property(nonatomic, weak) UIView* accessoryHostView;
-
-// TODO:(javierrobles) Update this constraint on rotation.
-// This constraint controls the height of the manual fill view, it follows the
-// size of the keyboard.
-@property(nonatomic) NSLayoutConstraint* manualFillViewHeightConstraint;
-
-// This controls the vertical position of the manual fill view. It is used to
-// anchor it to the bottom, and to hide it.
-@property(nonatomic) NSLayoutConstraint* manualFillViewBottomAnchorConstraint;
-
-@end
-
-@implementation ManualFillKeyboardViewController
-@synthesize lastAccessoryInputView = _lastAccessoryInputView;
-@synthesize shouldShowManualFillView = _shouldShowManualFillView;
-@synthesize manualFillView = _manualFillView;
-@synthesize keyboardAccessoryView = _keyboardAccessoryView;
-@synthesize accessoryHostView = _accessoryHostView;
-@synthesize manualFillViewHeightConstraint = _manualFillViewHeightConstraint;
-@synthesize manualFillViewBottomAnchorConstraint =
-    _manualFillViewBottomAnchorConstraint;
-
-#pragma mark - Life Cycle
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-
-  self.manualFillView = [[KeyboardBackgroundView alloc] initWithDelegate:self];
-  self.manualFillView.translatesAutoresizingMaskIntoConstraints = NO;
-  [self.view addSubview:self.manualFillView];
-
-  // Use an arbitrary height on load.
-  self.manualFillViewHeightConstraint =
-      [self.manualFillView.heightAnchor constraintEqualToConstant:200.0];
-  self.manualFillViewBottomAnchorConstraint = [self.manualFillView.bottomAnchor
-      constraintEqualToAnchor:self.view.bottomAnchor
-                     constant:self.manualFillViewHeightConstraint.constant];
-
-  [NSLayoutConstraint activateConstraints:@[
-    [self.manualFillView.leadingAnchor
-        constraintEqualToAnchor:self.view.leadingAnchor
-                       constant:0.0],
-    [self.manualFillView.trailingAnchor
-        constraintEqualToAnchor:self.view.trailingAnchor],
-    self.manualFillViewHeightConstraint,
-    self.manualFillViewBottomAnchorConstraint
-  ]];
-
-  NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
-  [defaultCenter addObserver:self
-                    selector:@selector(handleKeyboardWillShowNotification:)
-                        name:UIKeyboardWillShowNotification
-                      object:nil];
-  [defaultCenter addObserver:self
-                    selector:@selector(handleKeyboardWillHideNotification:)
-                        name:UIKeyboardWillHideNotification
-                      object:nil];
-  [defaultCenter addObserver:self
-                    selector:@selector(handleKeyboardDidShowNotification:)
-                        name:UIKeyboardDidShowNotification
-                      object:nil];
-}
-
-- (void)dealloc {
-  // Revert hidding the accessory host on dealloc.
-  self.accessoryHostView.hidden = NO;
-}
-
-#pragma mark - Keyboard Notifications
-
-- (void)handleKeyboardWillShowNotification:(NSNotification*)notification {
-  self.shouldShowManualFillView = NO;
-  if (!self.keyboardAccessoryView) {
-    UIView* inputAccessoryView = GetKeyboardAccessoryView();
-    if (inputAccessoryView) {
-      self.keyboardAccessoryView =
-          [[KeyboardAccessoryView alloc] initWithDelegate:self];
-      self.keyboardAccessoryView.translatesAutoresizingMaskIntoConstraints = NO;
-      [inputAccessoryView addSubview:self.keyboardAccessoryView];
-      AddSameConstraints(self.keyboardAccessoryView, inputAccessoryView);
-    }
-  }
-}
-
-- (void)handleKeyboardWillHideNotification:(NSNotification*)notification {
-  if (self.shouldShowManualFillView &&
-      // Only hidding the superview's superview will hide the gray background.
-      // So we look for it. This may change with newer iOS versions.
-      self.keyboardAccessoryView.superview.superview) {
-    // This can be done more paranoic by checking and preserving the hidden
-    // value.
-    self.accessoryHostView = self.keyboardAccessoryView.superview.superview;
-    self.accessoryHostView.hidden = YES;
-  }
-}
-
-- (void)handleKeyboardDidShowNotification:(NSNotification*)notification {
-  // Update the first responder and it's accessory view.
-  self.lastFirstResponder = manual_fill::GetFirstResponderSubview(self.view);
-  // This is needed to keep a strong reference to the input accessory view.
-  self.lastAccessoryInputView = self.lastFirstResponder.inputAccessoryView;
-
-  // Update the constraints for the manual fill view.
-  NSDictionary* userInfo = notification.userInfo;
-  CGRect keyboardFrameEnd =
-      [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
-
-  self.manualFillViewBottomAnchorConstraint.constant = 0.0;
-  self.manualFillViewHeightConstraint.constant = keyboardFrameEnd.size.height;
-}
-
-#pragma mark - KeyboardAccessoryViewDelegate
-
-- (void)accountButtonPressed {
-  // TODO:(javierrobles) support an account picker.
-  PasswordPickerViewController* passwordPickerViewController =
-      [[PasswordPickerViewController alloc] initWithDelegate:self];
-  [self prepareContainerWithViewController:passwordPickerViewController];
-}
-
-- (void)cardsButtonPressed {
-  // TODO:(javierrobles) support a credit card picker.
-  PasswordPickerViewController* passwordPickerViewController =
-      [[PasswordPickerViewController alloc] initWithDelegate:self];
-  [self prepareContainerWithViewController:passwordPickerViewController];
-}
-
-- (void)passwordButtonPressed {
-  PasswordPickerViewController* passwordPickerViewController =
-      [[PasswordPickerViewController alloc] initWithDelegate:self];
-  [self prepareContainerWithViewController:passwordPickerViewController];
-}
-
-- (void)cancelButtonPressed {
-  [self dismissComplementKeyboardAnimated:YES];
-  [self.webView endEditing:YES];
-}
-
-- (void)arrowUpPressed {
-  [self jumpPrevious];
-}
-
-- (void)arrowDownPressed {
-  [self jumpNext];
-}
-
-#pragma mark - ManualFillViewDelegate
-
-- (void)userDidPickContent:(NSString*)content {
-  [self fillLastSelectedFieldWithString:content];
-  [self jumpNext];
-}
-
-#pragma mark - Private Helpers
-
-// Adds the view controller and its view to the respective hierarchies. The view
-// is added in the container of the manual fill view.
-//
-// @param viewController The view controller to add.
-- (void)prepareContainerWithViewController:(UIViewController*)viewController {
-  [self updateActiveFieldID];
-  self.shouldShowManualFillView = YES;
-
-  // Remove any previous containment.
-  NSArray* childViewControllers = [self.childViewControllers copy];
-  for (UIViewController* childViewController in childViewControllers) {
-    [childViewController willMoveToParentViewController:nil];
-    [childViewController.view removeFromSuperview];
-    [childViewController removeFromParentViewController];
-  }
-
-  [self addChildViewController:viewController];
-  viewController.view.translatesAutoresizingMaskIntoConstraints = NO;
-  [self.manualFillView.containerView addSubview:viewController.view];
-  AddSameConstraints(self.manualFillView.containerView, viewController.view);
-  [viewController didMoveToParentViewController:self];
-
-  [self.webView endEditing:YES];
-}
-
-// Dismisses the manual fill keyboard, can be animated.
-//
-// @param animated If dismissal should be animated.
-- (void)dismissComplementKeyboardAnimated:(BOOL)animated {
-  self.manualFillViewBottomAnchorConstraint.constant =
-      self.manualFillViewHeightConstraint.constant;
-  [self.view setNeedsLayout];
-  // TODO:(javierrobles) This values should mimic the ones at keyboard dismiss.
-  [UIView animateWithDuration:animated ? 0.25 : 0.0
-      delay:0.0
-      options:(7 << 16)
-      animations:^{
-        [self.view layoutIfNeeded];
-      }
-      completion:^(BOOL finished) {
-        self.accessoryHostView.hidden = NO;
-      }];
-}
-
-// Move the focus to the next field in the web view.
-- (void)jumpNext {
-// TODO:(javierrobles) use action and target instead of NSSelectorFromString
-// (as Chromium currently does).
-// TODO:(javierrobles) add safety.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
-  [self.lastAccessoryInputView
-      performSelector:NSSelectorFromString(@"_nextTapped:")
-           withObject:nil];
-#pragma clang diagnostic pop
-}
-
-// Move the focus to the previous field in the web view.
-- (void)jumpPrevious {
-// TODO:(javierrobles) use action and target instead of NSSelectorFromString
-// (as Chromium currently does).
-// TODO:(javierrobles) add safety.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
-  [self.lastAccessoryInputView
-      performSelector:NSSelectorFromString(@"_previousTapped:")
-           withObject:nil];
-#pragma clang diagnostic pop
-}
-
-@end
diff --git a/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h b/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h
deleted file mode 100644
index 3ff88f5..0000000
--- a/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_MANUAL_FILL_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_MANUAL_FILL_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-#import <WebKit/WebKit.h>
-
-namespace manual_fill {
-
-// Searches for the first responder in the passed view hierarchy.
-//
-// @param view The view where the search is going to be done.
-// @return The first responder or `nil` if it wasn't found.
-UIView* GetFirstResponderSubview(UIView* view);
-
-}  // namespace manual_fill
-
-// Protocol to pass any user choice in a picker to be filled.
-@protocol ManualFillContentDelegate<NSObject>
-
-// Called after the user manually selects an element to be used as the input.
-//
-// @param content The string that is interesting to the user in the current
-//                context.
-- (void)userDidPickContent:(NSString*)content;
-
-@end
-
-// View Controller with the common logic for managing the manual fill views. As
-// well as sending user input to the web view. Meant to be subclassed.
-@interface ManualFillViewController
-    : UIViewController<ManualFillContentDelegate>
-
-// The web view to test the prototype.
-@property(nonatomic, readonly) WKWebView* webView;
-
-// The last known first responder.
-@property(nonatomic) UIView* lastFirstResponder;
-
-// Asynchronously updates the activeFieldID to the current active element.
-// Must be called before the web view resigns first responder.
-- (void)updateActiveFieldID;
-
-// Tries to inject the passed string into the web view last focused field.
-//
-// @param string The content to be injected. Must be JS encoded.
-- (void)fillLastSelectedFieldWithString:(NSString*)string;
-
-// Calls JS `focus()` on the last active element in an attemp to highlight it.
-- (void)callFocusOnLastActiveField;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_MANUAL_FILL_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.mm b/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.mm
deleted file mode 100644
index 34e60560..0000000
--- a/ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.mm
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/manual_fill_view_controller.h"
-
-#import <WebKit/WebKit.h>
-
-#import "ios/chrome/common/ui_util/constraints_ui_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace manual_fill {
-
-UIView* GetFirstResponderSubview(UIView* view) {
-  if ([view isFirstResponder])
-    return view;
-
-  for (UIView* subview in [view subviews]) {
-    UIView* firstResponder = GetFirstResponderSubview(subview);
-    if (firstResponder)
-      return firstResponder;
-  }
-
-  return nil;
-}
-
-}  // namespace manual_fill
-
-@interface ManualfillViewController ()
-
-// The last recorded active field identifier, used to interact with the web
-// view (i.e. overwrite the input of the field).
-@property(nonatomic, strong) NSString* activeFieldID;
-
-@end
-
-@implementation ManualfillViewController
-
-@synthesize activeFieldID = _activeFieldID;
-@synthesize lastFirstResponder = _lastFirstResponder;
-@synthesize webView = _webView;
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-
-  _webView = [[WKWebView alloc] initWithFrame:self.view.bounds
-                                configuration:[self webViewConfiguration]];
-  [self.view addSubview:self.webView];
-  self.webView.translatesAutoresizingMaskIntoConstraints = NO;
-  AddSameConstraints(self.webView, self.view);
-}
-
-- (void)viewWillAppear:(BOOL)animated {
-  [super viewWillAppear:animated];
-
-  NSURL* signupURL = [NSURL URLWithString:@"https://appleid.apple.com/account"];
-  NSURLRequest* request = [NSURLRequest requestWithURL:signupURL];
-  [self.webView loadRequest:request];
-}
-
-#pragma mark - ManualFillContentDelegate
-
-- (void)userDidPickContent:(NSString*)content {
-  // No-op. Subclasess can override.
-}
-
-#pragma mark - Document Interaction
-
-- (void)updateActiveFieldID {
-  __weak __typeof(self) weakSelf = self;
-  NSString* javaScriptQuery = @"__gCrWeb.manualFill.activeElementId()";
-  [self.webView evaluateJavaScript:javaScriptQuery
-                 completionHandler:^(id result, NSError* error) {
-                   NSLog(@"result: %@", [result description]);
-                   weakSelf.activeFieldID = result;
-                   [weakSelf callFocusOnLastActiveField];
-                 }];
-}
-
-- (void)fillLastSelectedFieldWithString:(NSString*)string {
-  NSString* javaScriptQuery =
-      [NSString stringWithFormat:
-                    @"__gCrWeb.manualFill.setValueForElementId(\"%@\", \"%@\")",
-                    string, self.activeFieldID];
-  [self.webView evaluateJavaScript:javaScriptQuery completionHandler:nil];
-}
-
-- (void)callFocusOnLastActiveField {
-  NSString* javaScriptQuery =
-      [NSString stringWithFormat:@"document.getElementById(\"%@\").focus()",
-                                 self.activeFieldID];
-  [self.webView evaluateJavaScript:javaScriptQuery completionHandler:nil];
-}
-
-#pragma mark JS Injection
-
-// Returns an NSString with the contents of the files passed. It is assumed the
-// files are of type ".js" and they are in the main bundle.
-- (NSString*)joinedJSFilesWithFilenames:(NSArray<NSString*>*)filenames {
-  NSMutableString* fullScript = [NSMutableString string];
-  for (NSString* filename in filenames) {
-    NSString* path =
-        [[NSBundle mainBundle] pathForResource:filename ofType:@"js"];
-    NSData* scriptData = [[NSData alloc] initWithContentsOfFile:path];
-    NSString* scriptString =
-        [[NSString alloc] initWithData:scriptData
-                              encoding:NSUTF8StringEncoding];
-    [fullScript appendFormat:@"%@\n", scriptString];
-  }
-  return fullScript;
-}
-
-- (NSString*)earlyJSStringMainFrame {
-  NSArray* filenames = @[
-    @"main_frame_web_bundle", @"chrome_bundle_main_frame",
-    @"manual_fill_controller"
-  ];
-  return [self joinedJSFilesWithFilenames:filenames];
-}
-
-- (NSString*)earlyJSStringAllFrames {
-  NSArray* filenames = @[
-    @"all_frames_web_bundle",
-    @"chrome_bundle_all_frames",
-  ];
-  return [self joinedJSFilesWithFilenames:filenames];
-}
-
-// Returns a WKWebViewConfiguration with early scripts for the main frame and
-// for all frames.
-- (WKWebViewConfiguration*)webViewConfiguration {
-  WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
-  WKUserScript* userScriptAllFrames = [[WKUserScript alloc]
-        initWithSource:[self earlyJSStringAllFrames]
-         injectionTime:WKUserScriptInjectionTimeAtDocumentStart
-      forMainFrameOnly:NO];
-  [configuration.userContentController addUserScript:userScriptAllFrames];
-
-  WKUserScript* userScriptMainFrame = [[WKUserScript alloc]
-        initWithSource:[self earlyJSStringMainFrame]
-         injectionTime:WKUserScriptInjectionTimeAtDocumentStart
-      forMainFrameOnly:YES];
-  [configuration.userContentController addUserScript:userScriptMainFrame];
-
-  return configuration;
-}
-
-@end
diff --git a/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.h b/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.h
deleted file mode 100644
index 1fb4f9a2..0000000
--- a/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_PASSWORD_PICKER_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_PASSWORD_PICKER_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-
-@protocol ManualFillContentDelegate;
-
-// This class presents a list of usernames and passwords in a collection view.
-@interface PasswordPickerViewController
-    : UICollectionViewController<UICollectionViewDelegateFlowLayout>
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)init NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout
-    NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithNibName:(NSString*)nibNameOrNil
-                         bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
-
-// Instances an object with the desired delegate.
-//
-// @param delegate The object interested in the user selection from the
-//                 presented options.
-// @return A fresh object with the passed delegate.
-- (instancetype)initWithDelegate:(id<ManualFillContentDelegate>)delegate
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_AUTOFILL_MANUAL_FILL_PASSWORD_PICKER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.mm b/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.mm
deleted file mode 100644
index 9ba83e51..0000000
--- a/ios/chrome/browser/autofill/manual_fill/password_picker_view_controller.mm
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/password_picker_view_controller.h"
-
-#import "ios/chrome/browser/autofill/manual_fill/manual_fill_view_controller.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-// The width for the cells in this collection view.
-static CGFloat CellWitdth = 200.0;
-
-// The height for the cells in this collection view.
-static CGFloat CellHeight = 60.0;
-
-// The reuse identifier for UICollectionViewCells in this collection.
-static NSString* CellReuseId = @"CellReuseId";
-
-}  // namespace
-
-@interface PasswordPickerViewController ()
-
-// The content to display in the collection view.
-@property(nonatomic) NSArray<NSString*>* content;
-
-// The delegate in charge of using the content selected by the user.
-@property(nonatomic, readonly, weak) id<ManualFillContentDelegate> delegate;
-
-@end
-
-@implementation PasswordPickerViewController
-
-@synthesize content = _content;
-@synthesize delegate = _delegate;
-
-- (instancetype)initWithDelegate:(id<ManualFillContentDelegate>)delegate {
-  _delegate = delegate;
-  UICollectionViewFlowLayout* flowLayout =
-      [[UICollectionViewFlowLayout alloc] init];
-  flowLayout.minimumInteritemSpacing = 0;
-  flowLayout.minimumLineSpacing = 1;
-  flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
-  flowLayout.estimatedItemSize = CGSizeMake(CellWitdth, CellHeight);
-  return [super initWithCollectionViewLayout:flowLayout];
-}
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-
-  // TODO:(javierrobles) abstract this color. It was taken arbitrarly.
-  self.collectionView.backgroundColor = [UIColor colorWithRed:250.0 / 255.0
-                                                        green:250.0 / 255.0
-                                                         blue:250.0 / 255.0
-                                                        alpha:1.0];
-
-  [self.collectionView registerClass:[UICollectionViewCell class]
-          forCellWithReuseIdentifier:CellReuseId];
-
-  if (@available(iOS 11.0, *)) {
-    self.collectionView.contentInsetAdjustmentBehavior =
-        UIScrollViewContentInsetAdjustmentAlways;
-  }
-
-  self.content = @[ @"Username", @"********" ];
-}
-
-- (CGSize)preferredContentSize {
-  return CGSizeMake(250, 144);
-}
-
-#pragma mark - UICollectionViewDataSource
-
-- (NSInteger)numberOfSectionsInCollectionView:
-    (UICollectionView*)collectionView {
-  return 1;
-}
-
-- (NSInteger)collectionView:(UICollectionView*)collectionView
-     numberOfItemsInSection:(NSInteger)section {
-  return [self.content count];
-}
-
-- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
-                 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
-  // TODO:(javierrobles) create a custom cell supporting a label.
-  UICollectionViewCell* cell =
-      [collectionView dequeueReusableCellWithReuseIdentifier:CellReuseId
-                                                forIndexPath:indexPath];
-
-  UILabel* titleLabel = [[UILabel alloc]
-      initWithFrame:CGRectMake(0.0, 0.0, CellWitdth, CellHeight)];
-  titleLabel.text = _content[indexPath.item];
-  [cell.contentView addSubview:titleLabel];
-  return cell;
-}
-
-#pragma mark - UICollectionViewDelegateFlowLayout
-
-- (UIEdgeInsets)collectionView:(UICollectionView*)collectionView
-                        layout:(UICollectionViewLayout*)collectionViewLayout
-        insetForSectionAtIndex:(NSInteger)section {
-  CGFloat horizontalInset =
-      (collectionView.bounds.size.width - CellWitdth) / 2.0;
-  return UIEdgeInsetsMake(0.0, horizontalInset, 0.0, horizontalInset);
-}
-
-- (void)collectionView:(UICollectionView*)collectionView
-    didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
-  UICollectionViewCell* cell =
-      [collectionView cellForItemAtIndexPath:indexPath];
-  UIView* firstSubview = cell.contentView.subviews.firstObject;
-  if ([firstSubview isKindOfClass:[UILabel class]]) {
-    [self.delegate userDidPickContent:((UILabel*)firstSubview).text];
-  }
-}
-
-@end
diff --git a/ios/chrome/browser/autofill/manual_fill/resources/manual_fill_controller.js b/ios/chrome/browser/autofill/manual_fill/resources/manual_fill_controller.js
deleted file mode 100644
index bbf35f8..0000000
--- a/ios/chrome/browser/autofill/manual_fill/resources/manual_fill_controller.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 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.
-
-goog.provide('__crWeb.manualFill');
-
-/* Beginning of anonymous object. */
-(function() {
-
-/**
- * Namespace for this file. It depends on |__gCrWeb| having already been
- * injected.
- */
-__gCrWeb.manualFill = {};
-__gCrWeb['manualFill'] = __gCrWeb.manualFill;
-
-/**
- * Returns the identifier to be used with `setValueForElementId`.
- *
- * @return {string} The id of the active element.
- */
-__gCrWeb.manualFill.activeElementId = function() {
-  var activeElementId = document.activeElement.id;
-  return activeElementId;
-};
-
-/**
- * Sets the value passed for the element with the identifier passed.
- *
- * @param {string} value The input to set in the element with the identifier.
- * @param {string} identifier The identifier of the element, found with
- *                 `activeElementId`.
- */
-__gCrWeb.manualFill.setValueForElementId = function(value, identifier) {
-  if (!identifier) {
-    return
-  }
-  var field = document.getElementById(identifier);
-  if (!field) {
-    return;
-  }
-  if (!value || value.length === 0) {
-    return;
-  }
-  __gCrWeb.fill.setInputElementValue(value, field);
-};
-
-}());  // End of anonymous object
diff --git a/ios/chrome/browser/browser_state/BUILD.gn b/ios/chrome/browser/browser_state/BUILD.gn
index 2cb9cac..7d2f9d9 100644
--- a/ios/chrome/browser/browser_state/BUILD.gn
+++ b/ios/chrome/browser/browser_state/BUILD.gn
@@ -116,6 +116,7 @@
     "//ios/chrome/browser/ui/overlays",
     "//ios/chrome/browser/ui/voice",
     "//ios/chrome/browser/undo",
+    "//ios/chrome/browser/unified_consent:unified_consent",
     "//ios/net",
     "//ios/public/provider/chrome/browser",
     "//ios/public/provider/chrome/browser/signin",
diff --git a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
index 5b94d1d..31f0cd6 100644
--- a/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
+++ b/ios/chrome/browser/browser_state/chrome_browser_state_manager_impl.cc
@@ -41,6 +41,7 @@
 #include "ios/chrome/browser/signin/gaia_cookie_manager_service_factory.h"
 #include "ios/chrome/browser/signin/signin_manager_factory.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
+#include "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
 
 namespace {
 
@@ -223,6 +224,9 @@
   ios::AccountFetcherServiceFactory::GetForBrowserState(browser_state)
       ->SetupInvalidationsOnProfileLoad(invalidation_service);
   ios::AccountReconcilorFactory::GetForBrowserState(browser_state);
+  // Initialization needs to happen after the browser context is available
+  // because ProfileSyncService needs the URL context getter.
+  UnifiedConsentServiceFactory::GetForBrowserState(browser_state);
   DesktopPromotionSyncServiceFactory::GetForBrowserState(browser_state);
 }
 
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index f25b3bd..61a4503a 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -43,6 +43,12 @@
 const char kWalletServiceUseSandboxDescription[] =
     "Uses the sandbox service for Google Payments API calls.";
 
+const char kAppLauncherRefreshName[] = "Enable the new AppLauncher logic";
+const char kAppLauncherRefreshDescription[] =
+    "AppLauncher will always prompt if there is no direct link navigation, "
+    "also Apps will launch asynchronously and there will be no logic that"
+    "depends on the success or the failure of launching an app.";
+
 const char kAutofillDynamicFormsName[] = "Autofill dynamic forms";
 const char kAutofillDynamicFormsDescription[] =
     "Refills forms that dynamically change after an initial fill";
@@ -252,6 +258,11 @@
     "features. This includes new confirmation screens and improved settings "
     "pages.";
 
+const char kForceUnifiedConsentBumpName[] = "Force Unified Consent Bump";
+const char kForceUnifiedConsentBumpDescription[] =
+    "Force the unified consent bump UI to be shown on every start-up. This "
+    "flag is for debug purpose, to test the UI.";
+
 const char kUseDdljsonApiName[] = "Use new ddljson API for Doodles";
 const char kUseDdljsonApiDescription[] =
     "Enables the new ddljson API to fetch Doodles for the NTP.";
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index a9d4713..9b5ca8b5 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -36,6 +36,10 @@
 extern const char kWalletServiceUseSandboxName[];
 extern const char kWalletServiceUseSandboxDescription[];
 
+// Title and description for the flag to control the new app launcher.
+extern const char kAppLauncherRefreshName[];
+extern const char kAppLauncherRefreshDescription[];
+
 // Title and description for the flag to control the dynamic autofill.
 extern const char kAutofillDynamicFormsName[];
 extern const char kAutofillDynamicFormsDescription[];
@@ -212,6 +216,10 @@
 extern const char kUnifiedConsentName[];
 extern const char kUnifiedConsentDescription[];
 
+// Title and description for the flag to force the consent bump.
+extern const char kForceUnifiedConsentBumpName[];
+extern const char kForceUnifiedConsentBumpDescription[];
+
 // Title and description for the flag to enable the ddljson Doodle API.
 extern const char kUseDdljsonApiName[];
 extern const char kUseDdljsonApiDescription[];
diff --git a/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h b/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h
index 2ee9ac2..3d090c00 100644
--- a/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h
+++ b/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h
@@ -39,6 +39,10 @@
  public:
   ~ITunesUrlsHandlerTabHelper() override;
   explicit ITunesUrlsHandlerTabHelper(web::WebState* web_state);
+
+  // Returns true, if ITunesUrlsHandlerTabHelper can handle the given |url|.
+  static bool CanHandleUrl(const GURL& url);
+
   // web::WebStatePolicyDecider implementation
   bool ShouldAllowRequest(
       NSURLRequest* request,
diff --git a/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.mm b/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.mm
index 2ca5faf..1b145d9 100644
--- a/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.mm
+++ b/ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.mm
@@ -77,8 +77,15 @@
   return params_dictionary;
 }
 
-// Returns true, if ITunesUrlsHandlerTabHelper can handle the given |url|.
-bool CanHandleUrl(const GURL& url) {
+}  // namespace
+
+ITunesUrlsHandlerTabHelper::~ITunesUrlsHandlerTabHelper() = default;
+
+ITunesUrlsHandlerTabHelper::ITunesUrlsHandlerTabHelper(web::WebState* web_state)
+    : web::WebStatePolicyDecider(web_state) {}
+
+// static
+bool ITunesUrlsHandlerTabHelper::CanHandleUrl(const GURL& url) {
   if (!IsITunesProductUrl(url))
     return false;
   // Valid iTunes URL structure:
@@ -101,13 +108,6 @@
   return path_components[media_type_index] == kITunesAppPathIdentifier;
 }
 
-}  // namespace
-
-ITunesUrlsHandlerTabHelper::~ITunesUrlsHandlerTabHelper() = default;
-
-ITunesUrlsHandlerTabHelper::ITunesUrlsHandlerTabHelper(web::WebState* web_state)
-    : web::WebStatePolicyDecider(web_state) {}
-
 bool ITunesUrlsHandlerTabHelper::ShouldAllowRequest(
     NSURLRequest* request,
     const web::WebStatePolicyDecider::RequestInfo& request_info) {
diff --git a/ios/chrome/browser/metrics/BUILD.gn b/ios/chrome/browser/metrics/BUILD.gn
index ff618e21..b51cf75 100644
--- a/ios/chrome/browser/metrics/BUILD.gn
+++ b/ios/chrome/browser/metrics/BUILD.gn
@@ -53,6 +53,7 @@
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/translate",
+    "//ios/chrome/browser/unified_consent:feature",
     "//ios/chrome/browser/variations",
     "//ios/chrome/browser/variations:ios_chrome_ui_string_overrider_factory",
     "//ios/chrome/browser/web_state_list",
diff --git a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
index 24a8139..0c6556985 100644
--- a/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
+++ b/ios/chrome/browser/metrics/ios_chrome_metrics_service_client.mm
@@ -46,6 +46,7 @@
 #include "components/variations/variations_associated_data.h"
 #include "components/version_info/version_info.h"
 #include "ios/chrome/browser/application_context.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
 #include "ios/chrome/browser/chrome_paths.h"
 #include "ios/chrome/browser/google/google_brand.h"
@@ -58,6 +59,7 @@
 #include "ios/chrome/browser/tab_parenting_global_observer.h"
 #import "ios/chrome/browser/tabs/tab_model_list.h"
 #include "ios/chrome/browser/translate/translate_ranker_metrics_provider.h"
+#include "ios/chrome/browser/unified_consent/feature.h"
 #include "ios/chrome/common/channel_info.h"
 #include "ios/web/public/web_thread.h"
 
@@ -290,7 +292,9 @@
   browser_sync::ProfileSyncService* sync =
       ProfileSyncServiceFactory::GetInstance()->GetForBrowserState(
           browser_state);
-  ObserveServiceForSyncDisables(static_cast<syncer::SyncService*>(sync));
+  ObserveServiceForSyncDisables(static_cast<syncer::SyncService*>(sync),
+                                browser_state->GetPrefs(),
+                                IsUnifiedConsentEnabled());
   return (history_service != nullptr && sync != nullptr);
 }
 
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
index 4086e9f..528479b2 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.h
@@ -89,7 +89,7 @@
       const override;
   const password_manager::LogManager* GetLogManager() const override;
   ukm::SourceId GetUkmSourceId() override;
-  password_manager::PasswordManagerMetricsRecorder& GetMetricsRecorder()
+  password_manager::PasswordManagerMetricsRecorder* GetMetricsRecorder()
       override;
 
  private:
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
index a10cf907..1e90248 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
@@ -195,7 +195,7 @@
   return ukm_source_id_;
 }
 
-PasswordManagerMetricsRecorder&
+PasswordManagerMetricsRecorder*
 IOSChromePasswordManagerClient::GetMetricsRecorder() {
   if (!metrics_recorder_) {
     // Query source_id first, because that has the side effect of initializing
@@ -203,7 +203,7 @@
     ukm::SourceId source_id = GetUkmSourceId();
     metrics_recorder_.emplace(source_id, ukm_source_url_);
   }
-  return metrics_recorder_.value();
+  return base::OptionalOrNullptr(metrics_recorder_);
 }
 
 void IOSChromePasswordManagerClient::PromptUserToEnableAutosignin() {
diff --git a/ios/chrome/browser/prerender/BUILD.gn b/ios/chrome/browser/prerender/BUILD.gn
index 4d7c66def..4e4ad6b 100644
--- a/ios/chrome/browser/prerender/BUILD.gn
+++ b/ios/chrome/browser/prerender/BUILD.gn
@@ -22,9 +22,11 @@
     "//components/prefs/ios",
     "//components/signin/ios/browser",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/app_launcher:app_launcher",
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/geolocation:geolocation_internal",
     "//ios/chrome/browser/history:tab_helper",
+    "//ios/chrome/browser/itunes_urls:itunes_urls",
     "//ios/chrome/browser/net",
     "//ios/chrome/browser/signin",
     "//ios/chrome/browser/tabs",
diff --git a/ios/chrome/browser/prerender/preload_controller.mm b/ios/chrome/browser/prerender/preload_controller.mm
index fb83d17ac..44a4427 100644
--- a/ios/chrome/browser/prerender/preload_controller.mm
+++ b/ios/chrome/browser/prerender/preload_controller.mm
@@ -14,9 +14,11 @@
 #import "components/prefs/ios/pref_observer_bridge.h"
 #include "components/prefs/pref_service.h"
 #import "components/signin/ios/browser/account_consistency_service.h"
+#import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
 #import "ios/chrome/browser/history/history_tab_helper.h"
+#import "ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h"
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/browser/prerender/preload_controller_delegate.h"
 #import "ios/chrome/browser/signin/account_consistency_service_factory.h"
@@ -31,6 +33,7 @@
 #import "ios/web/public/web_state/ui/crw_native_content.h"
 #import "ios/web/public/web_state/web_state.h"
 #include "ios/web/public/web_state/web_state_observer_bridge.h"
+#import "ios/web/public/web_state/web_state_policy_decider_bridge.h"
 #include "ios/web/public/web_thread.h"
 #import "ios/web/web_state/ui/crw_web_controller.h"
 #import "net/base/mac/url_conversions.h"
@@ -40,6 +43,8 @@
 #error "This file requires ARC support."
 #endif
 
+using web::WebStatePolicyDecider;
+
 namespace {
 // Delay before starting to prerender a URL.
 const NSTimeInterval kPrerenderDelay = 0.5;
@@ -89,6 +94,7 @@
 @end
 
 @interface PreloadController ()<CRWWebStateObserver,
+                                CRWWebStatePolicyDecider,
                                 ManageAccountsDelegate,
                                 PrefObserverDelegate>
 @end
@@ -143,6 +149,9 @@
   // Number of successful prerenders (i.e. the user viewed the prerendered page)
   // during the lifetime of this controller.
   int successfulPrerendersPerSessionCount_;
+
+  // Bridge to provide navigation policies for |webState_|.
+  std::unique_ptr<web::WebStatePolicyDeciderBridge> policyDeciderBridge_;
 }
 
 @synthesize prerenderedURL = prerenderedURL_;
@@ -256,7 +265,7 @@
   webState->SetShouldSuppressDialogs(false);
   webState->RemoveObserver(webStateObserver_.get());
   webState->SetDelegate(nullptr);
-
+  policyDeciderBridge_.reset();
   HistoryTabHelper::FromWebState(webState.get())
       ->SetDelayHistoryServiceNotification(false);
 
@@ -383,6 +392,11 @@
 
   web::WebState::CreateParams createParams(browserState_);
   webState_ = web::WebState::Create(createParams);
+  // Add the preload controller as a policyDecider before other tab helpers, so
+  // that it can block the navigation if needed before other policy deciders
+  // execute thier side effects (eg. AppLauncherTabHelper launching app).
+  policyDeciderBridge_ =
+      std::make_unique<web::WebStatePolicyDeciderBridge>(webState_.get(), self);
   AttachTabHelpers(webState_.get(), /*for_prerender=*/true);
 
   Tab* tab = LegacyTabHelper::GetTabForWebState(webState_.get());
@@ -465,12 +479,6 @@
   }
 }
 
-- (BOOL)isAppLaunchingAllowedForWebState:(web::WebState*)webState {
-  DCHECK([self isWebStatePrerendered:webState]);
-  [self schedulePrerenderCancel];
-  return NO;
-}
-
 #pragma mark - CRWWebStateObserver
 
 - (void)webState:(web::WebState*)webState
@@ -509,4 +517,18 @@
   [self schedulePrerenderCancel];
 }
 
+#pragma mark - CRWWebStatePolicyDecider
+
+- (BOOL)shouldAllowRequest:(NSURLRequest*)request
+               requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
+  GURL requestURL = net::GURLWithNSURL(request.URL);
+  // Don't allow preloading for requests that are handled by opening another
+  // application or by presenting a native UI.
+  if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
+      ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
+    [self schedulePrerenderCancel];
+    return NO;
+  }
+  return YES;
+}
 @end
diff --git a/ios/chrome/browser/ui/app_launcher/BUILD.gn b/ios/chrome/browser/ui/app_launcher/BUILD.gn
index 61fac104..177dfb2 100644
--- a/ios/chrome/browser/ui/app_launcher/BUILD.gn
+++ b/ios/chrome/browser/ui/app_launcher/BUILD.gn
@@ -20,6 +20,7 @@
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser",
     "//ios/chrome/browser/app_launcher",
+    "//ios/chrome/browser/app_launcher:feature_flags",
     "//ios/chrome/browser/mailto:feature_flags",
     "//ios/chrome/browser/ui/collection_view",
     "//ios/chrome/browser/web",
@@ -43,7 +44,9 @@
   deps = [
     ":app_launcher",
     "//base",
+    "//base/test:test_support",
     "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/browser/app_launcher:feature_flags",
     "//ios/chrome/browser/ui/collection_view:test_support",
     "//ios/chrome/browser/ui/collection_view/cells",
     "//ios/chrome/browser/web",
diff --git a/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.mm b/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.mm
index 64cc7cd3..948e6b7 100644
--- a/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.mm
+++ b/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.mm
@@ -9,6 +9,7 @@
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/strings/grit/components_strings.h"
+#include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #import "ios/chrome/browser/mailto/features.h"
 #include "ios/chrome/browser/procedural_block_types.h"
 #import "ios/chrome/browser/ui/app_launcher/app_launcher_util.h"
@@ -101,8 +102,7 @@
 
 // Shows an alert that the app will open in another application. If the user
 // accepts, the |URL| is launched.
-- (void)showAlertAndLaunchAppStoreURL:(const GURL&)URL {
-  DCHECK(UrlHasAppStoreScheme(URL));
+- (void)showAlertAndLaunchAppURL:(const GURL&)URL {
   NSString* prompt = l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP);
   NSString* openLabel =
       l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL);
@@ -174,7 +174,7 @@
 
 - (BOOL)appLauncherTabHelper:(AppLauncherTabHelper*)tabHelper
             launchAppWithURL:(const GURL&)URL
-                  linkTapped:(BOOL)linkTapped {
+              linkTransition:(BOOL)linkTransition {
   // Don't open application if chrome is not active.
   if ([[UIApplication sharedApplication] applicationState] !=
       UIApplicationStateActive) {
@@ -182,7 +182,7 @@
   }
   if (@available(iOS 10.3, *)) {
     if (UrlHasAppStoreScheme(URL)) {
-      [self showAlertAndLaunchAppStoreURL:URL];
+      [self showAlertAndLaunchAppURL:URL];
       return YES;
     }
   } else {
@@ -196,8 +196,8 @@
     // Prior to iOS 10.3, Chrome prompts user with an alert before opening
     // App Store when user did not tap on any links and an iTunes app URL is
     // opened. This maintains parity with Safari in pre-10.3 environment.
-    if (!linkTapped && UrlHasAppStoreScheme(URL)) {
-      [self showAlertAndLaunchAppStoreURL:URL];
+    if (!linkTransition && UrlHasAppStoreScheme(URL)) {
+      [self showAlertAndLaunchAppURL:URL];
       return YES;
     }
   }
@@ -214,6 +214,19 @@
     return YES;
   }
 
+  if (base::FeatureList::IsEnabled(kAppLauncherRefresh)) {
+    // For all other apps other than AppStore, show a prompt if there was no
+    // link transition.
+    if (linkTransition) {
+      [[UIApplication sharedApplication] openURL:net::NSURLWithGURL(URL)
+                                         options:@{}
+                               completionHandler:nil];
+    } else {
+      [self showAlertAndLaunchAppURL:URL];
+    }
+    return YES;
+  }
+
 // If the following call returns YES, an application is about to be
 // launched and Chrome will go into the background now.
 #pragma clang diagnostic push
diff --git a/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator_unittest.mm b/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator_unittest.mm
index 0f76ad2..6f5e8d1 100644
--- a/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/app_launcher/app_launcher_coordinator_unittest.mm
@@ -7,6 +7,8 @@
 #import <UIKit/UIKit.h>
 
 #include "base/mac/foundation_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/scoped_key_window.h"
 #import "ios/web/public/test/fakes/test_web_state.h"
@@ -45,7 +47,7 @@
 TEST_F(AppLauncherCoordinatorTest, EmptyUrl) {
   BOOL app_exists = [coordinator_ appLauncherTabHelper:nullptr
                                       launchAppWithURL:GURL::EmptyGURL()
-                                            linkTapped:NO];
+                                        linkTransition:NO];
   EXPECT_FALSE(app_exists);
   EXPECT_EQ(nil, base_view_controller_.presentedViewController);
 }
@@ -54,7 +56,7 @@
 TEST_F(AppLauncherCoordinatorTest, InvalidUrl) {
   BOOL app_exists = [coordinator_ appLauncherTabHelper:nullptr
                                       launchAppWithURL:GURL("invalid")
-                                            linkTapped:NO];
+                                        linkTransition:NO];
   EXPECT_FALSE(app_exists);
 }
 
@@ -62,9 +64,9 @@
 TEST_F(AppLauncherCoordinatorTest, ItmsUrlShowsAlert) {
   BOOL app_exists = [coordinator_ appLauncherTabHelper:nullptr
                                       launchAppWithURL:GURL("itms://1234")
-                                            linkTapped:NO];
+                                        linkTransition:NO];
   EXPECT_TRUE(app_exists);
-  EXPECT_TRUE([base_view_controller_.presentedViewController
+  ASSERT_TRUE([base_view_controller_.presentedViewController
       isKindOfClass:[UIAlertController class]]);
   UIAlertController* alert_controller =
       base::mac::ObjCCastStrict<UIAlertController>(
@@ -75,19 +77,59 @@
 
 // Tests that an app URL attempts to launch the application.
 TEST_F(AppLauncherCoordinatorTest, AppUrlLaunchesApp) {
+  // Make sure that the new AppLauncherRefresh logic is disabled.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kAppLauncherRefresh);
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
   OCMExpect([application_ openURL:[NSURL URLWithString:@"some-app://1234"]]);
 #pragma clang diagnostic pop
   [coordinator_ appLauncherTabHelper:nullptr
                     launchAppWithURL:GURL("some-app://1234")
-                          linkTapped:NO];
+                      linkTransition:NO];
   [application_ verify];
 }
 
-// Tests that |-appLauncherTabHelper:launchAppWithURL:linkTapped:| returns NO
-// if there is no application that corresponds to a given URL.
+// Tests that in the new AppLauncher, an app URL attempts to launch the
+// application.
+TEST_F(AppLauncherCoordinatorTest, AppLauncherRefreshAppUrlLaunchesApp) {
+  // Make sure that the new AppLauncherRefresh logic is enabled.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kAppLauncherRefresh);
+  OCMExpect([application_ openURL:[NSURL URLWithString:@"some-app://1234"]
+                          options:@{}
+                completionHandler:nil]);
+  [coordinator_ appLauncherTabHelper:nullptr
+                    launchAppWithURL:GURL("some-app://1234")
+                      linkTransition:YES];
+  [application_ verify];
+}
+
+// Tests that in the new AppLauncher, an app URL shows a prompt if there was no
+// link transition.
+TEST_F(AppLauncherCoordinatorTest, AppLauncherRefreshAppUrlShowsPrompt) {
+  // Make sure that the new AppLauncherRefresh logic is enabled.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kAppLauncherRefresh);
+  [coordinator_ appLauncherTabHelper:nullptr
+                    launchAppWithURL:GURL("some-app://1234")
+                      linkTransition:NO];
+  ASSERT_TRUE([base_view_controller_.presentedViewController
+      isKindOfClass:[UIAlertController class]]);
+  UIAlertController* alert_controller =
+      base::mac::ObjCCastStrict<UIAlertController>(
+          base_view_controller_.presentedViewController);
+  EXPECT_NSEQ(l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP),
+              alert_controller.message);
+}
+
+// Tests that |-appLauncherTabHelper:launchAppWithURL:linkTransition:| returns
+// NO if there is no application that corresponds to a given URL.
 TEST_F(AppLauncherCoordinatorTest, NoApplicationForUrl) {
+  // Make sure that the new AppLauncherRefresh logic is disabled.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kAppLauncherRefresh);
+
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
   OCMStub(
@@ -97,6 +139,6 @@
   BOOL app_exists =
       [coordinator_ appLauncherTabHelper:nullptr
                         launchAppWithURL:GURL("no-app-installed://1234")
-                              linkTapped:NO];
+                          linkTransition:NO];
   EXPECT_FALSE(app_exists);
 }
diff --git a/ios/chrome/browser/ui/authentication/authentication_constants.h b/ios/chrome/browser/ui/authentication/authentication_constants.h
index 7b2600c..8027d10d 100644
--- a/ios/chrome/browser/ui/authentication/authentication_constants.h
+++ b/ios/chrome/browser/ui/authentication/authentication_constants.h
@@ -27,6 +27,11 @@
 // Alpha for the text color.
 extern const CGFloat kAuthenticationTextColorAlpha;
 
+// Alpha for the separator color.
+extern const CGFloat kAuthenticationSeparatorColorAlpha;
+// Height of the separator.
+extern const CGFloat kAuthenticationSeparatorHeight;
+
 // Header image name.
 extern NSString* const kAuthenticationHeaderImageName;
 
diff --git a/ios/chrome/browser/ui/authentication/authentication_constants.mm b/ios/chrome/browser/ui/authentication/authentication_constants.mm
index 04b15a2b..4426b14 100644
--- a/ios/chrome/browser/ui/authentication/authentication_constants.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_constants.mm
@@ -21,4 +21,7 @@
 const CGFloat kAuthenticationTitleColorAlpha = 0.87;
 const CGFloat kAuthenticationTextColorAlpha = 0.54;
 
+const CGFloat kAuthenticationSeparatorColorAlpha = 0.12;
+const CGFloat kAuthenticationSeparatorHeight = 1;
+
 NSString* const kAuthenticationHeaderImageName = @"unified_consent_header";
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/BUILD.gn b/ios/chrome/browser/ui/authentication/consent_bump/BUILD.gn
index 2fdfc77..48305490 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/consent_bump/BUILD.gn
@@ -23,6 +23,8 @@
 source_set("consent_bump_ui") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "consent_bump_option_button.h",
+    "consent_bump_option_button.mm",
     "consent_bump_personalization_view_controller.h",
     "consent_bump_personalization_view_controller.mm",
     "consent_bump_view_controller.h",
@@ -30,6 +32,7 @@
     "consent_bump_view_controller_delegate.h",
   ]
   deps = [
+    "resources:consent_bump_checkmark",
     "//base",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui:ui_util",
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h
new file mode 100644
index 0000000..0ad69de
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h
@@ -0,0 +1,33 @@
+// Copyright 2018 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_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_OPTION_BUTTON_H_
+#define IOS_CHROME_BROWSER_UI_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_OPTION_BUTTON_H_
+
+#import <UIKit/UIKit.h>
+
+// Different option types.
+typedef NS_ENUM(NSInteger, ConsentBumpOptionType) {
+  ConsentBumpOptionTypeDefault = 0,
+  ConsentBumpOptionTypeNoChange,
+  ConsentBumpOptionTypeReview,
+  ConsentBumpOptionTypeTurnOn
+};
+
+// Button defining a consent bump option.
+@interface ConsentBumpOptionButton : UIButton
+
+// Whether this option is currently selected or not.
+@property(nonatomic, assign) BOOL checked;
+// The type of this option.
+@property(nonatomic, assign) ConsentBumpOptionType type;
+
+// Returns a ConsentBumpOptionButton with a |title| and |text|. |text| can be
+// nil, in that case the title will take the full height of the option.
++ (instancetype)consentBumpOptionButtonWithTitle:(NSString*)title
+                                            text:(NSString*)text;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_OPTION_BUTTON_H_
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm
new file mode 100644
index 0000000..b4b47be
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.mm
@@ -0,0 +1,207 @@
+// Copyright 2018 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/authentication/consent_bump/consent_bump_option_button.h"
+
+#import "ios/chrome/browser/ui/authentication/authentication_constants.h"
+#include "ios/chrome/browser/ui/uikit_ui_util.h"
+#import "ios/chrome/common/ui_util/constraints_ui_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const CGFloat kVerticalMargin = 8;
+const CGFloat kTitleTextMargin = 4;
+
+const CGFloat kImageViewMargin = 16;
+
+const CGFloat kCheckmarkSize = 18;
+const int kCheckmarkColor = 0x1A73E8;
+
+const CGFloat kHighlightAlpha = 0.07;
+
+const CGFloat kAnimationDuration = 0.15;
+}  // namespace
+
+@interface ConsentBumpOptionButton ()
+
+// Image view containing the check mark is the option is selected.
+@property(nonatomic, strong) UIImageView* checkMarkImageView;
+
+// Whether the highlight animation is running.
+@property(nonatomic, assign) BOOL highlightAnimationRunning;
+@end
+
+@implementation ConsentBumpOptionButton
+
+@synthesize checked = _checked;
+@synthesize type = _type;
+@synthesize checkMarkImageView = _checkMarkImageView;
+@synthesize highlightAnimationRunning = _highlightAnimationRunning;
+
++ (instancetype)consentBumpOptionButtonWithTitle:(NSString*)title
+                                            text:(NSString*)text {
+  ConsentBumpOptionButton* option = [self buttonWithType:UIButtonTypeCustom];
+
+  UILabel* titleLabel = [[UILabel alloc] init];
+  titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+  titleLabel.numberOfLines = 0;
+  titleLabel.text = title;
+  titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+  titleLabel.textColor =
+      [UIColor colorWithWhite:0 alpha:kAuthenticationTitleColorAlpha];
+  [option addSubview:titleLabel];
+
+  UIView* separator = [[UIView alloc] init];
+  separator.backgroundColor =
+      [UIColor colorWithWhite:0 alpha:kAuthenticationSeparatorColorAlpha];
+  separator.translatesAutoresizingMaskIntoConstraints = NO;
+  [option addSubview:separator];
+
+  UIImageView* checkMarkImageView = [[UIImageView alloc]
+      initWithImage:
+          [[UIImage imageNamed:@"consent_bump_checkmark"]
+              imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
+  option.checkMarkImageView = checkMarkImageView;
+  checkMarkImageView.translatesAutoresizingMaskIntoConstraints = NO;
+  checkMarkImageView.hidden = YES;
+  checkMarkImageView.tintColor = UIColorFromRGB(kCheckmarkColor);
+  [option addSubview:checkMarkImageView];
+
+  id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(option);
+
+  if (text) {
+    // There is text to be displayed. Make sure it is taken into account.
+    UILabel* textLabel = [[UILabel alloc] init];
+    textLabel.translatesAutoresizingMaskIntoConstraints = NO;
+    textLabel.numberOfLines = 0;
+    textLabel.text = text;
+    textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
+    textLabel.textColor =
+        [UIColor colorWithWhite:0 alpha:kAuthenticationTextColorAlpha];
+    [option addSubview:textLabel];
+
+    [NSLayoutConstraint activateConstraints:@[
+      [textLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor
+                                          constant:kTitleTextMargin],
+      [textLabel.leadingAnchor
+          constraintEqualToAnchor:safeArea.leadingAnchor
+                         constant:kAuthenticationHorizontalMargin],
+      [textLabel.bottomAnchor constraintEqualToAnchor:option.bottomAnchor
+                                             constant:-kVerticalMargin],
+      [textLabel.trailingAnchor
+          constraintLessThanOrEqualToAnchor:checkMarkImageView.leadingAnchor
+                                   constant:-kImageViewMargin],
+
+    ]];
+  } else {
+    // No text, constraint the title label to the bottom of the option.
+    [titleLabel.bottomAnchor constraintEqualToAnchor:option.bottomAnchor
+                                            constant:-kVerticalMargin]
+        .active = YES;
+  }
+
+  AddSameCenterYConstraint(option, checkMarkImageView);
+
+  [NSLayoutConstraint activateConstraints:@[
+    // Title.
+    [titleLabel.topAnchor constraintEqualToAnchor:option.topAnchor
+                                         constant:kVerticalMargin],
+    [titleLabel.leadingAnchor
+        constraintEqualToAnchor:safeArea.leadingAnchor
+                       constant:kAuthenticationHorizontalMargin],
+
+    // Check image.
+    [titleLabel.trailingAnchor
+        constraintLessThanOrEqualToAnchor:checkMarkImageView.leadingAnchor
+                                 constant:-kImageViewMargin],
+    [checkMarkImageView.trailingAnchor
+        constraintEqualToAnchor:safeArea.trailingAnchor
+                       constant:-kImageViewMargin],
+    [checkMarkImageView.topAnchor
+        constraintGreaterThanOrEqualToAnchor:option.topAnchor
+                                    constant:kImageViewMargin],
+    [checkMarkImageView.bottomAnchor
+        constraintLessThanOrEqualToAnchor:option.bottomAnchor
+                                 constant:-kImageViewMargin],
+    [checkMarkImageView.heightAnchor constraintEqualToConstant:kCheckmarkSize],
+    [checkMarkImageView.widthAnchor constraintEqualToConstant:kCheckmarkSize],
+
+    // Separator.
+    [separator.heightAnchor
+        constraintEqualToConstant:kAuthenticationSeparatorHeight],
+    [separator.leadingAnchor
+        constraintEqualToAnchor:safeArea.leadingAnchor
+                       constant:kAuthenticationHorizontalMargin],
+    [separator.trailingAnchor constraintEqualToAnchor:option.trailingAnchor],
+    [separator.bottomAnchor constraintEqualToAnchor:option.bottomAnchor],
+  ]];
+
+  return option;
+}
+
+#pragma mark - Properties
+
+- (void)setChecked:(BOOL)checked {
+  if (checked == _checked)
+    return;
+
+  _checked = checked;
+  self.checkMarkImageView.hidden = !checked;
+}
+
+#pragma mark - UIButton
+
+- (void)setHighlighted:(BOOL)highlighted {
+  [super setHighlighted:highlighted];
+
+  if (self.highlightAnimationRunning)
+    return;
+
+  if (highlighted) {
+    [self animateHighlighting];
+  } else {
+    [self animateUnhighlighting];
+  }
+}
+
+#pragma mark - Private
+
+// Highlights this view, in an animated way. If at the end of the animation the
+// view isn't |highlighted| anymore, the effect is removed.
+- (void)animateHighlighting {
+  self.highlightAnimationRunning = YES;
+  [UIView animateWithDuration:kAnimationDuration
+      delay:0
+      options:UIViewAnimationOptionCurveEaseOut
+      animations:^{
+        self.backgroundColor = [UIColor colorWithWhite:0 alpha:kHighlightAlpha];
+      }
+      completion:^(BOOL finished) {
+        self.highlightAnimationRunning = NO;
+        if (!self.highlighted)
+          [self animateUnhighlighting];
+      }];
+}
+
+// Unhighlights this view, in an animated way. If at the end of the animation
+// the view is |highlighted|, the effect is added back.
+- (void)animateUnhighlighting {
+  self.highlightAnimationRunning = YES;
+  [UIView animateWithDuration:kAnimationDuration
+      delay:0
+      options:UIViewAnimationOptionCurveEaseOut
+      animations:^{
+        self.backgroundColor = [UIColor clearColor];
+      }
+      completion:^(BOOL finished) {
+        self.highlightAnimationRunning = NO;
+        if (self.highlighted)
+          [self animateHighlighting];
+      }];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.h b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.h
index 4611b12d..4834e91c 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.h
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.h
@@ -5,11 +5,15 @@
 #ifndef IOS_CHROME_BROWSER_UI_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_PERSONALIZATION_COORDINATOR_H_
 #define IOS_CHROME_BROWSER_UI_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_PERSONALIZATION_COORDINATOR_H_
 
+#import "ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h"
 #import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
 
 // Coordinator for handling the ConsentBump Personalization screen.
 @interface ConsentBumpPersonalizationCoordinator : ChromeCoordinator
 
+// Type of the selected option.
+@property(nonatomic, assign, readonly) ConsentBumpOptionType selectedOption;
+
 // The view Controller for this coordinator.
 @property(nonatomic, strong, readonly) UIViewController* viewController;
 
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.mm
index 9dff249..0b8d8838 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_coordinator.mm
@@ -28,6 +28,10 @@
   return self.personalizationViewController;
 }
 
+- (ConsentBumpOptionType)selectedOption {
+  return self.personalizationViewController.selectedOption;
+}
+
 #pragma mark - ChromeCoordinator
 
 - (void)start {
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.h b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.h
index 064ba0f..4d13f61 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.h
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.h
@@ -7,9 +7,14 @@
 
 #import <UIKit/UIKit.h>
 
+#import "ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h"
+
 // View controller displaying the Personalization screen.
 @interface ConsentBumpPersonalizationViewController : UIViewController
 
+// The currently selected option.
+@property(nonatomic, assign, readonly) ConsentBumpOptionType selectedOption;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_CONSENT_BUMP_CONSENT_BUMP_PERSONALIZATION_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
index 7df7bda..3a41b4a 100644
--- a/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/authentication/consent_bump/consent_bump_personalization_view_controller.h"
 
 #import "ios/chrome/browser/ui/authentication/authentication_constants.h"
+#import "ios/chrome/browser/ui/authentication/consent_bump/consent_bump_option_button.h"
 #import "ios/chrome/browser/ui/uikit_ui_util.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
@@ -17,6 +18,7 @@
 
 namespace {
 const CGFloat kTitleTextMargin = 16;
+const CGFloat kOptionsVerticalMargin = 16;
 }  // namespace
 
 @interface ConsentBumpPersonalizationViewController ()
@@ -27,6 +29,11 @@
 @property(nonatomic, strong)
     NSLayoutConstraint* imageBackgroundViewHeightConstraint;
 
+// Array containing all the options presented by this ViewController.
+@property(nonatomic, copy) NSArray<ConsentBumpOptionButton*>* options;
+// Redefined as readwrite.
+@property(nonatomic, assign, readwrite) ConsentBumpOptionType selectedOption;
+
 @end
 
 @implementation ConsentBumpPersonalizationViewController
@@ -34,6 +41,8 @@
 @synthesize scrollView = _scrollView;
 @synthesize imageBackgroundViewHeightConstraint =
     _imageBackgroundViewHeightConstraint;
+@synthesize options = _options;
+@synthesize selectedOption = _selectedOption;
 
 - (void)viewDidLoad {
   [super viewDidLoad];
@@ -81,6 +90,7 @@
   title.numberOfLines = 0;
   [container addSubview:title];
 
+  // Text.
   UILabel* text = [[UILabel alloc] initWithFrame:CGRectZero];
   text.translatesAutoresizingMaskIntoConstraints = NO;
   text.text =
@@ -91,6 +101,34 @@
   text.numberOfLines = 0;
   [container addSubview:text];
 
+  // Options.
+  ConsentBumpOptionButton* noChangeOption = [ConsentBumpOptionButton
+      consentBumpOptionButtonWithTitle:l10n_util::GetNSString(
+                                           IDS_IOS_CONSENT_BUMP_NO_CHANGE_TITLE)
+                                  text:
+                                      l10n_util::GetNSString(
+                                          IDS_IOS_CONSENT_BUMP_NO_CHANGE_TEXT)];
+  noChangeOption.type = ConsentBumpOptionTypeNoChange;
+  noChangeOption.checked = YES;
+  self.selectedOption = ConsentBumpOptionTypeNoChange;
+
+  ConsentBumpOptionButton* reviewOption = [ConsentBumpOptionButton
+      consentBumpOptionButtonWithTitle:l10n_util::GetNSString(
+                                           IDS_IOS_CONSENT_BUMP_REVIEW_TITLE)
+                                  text:nil];
+  reviewOption.type = ConsentBumpOptionTypeReview;
+  reviewOption.checked = NO;
+
+  ConsentBumpOptionButton* turnOnOption = [ConsentBumpOptionButton
+      consentBumpOptionButtonWithTitle:l10n_util::GetNSString(
+                                           IDS_IOS_CONSENT_BUMP_TURN_ON_TITLE)
+                                  text:l10n_util::GetNSString(
+                                           IDS_IOS_CONSENT_BUMP_TURN_ON_TEXT)];
+  turnOnOption.type = ConsentBumpOptionTypeTurnOn;
+  turnOnOption.checked = NO;
+
+  self.options = @[ noChangeOption, reviewOption, turnOnOption ];
+
   id<LayoutGuideProvider> safeArea = SafeAreaLayoutGuideForView(self.view);
   AddSameConstraints(self.view, self.scrollView);
   AddSameConstraints(container, self.scrollView);
@@ -119,10 +157,40 @@
   NSArray* constraints = @[
     @"H:|-(HMargin)-[title]-(HMargin)-|",
     @"H:|-(HMargin)-[text]-(HMargin)-|",
-    @"V:|[header]-(HeaderTitleMargin)-[title]-(TitleTextMargin)-[text]|",
+    @"V:|[header]-(HeaderTitleMargin)-[title]-(TitleTextMargin)-[text]",
   ];
   ApplyVisualConstraintsWithMetrics(constraints, views, metrics);
 
+  // Options positioning.
+  UIView* viewAbove = nil;
+  for (ConsentBumpOptionButton* option in self.options) {
+    option.translatesAutoresizingMaskIntoConstraints = NO;
+    [container addSubview:option];
+    [option addTarget:self
+                  action:@selector(optionTapped:)
+        forControlEvents:UIControlEventTouchUpInside];
+
+    if (viewAbove) {
+      [option.topAnchor constraintEqualToAnchor:viewAbove.bottomAnchor].active =
+          YES;
+    }
+    [NSLayoutConstraint activateConstraints:@[
+      [option.leadingAnchor constraintEqualToAnchor:container.leadingAnchor],
+      [option.trailingAnchor constraintEqualToAnchor:container.trailingAnchor]
+    ]];
+    viewAbove = option;
+  }
+
+  // Positioning the first and last options.
+  [NSLayoutConstraint activateConstraints:@[
+    [self.options[0].topAnchor constraintEqualToAnchor:text.bottomAnchor
+                                              constant:kOptionsVerticalMargin],
+    [container.bottomAnchor
+        constraintEqualToAnchor:self.options[self.options.count - 1]
+                                    .bottomAnchor
+                       constant:kOptionsVerticalMargin],
+  ]];
+
   [self updateScrollViewAndImageBackgroundView];
 }
 
@@ -147,6 +215,14 @@
 
 #pragma mark - Private
 
+- (void)optionTapped:(ConsentBumpOptionButton*)tappedOption {
+  for (ConsentBumpOptionButton* option in self.options) {
+    option.checked = NO;
+  }
+  tappedOption.checked = YES;
+  self.selectedOption = tappedOption.type;
+}
+
 // Updates constraints and content insets for the |scrollView| and
 // |imageBackgroundView| related to non-safe area.
 - (void)updateScrollViewAndImageBackgroundView {
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/resources/BUILD.gn b/ios/chrome/browser/ui/authentication/consent_bump/resources/BUILD.gn
new file mode 100644
index 0000000..9d3ab16
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/resources/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/asset_catalog.gni")
+
+imageset("consent_bump_checkmark") {
+  sources = [
+    "consent_bump_checkmark.imageset/Contents.json",
+    "consent_bump_checkmark.imageset/consent_bump_checkmark.png",
+    "consent_bump_checkmark.imageset/consent_bump_checkmark@2x.png",
+    "consent_bump_checkmark.imageset/consent_bump_checkmark@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/Contents.json b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/Contents.json
new file mode 100644
index 0000000..445d0bc
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "1x",
+            "filename": "consent_bump_checkmark.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "consent_bump_checkmark@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "consent_bump_checkmark@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark.png b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark.png
new file mode 100644
index 0000000..b7ed067b
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark.png
Binary files differ
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@2x.png b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@2x.png
new file mode 100644
index 0000000..d4c0607
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@3x.png b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@3x.png
new file mode 100644
index 0000000..1c4739e0
--- /dev/null
+++ b/ios/chrome/browser/ui/authentication/consent_bump/resources/consent_bump_checkmark.imageset/consent_bump_checkmark@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
index ae5284c..e78bb622 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/unified_consent_view_controller.mm
@@ -31,8 +31,6 @@
 // Sizes.
 // Size of the small icons next to the text.
 const CGFloat kIconSize = 16.;
-// Separator line height.
-const CGFloat kSeparatorHeight = 1.;
 
 // Horizontal margin between the small icon and the text next to it.
 const CGFloat kIconTextMargin = 16.;
@@ -48,9 +46,6 @@
 // Vertical margin between separator and text.
 const CGFloat kVerticalSeparatorTextMargin = 16.;
 
-// Alpha for the separator color.
-const CGFloat kSeparatorColorAlpha = 0.12;
-
 // URL for the Settings link.
 const char* const kSettingsSyncURL = "internal://settings-sync";
 
@@ -223,7 +218,7 @@
   UIView* separator = [[UIView alloc] initWithFrame:CGRectZero];
   separator.translatesAutoresizingMaskIntoConstraints = NO;
   separator.backgroundColor =
-      [UIColor colorWithWhite:0 alpha:kSeparatorColorAlpha];
+      [UIColor colorWithWhite:0 alpha:kAuthenticationSeparatorColorAlpha];
   [container addSubview:separator];
   // Customize label.
   self.openSettingsStringId = IDS_IOS_ACCOUNT_UNIFIED_CONSENT_SETTINGS;
@@ -252,7 +247,7 @@
     @"VSeparatorText" : @(kVerticalSeparatorTextMargin),
     @"LeftSeparMrg" : @(kLeftSeparatorMargin),
     @"VTextMargin" : @(kVerticalTextMargin),
-    @"SeparatorHeight" : @(kSeparatorHeight),
+    @"SeparatorHeight" : @(kAuthenticationSeparatorHeight),
     @"HeaderHeight" : @(kAuthenticationHeaderImageHeight),
     @"HeaderWidth" : @(kAuthenticationHeaderImageWidth),
     @"HeaderTitleMargin" : @(kAuthenticationHeaderTitleMargin),
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
index 1791131..301f4a7 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/manual_fill/BUILD.gn
@@ -8,8 +8,6 @@
   sources = [
     "keyboard_accessory_view.h",
     "keyboard_accessory_view.mm",
-    "keyboard_background_view.h",
-    "keyboard_background_view.mm",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.h b/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.h
deleted file mode 100644
index 3928f12..0000000
--- a/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2018 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_AUTOFILL_MANUAL_FILL_KEYBOARD_BACKGROUND_VIEW_H_
-#define IOS_CHROME_BROWSER_UI_AUTOFILL_MANUAL_FILL_KEYBOARD_BACKGROUND_VIEW_H_
-
-#import <UIKit/UIKit.h>
-
-@protocol ManualFillKeyboardAccessoryViewDelegate;
-
-// View to show behind the keyboard. It contains an Accessory View and a
-// Container for more detailed content.
-@interface KeyboardBackgroundView : UIView
-
-// View to contain a picker (addresses, credit cards or credentials) for the
-// user.
-@property(nonatomic, strong) UIView* containerView;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)init NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
-
-// Unavailable. Use `initWithDelegate:`.
-- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
-
-// Instances an object with the desired delegate.
-//
-// @param delegate The delegate for this object.
-// @return A fresh object with the passed delegate.
-- (instancetype)initWithDelegate:
-    (id<ManualFillKeyboardAccessoryViewDelegate>)delegate
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_AUTOFILL_MANUAL_FILL_KEYBOARD_BACKGROUND_VIEW_H_
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.mm b/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.mm
deleted file mode 100644
index 1eee0f2..0000000
--- a/ios/chrome/browser/ui/autofill/manual_fill/keyboard_background_view.mm
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2018 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/autofill/manual_fill/keyboard_background_view.h"
-
-#import "ios/chrome/browser/ui/autofill/manual_fill/keyboard_accessory_view.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation KeyboardBackgroundView
-
-@synthesize containerView = _containerView;
-
-- (instancetype)initWithDelegate:
-    (id<ManualFillKeyboardAccessoryViewDelegate>)delegate {
-  self = [super initWithFrame:CGRectZero];
-  if (self) {
-    // TODO:(javierrobles) abstract this color to a constant.
-    self.backgroundColor = [UIColor colorWithRed:245.0 / 255.0
-                                           green:245.0 / 255.0
-                                            blue:245.0 / 255.0
-                                           alpha:1.0];
-
-    ManualFillKeyboardAccessoryView* toolbar =
-        [[ManualFillKeyboardAccessoryView alloc] initWithDelegate:delegate];
-    toolbar.translatesAutoresizingMaskIntoConstraints = NO;
-    [self addSubview:toolbar];
-
-    UIView* containerView = [[UIView alloc] init];
-    containerView.translatesAutoresizingMaskIntoConstraints = NO;
-    [self addSubview:containerView];
-    _containerView = containerView;
-
-    [NSLayoutConstraint activateConstraints:@[
-      [toolbar.leadingAnchor constraintEqualToAnchor:self.leadingAnchor
-                                            constant:0.0],
-      [toolbar.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
-      [toolbar.topAnchor constraintEqualToAnchor:self.topAnchor],
-      [toolbar.heightAnchor constraintEqualToConstant:44],
-
-      [containerView.topAnchor constraintEqualToAnchor:toolbar.bottomAnchor],
-      [containerView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor],
-      [containerView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
-      [containerView.trailingAnchor
-          constraintEqualToAnchor:self.trailingAnchor],
-    ]];
-  }
-  return self;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index a741217..e4b90fd8 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -3726,10 +3726,6 @@
                                       completionHandler:handler];
 }
 
-- (BOOL)isAppLaunchingAllowedForWebState:(web::WebState*)webState {
-  return YES;
-}
-
 #pragma mark - CRWWebStateDelegate helpers
 
 // Evaluates Javascript asynchronously using the current page context.
@@ -5373,8 +5369,11 @@
 
   // UI Refresh animation.
   if (IsUIRefreshPhase1Enabled()) {
+    // The animation will have the same frame as |self|, minus the status bar,
+    // so shift it down and reduce its height accordingly.
     CGRect frame = self.view.bounds;
     frame.origin.y += StatusBarHeight();
+    frame.size.height -= StatusBarHeight();
     frame = [self.contentArea convertRect:frame fromView:self.view];
     ForegroundTabAnimationView* animatedView =
         [[ForegroundTabAnimationView alloc] initWithFrame:frame];
diff --git a/ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.mm b/ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.mm
index 266a902..123493d6 100644
--- a/ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.mm
+++ b/ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.mm
@@ -24,6 +24,9 @@
 // after the bubble first becomes visible.
 const NSTimeInterval kBubbleEngagementDuration = 30.0;
 
+// Delay before posting the VoiceOver notification.
+const CGFloat kVoiceOverAnnouncementDelay = 1;
+
 // The name for the histogram that tracks why a bubble was dismissed.
 const char kBubbleDismissalHistogramName[] = "IOS.IPHBubbleDismissalReason";
 
@@ -183,8 +186,21 @@
                                       repeats:NO];
 
   if (self.voiceOverAnnouncement) {
-    UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification,
-                                    self.voiceOverAnnouncement);
+    // The VoiceOverAnnouncement should be dispatched after a delay to account
+    // the fact that it can be presented right after a screen change (for
+    // example when the application or a new tab is opened). This screen change
+    // is changing the VoiceOver focus to focus a newly visible element. If this
+    // announcement is currently being read, it is cancelled. The added delay
+    // allows the announcement to be posted after the element is focused, so it
+    // is not cancelled.
+    dispatch_after(
+        dispatch_time(DISPATCH_TIME_NOW,
+                      (int64_t)(kVoiceOverAnnouncementDelay * NSEC_PER_SEC)),
+        dispatch_get_main_queue(), ^{
+          UIAccessibilityPostNotification(
+              UIAccessibilityAnnouncementNotification,
+              self.voiceOverAnnouncement);
+        });
   }
 }
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index 9385a7bc..e2ab4de 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -362,6 +362,8 @@
 - (void)addFakeTapView {
   UIButton* fakeTapButton = [[UIButton alloc] init];
   fakeTapButton.translatesAutoresizingMaskIntoConstraints = NO;
+  fakeTapButton.accessibilityLabel =
+      l10n_util::GetNSString(IDS_ACCNAME_LOCATION);
   [self.headerView addToolbarView:fakeTapButton];
   [fakeTapButton addTarget:self
                     action:@selector(fakeOmniboxTapped:)
diff --git a/ios/chrome/browser/ui/history/history_clear_browsing_data_coordinator.mm b/ios/chrome/browser/ui/history/history_clear_browsing_data_coordinator.mm
index 5ae326d9..e7ab25f9 100644
--- a/ios/chrome/browser/ui/history/history_clear_browsing_data_coordinator.mm
+++ b/ios/chrome/browser/ui/history/history_clear_browsing_data_coordinator.mm
@@ -24,13 +24,17 @@
 @interface HistoryClearBrowsingDataCoordinator ()<
     UIViewControllerTransitioningDelegate>
 
-// ViewController being managed by this Coordinator.
+// ViewControllers being managed by this Coordinator.
 @property(strong, nonatomic)
     TableViewNavigationController* historyClearBrowsingDataNavigationController;
+@property(strong, nonatomic)
+    ClearBrowsingDataTableViewController* clearBrowsingDataTableViewController;
 
 @end
 
 @implementation HistoryClearBrowsingDataCoordinator
+@synthesize clearBrowsingDataTableViewController =
+    _clearBrowsingDataTableViewController;
 @synthesize dispatcher = _dispatcher;
 @synthesize historyClearBrowsingDataNavigationController =
     _historyClearBrowsingDataNavigationController;
@@ -39,16 +43,17 @@
 @synthesize presentationDelegate = _presentationDelegate;
 
 - (void)start {
-  ClearBrowsingDataTableViewController* clearBrowsingDataTableViewController =
+  self.clearBrowsingDataTableViewController =
       [[ClearBrowsingDataTableViewController alloc]
           initWithBrowserState:self.browserState];
-  clearBrowsingDataTableViewController.extendedLayoutIncludesOpaqueBars = YES;
-  clearBrowsingDataTableViewController.localDispatcher = self;
-  clearBrowsingDataTableViewController.dispatcher = self.dispatcher;
+  self.clearBrowsingDataTableViewController.extendedLayoutIncludesOpaqueBars =
+      YES;
+  self.clearBrowsingDataTableViewController.localDispatcher = self;
+  self.clearBrowsingDataTableViewController.dispatcher = self.dispatcher;
   // Configure and present ClearBrowsingDataNavigationController.
   self.historyClearBrowsingDataNavigationController =
       [[TableViewNavigationController alloc]
-          initWithTable:clearBrowsingDataTableViewController];
+          initWithTable:self.clearBrowsingDataTableViewController];
   self.historyClearBrowsingDataNavigationController.toolbarHidden = YES;
   // Stacks on top of history "bubble" for non-compact devices.
   self.historyClearBrowsingDataNavigationController.transitioningDelegate =
@@ -65,10 +70,7 @@
 
 - (void)stopWithCompletion:(ProceduralBlock)completionHandler {
   if (self.historyClearBrowsingDataNavigationController) {
-    [self.historyClearBrowsingDataNavigationController
-        dismissViewControllerAnimated:YES
-                           completion:completionHandler];
-    self.historyClearBrowsingDataNavigationController = nil;
+    [self dismissClearBrowsingDataWithCompletion:completionHandler];
   } else if (completionHandler) {
     completionHandler();
   }
@@ -93,10 +95,18 @@
 
 - (void)dismissClearBrowsingDataWithCompletion:
     (ProceduralBlock)completionHandler {
+  DCHECK(self.historyClearBrowsingDataNavigationController);
+  [self.clearBrowsingDataTableViewController prepareForDismissal];
   [self.historyClearBrowsingDataNavigationController
       dismissViewControllerAnimated:YES
-                         completion:completionHandler];
-  self.historyClearBrowsingDataNavigationController = nil;
+                         completion:^() {
+                           if (completionHandler) {
+                             completionHandler();
+                           }
+                           self.clearBrowsingDataTableViewController = nil;
+                           self.historyClearBrowsingDataNavigationController =
+                               nil;
+                         }];
 }
 
 #pragma mark - UIViewControllerTransitioningDelegate
diff --git a/ios/chrome/browser/ui/history/history_coordinator.mm b/ios/chrome/browser/ui/history/history_coordinator.mm
index f7a91f11..1e24fd0 100644
--- a/ios/chrome/browser/ui/history/history_coordinator.mm
+++ b/ios/chrome/browser/ui/history/history_coordinator.mm
@@ -110,9 +110,11 @@
       self.historyNavigationController = nil;
     };
     if (self.historyClearBrowsingDataCoordinator) {
-      [self.historyClearBrowsingDataCoordinator
-          stopWithCompletion:dismissHistoryNavigation];
-      self.historyClearBrowsingDataCoordinator = nil;
+      [self.historyClearBrowsingDataCoordinator stopWithCompletion:^() {
+        dismissHistoryNavigation();
+        self.historyClearBrowsingDataCoordinator = nil;
+      }];
+
     } else {
       dismissHistoryNavigation();
     }
diff --git a/ios/chrome/browser/ui/omnibox/clipping_textfield_container.mm b/ios/chrome/browser/ui/omnibox/clipping_textfield_container.mm
index 6127355..895d993d 100644
--- a/ios/chrome/browser/ui/omnibox/clipping_textfield_container.mm
+++ b/ios/chrome/browser/ui/omnibox/clipping_textfield_container.mm
@@ -14,6 +14,17 @@
 #error "This file requires ARC support."
 #endif
 
+namespace {
+
+// Clipping of long URLs is disabled on iOS 12 due to a bug with UITextField
+// not being rendered when the backing layer is large (approx. 915
+// characters with current font). This is used as the max number of characters
+// to clip.
+// TODO(crbug.com/860790) : reenable clipping on iOS 12.
+const CGFloat kMaxCharsToClipOnIOS12 = 800;
+
+}  // namespace
+
 @interface ClippingTextFieldContainer ()
 
 @property(nonatomic, strong) NSLayoutConstraint* leftConstraint;
@@ -69,11 +80,12 @@
 }
 
 - (void)startClipping {
-  // TODO(crbug.com/860790) : reenable this.
-  if (base::ios::IsRunningOnIOS12OrLater()) {
-    // Clipping is disabled on iOS 12 due to a bug with UITextField not being
-    // rendered when the backing layer is large (approx. 915 characters with
-    // current font).
+  // TODO(crbug.com/860790) : reenable clipping on iOS 12.
+  if (base::ios::IsRunningOnIOS12OrLater() &&
+      self.textField.text.length > kMaxCharsToClipOnIOS12) {
+    // Clipping of long URLs is disabled on iOS 12 due to a bug with UITextField
+    // not being rendered when the backing layer is large (approx. 915
+    // characters with current font).
     return;
   }
 
@@ -84,6 +96,16 @@
 }
 
 - (void)applyClipping {
+  // TODO(crbug.com/860790) : reenable clipping on iOS 12.
+  if (base::ios::IsRunningOnIOS12OrLater() &&
+      self.textField.text.length > kMaxCharsToClipOnIOS12) {
+    // Clipping of long URLs is disabled on iOS 12 due to a bug with UITextField
+    // not being rendered when the backing layer is large (approx. 915
+    // characters with current font).
+    [self stopClipping];
+    return;
+  }
+
   CGFloat suffixWidth = 0;
   CGFloat prefixWidth =
       -[self leftConstantWithAttributedText:self.textField.attributedText
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller_unittest.mm
index fafdc58c..cb8fe89 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data_collection_view_controller_unittest.mm
@@ -178,6 +178,8 @@
       .WillRepeatedly(Return(syncer::SyncService::DISABLE_REASON_NONE));
   EXPECT_CALL(*mock_sync_service_, GetState())
       .WillRepeatedly(Return(syncer::SyncService::State::ACTIVE));
+  EXPECT_CALL(*mock_sync_service_, IsFirstSetupComplete())
+      .WillRepeatedly(Return(true));
   EXPECT_CALL(*mock_sync_service_, GetActiveDataTypes())
       .WillRepeatedly(Return(syncer::ModelTypeSet()));
   EXPECT_CALL(*mock_sync_service_, IsUsingSecondaryPassphrase())
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.h b/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.h
index 1ee8d71..3ec73e3 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.h
@@ -26,6 +26,10 @@
                                (ChromeTableViewControllerStyle)appBarStyle
     NS_UNAVAILABLE;
 
+// Prepares view controller so that -dismissViewControllerAnimated dismisses it.
+// Call this method before dismissing view controller.
+- (void)prepareForDismissal;
+
 // Local Dispatcher for this ClearBrowsingDataTableView.
 @property(nonatomic, weak) id<ClearBrowsingDataLocalCommands> localDispatcher;
 
diff --git a/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.mm b/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.mm
index 104f8dd4..fb31d1e 100644
--- a/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/clear_browsing_data_table_view_controller.mm
@@ -115,10 +115,23 @@
 }
 
 - (void)dismiss {
-  [self.alertCoordinator stop];
+  [self prepareForDismissal];
   [self.localDispatcher dismissClearBrowsingDataWithCompletion:nil];
 }
 
+#pragma mark - Public Methods
+
+- (void)prepareForDismissal {
+  if (self.actionSheetCoordinator) {
+    [self.actionSheetCoordinator stop];
+    self.actionSheetCoordinator = nil;
+  }
+  if (self.alertCoordinator) {
+    [self.alertCoordinator stop];
+    self.alertCoordinator = nil;
+  }
+}
+
 #pragma mark - UITableViewDataSource
 
 - (UITableViewCell*)tableView:(UITableView*)tableView
diff --git a/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.mm b/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.mm
index dcf64f0..fae3592 100644
--- a/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.mm
+++ b/ios/chrome/browser/ui/tabs/foreground_tab_animation_view.mm
@@ -33,7 +33,13 @@
 
 - (void)animateFrom:(CGPoint)originPoint withCompletion:(void (^)())completion {
   CGPoint origin = [self convertPoint:originPoint fromView:nil];
-  self.contentView.frame = self.bounds;
+
+  // Position the content view so it sits at the bottom of |self|.
+  CGRect frame = self.contentView.frame;
+  CGFloat offset = self.frame.size.height - frame.size.height;
+  frame.origin.y = offset;
+  self.contentView.frame = frame;
+
   self.backgroundColor = UIColor.clearColor;
 
   // Translate the content view part of the way from the center of this view to
diff --git a/ios/chrome/browser/ui/ui_util.mm b/ios/chrome/browser/ui/ui_util.mm
index 541822f..f9be1b5 100644
--- a/ios/chrome/browser/ui/ui_util.mm
+++ b/ios/chrome/browser/ui/ui_util.mm
@@ -58,7 +58,7 @@
 }
 
 bool IsRefreshInfobarEnabled() {
-  return base::FeatureList::IsEnabled(kInfobarsUIReboot);
+  return base::FeatureList::IsEnabled(kUIRefreshPhase1);
 }
 
 bool IsRefreshLocationBarEnabled() {
diff --git a/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.cc b/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.cc
index 32690f9..14f48f3 100644
--- a/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.cc
+++ b/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.cc
@@ -11,49 +11,76 @@
 
 UnifiedConsentServiceClientImpl::UnifiedConsentServiceClientImpl(
     PrefService* pref_service)
-    : pref_service_(pref_service) {}
-
-void UnifiedConsentServiceClientImpl::SetAlternateErrorPagesEnabled(
-    bool enabled) {
-  // Feature not available on iOS.
-  NOTIMPLEMENTED();
+    : pref_service_(pref_service) {
+  DCHECK(pref_service_);
+  ObserveServicePrefChange(Service::kMetricsReporting,
+                           metrics::prefs::kMetricsReportingEnabled,
+                           pref_service_);
+  ObserveServicePrefChange(Service::kNetworkPrediction,
+                           prefs::kNetworkPredictionEnabled, pref_service_);
+  ObserveServicePrefChange(Service::kSearchSuggest,
+                           prefs::kSearchSuggestEnabled, pref_service_);
 }
 
-void UnifiedConsentServiceClientImpl::SetMetricsReportingEnabled(bool enabled) {
-  BooleanPrefMember basePreference;
-  basePreference.Init(metrics::prefs::kMetricsReportingEnabled, pref_service_);
-  basePreference.SetValue(enabled);
-  BooleanPrefMember wifiPreference;
-  wifiPreference.Init(prefs::kMetricsReportingWifiOnly, pref_service_);
-  wifiPreference.SetValue(enabled);
+UnifiedConsentServiceClientImpl::ServiceState
+UnifiedConsentServiceClientImpl::GetServiceState(Service service) {
+  bool enabled;
+  switch (service) {
+    case Service::kMetricsReporting: {
+      BooleanPrefMember metrics_pref;
+      metrics_pref.Init(metrics::prefs::kMetricsReportingEnabled,
+                        pref_service_);
+      enabled = metrics_pref.GetValue();
+      break;
+    }
+    case Service::kNetworkPrediction: {
+      BooleanPrefMember network_pref;
+      network_pref.Init(prefs::kNetworkPredictionEnabled, pref_service_);
+      enabled = network_pref.GetValue();
+      break;
+    }
+    case Service::kSearchSuggest:
+      enabled = pref_service_->GetBoolean(prefs::kSearchSuggestEnabled);
+      break;
+    case Service::kAlternateErrorPages:
+    case Service::kSafeBrowsing:
+    case Service::kSafeBrowsingExtendedReporting:
+    case Service::kSpellCheck:
+      return ServiceState::kNotSupported;
+  }
+  return enabled ? ServiceState::kEnabled : ServiceState::kDisabled;
 }
 
-void UnifiedConsentServiceClientImpl::SetSearchSuggestEnabled(bool enabled) {
-  pref_service_->SetBoolean(prefs::kSearchSuggestEnabled, enabled);
-}
-
-void UnifiedConsentServiceClientImpl::SetSafeBrowsingEnabled(bool enabled) {
-  // Feature not available on iOS.
-  NOTIMPLEMENTED();
-}
-
-void UnifiedConsentServiceClientImpl::SetSafeBrowsingExtendedReportingEnabled(
-    bool enabled) {
-  // Feature not available on iOS.
-  NOTIMPLEMENTED();
-}
-
-void UnifiedConsentServiceClientImpl::SetNetworkPredictionEnabled(
-    bool enabled) {
-  BooleanPrefMember basePreference;
-  basePreference.Init(prefs::kNetworkPredictionEnabled, pref_service_);
-  basePreference.SetValue(enabled);
-  BooleanPrefMember wifiPreference;
-  wifiPreference.Init(prefs::kNetworkPredictionWifiOnly, pref_service_);
-  wifiPreference.SetValue(enabled);
-}
-
-void UnifiedConsentServiceClientImpl::SetSpellCheckEnabled(bool enabled) {
-  // Feature not available on iOS.
-  NOTIMPLEMENTED();
+void UnifiedConsentServiceClientImpl::SetServiceEnabled(Service service,
+                                                        bool enabled) {
+  switch (service) {
+    case Service::kMetricsReporting: {
+      BooleanPrefMember metrics_pref;
+      metrics_pref.Init(metrics::prefs::kMetricsReportingEnabled,
+                        pref_service_);
+      metrics_pref.SetValue(enabled);
+      BooleanPrefMember metrics_wifi_pref;
+      metrics_wifi_pref.Init(prefs::kMetricsReportingWifiOnly, pref_service_);
+      metrics_wifi_pref.SetValue(enabled);
+      break;
+    }
+    case Service::kNetworkPrediction: {
+      BooleanPrefMember network_pref;
+      network_pref.Init(prefs::kNetworkPredictionEnabled, pref_service_);
+      network_pref.SetValue(enabled);
+      BooleanPrefMember network_wifi_pref;
+      network_wifi_pref.Init(prefs::kNetworkPredictionWifiOnly, pref_service_);
+      network_wifi_pref.SetValue(enabled);
+      break;
+    }
+    case Service::kSearchSuggest:
+      pref_service_->SetBoolean(prefs::kSearchSuggestEnabled, enabled);
+      break;
+    case Service::kAlternateErrorPages:
+    case Service::kSafeBrowsing:
+    case Service::kSafeBrowsingExtendedReporting:
+    case Service::kSpellCheck:
+      NOTIMPLEMENTED() << "Feature not available on iOS";
+      break;
+  }
 }
diff --git a/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.h b/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.h
index 1a424d3..3ba4145 100644
--- a/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.h
+++ b/ios/chrome/browser/unified_consent/unified_consent_service_client_impl.h
@@ -17,13 +17,9 @@
   explicit UnifiedConsentServiceClientImpl(PrefService* pref_service);
   ~UnifiedConsentServiceClientImpl() override = default;
 
-  void SetAlternateErrorPagesEnabled(bool enabled) override;
-  void SetMetricsReportingEnabled(bool enabled) override;
-  void SetSearchSuggestEnabled(bool enabled) override;
-  void SetSafeBrowsingEnabled(bool enabled) override;
-  void SetSafeBrowsingExtendedReportingEnabled(bool enabled) override;
-  void SetNetworkPredictionEnabled(bool enabled) override;
-  void SetSpellCheckEnabled(bool enabled) override;
+  // unified_consent::UnifiedConsentServiceClient overrides:
+  ServiceState GetServiceState(Service service) override;
+  void SetServiceEnabled(Service service, bool enabled) override;
 
  private:
   PrefService* pref_service_;
diff --git a/ios/third_party/material_components_ios/README.chromium b/ios/third_party/material_components_ios/README.chromium
index 3862f80..860bfec6 100644
--- a/ios/third_party/material_components_ios/README.chromium
+++ b/ios/third_party/material_components_ios/README.chromium
@@ -1,7 +1,7 @@
 Name: Material Components for iOS
 URL: https://github.com/material-components/material-components-ios
 Version: 0
-Revision: abbc6a8b2afc648c18569dc9b4b01d0f1a72bc0f
+Revision: 3a50c7a3b2f4ec760d2d2405b6b9787e5493c049
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index 392ef4b0..3e173aa9 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -256,7 +256,6 @@
     "navigation/navigation_manager_impl_unittest.mm",
     "navigation/navigation_manager_util_unittest.mm",
     "navigation/nscoder_util_unittest.mm",
-    "navigation/serializable_user_data_manager_impl_unittest.mm",
     "navigation/wk_based_navigation_manager_impl_unittest.mm",
     "navigation/wk_navigation_util_unittest.mm",
   ]
@@ -578,8 +577,8 @@
     "web_state/error_page_inttest.mm",
     "web_state/favicon_callbacks_inttest.mm",
     "web_state/http_auth_inttest.mm",
-    "web_state/navigation_and_load_callbacks_inttest.mm",
     "web_state/web_frames_manager_inttest.mm",
+    "web_state/web_state_observer_inttest.mm",
     "webui/web_ui_mojo_inttest.mm",
   ]
 
diff --git a/ios/web/navigation/serializable_user_data_manager_impl.h b/ios/web/navigation/serializable_user_data_manager_impl.h
index 9c62d8b..7817356 100644
--- a/ios/web/navigation/serializable_user_data_manager_impl.h
+++ b/ios/web/navigation/serializable_user_data_manager_impl.h
@@ -26,19 +26,7 @@
   // Returns the serializable data.
   NSDictionary<NSString*, id<NSCoding>>* data() { return data_; }
 
-  // Returns the dictionary mapping the key of value that used to be persisted
-  // directly in CRWSessionStorate to the corresponding key when serialised in
-  // SerializableUserData.
-  // TODO(crbug.com/691800): Remove legacy support.
-  static NSDictionary<NSString*, NSString*>* GetLegacyKeyConversion();
-
  private:
-  // Decodes the values that were previously encoded using CRWSessionStorage's
-  // NSCoding implementation and returns an NSDictionary using the new
-  // serialization keys.
-  // TODO(crbug.com/691800): Remove legacy support.
-  NSDictionary<NSString*, id<NSCoding>>* GetDecodedLegacyValues(NSCoder* coder);
-
   // The dictionary passed on initialization.  After calling Decode(), this will
   // contain the data that is decoded from the NSCoder.
   NSDictionary<NSString*, id<NSCoding>>* data_;
diff --git a/ios/web/navigation/serializable_user_data_manager_impl.mm b/ios/web/navigation/serializable_user_data_manager_impl.mm
index 45daf071..2ca66ea 100644
--- a/ios/web/navigation/serializable_user_data_manager_impl.mm
+++ b/ios/web/navigation/serializable_user_data_manager_impl.mm
@@ -71,45 +71,17 @@
 }
 
 void SerializableUserDataImpl::Decode(NSCoder* coder) {
-  NSMutableDictionary<NSString*, id<NSCoding>>* data =
-      [[coder decodeObjectForKey:kSerializedUserDataKey] mutableCopy];
-  if (!data) {
-    // Sessions saved with version M-57 or earlier do not have a serialized
-    // user data. Ensure that |data| is non-null.
-    // TODO(crbug.com/661633): remove this once migration from version M-57
-    // or earlier is no longer supported.
-    data = [NSMutableDictionary dictionary];
+  data_ = [[coder decodeObjectForKey:kSerializedUserDataKey] mutableCopy];
+  if (!data_) {
+    // Ensure that there is always a dictionary even if there was no data
+    // loaded from the coder (this can happen during unit testing or when
+    // loading really old session).
+    data_ = [NSMutableDictionary dictionary];
   }
-  [data addEntriesFromDictionary:GetDecodedLegacyValues(coder)];
-  data_ = [data copy];
   DCHECK(data_);
 }
 
 // static
-NSDictionary<NSString*, NSString*>*
-SerializableUserDataImpl::GetLegacyKeyConversion() {
-  // TODO(crbug.com/661633): those mappings where introduced between M57 and
-  // M58, so remove them after M67 has shipped to stable.
-  return @{
-    @"openerNavigationIndex" : @"OpenerNavigationIndex",
-  };
-}
-
-NSDictionary<NSString*, id<NSCoding>>*
-SerializableUserDataImpl::GetDecodedLegacyValues(NSCoder* coder) {
-  NSMutableDictionary<NSString*, id<NSCoding>>* legacy_values =
-      [[NSMutableDictionary alloc] init];
-  NSDictionary<NSString*, NSString*>* legacy_key_conversion =
-      GetLegacyKeyConversion();
-  for (NSString* legacy_key in [legacy_key_conversion allKeys]) {
-    id<NSCoding> value = [coder decodeObjectForKey:legacy_key];
-    NSString* new_key = [legacy_key_conversion objectForKey:legacy_key];
-    legacy_values[new_key] = value;
-  }
-  return [legacy_values copy];
-}
-
-// static
 SerializableUserDataManager* SerializableUserDataManager::FromWebState(
     web::WebState* web_state) {
   DCHECK(web_state);
diff --git a/ios/web/navigation/serializable_user_data_manager_impl_unittest.mm b/ios/web/navigation/serializable_user_data_manager_impl_unittest.mm
deleted file mode 100644
index bf77b79e..0000000
--- a/ios/web/navigation/serializable_user_data_manager_impl_unittest.mm
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web/navigation/serializable_user_data_manager_impl.h"
-
-#import "ios/web/public/test/fakes/test_web_state.h"
-#import "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-// User Data and Key to use for tests.
-NSString* const kTestUserData = @"TestUserData";
-NSString* const kTestUserDataKey = @"TestUserDataKey";
-}  // namespace
-
-class SerializableUserDataManagerImplTest : public PlatformTest {
- protected:
-  // Convenience getter for the user data manager.
-  web::SerializableUserDataManager* manager() {
-    return web::SerializableUserDataManager::FromWebState(&web_state_);
-  }
-
-  web::TestWebState web_state_;
-};
-
-// Test
-TEST_F(SerializableUserDataManagerImplTest, TestLegacyKeyConversion) {
-  NSDictionary<NSString*, NSString*>* legacy_key_conversion =
-      web::SerializableUserDataImpl::GetLegacyKeyConversion();
-
-  // Create data mapping legacy key to itself.
-  NSMutableData* data = [[NSMutableData alloc] init];
-  NSKeyedArchiver* archiver =
-      [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
-  for (NSString* key : [legacy_key_conversion allKeys]) {
-    [archiver encodeObject:key forKey:key];
-  }
-  [archiver finishEncoding];
-
-  // Decode data and check that legacy key have been converted.
-  std::unique_ptr<web::SerializableUserData> user_data =
-      web::SerializableUserData::Create();
-  NSKeyedUnarchiver* unarchiver =
-      [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
-  user_data->Decode(unarchiver);
-
-  web::TestWebState web_state;
-  web::SerializableUserDataManager* user_data_manager =
-      web::SerializableUserDataManager::FromWebState(&web_state);
-  user_data_manager->AddSerializableUserData(user_data.get());
-
-  // Check that all key have been converted.
-  for (NSString* key : [legacy_key_conversion allKeys]) {
-    id value = user_data_manager->GetValueForSerializationKey(key);
-    EXPECT_NSEQ(nil, value);
-    value = user_data_manager->GetValueForSerializationKey(
-        [legacy_key_conversion objectForKey:key]);
-    EXPECT_NSEQ(key, value);
-  }
-}
diff --git a/ios/web/public/test/fakes/test_web_state_delegate.h b/ios/web/public/test/fakes/test_web_state_delegate.h
index 1bc4ef0..e396a024e 100644
--- a/ios/web/public/test/fakes/test_web_state_delegate.h
+++ b/ios/web/public/test/fakes/test_web_state_delegate.h
@@ -95,7 +95,6 @@
   void CommitPreviewingViewController(
       WebState* source,
       UIViewController* previewing_view_controller) override;
-  bool ShouldAllowAppLaunching(WebState* source) override;
 
   // Allows popups requested by a page with |opener_url|.
   void allow_popups(const GURL& opener_url) {
diff --git a/ios/web/public/test/fakes/test_web_state_delegate.mm b/ios/web/public/test/fakes/test_web_state_delegate.mm
index e3b128b..acbce70 100644
--- a/ios/web/public/test/fakes/test_web_state_delegate.mm
+++ b/ios/web/public/test/fakes/test_web_state_delegate.mm
@@ -127,10 +127,6 @@
   return should_preview_link_;
 }
 
-bool TestWebStateDelegate::ShouldAllowAppLaunching(WebState* source) {
-  return should_allow_app_launching_;
-}
-
 UIViewController* TestWebStateDelegate::GetPreviewingViewController(
     WebState* source,
     const GURL& link_url) {
diff --git a/ios/web/public/web_state/web_state_delegate.h b/ios/web/public/web_state/web_state_delegate.h
index 93206a3..bd48ef2 100644
--- a/ios/web/public/web_state/web_state_delegate.h
+++ b/ios/web/public/web_state/web_state_delegate.h
@@ -89,9 +89,6 @@
       WebState* source,
       UIViewController* previewing_view_controller);
 
-  // Determines whether external applications launching is allowed or not.
-  virtual bool ShouldAllowAppLaunching(WebState* source);
-
  protected:
   virtual ~WebStateDelegate();
 
diff --git a/ios/web/public/web_state/web_state_delegate_bridge.h b/ios/web/public/web_state/web_state_delegate_bridge.h
index f3d64ac..4e0ee9d7 100644
--- a/ios/web/public/web_state/web_state_delegate_bridge.h
+++ b/ios/web/public/web_state/web_state_delegate_bridge.h
@@ -78,9 +78,6 @@
 - (void)webState:(web::WebState*)webState
     commitPreviewingViewController:(UIViewController*)previewingViewController;
 
-// Determines whether external applications launching is allowed or not.
-- (BOOL)isAppLaunchingAllowedForWebState:(web::WebState*)webState;
-
 @end
 
 namespace web {
@@ -116,7 +113,6 @@
   void CommitPreviewingViewController(
       WebState* source,
       UIViewController* previewing_view_controller) override;
-  bool ShouldAllowAppLaunching(WebState* web_state) override;
 
  private:
   // CRWWebStateDelegate which receives forwarded calls.
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index c53c0509..6827f32 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -383,6 +383,7 @@
 - (void)setDOMElementForLastTouch:(NSDictionary*)element {
   _contextMenuInfoForLastTouch.dom_element = [element copy];
   if (_contextMenuNeedsDisplay) {
+    _contextMenuNeedsDisplay = NO;
     UMA_HISTOGRAM_ENUMERATION(kContextMenuDelayedElementDetailsHistogram,
                               DelayedElementDetailsState::Show);
     [self processReceivedDOMElement];
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 5bcee55..d2313fb 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1369,29 +1369,9 @@
   // Transfer time is registered so that further transitions within the time
   // envelope are not also registered as links.
   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
-  bool redirect = transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
-  if (!redirect) {
-    // Before changing phases, the delegate should be informed that any existing
-    // request is being cancelled before completion.
-    [self loadCancelled];
-    DCHECK(_loadPhase == web::PAGE_LOADED);
-  }
 
-  _loadPhase = web::LOAD_REQUESTED;
-
-  // Record the state of outgoing web view. Do nothing if native controller
-  // exists, because in that case recordStateInHistory will record the state
-  // of incoming page as native controller is already inserted.
-  // TODO(crbug.com/811770) Don't record state under WKBasedNavigationManager
-  // because it may incorrectly clobber the incoming page if this is a
-  // back/forward navigation. WKWebView restores page scroll state for web view
-  // pages anyways so this only impacts user if WKWebView is deleted.
-  if (!redirect && !self.nativeController &&
-      !web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
-    [self recordStateInHistory];
-  }
-
-  // Add or update pending url.
+  // Add or update pending item before any WebStateObserver callbacks.
+  // See https://crbug.com/842151 for a scenario where this is important.
   web::NavigationItem* item = self.navigationManagerImpl->GetPendingItem();
   if (item) {
     // Update the existing pending entry.
@@ -1414,6 +1394,28 @@
     item = self.navigationManagerImpl->GetPendingItem();
   }
 
+  bool redirect = transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
+  if (!redirect) {
+    // Before changing phases, the delegate should be informed that any existing
+    // request is being cancelled before completion.
+    [self loadCancelled];
+    DCHECK(_loadPhase == web::PAGE_LOADED);
+  }
+
+  _loadPhase = web::LOAD_REQUESTED;
+
+  // Record the state of outgoing web view. Do nothing if native controller
+  // exists, because in that case recordStateInHistory will record the state
+  // of incoming page as native controller is already inserted.
+  // TODO(crbug.com/811770) Don't record state under WKBasedNavigationManager
+  // because it may incorrectly clobber the incoming page if this is a
+  // back/forward navigation. WKWebView restores page scroll state for web view
+  // pages anyways so this only impacts user if WKWebView is deleted.
+  if (!redirect && !self.nativeController &&
+      !web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    [self recordStateInHistory];
+  }
+
   bool isRendererInitiated =
       item ? (static_cast<web::NavigationItemImpl*>(item)
                   ->NavigationInitiationType() ==
@@ -2915,9 +2917,6 @@
   // handled by the embedder. In that case, update the web controller to
   // correctly reflect the current state.
   if (![CRWWebController webControllerCanShow:requestURL]) {
-    if (!_webStateImpl->ShouldAllowAppLaunching()) {
-      return NO;
-    }
     // Stop load if navigation is believed to be happening on the main frame.
     if ([self isMainFrameNavigationAction:action])
       [self stopLoading];
@@ -2927,6 +2926,9 @@
     // loaded using Go Back or Go Forward navigation (in which case document URL
     // will point to the previous page).  If this is the first load for a
     // NavigationManager, there will be no last committed item, so check here.
+    // TODO(crbug.com/850760): Check if this code is still needed. The current
+    // implementation doesn't put external apps URLs in the history, so they
+    // shouldn't be accessable by Go Back or Go Forward navigation.
     web::NavigationItem* lastCommittedItem =
         self.webState->GetNavigationManager()->GetLastCommittedItem();
     if (lastCommittedItem) {
@@ -4325,7 +4327,7 @@
       [_pendingNavigationInfo setCancelled:YES];
       // Discard the pending item to ensure that the current URL is not
       // different from what is displayed on the view.
-      [self discardNonCommittedItemsIfLastCommittedWasNotNativeView];
+      self.navigationManagerImpl->DiscardNonCommittedItems();
       if (!_isBeingDestroyed && [self shouldClosePageOnNativeApplicationLoad])
         _webStateImpl->CloseWebState();
     }
@@ -4837,6 +4839,17 @@
 
     web::NavigationItemImpl* item = web::GetItemWithUniqueID(
         self.navigationManagerImpl, context->GetNavigationItemUniqueID());
+    if (!IsWKInternalUrl(currentWKItemURL) && currentWKItemURL == webViewURL &&
+        currentWKItemURL != context->GetUrl()) {
+      // WKWebView sometimes changes URL on the same navigation, likely due to
+      // location.replace() in onload handler that only changes page fragment.
+      // It's safe to update |item| and |context| URL because they are both
+      // associated to WKNavigation*, which is a stable ID for the navigation.
+      // See https://crbug.com/869540 for a real-world case.
+      DCHECK(item->GetURL().EqualsIgnoringRef(currentWKItemURL));
+      item->SetURL(currentWKItemURL);
+      context->SetUrl(currentWKItemURL);
+    }
 
     if (IsPlaceholderUrl(webViewURL)) {
       GURL originalURL = ExtractUrlFromPlaceholderUrl(webViewURL);
diff --git a/ios/web/web_state/web_state_delegate.mm b/ios/web/web_state/web_state_delegate.mm
index 0512352..f7ccd45 100644
--- a/ios/web/web_state/web_state_delegate.mm
+++ b/ios/web/web_state/web_state_delegate.mm
@@ -72,10 +72,6 @@
     WebState* source,
     UIViewController* previewing_view_controller) {}
 
-bool WebStateDelegate::ShouldAllowAppLaunching(WebState* source) {
-  return false;
-}
-
 void WebStateDelegate::Attach(WebState* source) {
   DCHECK(attached_states_.find(source) == attached_states_.end());
   attached_states_.insert(source);
diff --git a/ios/web/web_state/web_state_delegate_bridge.mm b/ios/web/web_state/web_state_delegate_bridge.mm
index e181560..90b44be 100644
--- a/ios/web/web_state/web_state_delegate_bridge.mm
+++ b/ios/web/web_state/web_state_delegate_bridge.mm
@@ -132,12 +132,4 @@
   }
 }
 
-bool WebStateDelegateBridge::ShouldAllowAppLaunching(WebState* source) {
-  if ([delegate_
-          respondsToSelector:@selector(isAppLaunchingAllowedForWebState:)]) {
-    return [delegate_ isAppLaunchingAllowedForWebState:source];
-  }
-  return false;
-}
-
 }  // web
diff --git a/ios/web/web_state/web_state_delegate_bridge_unittest.mm b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
index a87bb8d..bae5bad1 100644
--- a/ios/web/web_state/web_state_delegate_bridge_unittest.mm
+++ b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
@@ -216,17 +216,4 @@
   EXPECT_EQ(previewing_view_controller, delegate_.previewingViewController);
 }
 
-// Tests |ShouldAllowAppLaunching| forwarding.
-TEST_F(WebStateDelegateBridgeTest, ShouldAllowAppLaunching) {
-  EXPECT_FALSE(delegate_.webState);
-
-  delegate_.isAppLaunchingAllowedForWebStateReturnValue = YES;
-  EXPECT_TRUE(bridge_->ShouldAllowAppLaunching(&test_web_state_));
-  EXPECT_EQ(&test_web_state_, delegate_.webState);
-
-  delegate_.isAppLaunchingAllowedForWebStateReturnValue = NO;
-  EXPECT_FALSE(bridge_->ShouldAllowAppLaunching(&test_web_state_));
-  EXPECT_EQ(&test_web_state_, delegate_.webState);
-}
-
 }  // namespace web
diff --git a/ios/web/web_state/web_state_impl.h b/ios/web/web_state/web_state_impl.h
index 73c2eb8..42fb10b 100644
--- a/ios/web/web_state/web_state_impl.h
+++ b/ios/web/web_state/web_state_impl.h
@@ -268,9 +268,6 @@
                       NSURLCredential* proposed_credential,
                       const WebStateDelegate::AuthCallback& callback);
 
-  // Asks the delegate if Application launching is allowed.
-  bool ShouldAllowAppLaunching();
-
   // Cancels all dialogs associated with this web_state.
   void CancelDialogs();
 
diff --git a/ios/web/web_state/web_state_impl.mm b/ios/web/web_state/web_state_impl.mm
index 58707be..3fa60c5 100644
--- a/ios/web/web_state/web_state_impl.mm
+++ b/ios/web/web_state/web_state_impl.mm
@@ -460,12 +460,6 @@
   }
 }
 
-bool WebStateImpl::ShouldAllowAppLaunching() {
-  if (delegate_)
-    return delegate_->ShouldAllowAppLaunching(this);
-  return false;
-}
-
 void WebStateImpl::CancelDialogs() {
   if (delegate_) {
     JavaScriptDialogPresenter* presenter =
diff --git a/ios/web/web_state/web_state_impl_unittest.mm b/ios/web/web_state/web_state_impl_unittest.mm
index 609cfbe..2c04802 100644
--- a/ios/web/web_state/web_state_impl_unittest.mm
+++ b/ios/web/web_state/web_state_impl_unittest.mm
@@ -636,12 +636,6 @@
   web_state_->CommitPreviewingViewController(previewing_view_controller);
   EXPECT_EQ(previewing_view_controller,
             delegate.last_previewing_view_controller());
-
-  // Test that ShouldAllowAppLaunching() is delegated correctly.
-  delegate.SetShouldAllowAppLaunching(true);
-  EXPECT_TRUE(web_state_->ShouldAllowAppLaunching());
-  delegate.SetShouldAllowAppLaunching(false);
-  EXPECT_FALSE(web_state_->ShouldAllowAppLaunching());
 }
 
 // Verifies that GlobalWebStateObservers are called when expected.
diff --git a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
similarity index 97%
rename from ios/web/web_state/navigation_and_load_callbacks_inttest.mm
rename to ios/web/web_state/web_state_observer_inttest.mm
index 84a7142..a7d4a6e 100644
--- a/ios/web/web_state/navigation_and_load_callbacks_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -50,7 +50,7 @@
 
 const char kExpectedMimeType[] = "text/html";
 
-// NavigationAndLoadCallbacksTest is parameterized on this enum to test both
+// WebStateObserverTest is parameterized on this enum to test both
 // LegacyNavigationManagerImpl and WKBasedNavigationManagerImpl.
 enum NavigationManagerChoice {
   TEST_LEGACY_NAVIGATION_MANAGER,
@@ -527,11 +527,11 @@
 
 // Test fixture to test navigation and load callbacks from WebStateObserver and
 // WebStatePolicyDecider.
-class NavigationAndLoadCallbacksTest
+class WebStateObserverTest
     : public WebIntTest,
       public ::testing::WithParamInterface<NavigationManagerChoice> {
  public:
-  NavigationAndLoadCallbacksTest() : scoped_observer_(&observer_) {}
+  WebStateObserverTest() : scoped_observer_(&observer_) {}
 
   void SetUp() override {
     if (GetParam() == TEST_LEGACY_NAVIGATION_MANAGER) {
@@ -584,11 +584,11 @@
   ScopedObserver<WebState, WebStateObserver> scoped_observer_;
   testing::InSequence callbacks_sequence_checker_;
 
-  DISALLOW_COPY_AND_ASSIGN(NavigationAndLoadCallbacksTest);
+  DISALLOW_COPY_AND_ASSIGN(WebStateObserverTest);
 };
 
 // Tests successful navigation to a new page.
-TEST_P(NavigationAndLoadCallbacksTest, NewPageNavigation) {
+TEST_P(WebStateObserverTest, NewPageNavigation) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Perform new page navigation.
@@ -620,7 +620,7 @@
 // Tests that if web usage is already enabled, enabling it again would not cause
 // any page loads (related to restoring cached session). This is a regression
 // test for crbug.com/781916.
-TEST_P(NavigationAndLoadCallbacksTest, EnableWebUsageTwice) {
+TEST_P(WebStateObserverTest, EnableWebUsageTwice) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Only expect one set of load events from the first LoadUrl(), not subsequent
@@ -655,7 +655,7 @@
 }
 
 // Tests failed navigation to a new page.
-TEST_P(NavigationAndLoadCallbacksTest, FailedNavigation) {
+TEST_P(WebStateObserverTest, FailedNavigation) {
   // TODO(crbug.com/851119): temporarily disable this failing test.
   if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
     return;
@@ -698,7 +698,7 @@
 }
 
 // Tests web page reload navigation.
-TEST_P(NavigationAndLoadCallbacksTest, WebPageReloadNavigation) {
+TEST_P(WebStateObserverTest, WebPageReloadNavigation) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Perform new page navigation.
@@ -754,7 +754,7 @@
 }
 
 // Tests web page reload with user agent override.
-TEST_P(NavigationAndLoadCallbacksTest, ReloadWithUserAgentType) {
+TEST_P(WebStateObserverTest, ReloadWithUserAgentType) {
   // TODO(crbug.com/851119): temporarily disable this failing test.
   if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
     return;
@@ -805,7 +805,7 @@
 }
 
 // Tests user-initiated hash change.
-TEST_P(NavigationAndLoadCallbacksTest, UserInitiatedHashChangeNavigation) {
+TEST_P(WebStateObserverTest, UserInitiatedHashChangeNavigation) {
   // TODO(crbug.com/851119): temporarily disable this failing test.
   if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
     return;
@@ -888,7 +888,7 @@
 }
 
 // Tests renderer-initiated hash change.
-TEST_P(NavigationAndLoadCallbacksTest, RendererInitiatedHashChangeNavigation) {
+TEST_P(WebStateObserverTest, RendererInitiatedHashChangeNavigation) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Perform new page navigation.
@@ -943,7 +943,7 @@
 }
 
 // Tests state change.
-TEST_P(NavigationAndLoadCallbacksTest, StateNavigation) {
+TEST_P(WebStateObserverTest, StateNavigation) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Perform new page navigation.
@@ -1008,7 +1008,7 @@
 }
 
 // Tests native content navigation.
-TEST_P(NavigationAndLoadCallbacksTest, NativeContentNavigation) {
+TEST_P(WebStateObserverTest, NativeContentNavigation) {
   GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
   NavigationContext* context = nullptr;
   int32_t nav_id = 0;
@@ -1030,7 +1030,7 @@
 }
 
 // Tests native content reload navigation.
-TEST_P(NavigationAndLoadCallbacksTest, NativeContentReload) {
+TEST_P(WebStateObserverTest, NativeContentReload) {
   GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
   EXPECT_CALL(observer_, DidStartLoading(web_state()));
   EXPECT_CALL(observer_, DidStartNavigation(web_state(), _));
@@ -1065,7 +1065,7 @@
 
 // Tests WasTitleSet callback triggered when navigating back to native content
 // from web content.
-TEST_P(NavigationAndLoadCallbacksTest, GoBackToNativeContent) {
+TEST_P(WebStateObserverTest, GoBackToNativeContent) {
   // Load a native content URL.
   GURL url(url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize());
   EXPECT_CALL(observer_, DidStartLoading(web_state()));
@@ -1136,7 +1136,7 @@
 }
 
 // Tests successful navigation to a new page with post HTTP method.
-TEST_P(NavigationAndLoadCallbacksTest, UserInitiatedPostNavigation) {
+TEST_P(WebStateObserverTest, UserInitiatedPostNavigation) {
   // Prior to iOS 11, POST navigation is implemented as an XMLHttpRequest in
   // JavaScript. This doesn't create a WKBackForwardListItem in WKWebView on
   // which to attach the pending NavigationItem, if WKBasedNavigationManager is
@@ -1216,7 +1216,7 @@
 }
 
 // Tests successful navigation to a new page with post HTTP method.
-TEST_P(NavigationAndLoadCallbacksTest, RendererInitiatedPostNavigation) {
+TEST_P(WebStateObserverTest, RendererInitiatedPostNavigation) {
   const GURL url = test_server_->GetURL("/form?echo");
   const GURL action = test_server_->GetURL("/echo");
 
@@ -1273,7 +1273,7 @@
 }
 
 // Tests successful reload of a page returned for post request.
-TEST_P(NavigationAndLoadCallbacksTest, ReloadPostNavigation) {
+TEST_P(WebStateObserverTest, ReloadPostNavigation) {
   const GURL url = test_server_->GetURL("/form?echo");
   const GURL action = test_server_->GetURL("/echo");
 
@@ -1370,7 +1370,7 @@
 }
 
 // Tests going forward to a page rendered from post response.
-TEST_P(NavigationAndLoadCallbacksTest, ForwardPostNavigation) {
+TEST_P(WebStateObserverTest, ForwardPostNavigation) {
   // TODO(crbug.com/851119): temporarily disable this failing test.
   if (GetParam() == TEST_WK_BASED_NAVIGATION_MANAGER) {
     return;
@@ -1493,7 +1493,7 @@
 }
 
 // Tests server redirect navigation.
-TEST_P(NavigationAndLoadCallbacksTest, RedirectNavigation) {
+TEST_P(WebStateObserverTest, RedirectNavigation) {
   const GURL url = test_server_->GetURL("/server-redirect?echo");
   const GURL redirect_url = test_server_->GetURL("/echo");
 
@@ -1528,7 +1528,7 @@
 }
 
 // Tests download navigation.
-TEST_P(NavigationAndLoadCallbacksTest, DownloadNavigation) {
+TEST_P(WebStateObserverTest, DownloadNavigation) {
   GURL url = test_server_->GetURL("/download");
 
   // Perform download navigation.
@@ -1563,7 +1563,7 @@
 #else
 #define MAYBE_FailedLoad FLAKY_FailedLoad
 #endif
-TEST_P(NavigationAndLoadCallbacksTest, FLAKY_FailedLoad) {
+TEST_P(WebStateObserverTest, FLAKY_FailedLoad) {
   GURL url = test_server_->GetURL("/exabyte_response");
 
   NavigationContext* context = nullptr;
@@ -1603,7 +1603,7 @@
 
 // Tests rejecting the navigation from ShouldAllowRequest. The load should stop,
 // but no other callbacks are called.
-TEST_P(NavigationAndLoadCallbacksTest, DisallowRequest) {
+TEST_P(WebStateObserverTest, DisallowRequest) {
   EXPECT_CALL(observer_, DidStartLoading(web_state()));
   WebStatePolicyDecider::RequestInfo expected_request_info(
       ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT,
@@ -1619,7 +1619,7 @@
 
 // Tests rejecting the navigation from ShouldAllowResponse. PageLoaded callback
 // is not called.
-TEST_P(NavigationAndLoadCallbacksTest, DisallowResponse) {
+TEST_P(WebStateObserverTest, DisallowResponse) {
   const GURL url = test_server_->GetURL("/echo");
 
   // Perform new page navigation.
@@ -1648,7 +1648,7 @@
 
 // Tests stopping a navigation. Did FinishLoading and PageLoaded are never
 // called.
-TEST_P(NavigationAndLoadCallbacksTest, StopNavigation) {
+TEST_P(WebStateObserverTest, StopNavigation) {
   EXPECT_CALL(observer_, DidStartLoading(web_state()));
   EXPECT_CALL(observer_, DidStopLoading(web_state()));
   WebStatePolicyDecider::RequestInfo expected_request_info(
@@ -1664,7 +1664,7 @@
 }
 
 // Tests stopping a finished navigation. PageLoaded is never called.
-TEST_P(NavigationAndLoadCallbacksTest, StopFinishedNavigation) {
+TEST_P(WebStateObserverTest, StopFinishedNavigation) {
   GURL url = test_server_->GetURL("/exabyte_response");
 
   NavigationContext* context = nullptr;
@@ -1701,7 +1701,7 @@
 }
 
 // Tests that iframe navigation triggers DidChangeBackForwardState.
-TEST_P(NavigationAndLoadCallbacksTest, IframeNavigation) {
+TEST_P(WebStateObserverTest, IframeNavigation) {
   // LegacyNavigationManager doesn't support iframe navigation history.
   if (!GetWebClient()->IsSlimNavigationManagerEnabled())
     return;
@@ -1785,8 +1785,8 @@
 }
 
 INSTANTIATE_TEST_CASE_P(
-    ProgrammaticNavigationAndLoadCallbacksTest,
-    NavigationAndLoadCallbacksTest,
+    ProgrammaticWebStateObserverTest,
+    WebStateObserverTest,
     ::testing::Values(
         NavigationManagerChoice::TEST_LEGACY_NAVIGATION_MANAGER,
         NavigationManagerChoice::TEST_WK_BASED_NAVIGATION_MANAGER));
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 53db35cbe..68f7fbd 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -125,6 +125,16 @@
   "internal/language/web_view_language_model_manager_factory.h",
   "internal/language/web_view_url_language_histogram_factory.cc",
   "internal/language/web_view_url_language_histogram_factory.h",
+  "internal/passwords/mock_credentials_filter.h",
+  "internal/passwords/mock_credentials_filter.mm",
+  "internal/passwords/web_view_password_manager_client.h",
+  "internal/passwords/web_view_password_manager_client.mm",
+  "internal/passwords/web_view_password_manager_driver.h",
+  "internal/passwords/web_view_password_manager_driver.mm",
+  "internal/passwords/web_view_password_manager_internals_service_factory.h",
+  "internal/passwords/web_view_password_manager_internals_service_factory.mm",
+  "internal/passwords/web_view_password_store_factory.h",
+  "internal/passwords/web_view_password_store_factory.mm",
   "internal/pref_names.cc",
   "internal/pref_names.h",
   "internal/signin/ios_web_view_signin_client.h",
@@ -229,6 +239,11 @@
   "//components/language/core/browser",
   "//components/language/core/common",
   "//components/net_log",
+  "//components/network_time",
+  "//components/password_manager/core/browser",
+  "//components/password_manager/core/browser/form_parsing:form_parsing",
+  "//components/password_manager/core/common",
+  "//components/password_manager/sync/browser",
   "//components/pref_registry",
   "//components/prefs",
   "//components/proxy_config",
@@ -252,6 +267,7 @@
   "//net",
   "//net:extras",
   "//services/identity/public/cpp",
+  "//services/metrics/public/cpp:metrics_cpp",
   "//ui/base",
   "//url",
 ]
diff --git a/ios/web_view/internal/DEPS b/ios/web_view/internal/DEPS
index ef4b037..f533982 100644
--- a/ios/web_view/internal/DEPS
+++ b/ios/web_view/internal/DEPS
@@ -12,6 +12,7 @@
   "+components/language/core/browser",
   "+components/language/core/common",
   "+components/net_log",
+  "+components/password_manager",
   "+components/pref_registry",
   "+components/prefs",
   "+components/proxy_config",
diff --git a/ios/web_view/internal/cwv_preferences_unittest.mm b/ios/web_view/internal/cwv_preferences_unittest.mm
index bb3ce4d9..f3a68e2 100644
--- a/ios/web_view/internal/cwv_preferences_unittest.mm
+++ b/ios/web_view/internal/cwv_preferences_unittest.mm
@@ -9,6 +9,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "components/autofill/core/common/autofill_pref_names.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/in_memory_pref_store.h"
 #include "components/prefs/pref_service.h"
@@ -31,6 +32,8 @@
     scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry =
         new user_prefs::PrefRegistrySyncable;
     pref_registry->RegisterBooleanPref(autofill::prefs::kAutofillEnabled, true);
+    pref_registry->RegisterBooleanPref(
+        password_manager::prefs::kCredentialsEnableService, true);
     pref_registry->RegisterBooleanPref(prefs::kOfferTranslateEnabled, true);
 
     scoped_refptr<PersistentPrefStore> pref_store = new InMemoryPrefStore();
diff --git a/ios/web_view/internal/passwords/mock_credentials_filter.h b/ios/web_view/internal/passwords/mock_credentials_filter.h
new file mode 100644
index 0000000..75a58085
--- /dev/null
+++ b/ios/web_view/internal/passwords/mock_credentials_filter.h
@@ -0,0 +1,46 @@
+// Copyright 2018 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_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
+
+#include "base/macros.h"
+#include "components/password_manager/core/browser/credentials_filter.h"
+
+namespace ios_web_view {
+
+// Mock of the CredentialsFilter API, to be used in tests. This filter does
+// not filter out anything.
+class MockCredentialsFilter : public password_manager::CredentialsFilter {
+ public:
+  MockCredentialsFilter();
+
+  ~MockCredentialsFilter() override;
+
+  // CredentialsFilter
+  std::vector<std::unique_ptr<autofill::PasswordForm>> FilterResults(
+      std::vector<std::unique_ptr<autofill::PasswordForm>> results)
+      const override;
+  bool ShouldSave(const autofill::PasswordForm& form) const override;
+  bool ShouldSaveGaiaPasswordHash(
+      const autofill::PasswordForm& form) const override;
+  bool ShouldSaveEnterprisePasswordHash(
+      const autofill::PasswordForm& form) const override;
+  void ReportFormLoginSuccess(
+      const password_manager::PasswordFormManager& form_manager) const override;
+  bool IsSyncAccountEmail(const std::string& username) const override;
+
+  // A version of FilterResult without moveable arguments, which cannot be
+  // mocked in GMock. MockCredentialsFilter::FilterResults(arg) calls
+  // FilterResultsPtr(&arg).
+  virtual void FilterResultsPtr(
+      std::vector<std::unique_ptr<autofill::PasswordForm>>* results) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockCredentialsFilter);
+};
+
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_MOCK_CREDENTIALS_FILTER_H_
diff --git a/ios/web_view/internal/passwords/mock_credentials_filter.mm b/ios/web_view/internal/passwords/mock_credentials_filter.mm
new file mode 100644
index 0000000..dc1a34aa
--- /dev/null
+++ b/ios/web_view/internal/passwords/mock_credentials_filter.mm
@@ -0,0 +1,50 @@
+// Copyright 2018 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 "ios/web_view/internal/passwords/mock_credentials_filter.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ios_web_view {
+
+MockCredentialsFilter::MockCredentialsFilter() = default;
+
+MockCredentialsFilter::~MockCredentialsFilter() = default;
+
+std::vector<std::unique_ptr<autofill::PasswordForm>>
+MockCredentialsFilter::FilterResults(
+    std::vector<std::unique_ptr<autofill::PasswordForm>> results) const {
+  FilterResultsPtr(&results);
+  return results;
+}
+
+bool MockCredentialsFilter::ShouldSave(
+    const autofill::PasswordForm& form) const {
+  return true;
+}
+
+bool MockCredentialsFilter::ShouldSaveGaiaPasswordHash(
+    const autofill::PasswordForm& form) const {
+  return false;
+}
+
+bool MockCredentialsFilter::ShouldSaveEnterprisePasswordHash(
+    const autofill::PasswordForm& form) const {
+  return false;
+}
+
+void MockCredentialsFilter::ReportFormLoginSuccess(
+    const password_manager::PasswordFormManager& form_manager) const {}
+
+bool MockCredentialsFilter::IsSyncAccountEmail(
+    const std::string& username) const {
+  return false;
+}
+
+void MockCredentialsFilter::FilterResultsPtr(
+    std::vector<std::unique_ptr<autofill::PasswordForm>>* results) const {}
+
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.h b/ios/web_view/internal/passwords/web_view_password_manager_client.h
new file mode 100644
index 0000000..50a30d65
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.h
@@ -0,0 +1,119 @@
+// Copyright 2018 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_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_CLIENT_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_CLIENT_H_
+
+#include "base/macros.h"
+#import "components/password_manager/core/browser/password_manager_client.h"
+#include "components/password_manager/core/browser/password_manager_client_helper.h"
+#include "components/password_manager/core/browser/password_manager_metrics_recorder.h"
+#include "components/prefs/pref_member.h"
+#include "ios/web_view/internal/passwords/mock_credentials_filter.h"
+
+namespace ios_web_view {
+class WebViewBrowserState;
+}
+
+namespace password_manager {
+class PasswordFormManagerForUI;
+}
+
+@protocol CWVPasswordManagerClientDelegate
+
+// Shows UI to prompt the user to save the password.
+- (void)showSavePasswordInfoBar:
+    (std::unique_ptr<password_manager::PasswordFormManagerForUI>)formToSave;
+
+// Shows UI to prompt the user to update the password.
+- (void)showUpdatePasswordInfoBar:
+    (std::unique_ptr<password_manager::PasswordFormManagerForUI>)formToUpdate;
+
+// Shows UI to notify the user about auto sign in.
+- (void)showAutosigninNotification:
+    (std::unique_ptr<autofill::PasswordForm>)formSignedIn;
+
+@property(readonly, nonatomic) ios_web_view::WebViewBrowserState* browserState;
+
+@property(readonly, nonatomic)
+    password_manager::PasswordManager* passwordManager;
+
+@property(readonly, nonatomic) const GURL& lastCommittedURL;
+
+@end
+
+namespace ios_web_view {
+// An //ios/web_view implementation of password_manager::PasswordManagerClient.
+class WebViewPasswordManagerClient
+    : public password_manager::PasswordManagerClient,
+      public password_manager::PasswordManagerClientHelperDelegate {
+ public:
+  explicit WebViewPasswordManagerClient(
+      id<CWVPasswordManagerClientDelegate> delegate);
+
+  ~WebViewPasswordManagerClient() override;
+
+  // password_manager::PasswordManagerClient implementation.
+  password_manager::SyncState GetPasswordSyncState() const override;
+  bool PromptUserToSaveOrUpdatePassword(
+      std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
+      bool update_password) override;
+  void ShowManualFallbackForSaving(
+      std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
+      bool has_generated_password,
+      bool is_update) override;
+  void HideManualFallbackForSaving() override;
+  bool PromptUserToChooseCredentials(
+      std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms,
+      const GURL& origin,
+      const CredentialsCallback& callback) override;
+  void AutomaticPasswordSave(
+      std::unique_ptr<password_manager::PasswordFormManagerForUI>
+          saved_form_manager) override;
+  bool IsIncognito() const override;
+  const password_manager::PasswordManager* GetPasswordManager() const override;
+  PrefService* GetPrefs() const override;
+  password_manager::PasswordStore* GetPasswordStore() const override;
+  void NotifyUserAutoSignin(
+      std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms,
+      const GURL& origin) override;
+  void NotifyUserCouldBeAutoSignedIn(
+      std::unique_ptr<autofill::PasswordForm> form) override;
+  void NotifySuccessfulLoginWithExistingPassword(
+      const autofill::PasswordForm& form) override;
+  void NotifyStorePasswordCalled() override;
+  bool IsSavingAndFillingEnabledForCurrentPage() const override;
+  const GURL& GetLastCommittedEntryURL() const override;
+  const password_manager::CredentialsFilter* GetStoreResultFilter()
+      const override;
+  const password_manager::LogManager* GetLogManager() const override;
+  ukm::SourceId GetUkmSourceId() override;
+  password_manager::PasswordManagerMetricsRecorder* GetMetricsRecorder()
+      override;
+
+ private:
+  // password_manager::PasswordManagerClientHelperDelegate implementation.
+  void PromptUserToEnableAutosignin() override;
+  password_manager::PasswordManager* GetPasswordManager() override;
+
+  id<CWVPasswordManagerClientDelegate> delegate_;  // (weak)
+
+  // The preference associated with
+  // password_manager::prefs::kCredentialsEnableService.
+  BooleanPrefMember saving_passwords_enabled_;
+
+  // TODO(crbug.com/867297) Replace with SyncCredentialsFilter
+  const MockCredentialsFilter credentials_filter_;
+
+  std::unique_ptr<password_manager::LogManager> log_manager_;
+
+  // Helper for performing logic that is common between
+  // ChromePasswordManagerClient and IOSChromePasswordManagerClient.
+  password_manager::PasswordManagerClientHelper helper_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebViewPasswordManagerClient);
+};
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_CLIENT_H_
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.mm b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
new file mode 100644
index 0000000..a42e38e
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
@@ -0,0 +1,182 @@
+// Copyright 2018 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/web_view/internal/passwords/web_view_password_manager_client.h"
+
+#include <memory>
+#include <utility>
+
+#include "components/autofill/core/common/password_form.h"
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/password_manager/core/browser/log_manager.h"
+#include "components/password_manager/core/browser/password_form_manager_for_ui.h"
+#include "components/password_manager/core/browser/password_manager.h"
+#include "components/password_manager/core/browser/password_manager_internals_service.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/signin/core/browser/signin_manager_base.h"
+#include "ios/web_view/internal/app/application_context.h"
+#import "ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h"
+#include "ios/web_view/internal/passwords/web_view_password_store_factory.h"
+#include "ios/web_view/internal/signin/web_view_signin_manager_factory.h"
+#include "ios/web_view/internal/web_view_browser_state.h"
+#include "net/cert/cert_status_flags.h"
+#include "url/gurl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using password_manager::PasswordFormManagerForUI;
+using password_manager::PasswordManagerMetricsRecorder;
+using password_manager::PasswordStore;
+using password_manager::SyncState;
+
+// TODO(crbug.com/867297): Support sync service and signin manager.
+
+namespace ios_web_view {
+// TODO(crbug.com/867297): Replace with sync credentials filter.
+WebViewPasswordManagerClient::WebViewPasswordManagerClient(
+    id<CWVPasswordManagerClientDelegate> delegate)
+    : delegate_(delegate),
+      credentials_filter_(),
+      log_manager_(password_manager::LogManager::Create(
+          ios_web_view::WebViewPasswordManagerInternalsServiceFactory::
+              GetForBrowserState(delegate_.browserState),
+          base::RepeatingClosure())),
+      helper_(this) {
+  saving_passwords_enabled_.Init(
+      password_manager::prefs::kCredentialsEnableService, GetPrefs());
+}
+
+WebViewPasswordManagerClient::~WebViewPasswordManagerClient() = default;
+
+SyncState WebViewPasswordManagerClient::GetPasswordSyncState() const {
+  // Disable sync for Demo.
+  // TODO(crbug.com/867297): Enable sync.
+  return password_manager::NOT_SYNCING;
+}
+
+bool WebViewPasswordManagerClient::PromptUserToChooseCredentials(
+    std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms,
+    const GURL& origin,
+    const CredentialsCallback& callback) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool WebViewPasswordManagerClient::PromptUserToSaveOrUpdatePassword(
+    std::unique_ptr<PasswordFormManagerForUI> form_to_save,
+    bool update_password) {
+  if (form_to_save->IsBlacklisted()) {
+    return false;
+  }
+
+  if (update_password) {
+    [delegate_ showUpdatePasswordInfoBar:std::move(form_to_save)];
+  } else {
+    [delegate_ showSavePasswordInfoBar:std::move(form_to_save)];
+  }
+
+  return true;
+}
+
+void WebViewPasswordManagerClient::ShowManualFallbackForSaving(
+    std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
+    bool has_generated_password,
+    bool is_update) {
+  NOTIMPLEMENTED();
+}
+
+void WebViewPasswordManagerClient::HideManualFallbackForSaving() {
+  NOTIMPLEMENTED();
+}
+
+void WebViewPasswordManagerClient::AutomaticPasswordSave(
+    std::unique_ptr<PasswordFormManagerForUI> saved_form_manager) {
+  NOTIMPLEMENTED();
+}
+
+bool WebViewPasswordManagerClient::IsIncognito() const {
+  return delegate_.browserState->IsOffTheRecord();
+}
+
+const password_manager::PasswordManager*
+WebViewPasswordManagerClient::GetPasswordManager() const {
+  return delegate_.passwordManager;
+}
+
+PrefService* WebViewPasswordManagerClient::GetPrefs() const {
+  return delegate_.browserState->GetPrefs();
+}
+
+PasswordStore* WebViewPasswordManagerClient::GetPasswordStore() const {
+  return ios_web_view::WebViewPasswordStoreFactory::GetForBrowserState(
+             delegate_.browserState, ServiceAccessType::EXPLICIT_ACCESS)
+      .get();
+}
+
+void WebViewPasswordManagerClient::NotifyUserAutoSignin(
+    std::vector<std::unique_ptr<autofill::PasswordForm>> local_forms,
+    const GURL& origin) {
+  DCHECK(!local_forms.empty());
+  helper_.NotifyUserAutoSignin();
+  [delegate_ showAutosigninNotification:std::move(local_forms[0])];
+}
+
+void WebViewPasswordManagerClient::NotifyUserCouldBeAutoSignedIn(
+    std::unique_ptr<autofill::PasswordForm> form) {
+  helper_.NotifyUserCouldBeAutoSignedIn(std::move(form));
+}
+
+void WebViewPasswordManagerClient::NotifySuccessfulLoginWithExistingPassword(
+    const autofill::PasswordForm& form) {
+  helper_.NotifySuccessfulLoginWithExistingPassword(form);
+}
+
+void WebViewPasswordManagerClient::NotifyStorePasswordCalled() {
+  helper_.NotifyStorePasswordCalled();
+}
+
+bool WebViewPasswordManagerClient::IsSavingAndFillingEnabledForCurrentPage()
+    const {
+  return *saving_passwords_enabled_ && !IsIncognito() &&
+         !net::IsCertStatusError(GetMainFrameCertStatus()) &&
+         IsFillingEnabledForCurrentPage();
+}
+
+const GURL& WebViewPasswordManagerClient::GetLastCommittedEntryURL() const {
+  return delegate_.lastCommittedURL;
+}
+
+const password_manager::CredentialsFilter*
+WebViewPasswordManagerClient::GetStoreResultFilter() const {
+  return &credentials_filter_;
+}
+
+const password_manager::LogManager*
+WebViewPasswordManagerClient::GetLogManager() const {
+  return log_manager_.get();
+}
+
+ukm::SourceId WebViewPasswordManagerClient::GetUkmSourceId() {
+  // We don't collect UKM metrics from //ios/web_view.
+  return ukm::kInvalidSourceId;
+}
+
+PasswordManagerMetricsRecorder*
+WebViewPasswordManagerClient::GetMetricsRecorder() {
+  // We don't collect UKM metrics from //ios/web_view.
+  return nullptr;
+}
+
+void WebViewPasswordManagerClient::PromptUserToEnableAutosignin() {
+  // TODO(crbug.com/435048): Implement this method.
+}
+
+password_manager::PasswordManager*
+WebViewPasswordManagerClient::GetPasswordManager() {
+  return delegate_.passwordManager;
+}
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_driver.h b/ios/web_view/internal/passwords/web_view_password_manager_driver.h
new file mode 100644
index 0000000..5b24f1fb
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_driver.h
@@ -0,0 +1,77 @@
+// Copyright 2018 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_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_DRIVER_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_DRIVER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "components/password_manager/core/browser/password_manager_driver.h"
+
+namespace autofill {
+struct PasswordForm;
+struct PasswordFormFillData;
+}  // namespace autofill
+
+namespace password_manager {
+class PasswordAutofillManager;
+class PasswordManager;
+}  // namespace password_manager
+
+// Defines the interface the driver needs to the controller.
+@protocol CWVPasswordManagerDriverDelegate
+
+- (password_manager::PasswordManager*)passwordManager;
+
+// Finds and fills the password form using the supplied |formData| to
+// match the password form and to populate the field values.
+- (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData;
+
+// Informs delegate that there are no saved credentials for the current page.
+- (void)informNoSavedCredentials;
+@end
+
+namespace ios_web_view {
+// An //ios/web_view implementation of password_manager::PasswordManagerDriver.
+class WebViewPasswordManagerDriver
+    : public password_manager::PasswordManagerDriver {
+ public:
+  explicit WebViewPasswordManagerDriver(
+      id<CWVPasswordManagerDriverDelegate> delegate);
+  ~WebViewPasswordManagerDriver() override;
+
+  // password_manager::PasswordManagerDriver implementation.
+  void FillPasswordForm(
+      const autofill::PasswordFormFillData& form_data) override;
+  void InformNoSavedCredentials() override;
+  void AllowPasswordGenerationForForm(
+      const autofill::PasswordForm& form) override;
+  void FormsEligibleForGenerationFound(
+      const std::vector<autofill::PasswordFormGenerationData>& forms) override;
+  void GeneratedPasswordAccepted(const base::string16& password) override;
+  void FillSuggestion(const base::string16& username,
+                      const base::string16& password) override;
+  void PreviewSuggestion(const base::string16& username,
+                         const base::string16& password) override;
+  void ShowInitialPasswordAccountSuggestions(
+      const autofill::PasswordFormFillData& form_data) override;
+  void ClearPreviewedForm() override;
+  password_manager::PasswordGenerationManager* GetPasswordGenerationManager()
+      override;
+  password_manager::PasswordManager* GetPasswordManager() override;
+  password_manager::PasswordAutofillManager* GetPasswordAutofillManager()
+      override;
+  void ForceSavePassword() override;
+  autofill::AutofillDriver* GetAutofillDriver() override;
+  bool IsMainFrame() const override;
+
+ private:
+  id<CWVPasswordManagerDriverDelegate> delegate_;  // (weak)
+
+  DISALLOW_COPY_AND_ASSIGN(WebViewPasswordManagerDriver);
+};
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_DRIVER_H_
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_driver.mm b/ios/web_view/internal/passwords/web_view_password_manager_driver.mm
new file mode 100644
index 0000000..8c9eec1
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_driver.mm
@@ -0,0 +1,95 @@
+// Copyright 2018 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/web_view/internal/passwords/web_view_password_manager_driver.h"
+
+#include "base/strings/string16.h"
+#include "components/autofill/core/common/password_form.h"
+#include "components/autofill/core/common/password_form_fill_data.h"
+#include "components/password_manager/core/browser/password_manager.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+using password_manager::PasswordAutofillManager;
+using password_manager::PasswordGenerationManager;
+using password_manager::PasswordManager;
+
+namespace ios_web_view {
+WebViewPasswordManagerDriver::WebViewPasswordManagerDriver(
+    id<CWVPasswordManagerDriverDelegate> delegate)
+    : delegate_(delegate) {}
+
+WebViewPasswordManagerDriver::~WebViewPasswordManagerDriver() = default;
+
+void WebViewPasswordManagerDriver::FillPasswordForm(
+    const autofill::PasswordFormFillData& form_data) {
+  [delegate_ fillPasswordForm:form_data];
+}
+
+void WebViewPasswordManagerDriver::InformNoSavedCredentials() {
+  [delegate_ informNoSavedCredentials];
+}
+
+void WebViewPasswordManagerDriver::FormsEligibleForGenerationFound(
+    const std::vector<autofill::PasswordFormGenerationData>& forms) {
+  // Password generation is not supported.
+}
+
+void WebViewPasswordManagerDriver::GeneratedPasswordAccepted(
+    const base::string16& password) {
+  NOTIMPLEMENTED();
+}
+
+void WebViewPasswordManagerDriver::FillSuggestion(
+    const base::string16& username,
+    const base::string16& password) {
+  NOTIMPLEMENTED();
+}
+
+void WebViewPasswordManagerDriver::PreviewSuggestion(
+    const base::string16& username,
+    const base::string16& password) {
+  NOTIMPLEMENTED();
+}
+
+void WebViewPasswordManagerDriver::ShowInitialPasswordAccountSuggestions(
+    const autofill::PasswordFormFillData& form_data) {}
+
+void WebViewPasswordManagerDriver::ClearPreviewedForm() {
+  NOTIMPLEMENTED();
+}
+
+PasswordGenerationManager*
+WebViewPasswordManagerDriver::GetPasswordGenerationManager() {
+  return nullptr;
+}
+
+PasswordManager* WebViewPasswordManagerDriver::GetPasswordManager() {
+  return [delegate_ passwordManager];
+}
+
+void WebViewPasswordManagerDriver::AllowPasswordGenerationForForm(
+    const autofill::PasswordForm& form) {}
+
+PasswordAutofillManager*
+WebViewPasswordManagerDriver::GetPasswordAutofillManager() {
+  return nullptr;
+}
+
+void WebViewPasswordManagerDriver::ForceSavePassword() {
+  NOTIMPLEMENTED();
+}
+
+autofill::AutofillDriver* WebViewPasswordManagerDriver::GetAutofillDriver() {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+bool WebViewPasswordManagerDriver::IsMainFrame() const {
+  // On IOS only processing of password forms in main frame is implemented.
+  return true;
+}
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h b/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h
new file mode 100644
index 0000000..b1fb6ac
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h
@@ -0,0 +1,49 @@
+// Copyright 2018 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_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_INTERNALS_SERVICE_FACTORY_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_INTERNALS_SERVICE_FACTORY_H_
+
+#include "base/macros.h"
+#include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace password_manager {
+class PasswordManagerInternalsService;
+}
+
+namespace ios_web_view {
+class WebViewBrowserState;
+
+// Singleton that owns all PasswordStores and associates them with
+// WebViewBrowserState.
+class WebViewPasswordManagerInternalsServiceFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static password_manager::PasswordManagerInternalsService* GetForBrowserState(
+      WebViewBrowserState* browser_state);
+
+  static WebViewPasswordManagerInternalsServiceFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<
+      WebViewPasswordManagerInternalsServiceFactory>;
+
+  WebViewPasswordManagerInternalsServiceFactory();
+  ~WebViewPasswordManagerInternalsServiceFactory() override;
+
+  // BrowserStateKeyedServiceFactory:
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(WebViewPasswordManagerInternalsServiceFactory);
+};
+
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_MANAGER_INTERNALS_SERVICE_FACTORY_H_
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.mm b/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.mm
new file mode 100644
index 0000000..502e707
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.mm
@@ -0,0 +1,50 @@
+// Copyright 2018 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/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/singleton.h"
+#include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "components/password_manager/core/browser/password_manager_internals_service.h"
+#include "ios/web_view/internal/web_view_browser_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ios_web_view {
+
+// static
+password_manager::PasswordManagerInternalsService*
+WebViewPasswordManagerInternalsServiceFactory::GetForBrowserState(
+    WebViewBrowserState* browser_state) {
+  return static_cast<password_manager::PasswordManagerInternalsService*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true));
+}
+
+// static
+WebViewPasswordManagerInternalsServiceFactory*
+WebViewPasswordManagerInternalsServiceFactory::GetInstance() {
+  return base::Singleton<WebViewPasswordManagerInternalsServiceFactory>::get();
+}
+
+WebViewPasswordManagerInternalsServiceFactory::
+    WebViewPasswordManagerInternalsServiceFactory()
+    : BrowserStateKeyedServiceFactory(
+          "PasswordManagerInternalsService",
+          BrowserStateDependencyManager::GetInstance()) {}
+
+WebViewPasswordManagerInternalsServiceFactory::
+    ~WebViewPasswordManagerInternalsServiceFactory() {}
+
+std::unique_ptr<KeyedService>
+WebViewPasswordManagerInternalsServiceFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  return std::make_unique<password_manager::PasswordManagerInternalsService>();
+}
+
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_password_store_factory.h b/ios/web_view/internal/passwords/web_view_password_store_factory.h
new file mode 100644
index 0000000..6ff8b8a
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_store_factory.h
@@ -0,0 +1,61 @@
+// Copyright 2018 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_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_STORE_FACTORY_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_STORE_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/keyed_service/ios/refcounted_browser_state_keyed_service_factory.h"
+
+enum class ServiceAccessType;
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace password_manager {
+class PasswordStore;
+}
+
+namespace ios_web_view {
+
+class WebViewBrowserState;
+
+// Singleton that owns all PasswordStores and associates them with
+// WebViewBrowserState.
+class WebViewPasswordStoreFactory
+    : public RefcountedBrowserStateKeyedServiceFactory {
+ public:
+  static scoped_refptr<password_manager::PasswordStore> GetForBrowserState(
+      WebViewBrowserState* browser_state,
+      ServiceAccessType access_type);
+
+  static WebViewPasswordStoreFactory* GetInstance();
+
+  // Called by the PasswordDataTypeController whenever there is a possibility
+  // that syncing passwords has just started or ended for |browser_state|.
+  static void OnPasswordsSyncedStatePotentiallyChanged(
+      WebViewBrowserState* browser_state);
+
+ private:
+  friend struct base::DefaultSingletonTraits<WebViewPasswordStoreFactory>;
+
+  WebViewPasswordStoreFactory();
+  ~WebViewPasswordStoreFactory() override;
+
+  // BrowserStateKeyedServiceFactory:
+  scoped_refptr<RefcountedKeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+  web::BrowserState* GetBrowserStateToUse(
+      web::BrowserState* context) const override;
+  bool ServiceIsNULLWhileTesting() const override;
+
+  DISALLOW_COPY_AND_ASSIGN(WebViewPasswordStoreFactory);
+};
+
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_PASSWORD_STORE_FACTORY_H_
diff --git a/ios/web_view/internal/passwords/web_view_password_store_factory.mm b/ios/web_view/internal/passwords/web_view_password_store_factory.mm
new file mode 100644
index 0000000..7f180526
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_password_store_factory.mm
@@ -0,0 +1,101 @@
+// Copyright 2018 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 "ios/web_view/internal/passwords/web_view_password_store_factory.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/memory/singleton.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/keyed_service/core/service_access_type.h"
+#include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliated_match_helper.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_service.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
+#include "components/password_manager/core/browser/login_database.h"
+#include "components/password_manager/core/browser/password_store_default.h"
+#include "components/password_manager/core/browser/password_store_factory_util.h"
+#include "ios/web_view/internal/web_view_browser_state.h"
+#include "ios/web_view/internal/webdata_services/web_view_web_data_service_wrapper_factory.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ios_web_view {
+
+// static
+scoped_refptr<password_manager::PasswordStore>
+WebViewPasswordStoreFactory::GetForBrowserState(
+    WebViewBrowserState* browser_state,
+    ServiceAccessType access_type) {
+  // |profile| gets always redirected to a non-Incognito profile below, so
+  // Incognito & IMPLICIT_ACCESS means that incognito browsing session would
+  // result in traces in the normal profile without the user knowing it.
+  if (access_type == ServiceAccessType::IMPLICIT_ACCESS &&
+      browser_state->IsOffTheRecord()) {
+    return nullptr;
+  }
+  return base::WrapRefCounted(static_cast<password_manager::PasswordStore*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true).get()));
+}
+
+// static
+WebViewPasswordStoreFactory* WebViewPasswordStoreFactory::GetInstance() {
+  return base::Singleton<WebViewPasswordStoreFactory>::get();
+}
+
+WebViewPasswordStoreFactory::WebViewPasswordStoreFactory()
+    : RefcountedBrowserStateKeyedServiceFactory(
+          "PasswordStore",
+          BrowserStateDependencyManager::GetInstance()) {
+  DependsOn(WebViewWebDataServiceWrapperFactory::GetInstance());
+}
+
+WebViewPasswordStoreFactory::~WebViewPasswordStoreFactory() {}
+
+scoped_refptr<RefcountedKeyedService>
+WebViewPasswordStoreFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  std::unique_ptr<password_manager::LoginDatabase> login_db(
+      password_manager::CreateLoginDatabase(context->GetStatePath()));
+
+  scoped_refptr<base::SequencedTaskRunner> main_task_runner(
+      base::SequencedTaskRunnerHandle::Get());
+  // USER_VISIBLE priority is chosen for the background task runner, because
+  // the passwords obtained through tasks on the background runner influence
+  // what the user sees.
+  scoped_refptr<base::SequencedTaskRunner> db_task_runner(
+      base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE}));
+
+  scoped_refptr<password_manager::PasswordStore> store =
+      new password_manager::PasswordStoreDefault(std::move(login_db));
+  if (!store->Init(base::RepeatingCallback<void(syncer::ModelType)>(),
+                   nullptr)) {
+    // TODO(crbug.com/479725): Remove the LOG once this error is visible in the
+    // UI.
+    LOG(WARNING) << "Could not initialize password store.";
+    return nullptr;
+  }
+  return store;
+}
+
+web::BrowserState* WebViewPasswordStoreFactory::GetBrowserStateToUse(
+    web::BrowserState* context) const {
+  WebViewBrowserState* browser_state =
+      WebViewBrowserState::FromBrowserState(context);
+  return browser_state->GetRecordingBrowserState();
+}
+
+bool WebViewPasswordStoreFactory::ServiceIsNULLWhileTesting() const {
+  return true;
+}
+
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/web_view_browser_state.mm b/ios/web_view/internal/web_view_browser_state.mm
index 18375f11..3ca109f 100644
--- a/ios/web_view/internal/web_view_browser_state.mm
+++ b/ios/web_view/internal/web_view_browser_state.mm
@@ -14,6 +14,7 @@
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/gcm_driver/gcm_channel_status_syncer.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "components/password_manager/core/browser/password_manager.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/in_memory_pref_store.h"
 #include "components/prefs/json_pref_store.h"
@@ -29,6 +30,8 @@
 #include "ios/web_view/internal/content_settings/web_view_host_content_settings_map_factory.h"
 #include "ios/web_view/internal/language/web_view_language_model_manager_factory.h"
 #include "ios/web_view/internal/language/web_view_url_language_histogram_factory.h"
+#import "ios/web_view/internal/passwords/web_view_password_manager_internals_service_factory.h"
+#include "ios/web_view/internal/passwords/web_view_password_store_factory.h"
 #include "ios/web_view/internal/pref_names.h"
 #include "ios/web_view/internal/signin/web_view_account_fetcher_service_factory.h"
 #include "ios/web_view/internal/signin/web_view_account_tracker_service_factory.h"
@@ -161,6 +164,7 @@
 
 #if BUILDFLAG(IOS_WEB_VIEW_ENABLE_AUTOFILL)
   autofill::AutofillManager::RegisterProfilePrefs(pref_registry);
+  password_manager::PasswordManager::RegisterProfilePrefs(pref_registry);
 #endif  // BUILDFLAG(IOS_WEB_VIEW_ENABLE_AUTOFILL)
 
 #if BUILDFLAG(IOS_WEB_VIEW_ENABLE_SYNC)
@@ -176,6 +180,8 @@
 #if BUILDFLAG(IOS_WEB_VIEW_ENABLE_AUTOFILL)
   WebViewPersonalDataManagerFactory::GetInstance();
   WebViewWebDataServiceWrapperFactory::GetInstance();
+  WebViewPasswordManagerInternalsServiceFactory::GetInstance();
+  WebViewPasswordStoreFactory::GetInstance();
 #endif  // BUILDFLAG(IOS_WEB_VIEW_ENABLE_AUTOFILL)
 
 #if BUILDFLAG(IOS_WEB_VIEW_ENABLE_SYNC)
diff --git a/media/base/android_overlay_config.h b/media/base/android_overlay_config.h
index 744da408..d511310 100644
--- a/media/base/android_overlay_config.h
+++ b/media/base/android_overlay_config.h
@@ -65,8 +65,14 @@
   bool power_efficient = false;
 
   // Convenient helpers since the syntax is weird.
-  void is_ready(AndroidOverlay* overlay) { std::move(ready_cb).Run(overlay); }
-  void is_failed(AndroidOverlay* overlay) { std::move(failed_cb).Run(overlay); }
+  void is_ready(AndroidOverlay* overlay) {
+    if (ready_cb)
+      std::move(ready_cb).Run(overlay);
+  }
+  void is_failed(AndroidOverlay* overlay) {
+    if (failed_cb)
+      std::move(failed_cb).Run(overlay);
+  }
 
   ReadyCB ready_cb;
   FailedCB failed_cb;
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 421609f..7361893 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -327,7 +327,7 @@
 
 // Enable VA-API hardware encode acceleration for VP8.
 const base::Feature kVaapiVP8Encoder{"VaapiVP8Encoder",
-                                     base::FEATURE_ENABLED_BY_DEFAULT};
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Inform video blitter of video color space.
 const base::Feature kVideoBlitColorAccuracy{"video-blit-color-accuracy",
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index e0ea370..3abb9a2 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -222,6 +222,8 @@
     sources += [
       "video/linux/camera_config_chromeos.cc",
       "video/linux/camera_config_chromeos.h",
+      "video/linux/scoped_v4l2_device_fd.cc",
+      "video/linux/scoped_v4l2_device_fd.h",
       "video/linux/v4l2_capture_delegate.cc",
       "video/linux/v4l2_capture_delegate.h",
       "video/linux/v4l2_capture_device.h",
diff --git a/media/capture/video/linux/fake_v4l2_impl.cc b/media/capture/video/linux/fake_v4l2_impl.cc
index 2c33b40..5221c49f 100644
--- a/media/capture/video/linux/fake_v4l2_impl.cc
+++ b/media/capture/video/linux/fake_v4l2_impl.cc
@@ -4,10 +4,18 @@
 
 #include "media/capture/video/linux/fake_v4l2_impl.h"
 
-#include <linux/videodev2.h>
 #include <string.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <queue>
 
+#include "base/bind.h"
+#include "base/bits.h"
 #include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "media/base/video_frame.h"
 
 #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
 
@@ -16,18 +24,302 @@
 static const int kInvalidId = -1;
 static const int kSuccessReturnValue = 0;
 static const int kErrorReturnValue = -1;
+static const uint32_t kMaxBufferCount = 5;
+
+__u32 RoundUpToMultipleOfPageSize(__u32 size) {
+  CHECK(base::bits::IsPowerOfTwo(getpagesize()));
+  return base::bits::Align(size, getpagesize());
+}
+
+struct FakeV4L2Buffer {
+  FakeV4L2Buffer(__u32 index, __u32 offset, __u32 length)
+      : index(index),
+        offset(offset),
+        length(length),
+        flags(V4L2_BUF_FLAG_MAPPED),
+        sequence(0) {}
+
+  const __u32 index;
+  const __u32 offset;
+  const __u32 length;
+  __u32 flags;
+  timeval timestamp;
+  __u32 sequence;
+  std::unique_ptr<uint8_t[]> data;
+};
 
 class FakeV4L2Impl::OpenedDevice {
  public:
-  explicit OpenedDevice(const std::string& device_id, int open_flags)
-      : device_id_(device_id), open_flags_(open_flags) {}
+  explicit OpenedDevice(const FakeV4L2DeviceConfig& config, int open_flags)
+      : config_(config),
+        open_flags_(open_flags),
+        wait_for_outgoing_queue_event_(
+            base::WaitableEvent::ResetPolicy::AUTOMATIC),
+        frame_production_thread_("FakeV4L2Impl FakeProductionThread") {
+    static const int kDefaultWidth = 640;
+    static const int kDefaultHeight = 480;
+    selected_format_.width = kDefaultWidth;
+    selected_format_.height = kDefaultHeight;
+    selected_format_.pixelformat = V4L2_PIX_FMT_YUV420;
+    selected_format_.field = V4L2_FIELD_NONE;
+    selected_format_.bytesperline = kDefaultWidth;
+    selected_format_.sizeimage = VideoFrame::AllocationSize(
+        PIXEL_FORMAT_I420, gfx::Size(kDefaultWidth, kDefaultHeight));
+    selected_format_.colorspace = V4L2_COLORSPACE_REC709;
+    selected_format_.priv = 0;
 
-  const std::string& device_id() const { return device_id_; }
+    // 20 fps
+    timeperframe_.numerator = 50;
+    timeperframe_.denominator = 1000;
+  }
+
+  const std::string& device_id() const { return config_.descriptor.device_id; }
+
   int open_flags() const { return open_flags_; }
 
+  FakeV4L2Buffer* LookupBufferFromOffset(off_t offset) {
+    auto buffer_iter =
+        std::find_if(device_buffers_.begin(), device_buffers_.end(),
+                     [offset](const FakeV4L2Buffer& buffer) {
+                       return buffer.offset == offset;
+                     });
+    if (buffer_iter == device_buffers_.end())
+      return nullptr;
+    return &(*buffer_iter);
+  }
+
+  bool BlockUntilOutputQueueHasBuffer(int timeout_in_milliseconds) {
+    {
+      base::AutoLock lock(outgoing_queue_lock_);
+      if (!outgoing_queue_.empty()) {
+        return true;
+      }
+    }
+    return wait_for_outgoing_queue_event_.TimedWait(
+        base::TimeDelta::FromMilliseconds(timeout_in_milliseconds));
+  }
+
+  int enum_fmt(v4l2_fmtdesc* fmtdesc) {
+    if (fmtdesc->index > 0u) {
+      // We only support a single format for now.
+      return EINVAL;
+    }
+    if (fmtdesc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+      // We only support video capture.
+      return EINVAL;
+    }
+    fmtdesc->flags = 0u;
+    strcpy(reinterpret_cast<char*>(fmtdesc->description), "YUV420");
+    fmtdesc->pixelformat = V4L2_PIX_FMT_YUV420;
+    memset(fmtdesc->reserved, 0, sizeof(fmtdesc->reserved));
+    return kSuccessReturnValue;
+  }
+
+  int querycap(v4l2_capability* cap) {
+    strcpy(reinterpret_cast<char*>(cap->driver), "FakeV4L2");
+    CHECK(config_.descriptor.display_name().size() < 31);
+    strcpy(reinterpret_cast<char*>(cap->driver),
+           config_.descriptor.display_name().c_str());
+    cap->bus_info[0] = 0;
+    // Provide arbitrary version info
+    cap->version = KERNEL_VERSION(1, 0, 0);
+    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE;
+    memset(cap->reserved, 0, sizeof(cap->reserved));
+    return kSuccessReturnValue;
+  }
+
+  int s_ctrl(v4l2_control* control) { return kSuccessReturnValue; }
+
+  int s_ext_ctrls(v4l2_ext_controls* control) { return kSuccessReturnValue; }
+
+  int queryctrl(v4l2_queryctrl* control) { return EINVAL; }
+
+  int s_fmt(v4l2_format* format) {
+    if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    v4l2_pix_format& pix_format = format->fmt.pix;
+    // We only support YUV420 output for now. Tell this to the client by
+    // overwriting whatever format it requested.
+    pix_format.pixelformat = V4L2_PIX_FMT_YUV420;
+    // We only support non-interlaced output
+    pix_format.field = V4L2_FIELD_NONE;
+    // We do not support padding bytes
+    pix_format.bytesperline = pix_format.width;
+    pix_format.sizeimage = VideoFrame::AllocationSize(
+        PIXEL_FORMAT_I420, gfx::Size(pix_format.width, pix_format.height));
+    // Arbitrary colorspace
+    pix_format.colorspace = V4L2_COLORSPACE_REC709;
+    pix_format.priv = 0;
+    selected_format_ = pix_format;
+    return kSuccessReturnValue;
+  }
+
+  int g_parm(v4l2_streamparm* parm) {
+    if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    v4l2_captureparm& captureparm = parm->parm.capture;
+    captureparm.capability = V4L2_CAP_TIMEPERFRAME;
+    captureparm.timeperframe = timeperframe_;
+    captureparm.extendedmode = 0;
+    captureparm.readbuffers = 3;  // arbitrary choice
+    memset(captureparm.reserved, 0, sizeof(captureparm.reserved));
+    return kSuccessReturnValue;
+  }
+
+  int s_parm(v4l2_streamparm* parm) {
+    if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    v4l2_captureparm& captureparm = parm->parm.capture;
+    captureparm.capability = V4L2_CAP_TIMEPERFRAME;
+    timeperframe_ = captureparm.timeperframe;
+    captureparm.extendedmode = 0;
+    captureparm.readbuffers = 3;  // arbitrary choice
+    memset(captureparm.reserved, 0, sizeof(captureparm.reserved));
+    return kSuccessReturnValue;
+  }
+
+  int reqbufs(v4l2_requestbuffers* bufs) {
+    if (bufs->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    if (bufs->memory != V4L2_MEMORY_MMAP) {
+      // We only support device-owned buffers
+      return EINVAL;
+    }
+    incoming_queue_ = std::queue<FakeV4L2Buffer*>();
+    outgoing_queue_ = std::queue<FakeV4L2Buffer*>();
+    device_buffers_.clear();
+    uint32_t target_buffer_count = std::min(bufs->count, kMaxBufferCount);
+    bufs->count = target_buffer_count;
+    __u32 current_offset = 0;
+    for (uint32_t i = 0; i < target_buffer_count; i++) {
+      device_buffers_.emplace_back(i, current_offset,
+                                   selected_format_.sizeimage);
+      current_offset += RoundUpToMultipleOfPageSize(selected_format_.sizeimage);
+    }
+    memset(bufs->reserved, 0, sizeof(bufs->reserved));
+    return kSuccessReturnValue;
+  }
+
+  int querybuf(v4l2_buffer* buf) {
+    if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    if (buf->index >= device_buffers_.size())
+      return EINVAL;
+    auto& buffer = device_buffers_[buf->index];
+    buf->memory = V4L2_MEMORY_MMAP;
+    buf->flags = buffer.flags;
+    buf->m.offset = buffer.offset;
+    buf->length = buffer.length;
+    return kSuccessReturnValue;
+  }
+
+  int qbuf(v4l2_buffer* buf) {
+    if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    if (buf->memory != V4L2_MEMORY_MMAP)
+      return EINVAL;
+    if (buf->index >= device_buffers_.size())
+      return EINVAL;
+    auto& buffer = device_buffers_[buf->index];
+    buffer.flags = V4L2_BUF_FLAG_MAPPED & V4L2_BUF_FLAG_QUEUED;
+    buf->flags = buffer.flags;
+
+    base::AutoLock lock(incoming_queue_lock_);
+    incoming_queue_.push(&buffer);
+    return kSuccessReturnValue;
+  }
+
+  int dqbuf(v4l2_buffer* buf) {
+    if (buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    if (buf->memory != V4L2_MEMORY_MMAP)
+      return EINVAL;
+    base::AutoLock lock(outgoing_queue_lock_);
+    if (outgoing_queue_.empty()) {
+      if (open_flags_ & O_NONBLOCK)
+        return EAGAIN;
+      wait_for_outgoing_queue_event_.Wait();
+    }
+    auto* buffer = outgoing_queue_.front();
+    outgoing_queue_.pop();
+    buffer->flags = V4L2_BUF_FLAG_MAPPED & V4L2_BUF_FLAG_DONE;
+    buf->index = buffer->index;
+    buf->bytesused = VideoFrame::AllocationSize(
+        PIXEL_FORMAT_I420,
+        gfx::Size(selected_format_.width, selected_format_.height));
+    buf->flags = buffer->flags;
+    buf->field = V4L2_FIELD_NONE;
+    buf->timestamp = buffer->timestamp;
+    buf->sequence = buffer->sequence;
+    buf->m.offset = buffer->offset;
+    buf->length = buffer->length;
+
+    return kSuccessReturnValue;
+  }
+
+  int streamon(const int* type) {
+    if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    frame_production_thread_.Start();
+    should_quit_frame_production_loop_.UnsafeResetForTesting();
+    frame_production_thread_.task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&FakeV4L2Impl::OpenedDevice::RunFrameProductionLoop,
+                       base::Unretained(this)));
+    return kSuccessReturnValue;
+  }
+
+  int streamoff(const int* type) {
+    if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+      return EINVAL;
+    should_quit_frame_production_loop_.Set();
+    frame_production_thread_.Stop();
+    incoming_queue_ = std::queue<FakeV4L2Buffer*>();
+    outgoing_queue_ = std::queue<FakeV4L2Buffer*>();
+    return kSuccessReturnValue;
+  }
+
  private:
-  const std::string device_id_;
+  void RunFrameProductionLoop() {
+    while (!should_quit_frame_production_loop_.IsSet()) {
+      MaybeProduceOneFrame();
+      // Sleep for a bit.
+      // We ignore the requested frame rate here, and just sleep for a fixed
+      // duration.
+      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+    }
+  }
+
+  void MaybeProduceOneFrame() {
+    // We do not actually produce any frame data. Just move a buffer from
+    // incoming queue to outgoing queue.
+    base::AutoLock lock(incoming_queue_lock_);
+    base::AutoLock lock2(outgoing_queue_lock_);
+    if (incoming_queue_.empty()) {
+      return;
+    }
+
+    auto* buffer = incoming_queue_.front();
+    gettimeofday(&buffer->timestamp, NULL);
+    static __u32 frame_counter = 0;
+    buffer->sequence = frame_counter++;
+    incoming_queue_.pop();
+    outgoing_queue_.push(buffer);
+    wait_for_outgoing_queue_event_.Signal();
+  }
+
+  const FakeV4L2DeviceConfig config_;
   const int open_flags_;
+  v4l2_pix_format selected_format_;
+  v4l2_fract timeperframe_;
+  std::vector<FakeV4L2Buffer> device_buffers_;
+  std::queue<FakeV4L2Buffer*> incoming_queue_;
+  std::queue<FakeV4L2Buffer*> outgoing_queue_;
+  base::WaitableEvent wait_for_outgoing_queue_event_;
+  base::Thread frame_production_thread_;
+  base::AtomicFlag should_quit_frame_production_loop_;
+  base::Lock incoming_queue_lock_;
+  base::Lock outgoing_queue_lock_;
 };
 
 FakeV4L2Impl::FakeV4L2Impl() : next_id_to_return_from_open_(1) {}
@@ -41,7 +333,8 @@
 
 int FakeV4L2Impl::open(const char* device_name, int flags) {
   std::string device_name_as_string(device_name);
-  if (!base::ContainsKey(device_configs_, device_name_as_string))
+  auto device_configs_iter = device_configs_.find(device_name_as_string);
+  if (device_configs_iter == device_configs_.end())
     return kInvalidId;
 
   auto id_iter = device_name_to_open_id_map_.find(device_name_as_string);
@@ -52,8 +345,8 @@
 
   auto device_id = next_id_to_return_from_open_++;
   device_name_to_open_id_map_.emplace(device_name_as_string, device_id);
-  opened_devices_.emplace(
-      device_id, std::make_unique<OpenedDevice>(device_name_as_string, flags));
+  opened_devices_.emplace(device_id, std::make_unique<OpenedDevice>(
+                                         device_configs_iter->second, flags));
   return device_id;
 }
 
@@ -70,43 +363,39 @@
   auto device_iter = opened_devices_.find(fd);
   if (device_iter == opened_devices_.end())
     return EBADF;
-  auto& opened_device = device_iter->second;
-  auto& device_config = device_configs_.at(opened_device->device_id());
+  auto* opened_device = device_iter->second.get();
 
   switch (request) {
-    case VIDIOC_ENUM_FMT: {
-      auto* fmtdesc = reinterpret_cast<v4l2_fmtdesc*>(argp);
-      if (fmtdesc->index > 0u) {
-        // We only support a single format for now.
-        return EINVAL;
-      }
-      if (fmtdesc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
-        // We only support video capture.
-        return EINVAL;
-      }
-      fmtdesc->flags = 0u;
-      strcpy(reinterpret_cast<char*>(fmtdesc->description), "YUV420");
-      fmtdesc->pixelformat = V4L2_PIX_FMT_YUV420;
-      memset(fmtdesc->reserved, 0, 4);
-      return kSuccessReturnValue;
-    }
-    case VIDIOC_QUERYCAP: {
-      auto* cap = reinterpret_cast<v4l2_capability*>(argp);
-      strcpy(reinterpret_cast<char*>(cap->driver), "FakeV4L2");
-      CHECK(device_config.descriptor.display_name().size() < 31);
-      strcpy(reinterpret_cast<char*>(cap->driver),
-             device_config.descriptor.display_name().c_str());
-      cap->bus_info[0] = 0;
-      // Provide arbitrary version info
-      cap->version = KERNEL_VERSION(1, 0, 0);
-      cap->capabilities = V4L2_CAP_VIDEO_CAPTURE;
-      memset(cap->reserved, 0, 4);
-      return kSuccessReturnValue;
-    }
+    case VIDIOC_ENUM_FMT:
+      return opened_device->enum_fmt(reinterpret_cast<v4l2_fmtdesc*>(argp));
+    case VIDIOC_QUERYCAP:
+      return opened_device->querycap(reinterpret_cast<v4l2_capability*>(argp));
+    case VIDIOC_S_CTRL:
+      return opened_device->s_ctrl(reinterpret_cast<v4l2_control*>(argp));
+    case VIDIOC_S_EXT_CTRLS:
+      return opened_device->s_ext_ctrls(
+          reinterpret_cast<v4l2_ext_controls*>(argp));
+    case VIDIOC_QUERYCTRL:
+      return opened_device->queryctrl(reinterpret_cast<v4l2_queryctrl*>(argp));
+    case VIDIOC_S_FMT:
+      return opened_device->s_fmt(reinterpret_cast<v4l2_format*>(argp));
+    case VIDIOC_G_PARM:
+      return opened_device->g_parm(reinterpret_cast<v4l2_streamparm*>(argp));
+    case VIDIOC_S_PARM:
+      return opened_device->s_parm(reinterpret_cast<v4l2_streamparm*>(argp));
+    case VIDIOC_REQBUFS:
+      return opened_device->reqbufs(
+          reinterpret_cast<v4l2_requestbuffers*>(argp));
+    case VIDIOC_QUERYBUF:
+      return opened_device->querybuf(reinterpret_cast<v4l2_buffer*>(argp));
+    case VIDIOC_QBUF:
+      return opened_device->qbuf(reinterpret_cast<v4l2_buffer*>(argp));
+    case VIDIOC_DQBUF:
+      return opened_device->dqbuf(reinterpret_cast<v4l2_buffer*>(argp));
     case VIDIOC_STREAMON:
+      return opened_device->streamon(reinterpret_cast<int*>(argp));
     case VIDIOC_STREAMOFF:
-      NOTIMPLEMENTED();
-      return kSuccessReturnValue;
+      return opened_device->streamoff(reinterpret_cast<int*>(argp));
     case VIDIOC_CROPCAP:
     case VIDIOC_DBG_G_REGISTER:
     case VIDIOC_DBG_S_REGISTER:
@@ -126,15 +415,12 @@
     case VIDIOC_G_CROP:
     case VIDIOC_S_CROP:
     case VIDIOC_G_CTRL:
-    case VIDIOC_S_CTRL:
     case VIDIOC_G_ENC_INDEX:
     case VIDIOC_G_EXT_CTRLS:
-    case VIDIOC_S_EXT_CTRLS:
     case VIDIOC_TRY_EXT_CTRLS:
     case VIDIOC_G_FBUF:
     case VIDIOC_S_FBUF:
     case VIDIOC_G_FMT:
-    case VIDIOC_S_FMT:
     case VIDIOC_TRY_FMT:
     case VIDIOC_G_FREQUENCY:
     case VIDIOC_S_FREQUENCY:
@@ -146,8 +432,6 @@
     case VIDIOC_S_MODULATOR:
     case VIDIOC_G_OUTPUT:
     case VIDIOC_S_OUTPUT:
-    case VIDIOC_G_PARM:
-    case VIDIOC_S_PARM:
     case VIDIOC_G_PRIORITY:
     case VIDIOC_S_PRIORITY:
     case VIDIOC_G_SLICED_VBI_CAP:
@@ -157,13 +441,8 @@
     case VIDIOC_S_TUNER:
     case VIDIOC_LOG_STATUS:
     case VIDIOC_OVERLAY:
-    case VIDIOC_QBUF:
-    case VIDIOC_DQBUF:
-    case VIDIOC_QUERYBUF:
-    case VIDIOC_QUERYCTRL:
     case VIDIOC_QUERYMENU:
     case VIDIOC_QUERYSTD:
-    case VIDIOC_REQBUFS:
     case VIDIOC_S_HW_FREQ_SEEK:
       // Unsupported |request| code.
       NOTREACHED() << "Unsupported request code " << request;
@@ -175,24 +454,64 @@
   return kErrorReturnValue;
 }
 
-void* FakeV4L2Impl::mmap(void* start,
+// We ignore |start| in this implementation
+void* FakeV4L2Impl::mmap(void* /*start*/,
                          size_t length,
                          int prot,
                          int flags,
                          int fd,
                          off_t offset) {
-  NOTREACHED();
-  return nullptr;
+  if (flags & MAP_FIXED) {
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+  if (prot != (PROT_READ | PROT_WRITE)) {
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+  auto device_iter = opened_devices_.find(fd);
+  if (device_iter == opened_devices_.end()) {
+    errno = EBADF;
+    return MAP_FAILED;
+  }
+  auto* opened_device = device_iter->second.get();
+  auto* buffer = opened_device->LookupBufferFromOffset(offset);
+  if (!buffer || (buffer->length != length)) {
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+  if (!buffer->data)
+    buffer->data = std::make_unique<uint8_t[]>(length);
+  return buffer->data.get();
 }
 
 int FakeV4L2Impl::munmap(void* start, size_t length) {
-  NOTREACHED();
-  return kErrorReturnValue;
+  return kSuccessReturnValue;
 }
 
 int FakeV4L2Impl::poll(struct pollfd* ufds, unsigned int nfds, int timeout) {
-  NOTREACHED();
-  return kErrorReturnValue;
+  if (nfds != 1) {
+    // We only support polling of a single device.
+    errno = EINVAL;
+    return kErrorReturnValue;
+  }
+  pollfd& ufd = ufds[0];
+  auto device_iter = opened_devices_.find(ufd.fd);
+  if (device_iter == opened_devices_.end()) {
+    errno = EBADF;
+    return kErrorReturnValue;
+  }
+  auto* opened_device = device_iter->second.get();
+  if (ufd.events != POLLIN) {
+    // We only support waiting for data to become readable.
+    errno = EINVAL;
+    return kErrorReturnValue;
+  }
+  if (!opened_device->BlockUntilOutputQueueHasBuffer(timeout)) {
+    return 0;
+  }
+  ufd.revents |= POLLIN;
+  return 1;
 }
 
 }  // namespace media
diff --git a/media/capture/video/linux/fake_v4l2_impl.h b/media/capture/video/linux/fake_v4l2_impl.h
index 1dd4b6c1..0a035a9 100644
--- a/media/capture/video/linux/fake_v4l2_impl.h
+++ b/media/capture/video/linux/fake_v4l2_impl.h
@@ -8,6 +8,8 @@
 #include <map>
 #include <string>
 
+#include <linux/videodev2.h>
+
 #include "media/capture/capture_export.h"
 #include "media/capture/video/linux/v4l2_capture_device.h"
 #include "media/capture/video/video_capture_device_descriptor.h"
diff --git a/media/capture/video/linux/scoped_v4l2_device_fd.cc b/media/capture/video/linux/scoped_v4l2_device_fd.cc
new file mode 100644
index 0000000..76de9b0
--- /dev/null
+++ b/media/capture/video/linux/scoped_v4l2_device_fd.cc
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/capture/video/linux/scoped_v4l2_device_fd.h"
+
+namespace media {
+
+ScopedV4L2DeviceFD::ScopedV4L2DeviceFD(V4L2CaptureDevice* v4l2)
+    : device_fd_(kInvalidId), v4l2_(v4l2) {}
+
+ScopedV4L2DeviceFD::ScopedV4L2DeviceFD(V4L2CaptureDevice* v4l2, int device_fd)
+    : device_fd_(device_fd), v4l2_(v4l2) {}
+
+ScopedV4L2DeviceFD::~ScopedV4L2DeviceFD() {
+  if (is_valid())
+    reset();
+}
+
+int ScopedV4L2DeviceFD::get() const {
+  return device_fd_;
+}
+
+void ScopedV4L2DeviceFD::reset(int fd /*= kInvalidId*/) {
+  if (is_valid())
+    v4l2_->close(device_fd_);
+  device_fd_ = fd;
+}
+
+bool ScopedV4L2DeviceFD::is_valid() const {
+  return device_fd_ != kInvalidId;
+}
+
+}  // namespace media
diff --git a/media/capture/video/linux/scoped_v4l2_device_fd.h b/media/capture/video/linux/scoped_v4l2_device_fd.h
new file mode 100644
index 0000000..23d42ce
--- /dev/null
+++ b/media/capture/video/linux/scoped_v4l2_device_fd.h
@@ -0,0 +1,29 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_CAPTURE_VIDEO_LINUX_SCOPED_V4L2_DEVICE_FD_H_
+#define MEDIA_CAPTURE_VIDEO_LINUX_SCOPED_V4L2_DEVICE_FD_H_
+
+#include "media/capture/video/linux/v4l2_capture_device.h"
+
+namespace media {
+
+class ScopedV4L2DeviceFD {
+ public:
+  static constexpr int kInvalidId = -1;
+  explicit ScopedV4L2DeviceFD(V4L2CaptureDevice* v4l2);
+  ScopedV4L2DeviceFD(V4L2CaptureDevice* v4l2, int device_fd);
+  ~ScopedV4L2DeviceFD();
+  int get() const;
+  void reset(int fd = kInvalidId);
+  bool is_valid() const;
+
+ private:
+  int device_fd_;
+  V4L2CaptureDevice* const v4l2_;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_CAPTURE_VIDEO_LINUX_SCOPED_V4L2_DEVICE_FD_H_
diff --git a/media/capture/video/linux/v4l2_capture_delegate.cc b/media/capture/video/linux/v4l2_capture_delegate.cc
index c152e8c..dc08d20 100644
--- a/media/capture/video/linux/v4l2_capture_delegate.cc
+++ b/media/capture/video/linux/v4l2_capture_delegate.cc
@@ -55,9 +55,8 @@
 // Typical framerate, in fps
 const int kTypicalFramerate = 30;
 
-// V4L2CaptureDevice color formats supported by V4L2CaptureDelegate derived
-// classes. This list is ordered by precedence of use -- but see caveats for
-// MJPEG.
+// V4L2 color formats supported by V4L2CaptureDelegate derived classes.
+// This list is ordered by precedence of use -- but see caveats for MJPEG.
 static struct {
   uint32_t fourcc;
   VideoPixelFormat pixel_format;
@@ -166,8 +165,8 @@
   return false;
 }
 
-// Class keeping track of a SPLANE V4L2CaptureDevice buffer, mmap()ed on
-// construction and munmap()ed on destruction.
+// Class keeping track of a SPLANE V4L2 buffer, mmap()ed on construction and
+// munmap()ed on destruction.
 class V4L2CaptureDelegate::BufferTracker
     : public base::RefCounted<BufferTracker> {
  public:
@@ -230,29 +229,6 @@
   return supported_formats;
 }
 
-V4L2CaptureDelegate::ScopedV4L2DeviceFD::ScopedV4L2DeviceFD(
-    V4L2CaptureDevice* v4l2)
-    : device_fd_(kInvalidId), v4l2_(v4l2) {}
-
-V4L2CaptureDelegate::ScopedV4L2DeviceFD::~ScopedV4L2DeviceFD() {
-  if (is_valid())
-    reset();
-}
-
-int V4L2CaptureDelegate::ScopedV4L2DeviceFD::get() {
-  return device_fd_;
-}
-
-void V4L2CaptureDelegate::ScopedV4L2DeviceFD::reset(int fd /*= kInvalidId*/) {
-  if (is_valid())
-    v4l2_->close(device_fd_);
-  device_fd_ = fd;
-}
-
-bool V4L2CaptureDelegate::ScopedV4L2DeviceFD::is_valid() {
-  return device_fd_ != kInvalidId;
-}
-
 V4L2CaptureDelegate::V4L2CaptureDelegate(
     V4L2CaptureDevice* v4l2,
     const VideoCaptureDeviceDescriptor& device_descriptor,
@@ -281,8 +257,7 @@
   device_fd_.reset(
       HANDLE_EINTR(v4l2_->open(device_descriptor_.device_id.c_str(), O_RDWR)));
   if (!device_fd_.is_valid()) {
-    SetErrorState(FROM_HERE,
-                  "Failed to open V4L2CaptureDevice device driver file.");
+    SetErrorState(FROM_HERE, "Failed to open V4L2 device driver file.");
     return;
   }
 
@@ -294,8 +269,7 @@
         ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
          !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)))) {
     device_fd_.reset();
-    SetErrorState(FROM_HERE,
-                  "This is not a V4L2CaptureDevice video capture device");
+    SetErrorState(FROM_HERE, "This is not a V4L2 video capture device");
     return;
   }
 
@@ -382,8 +356,7 @@
   FillV4L2RequestBuffer(&r_buffer, kNumVideoBuffers);
   if (HANDLE_EINTR(v4l2_->ioctl(device_fd_.get(), VIDIOC_REQBUFS, &r_buffer)) <
       0) {
-    SetErrorState(FROM_HERE,
-                  "Error requesting MMAP buffers from V4L2CaptureDevice");
+    SetErrorState(FROM_HERE, "Error requesting MMAP buffers from V4L2");
     return;
   }
   for (unsigned int i = 0; i < r_buffer.count; ++i) {
@@ -671,7 +644,7 @@
        ++num_retries) {
     DPLOG(WARNING) << "ioctl";
   }
-  DPLOG_IF(ERROR, num_retries != kMaxIOCtrlRetries);
+  DPLOG_IF(ERROR, num_retries == kMaxIOCtrlRetries);
   return num_retries != kMaxIOCtrlRetries;
 }
 
@@ -811,7 +784,7 @@
 
   if (HANDLE_EINTR(v4l2_->ioctl(device_fd_.get(), VIDIOC_QUERYBUF, &buffer)) <
       0) {
-    DLOG(ERROR) << "Error querying status of a MMAP V4L2CaptureDevice buffer";
+    DLOG(ERROR) << "Error querying status of a MMAP V4L2 buffer";
     return false;
   }
 
@@ -824,8 +797,7 @@
 
   // Enqueue the buffer in the drivers incoming queue.
   if (HANDLE_EINTR(v4l2_->ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) {
-    DLOG(ERROR)
-        << "Error enqueuing a V4L2CaptureDevice buffer back into the driver";
+    DLOG(ERROR) << "Error enqueuing a V4L2 buffer back into the driver";
     return false;
   }
   return true;
@@ -839,12 +811,14 @@
   pollfd device_pfd = {};
   device_pfd.fd = device_fd_.get();
   device_pfd.events = POLLIN;
+
   const int result =
       HANDLE_EINTR(v4l2_->poll(&device_pfd, 1, kCaptureTimeoutMs));
   if (result < 0) {
     SetErrorState(FROM_HERE, "Poll failed");
     return;
   }
+
   // Check if poll() timed out; track the amount of times it did in a row and
   // throw an error if it times out too many times.
   if (result == 0) {
@@ -936,7 +910,7 @@
   if (start_ == nullptr)
     return;
   const int result = v4l2_->munmap(start_, length_);
-  PLOG_IF(ERROR, result < 0) << "Error munmap()ing V4L2CaptureDevice buffer";
+  PLOG_IF(ERROR, result < 0) << "Error munmap()ing V4L2 buffer";
 }
 
 bool V4L2CaptureDelegate::BufferTracker::Init(int fd,
@@ -946,7 +920,7 @@
   void* const start = v4l2_->mmap(NULL, buffer.length, PROT_READ | PROT_WRITE,
                                   MAP_SHARED, fd, buffer.m.offset);
   if (start == MAP_FAILED) {
-    DLOG(ERROR) << "Error mmap()ing a V4L2CaptureDevice buffer into userspace";
+    DLOG(ERROR) << "Error mmap()ing a V4L2 buffer into userspace";
     return false;
   }
   start_ = static_cast<uint8_t*>(start);
diff --git a/media/capture/video/linux/v4l2_capture_delegate.h b/media/capture/video/linux/v4l2_capture_delegate.h
index 5a246a3..7503343 100644
--- a/media/capture/video/linux/v4l2_capture_delegate.h
+++ b/media/capture/video/linux/v4l2_capture_delegate.h
@@ -12,6 +12,7 @@
 #include "base/files/scoped_file.h"
 #include "base/macros.h"
 #include "build/build_config.h"
+#include "media/capture/video/linux/scoped_v4l2_device_fd.h"
 #include "media/capture/video/linux/v4l2_capture_device_impl.h"
 #include "media/capture/video/video_capture_device.h"
 
@@ -27,10 +28,9 @@
 
 namespace media {
 
-// Class doing the actual Linux capture using V4L2CaptureDevice API.
-// V4L2CaptureDevice SPLANE/MPLANE capture specifics are implemented in derived
-// classes. Created on the owner's thread, otherwise living, operating and
-// destroyed on |v4l2_task_runner_|.
+// Class doing the actual Linux capture using V4L2 API. V4L2 SPLANE/MPLANE
+// capture specifics are implemented in derived classes. Created on the owner's
+// thread, otherwise living, operating and destroyed on |v4l2_task_runner_|.
 class CAPTURE_EXPORT V4L2CaptureDelegate final {
  public:
   // Retrieves the #planes for a given |fourcc|, or 0 if unknown.
@@ -71,19 +71,6 @@
   friend class V4L2CaptureDelegateTest;
 
   class BufferTracker;
-  class ScopedV4L2DeviceFD {
-   public:
-    static constexpr int kInvalidId = -1;
-    ScopedV4L2DeviceFD(V4L2CaptureDevice* v4l2);
-    ~ScopedV4L2DeviceFD();
-    int get();
-    void reset(int fd = kInvalidId);
-    bool is_valid();
-
-   private:
-    int device_fd_;
-    V4L2CaptureDevice* const v4l2_;
-  };
 
   bool RunIoctl(int fd, int request, void* argp);
   mojom::RangePtr RetrieveUserControlRange(int device_fd, int control_id);
@@ -91,8 +78,8 @@
 
   // void CloseDevice();
 
-  // VIDIOC_QUERYBUFs a buffer from V4L2CaptureDevice, creates a BufferTracker
-  // for it and enqueues it (VIDIOC_QBUF) back into V4L2CaptureDevice.
+  // VIDIOC_QUERYBUFs a buffer from V4L2, creates a BufferTracker for it and
+  // enqueues it (VIDIOC_QBUF) back into V4L2.
   bool MapAndQueueBuffer(int index);
 
   void DoCapture();
diff --git a/media/capture/video/linux/video_capture_device_factory_linux.cc b/media/capture/video/linux/video_capture_device_factory_linux.cc
index 99c9912..75ae293 100644
--- a/media/capture/video/linux/video_capture_device_factory_linux.cc
+++ b/media/capture/video/linux/video_capture_device_factory_linux.cc
@@ -11,7 +11,6 @@
 
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
-#include "base/files/scoped_file.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -27,6 +26,8 @@
 #include "media/capture/video/linux/camera_config_chromeos.h"
 #include "media/capture/video/linux/video_capture_device_chromeos.h"
 #endif
+
+#include "media/capture/video/linux/scoped_v4l2_device_fd.h"
 #include "media/capture/video/linux/video_capture_device_linux.h"
 
 namespace media {
@@ -174,8 +175,9 @@
   // Test opening the device driver. This is to make sure it is available.
   // We will reopen it again in our worker thread when someone
   // allocates the camera.
-  base::ScopedFD fd(
-      HANDLE_EINTR(open(device_descriptor.device_id.c_str(), O_RDONLY)));
+  ScopedV4L2DeviceFD fd(
+      v4l2_.get(),
+      HANDLE_EINTR(v4l2_->open(device_descriptor.device_id.c_str(), O_RDONLY)));
   if (!fd.is_valid()) {
     DLOG(ERROR) << "Cannot open device";
     delete self;
@@ -192,8 +194,8 @@
   std::vector<std::string> filepaths;
   device_provider_->GetDeviceIds(&filepaths);
   for (auto& unique_id : filepaths) {
-    const base::ScopedFD fd(
-        HANDLE_EINTR(v4l2_->open(unique_id.c_str(), O_RDONLY)));
+    const ScopedV4L2DeviceFD fd(
+        v4l2_.get(), HANDLE_EINTR(v4l2_->open(unique_id.c_str(), O_RDONLY)));
     if (!fd.is_valid()) {
       DLOG(ERROR) << "Couldn't open " << unique_id;
       continue;
@@ -238,8 +240,8 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   if (device.device_id.empty())
     return;
-  base::ScopedFD fd(
-      HANDLE_EINTR(v4l2_->open(device.device_id.c_str(), O_RDONLY)));
+  ScopedV4L2DeviceFD fd(v4l2_.get(), HANDLE_EINTR(v4l2_->open(
+                                         device.device_id.c_str(), O_RDONLY)));
   if (!fd.is_valid())  // Failed to open this device.
     return;
   supported_formats->clear();
diff --git a/media/capture/video/linux/video_capture_device_factory_linux_unittest.cc b/media/capture/video/linux/video_capture_device_factory_linux_unittest.cc
index aa25be8..d6363ea 100644
--- a/media/capture/video/linux/video_capture_device_factory_linux_unittest.cc
+++ b/media/capture/video/linux/video_capture_device_factory_linux_unittest.cc
@@ -6,10 +6,12 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "media/capture/video/linux/fake_v4l2_impl.h"
+#include "media/capture/video/mock_video_capture_device_client.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::_;
+using ::testing::InvokeWithoutArgs;
 
 namespace media {
 
@@ -104,4 +106,41 @@
   ASSERT_EQ(stub_display_name, descriptors[0].display_name());
 }
 
+TEST_F(VideoCaptureDeviceFactoryLinuxTest,
+       ReceiveFramesFromSinglePlaneFakeDevice) {
+  // Setup
+  const std::string stub_display_name = "Fake Device 0";
+  const std::string stub_device_id = "/dev/video0";
+  VideoCaptureDeviceDescriptor descriptor(
+      stub_display_name, stub_device_id,
+      VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE);
+  fake_device_provider_->AddDevice(descriptor);
+  fake_v4l2_->AddDevice(stub_device_id, FakeV4L2DeviceConfig(descriptor));
+
+  // Exercise
+  auto device = factory_->CreateDevice(descriptor);
+  VideoCaptureParams arbitrary_params;
+  arbitrary_params.requested_format.frame_size = gfx::Size(1280, 720);
+  arbitrary_params.requested_format.frame_rate = 30.0f;
+  arbitrary_params.requested_format.pixel_format = PIXEL_FORMAT_I420;
+  auto client = std::make_unique<MockVideoCaptureDeviceClient>();
+  MockVideoCaptureDeviceClient* client_ptr = client.get();
+
+  base::RunLoop wait_loop;
+  static const int kFrameToReceive = 3;
+  EXPECT_CALL(*client_ptr, OnIncomingCapturedData(_, _, _, _, _, _, _))
+      .WillRepeatedly(InvokeWithoutArgs([&wait_loop]() {
+        static int received_frame_count = 0;
+        received_frame_count++;
+        if (received_frame_count == kFrameToReceive) {
+          wait_loop.Quit();
+        }
+      }));
+
+  device->AllocateAndStart(arbitrary_params, std::move(client));
+  wait_loop.Run();
+
+  device->StopAndDeAllocate();
+}
+
 };  // namespace media
diff --git a/media/gpu/command_buffer_helper.cc b/media/gpu/command_buffer_helper.cc
index e39d291..3b2fd63 100644
--- a/media/gpu/command_buffer_helper.cc
+++ b/media/gpu/command_buffer_helper.cc
@@ -174,10 +174,6 @@
     DVLOG(3) << __func__;
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-    // Drop all textures.  Note that it's okay if the context isn't current,
-    // since AbstractTexture handles that case.
-    textures_.clear();
-
     decoder_helper_ = nullptr;
 
     // If the last reference to |this| is in a |done_cb|, destroying the wait
diff --git a/media/gpu/fake_command_buffer_helper.cc b/media/gpu/fake_command_buffer_helper.cc
index cb54b70..de57be99 100644
--- a/media/gpu/fake_command_buffer_helper.cc
+++ b/media/gpu/fake_command_buffer_helper.cc
@@ -28,7 +28,6 @@
   has_stub_ = false;
   is_context_lost_ = true;
   is_context_current_ = false;
-  service_ids_.clear();
   waits_.clear();
 }
 
@@ -110,7 +109,6 @@
   DVLOG(2) << __func__ << "(" << service_id << ")";
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(service_ids_.count(service_id));
-  DCHECK(image);
   return has_stub_;
 }
 
diff --git a/media/mojo/services/media_metrics_provider.cc b/media/mojo/services/media_metrics_provider.cc
index 68b61055..cac2c21 100644
--- a/media/mojo/services/media_metrics_provider.cc
+++ b/media/mojo/services/media_metrics_provider.cc
@@ -29,7 +29,7 @@
   if (!ukm_recorder || !initialized_)
     return;
 
-  const int32_t source_id = ukm_recorder->GetNewSourceID();
+  const ukm::SourceId source_id = ukm_recorder->GetNewSourceID();
 
   // TODO(crbug.com/787209): Stop getting origin from the renderer.
   ukm_recorder->UpdateSourceURL(source_id, untrusted_top_origin_.GetURL());
diff --git a/media/mojo/services/video_decode_perf_history.cc b/media/mojo/services/video_decode_perf_history.cc
index 3ee0ae8..d40fe8e3 100644
--- a/media/mojo/services/video_decode_perf_history.cc
+++ b/media/mojo/services/video_decode_perf_history.cc
@@ -282,7 +282,7 @@
   if (!ukm_recorder)
     return;
 
-  const int32_t source_id = ukm_recorder->GetNewSourceID();
+  const ukm::SourceId source_id = ukm_recorder->GetNewSourceID();
   ukm::builders::Media_VideoDecodePerfRecord builder(source_id);
 
   // TODO(crbug.com/787209): Stop getting origin from the renderer.
diff --git a/media/mojo/services/watch_time_recorder.cc b/media/mojo/services/watch_time_recorder.cc
index de615f91c..c8e30f0 100644
--- a/media/mojo/services/watch_time_recorder.cc
+++ b/media/mojo/services/watch_time_recorder.cc
@@ -344,7 +344,7 @@
   }
 
   for (auto& ukm_record : ukm_records_) {
-    const int32_t source_id = ukm_recorder->GetNewSourceID();
+    const ukm::SourceId source_id = ukm_recorder->GetNewSourceID();
 
     // TODO(crbug.com/787209): Stop getting origin from the renderer.
     ukm_recorder->UpdateSourceURL(source_id, untrusted_top_origin_.GetURL());
diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md
index 89e5d95..5630adb1 100644
--- a/mojo/public/cpp/bindings/README.md
+++ b/mojo/public/cpp/bindings/README.md
@@ -669,7 +669,7 @@
 ```cpp
 union Value {
   int64 int_value;
-  float32 float_value;
+  float float_value;
   string string_value;
 };
 
@@ -678,12 +678,17 @@
 };
 ```
 
-This generates a the following C++ interface:
+This generates the following C++ interface:
 
 ```cpp
 class Value {
  public:
-  virtual ~Value() {}
+  ~Value() {}
+};
+
+class Dictionary {
+ public:
+  virtual ~Dictionary() {}
 
   virtual void AddValue(const std::string& key, ValuePtr value) = 0;
 };
diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
index 59c6fee7..7af57bd 100644
--- a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl
@@ -175,6 +175,7 @@
             org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY);
             final int elementsOrVersion = mainDataHeader.elementsOrVersion;
             result = new {{struct|name}}(elementsOrVersion);
+
 {%- set prev_ver = [0] %}
 {%- for byte in struct.bytes %}
 {%-   for packed_field in byte.packed_fields %}
@@ -183,7 +184,9 @@
             }
 {%-       endif %}
 {%-       set _ = prev_ver.append(packed_field.min_version) %}
+{%-       if prev_ver[-1] != 0 %}
             if (elementsOrVersion >= {{packed_field.min_version}}) {
+{%-       endif %}
 {%-     endif %}
                 {
                     {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}}
@@ -193,6 +196,7 @@
 {%- if prev_ver[-1] != 0 %}
             }
 {%- endif %}
+
         } finally {
             decoder0.decreaseStackDepth();
         }
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index ecd0d81..5e3ad57 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -236,6 +236,453 @@
 #endif
 }
 
+std::string MakeRandomHexString(size_t num_bytes) {
+  std::vector<char> rand_bytes;
+  rand_bytes.resize(num_bytes);
+
+  base::RandBytes(&rand_bytes[0], rand_bytes.size());
+  return base::HexEncode(&rand_bytes[0], rand_bytes.size());
+}
+
+// CertBuilder is a helper class to dynamically create a test certificate.
+//
+// CertBuilder is initialized using an existing certificate, from which it
+// copies most properties (see InitFromCert for details).
+//
+// The subject, serial number, and key for the final certificate are chosen
+// randomly. Using a randomized subject and serial number is important to defeat
+// certificate caching done by NSS, which otherwise can make test outcomes
+// dependent on ordering.
+class CertBuilder {
+ public:
+  // Initializes the CertBuilder using |orig_cert|. If |issuer| is null
+  // then the generated certificate will be self-signed. Otherwise, it
+  // will be signed using |issuer|.
+  CertBuilder(CRYPTO_BUFFER* orig_cert, CertBuilder* issuer) : issuer_(issuer) {
+    if (!issuer_)
+      issuer_ = this;
+
+    crypto::EnsureOpenSSLInit();
+    InitFromCert(der::Input(x509_util::CryptoBufferAsStringPiece(orig_cert)));
+  }
+
+  // Sets a value for the indicated X.509 (v3) extension.
+  void SetExtension(const der::Input& oid,
+                    std::string value,
+                    bool critical = false) {
+    auto& extension_value = extensions_[oid.AsString()];
+    extension_value.critical = critical;
+    extension_value.value = std::move(value);
+
+    Invalidate();
+  }
+
+  // Sets an AIA extension with a single caIssuers access method.
+  void SetCaIssuersUrl(const GURL& url) {
+    std::string url_spec = url.spec();
+
+    // From RFC 5280:
+    //
+    //   AuthorityInfoAccessSyntax  ::=
+    //           SEQUENCE SIZE (1..MAX) OF AccessDescription
+    //
+    //   AccessDescription  ::=  SEQUENCE {
+    //           accessMethod          OBJECT IDENTIFIER,
+    //           accessLocation        GeneralName  }
+    bssl::ScopedCBB cbb;
+    CBB aia, ca_issuer, access_method, access_location;
+    ASSERT_TRUE(CBB_init(cbb.get(), url_spec.size()));
+    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &aia, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(CBB_add_asn1(&aia, &ca_issuer, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(CBB_add_asn1(&ca_issuer, &access_method, CBS_ASN1_OBJECT));
+    ASSERT_TRUE(
+        AddBytesToCBB(&access_method, AdCaIssuersOid().AsStringPiece()));
+    ASSERT_TRUE(CBB_add_asn1(&ca_issuer, &access_location,
+                             CBS_ASN1_CONTEXT_SPECIFIC | 6));
+    ASSERT_TRUE(AddBytesToCBB(&access_location, url_spec));
+
+    SetExtension(AuthorityInfoAccessOid(), FinishCBB(cbb.get()));
+  }
+
+  // Sets the SAN for the certificate to a single dNSName.
+  void SetSubjectAltName(const std::string& dns_name) {
+    // From RFC 5280:
+    //
+    //   SubjectAltName ::= GeneralNames
+    //
+    //   GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+    //
+    //   GeneralName ::= CHOICE {
+    //        otherName                       [0]     OtherName,
+    //        rfc822Name                      [1]     IA5String,
+    //        dNSName                         [2]     IA5String,
+    //        ... }
+    bssl::ScopedCBB cbb;
+    CBB general_names, general_name;
+    ASSERT_TRUE(CBB_init(cbb.get(), dns_name.size()));
+    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &general_names, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(CBB_add_asn1(&general_names, &general_name,
+                             CBS_ASN1_CONTEXT_SPECIFIC | 2));
+    ASSERT_TRUE(AddBytesToCBB(&general_name, dns_name));
+
+    SetExtension(SubjectAltNameOid(), FinishCBB(cbb.get()));
+  }
+
+  // Sets the signature algorithm for the certificate to either
+  // sha256WithRSAEncryption or sha1WithRSAEncryption.
+  void SetSignatureAlgorithmRsaPkca1(DigestAlgorithm digest) {
+    switch (digest) {
+      case DigestAlgorithm::Sha256: {
+        const uint8_t kSha256WithRSAEncryption[] = {
+            0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+            0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00};
+        SetSignatureAlgorithm(std::string(std::begin(kSha256WithRSAEncryption),
+                                          std::end(kSha256WithRSAEncryption)));
+        break;
+      }
+
+      case DigestAlgorithm::Sha1: {
+        const uint8_t kSha1WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
+                                                  0x86, 0x48, 0x86, 0xf7, 0x0d,
+                                                  0x01, 0x01, 0x05, 0x05, 0x00};
+        SetSignatureAlgorithm(std::string(std::begin(kSha1WithRSAEncryption),
+                                          std::end(kSha1WithRSAEncryption)));
+        break;
+      }
+
+      default:
+        ASSERT_TRUE(false);
+    }
+  }
+
+  void SetSignatureAlgorithm(std::string algorithm_tlv) {
+    signature_algorithm_tlv_ = std::move(algorithm_tlv);
+    Invalidate();
+  }
+
+  void SetRandomSerialNumber() {
+    serial_number_ = base::RandUint64();
+    Invalidate();
+  }
+
+  // Returns a CRYPTO_BUFFER to the generated certificate.
+  CRYPTO_BUFFER* GetCertBuffer() {
+    if (!cert_)
+      GenerateCertificate();
+    return cert_.get();
+  }
+
+  bssl::UniquePtr<CRYPTO_BUFFER> DupCertBuffer() {
+    return bssl::UpRef(GetCertBuffer());
+  }
+
+  // Returns the subject of the generated certificate.
+  const std::string& GetSubject() {
+    if (subject_tlv_.empty())
+      GenerateSubject();
+    return subject_tlv_;
+  }
+
+  // Returns the (RSA) key for the generated certificate.
+  EVP_PKEY* GetKey() {
+    if (!key_)
+      GenerateKey();
+    return key_.get();
+  }
+
+  // Returns an X509Certificate for the generated certificate.
+  scoped_refptr<X509Certificate> GetX509Certificate() {
+    return X509Certificate::CreateFromBuffer(DupCertBuffer(), {});
+  }
+
+  // Returns a copy of the certificate's DER.
+  std::string GetDER() {
+    return x509_util::CryptoBufferAsStringPiece(GetCertBuffer()).as_string();
+  }
+
+ private:
+  // Marks the generated certificate DER as invalid, so it will need to
+  // be re-generated next time the DER is accessed.
+  void Invalidate() { cert_.reset(); }
+
+  // Sets the |key_| to a 2048-bit RSA key.
+  void GenerateKey() {
+    ASSERT_FALSE(key_);
+
+    auto private_key = crypto::RSAPrivateKey::Create(2048);
+    key_ = bssl::UpRef(private_key->key());
+  }
+
+  // Adds bytes (specified as a StringPiece) to the given CBB.
+  static bool AddBytesToCBB(CBB* cbb, base::StringPiece bytes) {
+    return CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(bytes.data()),
+                         bytes.size());
+  }
+
+  // Finalizes the CBB to a std::string.
+  static std::string FinishCBB(CBB* cbb) {
+    size_t cbb_len;
+    uint8_t* cbb_bytes;
+
+    if (!CBB_finish(cbb, &cbb_bytes, &cbb_len)) {
+      ADD_FAILURE() << "CBB_finish() failed";
+      return std::string();
+    }
+
+    bssl::UniquePtr<uint8_t> delete_bytes(cbb_bytes);
+    return std::string(reinterpret_cast<char*>(cbb_bytes), cbb_len);
+  }
+
+  // Generates a random subject for the certificate, comprised of just a CN.
+  void GenerateSubject() {
+    ASSERT_TRUE(subject_tlv_.empty());
+
+    // Use a random common name comprised of 12 bytes in hex.
+    std::string common_name = MakeRandomHexString(12);
+
+    // See RFC 4519.
+    static const uint8_t kCommonName[] = {0x55, 0x04, 0x03};
+
+    // See RFC 5280, section 4.1.2.4.
+    bssl::ScopedCBB cbb;
+    CBB rdns, rdn, attr, type, value;
+    ASSERT_TRUE(CBB_init(cbb.get(), 64));
+    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &rdns, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET));
+    ASSERT_TRUE(CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT));
+    ASSERT_TRUE(CBB_add_bytes(&type, kCommonName, sizeof(kCommonName)));
+    ASSERT_TRUE(CBB_add_asn1(&attr, &value, CBS_ASN1_UTF8STRING));
+    ASSERT_TRUE(AddBytesToCBB(&value, common_name));
+
+    subject_tlv_ = FinishCBB(cbb.get());
+  }
+
+  // Returns the serial number for the generated certificate.
+  uint64_t GetSerialNumber() {
+    if (!serial_number_)
+      serial_number_ = base::RandUint64();
+    return serial_number_;
+  }
+
+  // Parses |cert| and copies the following properties:
+  //   * All extensions (dropping any duplicates)
+  //   * Signature algorithm (from Certificate)
+  //   * Validity (expiration)
+  void InitFromCert(const der::Input& cert) {
+    extensions_.clear();
+    Invalidate();
+
+    // From RFC 5280, section 4.1
+    //    Certificate  ::=  SEQUENCE  {
+    //      tbsCertificate       TBSCertificate,
+    //      signatureAlgorithm   AlgorithmIdentifier,
+    //      signatureValue       BIT STRING  }
+
+    // TBSCertificate  ::=  SEQUENCE  {
+    //      version         [0]  EXPLICIT Version DEFAULT v1,
+    //      serialNumber         CertificateSerialNumber,
+    //      signature            AlgorithmIdentifier,
+    //      issuer               Name,
+    //      validity             Validity,
+    //      subject              Name,
+    //      subjectPublicKeyInfo SubjectPublicKeyInfo,
+    //      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+    //                           -- If present, version MUST be v2 or v3
+    //      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+    //                           -- If present, version MUST be v2 or v3
+    //      extensions      [3]  EXPLICIT Extensions OPTIONAL
+    //                           -- If present, version MUST be v3
+    //      }
+    der::Parser parser(cert);
+    der::Parser certificate;
+    der::Parser tbs_certificate;
+    ASSERT_TRUE(parser.ReadSequence(&certificate));
+    ASSERT_TRUE(certificate.ReadSequence(&tbs_certificate));
+
+    // version
+    bool unused;
+    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
+        der::kTagConstructed | der::kTagContextSpecific | 0, &unused));
+    // serialNumber
+    ASSERT_TRUE(tbs_certificate.SkipTag(der::kInteger));
+
+    // signature
+    der::Input signature_algorithm_tlv;
+    ASSERT_TRUE(tbs_certificate.ReadRawTLV(&signature_algorithm_tlv));
+    signature_algorithm_tlv_ = signature_algorithm_tlv.AsString();
+
+    // issuer
+    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
+
+    // validity
+    der::Input validity_tlv;
+    ASSERT_TRUE(tbs_certificate.ReadRawTLV(&validity_tlv));
+    validity_tlv_ = validity_tlv.AsString();
+
+    // subject
+    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
+    // subjectPublicKeyInfo
+    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
+    // issuerUniqueID
+    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
+        der::ContextSpecificPrimitive(1), &unused));
+    // subjectUniqueID
+    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
+        der::ContextSpecificPrimitive(2), &unused));
+
+    // extensions
+    bool has_extensions = false;
+    der::Input extensions_tlv;
+    ASSERT_TRUE(tbs_certificate.ReadOptionalTag(
+        der::ContextSpecificConstructed(3), &extensions_tlv, &has_extensions));
+    if (has_extensions) {
+      std::map<der::Input, ParsedExtension> parsed_extensions;
+      ASSERT_TRUE(ParseExtensions(extensions_tlv, &parsed_extensions));
+
+      for (const auto& parsed_extension : parsed_extensions) {
+        SetExtension(parsed_extension.second.oid,
+                     parsed_extension.second.value.AsString(),
+                     parsed_extension.second.critical);
+      }
+    }
+  }
+
+  // Assembles the CertBuilder into a TBSCertificate.
+  void BuildTBSCertificate(std::string* out) {
+    bssl::ScopedCBB cbb;
+    CBB tbs_cert, version, extensions_context, extensions;
+
+    ASSERT_TRUE(CBB_init(cbb.get(), 64));
+    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(
+        CBB_add_asn1(&tbs_cert, &version,
+                     CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
+    // Always use v3 certificates.
+    ASSERT_TRUE(CBB_add_asn1_uint64(&version, 2));
+    ASSERT_TRUE(CBB_add_asn1_uint64(&tbs_cert, GetSerialNumber()));
+    ASSERT_TRUE(AddSignatureAlgorithm(&tbs_cert));
+    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, issuer_->GetSubject()));
+    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, validity_tlv_));
+    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, GetSubject()));
+    ASSERT_TRUE(EVP_marshal_public_key(&tbs_cert, GetKey()));
+
+    // Serialize all the extensions.
+    if (!extensions_.empty()) {
+      ASSERT_TRUE(
+          CBB_add_asn1(&tbs_cert, &extensions_context,
+                       CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 3));
+      ASSERT_TRUE(
+          CBB_add_asn1(&extensions_context, &extensions, CBS_ASN1_SEQUENCE));
+
+      //   Extension  ::=  SEQUENCE  {
+      //        extnID      OBJECT IDENTIFIER,
+      //        critical    BOOLEAN DEFAULT FALSE,
+      //        extnValue   OCTET STRING
+      //                    -- contains the DER encoding of an ASN.1 value
+      //                    -- corresponding to the extension type identified
+      //                    -- by extnID
+      //        }
+      for (const auto& extension_it : extensions_) {
+        CBB extension_seq, oid, extn_value;
+        ASSERT_TRUE(
+            CBB_add_asn1(&extensions, &extension_seq, CBS_ASN1_SEQUENCE));
+        ASSERT_TRUE(CBB_add_asn1(&extension_seq, &oid, CBS_ASN1_OBJECT));
+        ASSERT_TRUE(AddBytesToCBB(&oid, extension_it.first));
+        if (extension_it.second.critical) {
+          ASSERT_TRUE(CBB_add_asn1_bool(&extension_seq, true));
+        }
+
+        ASSERT_TRUE(
+            CBB_add_asn1(&extension_seq, &extn_value, CBS_ASN1_OCTETSTRING));
+        ASSERT_TRUE(AddBytesToCBB(&extn_value, extension_it.second.value));
+        ASSERT_TRUE(CBB_flush(&extensions));
+      }
+    }
+
+    *out = FinishCBB(cbb.get());
+  }
+
+  bool AddSignatureAlgorithm(CBB* cbb) {
+    return AddBytesToCBB(cbb, signature_algorithm_tlv_);
+  }
+
+  void GenerateCertificate() {
+    ASSERT_FALSE(cert_);
+
+    std::string tbs_cert;
+    BuildTBSCertificate(&tbs_cert);
+    const uint8_t* tbs_cert_bytes =
+        reinterpret_cast<const uint8_t*>(tbs_cert.data());
+
+    // Determine the correct digest algorithm to use (assumes RSA PKCS#1
+    // signatures).
+    auto signature_algorithm = SignatureAlgorithm::Create(
+        der::Input(&signature_algorithm_tlv_), nullptr);
+    ASSERT_TRUE(signature_algorithm);
+    ASSERT_EQ(SignatureAlgorithmId::RsaPkcs1, signature_algorithm->algorithm());
+    const EVP_MD* md = nullptr;
+
+    switch (signature_algorithm->digest()) {
+      case DigestAlgorithm::Sha256:
+        md = EVP_sha256();
+        break;
+
+      case DigestAlgorithm::Sha1:
+        md = EVP_sha1();
+        break;
+
+      default:
+        ASSERT_TRUE(false) << "Only rsaEncryptionWithSha256 or "
+                              "rsaEnryptionWithSha1 are supported";
+        break;
+    }
+
+    // Sign the TBSCertificate and write the entire certificate.
+    bssl::ScopedCBB cbb;
+    CBB cert, signature;
+    bssl::ScopedEVP_MD_CTX ctx;
+    uint8_t* sig_out;
+    size_t sig_len;
+
+    ASSERT_TRUE(CBB_init(cbb.get(), tbs_cert.size()));
+    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE));
+    ASSERT_TRUE(AddBytesToCBB(&cert, tbs_cert));
+    ASSERT_TRUE(AddSignatureAlgorithm(&cert));
+    ASSERT_TRUE(CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING));
+    ASSERT_TRUE(CBB_add_u8(&signature, 0 /* no unused bits */));
+    ASSERT_TRUE(
+        EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, issuer_->GetKey()));
+    ASSERT_TRUE(EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes,
+                               tbs_cert.size()));
+    ASSERT_TRUE(CBB_reserve(&signature, &sig_out, sig_len));
+    ASSERT_TRUE(EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes,
+                               tbs_cert.size()));
+    ASSERT_TRUE(CBB_did_write(&signature, sig_len));
+
+    auto cert_der = FinishCBB(cbb.get());
+    cert_ = x509_util::CreateCryptoBuffer(
+        reinterpret_cast<const uint8_t*>(cert_der.data()), cert_der.size());
+  }
+
+  struct ExtensionValue {
+    bool critical = false;
+    std::string value;
+  };
+
+  std::string validity_tlv_;
+  std::string subject_tlv_;
+  std::string signature_algorithm_tlv_;
+  uint64_t serial_number_ = 0;
+
+  std::map<std::string, ExtensionValue> extensions_;
+
+  bssl::UniquePtr<CRYPTO_BUFFER> cert_;
+  bssl::UniquePtr<EVP_PKEY> key_;
+
+  CertBuilder* issuer_ = nullptr;
+};
+
 }  // namespace
 
 // This fixture is for tests that apply to concrete implementations of
@@ -371,8 +818,8 @@
 
   CertVerifyResult verify_result;
   int flags = 0;
-  int error = Verify(chain.get(), "login.trustwave.com", flags,
-                     crl_set.get(), CertificateList(), &verify_result);
+  int error = Verify(chain.get(), "login.trustwave.com", flags, crl_set.get(),
+                     CertificateList(), &verify_result);
   EXPECT_THAT(error, IsOk());
   EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
 }
@@ -2157,14 +2604,6 @@
   }
 }
 
-std::string MakeRandomHexString(size_t num_bytes) {
-  std::vector<char> rand_bytes;
-  rand_bytes.resize(num_bytes);
-
-  base::RandBytes(&rand_bytes[0], rand_bytes.size());
-  return base::HexEncode(&rand_bytes[0], rand_bytes.size());
-}
-
 // This is the same as CertVerifyProcInternalTest, but it additionally sets up
 // networking capabilities for the cert verifiers, and a test server that can be
 // used to serve mock responses for AIA/OCSP/CRL.
@@ -2249,6 +2688,44 @@
     return test_server_.GetURL(path);
   }
 
+  // Creates a certificate chain for www.example.com, where the leaf certificate
+  // has an AIA URL pointing to the test server.
+  void CreateSimpleChainWithAIA(
+      scoped_refptr<X509Certificate>* out_leaf,
+      std::string* ca_issuers_path,
+      bssl::UniquePtr<CRYPTO_BUFFER>* out_intermediate,
+      scoped_refptr<X509Certificate>* out_root) {
+    const char kHostname[] = "www.example.com";
+
+    base::FilePath certs_dir =
+        GetTestNetDataDirectory()
+            .AppendASCII("verify_certificate_chain_unittest")
+            .AppendASCII("target-and-intermediate");
+
+    CertificateList orig_certs = CreateCertificateListFromFile(
+        certs_dir, "chain.pem", X509Certificate::FORMAT_AUTO);
+    ASSERT_EQ(3U, orig_certs.size());
+
+    // Build a slightly modified variant of |orig_certs|.
+    CertBuilder root(orig_certs[2]->cert_buffer(), nullptr);
+    CertBuilder intermediate(orig_certs[1]->cert_buffer(), &root);
+    CertBuilder leaf(orig_certs[0]->cert_buffer(), &intermediate);
+
+    // Make the leaf certificate have an AIA (CA Issuers) that points to the
+    // embedded test server. This uses a random URL for predictable behavior in
+    // the presence of global caching.
+    *ca_issuers_path = MakeRandomPath(".cer");
+    GURL ca_issuers_url = GetTestServerAbsoluteUrl(*ca_issuers_path);
+    leaf.SetCaIssuersUrl(ca_issuers_url);
+    leaf.SetSubjectAltName(kHostname);
+
+    // The chain being verified is solely the leaf certificate (missing the
+    // intermediate and root).
+    *out_leaf = leaf.GetX509Certificate();
+    *out_root = root.GetX509Certificate();
+    *out_intermediate = intermediate.DupCertBuffer();
+  }
+
  private:
   std::unique_ptr<test_server::HttpResponse> DispatchToRequestHandler(
       const test_server::HttpRequest& request) {
@@ -2325,445 +2802,6 @@
       request_handlers_;
 };
 
-// CertBuilder is a helper class to dynamically create a test certificate.
-//
-// CertBuilder is initialized using an existing certificate, from which it
-// copies most properties (see InitFromCert for details).
-//
-// The subject, serial number, and key for the final certificate are chosen
-// randomly. Using a randomized subject and serial number is important to defeat
-// certificate caching done by NSS, which otherwise can make test outcomes
-// dependent on ordering.
-class CertBuilder {
- public:
-  // Initializes the CertBuilder using |orig_cert|. If |issuer| is null
-  // then the generated certificate will be self-signed. Otherwise, it
-  // will be signed using |issuer|.
-  CertBuilder(CRYPTO_BUFFER* orig_cert, CertBuilder* issuer) : issuer_(issuer) {
-    if (!issuer_)
-      issuer_ = this;
-
-    crypto::EnsureOpenSSLInit();
-    InitFromCert(der::Input(x509_util::CryptoBufferAsStringPiece(orig_cert)));
-  }
-
-  // Sets a value for the indicated X.509 (v3) extension.
-  void SetExtension(const der::Input& oid,
-                    std::string value,
-                    bool critical = false) {
-    auto& extension_value = extensions_[oid.AsString()];
-    extension_value.critical = critical;
-    extension_value.value = std::move(value);
-
-    Invalidate();
-  }
-
-  // Sets an AIA extension with a single caIssuers access method.
-  void SetCaIssuersUrl(const GURL& url) {
-    std::string url_spec = url.spec();
-
-    // From RFC 5280:
-    //
-    //   AuthorityInfoAccessSyntax  ::=
-    //           SEQUENCE SIZE (1..MAX) OF AccessDescription
-    //
-    //   AccessDescription  ::=  SEQUENCE {
-    //           accessMethod          OBJECT IDENTIFIER,
-    //           accessLocation        GeneralName  }
-    bssl::ScopedCBB cbb;
-    CBB aia, ca_issuer, access_method, access_location;
-    ASSERT_TRUE(CBB_init(cbb.get(), url_spec.size()));
-    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &aia, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(CBB_add_asn1(&aia, &ca_issuer, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(CBB_add_asn1(&ca_issuer, &access_method, CBS_ASN1_OBJECT));
-    ASSERT_TRUE(
-        AddBytesToCBB(&access_method, AdCaIssuersOid().AsStringPiece()));
-    ASSERT_TRUE(CBB_add_asn1(&ca_issuer, &access_location,
-                             CBS_ASN1_CONTEXT_SPECIFIC | 6));
-    ASSERT_TRUE(AddBytesToCBB(&access_location, url_spec));
-
-    SetExtension(AuthorityInfoAccessOid(), FinishCBB(cbb.get()));
-  }
-
-  // Sets the SAN for the certificate to a single dNSName.
-  void SetSubjectAltName(const std::string& dns_name) {
-    // From RFC 5280:
-    //
-    //   SubjectAltName ::= GeneralNames
-    //
-    //   GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
-    //
-    //   GeneralName ::= CHOICE {
-    //        otherName                       [0]     OtherName,
-    //        rfc822Name                      [1]     IA5String,
-    //        dNSName                         [2]     IA5String,
-    //        ... }
-    bssl::ScopedCBB cbb;
-    CBB general_names, general_name;
-    ASSERT_TRUE(CBB_init(cbb.get(), dns_name.size()));
-    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &general_names, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(CBB_add_asn1(&general_names, &general_name,
-                             CBS_ASN1_CONTEXT_SPECIFIC | 2));
-    ASSERT_TRUE(AddBytesToCBB(&general_name, dns_name));
-
-    SetExtension(SubjectAltNameOid(), FinishCBB(cbb.get()));
-  }
-
-  // Sets the signature algorithm for the certificate to either
-  // sha256WithRSAEncryption or sha1WithRSAEncryption.
-  void SetSignatureAlgorithmRsaPkca1(DigestAlgorithm digest) {
-    switch (digest) {
-      case DigestAlgorithm::Sha256: {
-        const uint8_t kSha256WithRSAEncryption[] = {
-            0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
-            0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00};
-        SetSignatureAlgorithm(std::string(std::begin(kSha256WithRSAEncryption),
-                                          std::end(kSha256WithRSAEncryption)));
-        break;
-      }
-
-      case DigestAlgorithm::Sha1: {
-        const uint8_t kSha1WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
-                                                  0x86, 0x48, 0x86, 0xf7, 0x0d,
-                                                  0x01, 0x01, 0x05, 0x05, 0x00};
-        SetSignatureAlgorithm(std::string(std::begin(kSha1WithRSAEncryption),
-                                          std::end(kSha1WithRSAEncryption)));
-        break;
-      }
-
-      default:
-        ASSERT_TRUE(false);
-    }
-  }
-
-  void SetSignatureAlgorithm(std::string algorithm_tlv) {
-    signature_algorithm_tlv_ = std::move(algorithm_tlv);
-    Invalidate();
-  }
-
-  void SetRandomSerialNumber() {
-    serial_number_ = base::RandUint64();
-    Invalidate();
-  }
-
-  // Returns a CRYPTO_BUFFER to the generated certificate.
-  CRYPTO_BUFFER* GetCertBuffer() {
-    if (!cert_)
-      GenerateCertificate();
-    return cert_.get();
-  }
-
-  bssl::UniquePtr<CRYPTO_BUFFER> DupCertBuffer() {
-    return bssl::UpRef(GetCertBuffer());
-  }
-
-  // Returns the subject of the generated certificate.
-  const std::string& GetSubject() {
-    if (subject_tlv_.empty())
-      GenerateSubject();
-    return subject_tlv_;
-  }
-
-  // Returns the (RSA) key for the generated certificate.
-  EVP_PKEY* GetKey() {
-    if (!key_)
-      GenerateKey();
-    return key_.get();
-  }
-
-  // Returns an X509Certificate for the generated certificate.
-  scoped_refptr<X509Certificate> GetX509Certificate() {
-    return X509Certificate::CreateFromBuffer(DupCertBuffer(), {});
-  }
-
-  // Returns a copy of the certificate's DER.
-  std::string GetDER() {
-    return x509_util::CryptoBufferAsStringPiece(GetCertBuffer()).as_string();
-  }
-
- private:
-  // Marks the generated certificate DER as invalid, so it will need to
-  // be re-generated next time the DER is accessed.
-  void Invalidate() { cert_.reset(); }
-
-  // Sets the |key_| to a 2048-bit RSA key.
-  void GenerateKey() {
-    ASSERT_FALSE(key_);
-
-    auto private_key = crypto::RSAPrivateKey::Create(2048);
-    key_ = bssl::UpRef(private_key->key());
-  }
-
-  // Adds bytes (specified as a StringPiece) to the given CBB.
-  static bool AddBytesToCBB(CBB* cbb, base::StringPiece bytes) {
-    return CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(bytes.data()),
-                         bytes.size());
-  }
-
-  // Finalizes the CBB to a std::string.
-  static std::string FinishCBB(CBB* cbb) {
-    size_t cbb_len;
-    uint8_t* cbb_bytes;
-
-    if (!CBB_finish(cbb, &cbb_bytes, &cbb_len)) {
-      ADD_FAILURE() << "CBB_finish() failed";
-      return std::string();
-    }
-
-    bssl::UniquePtr<uint8_t> delete_bytes(cbb_bytes);
-    return std::string(reinterpret_cast<char*>(cbb_bytes), cbb_len);
-  }
-
-  // Generates a random subject for the certificate, comprised of just a CN.
-  void GenerateSubject() {
-    ASSERT_TRUE(subject_tlv_.empty());
-
-    // Use a random common name comprised of 12 bytes in hex.
-    std::string common_name = MakeRandomHexString(12);
-
-    // See RFC 4519.
-    static const uint8_t kCommonName[] = {0x55, 0x04, 0x03};
-
-    // See RFC 5280, section 4.1.2.4.
-    bssl::ScopedCBB cbb;
-    CBB rdns, rdn, attr, type, value;
-    ASSERT_TRUE(CBB_init(cbb.get(), 64));
-    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &rdns, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(CBB_add_asn1(&rdns, &rdn, CBS_ASN1_SET));
-    ASSERT_TRUE(CBB_add_asn1(&rdn, &attr, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(CBB_add_asn1(&attr, &type, CBS_ASN1_OBJECT));
-    ASSERT_TRUE(CBB_add_bytes(&type, kCommonName, sizeof(kCommonName)));
-    ASSERT_TRUE(CBB_add_asn1(&attr, &value, CBS_ASN1_UTF8STRING));
-    ASSERT_TRUE(AddBytesToCBB(&value, common_name));
-
-    subject_tlv_ = FinishCBB(cbb.get());
-  }
-
-  // Returns the serial number for the generated certificate.
-  uint64_t GetSerialNumber() {
-    if (!serial_number_)
-      serial_number_ = base::RandUint64();
-    return serial_number_;
-  }
-
-  // Parses |cert| and copies the following properties:
-  //   * All extensions (dropping any duplicates)
-  //   * Signature algorithm (from Certificate)
-  //   * Validity (expiration)
-  void InitFromCert(const der::Input& cert) {
-    extensions_.clear();
-    Invalidate();
-
-    // From RFC 5280, section 4.1
-    //    Certificate  ::=  SEQUENCE  {
-    //      tbsCertificate       TBSCertificate,
-    //      signatureAlgorithm   AlgorithmIdentifier,
-    //      signatureValue       BIT STRING  }
-
-    // TBSCertificate  ::=  SEQUENCE  {
-    //      version         [0]  EXPLICIT Version DEFAULT v1,
-    //      serialNumber         CertificateSerialNumber,
-    //      signature            AlgorithmIdentifier,
-    //      issuer               Name,
-    //      validity             Validity,
-    //      subject              Name,
-    //      subjectPublicKeyInfo SubjectPublicKeyInfo,
-    //      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
-    //                           -- If present, version MUST be v2 or v3
-    //      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
-    //                           -- If present, version MUST be v2 or v3
-    //      extensions      [3]  EXPLICIT Extensions OPTIONAL
-    //                           -- If present, version MUST be v3
-    //      }
-    der::Parser parser(cert);
-    der::Parser certificate;
-    der::Parser tbs_certificate;
-    ASSERT_TRUE(parser.ReadSequence(&certificate));
-    ASSERT_TRUE(certificate.ReadSequence(&tbs_certificate));
-
-    // version
-    bool unused;
-    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
-        der::kTagConstructed | der::kTagContextSpecific | 0, &unused));
-    // serialNumber
-    ASSERT_TRUE(tbs_certificate.SkipTag(der::kInteger));
-
-    // signature
-    der::Input signature_algorithm_tlv;
-    ASSERT_TRUE(tbs_certificate.ReadRawTLV(&signature_algorithm_tlv));
-    signature_algorithm_tlv_ = signature_algorithm_tlv.AsString();
-
-    // issuer
-    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
-
-    // validity
-    der::Input validity_tlv;
-    ASSERT_TRUE(tbs_certificate.ReadRawTLV(&validity_tlv));
-    validity_tlv_ = validity_tlv.AsString();
-
-    // subject
-    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
-    // subjectPublicKeyInfo
-    ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
-    // issuerUniqueID
-    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
-        der::ContextSpecificPrimitive(1), &unused));
-    // subjectUniqueID
-    ASSERT_TRUE(tbs_certificate.SkipOptionalTag(
-        der::ContextSpecificPrimitive(2), &unused));
-
-    // extensions
-    bool has_extensions = false;
-    der::Input extensions_tlv;
-    ASSERT_TRUE(tbs_certificate.ReadOptionalTag(
-        der::ContextSpecificConstructed(3), &extensions_tlv, &has_extensions));
-    if (has_extensions) {
-      std::map<der::Input, ParsedExtension> parsed_extensions;
-      ASSERT_TRUE(ParseExtensions(extensions_tlv, &parsed_extensions));
-
-      for (const auto& parsed_extension : parsed_extensions) {
-        SetExtension(parsed_extension.second.oid,
-                     parsed_extension.second.value.AsString(),
-                     parsed_extension.second.critical);
-      }
-    }
-  }
-
-  // Assembles the CertBuilder into a TBSCertificate.
-  void BuildTBSCertificate(std::string* out) {
-    bssl::ScopedCBB cbb;
-    CBB tbs_cert, version, extensions_context, extensions;
-
-    ASSERT_TRUE(CBB_init(cbb.get(), 64));
-    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &tbs_cert, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(
-        CBB_add_asn1(&tbs_cert, &version,
-                     CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
-    // Always use v3 certificates.
-    ASSERT_TRUE(CBB_add_asn1_uint64(&version, 2));
-    ASSERT_TRUE(CBB_add_asn1_uint64(&tbs_cert, GetSerialNumber()));
-    ASSERT_TRUE(AddSignatureAlgorithm(&tbs_cert));
-    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, issuer_->GetSubject()));
-    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, validity_tlv_));
-    ASSERT_TRUE(AddBytesToCBB(&tbs_cert, GetSubject()));
-    ASSERT_TRUE(EVP_marshal_public_key(&tbs_cert, GetKey()));
-
-    // Serialize all the extensions.
-    if (!extensions_.empty()) {
-      ASSERT_TRUE(
-          CBB_add_asn1(&tbs_cert, &extensions_context,
-                       CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 3));
-      ASSERT_TRUE(
-          CBB_add_asn1(&extensions_context, &extensions, CBS_ASN1_SEQUENCE));
-
-      //   Extension  ::=  SEQUENCE  {
-      //        extnID      OBJECT IDENTIFIER,
-      //        critical    BOOLEAN DEFAULT FALSE,
-      //        extnValue   OCTET STRING
-      //                    -- contains the DER encoding of an ASN.1 value
-      //                    -- corresponding to the extension type identified
-      //                    -- by extnID
-      //        }
-      for (const auto& extension_it : extensions_) {
-        CBB extension_seq, oid, extn_value;
-        ASSERT_TRUE(
-            CBB_add_asn1(&extensions, &extension_seq, CBS_ASN1_SEQUENCE));
-        ASSERT_TRUE(CBB_add_asn1(&extension_seq, &oid, CBS_ASN1_OBJECT));
-        ASSERT_TRUE(AddBytesToCBB(&oid, extension_it.first));
-        if (extension_it.second.critical) {
-          ASSERT_TRUE(CBB_add_asn1_bool(&extension_seq, true));
-        }
-
-        ASSERT_TRUE(
-            CBB_add_asn1(&extension_seq, &extn_value, CBS_ASN1_OCTETSTRING));
-        ASSERT_TRUE(AddBytesToCBB(&extn_value, extension_it.second.value));
-        ASSERT_TRUE(CBB_flush(&extensions));
-      }
-    }
-
-    *out = FinishCBB(cbb.get());
-  }
-
-  bool AddSignatureAlgorithm(CBB* cbb) {
-    return AddBytesToCBB(cbb, signature_algorithm_tlv_);
-  }
-
-  void GenerateCertificate() {
-    ASSERT_FALSE(cert_);
-
-    std::string tbs_cert;
-    BuildTBSCertificate(&tbs_cert);
-    const uint8_t* tbs_cert_bytes =
-        reinterpret_cast<const uint8_t*>(tbs_cert.data());
-
-    // Determine the correct digest algorithm to use (assumes RSA PKCS#1
-    // signatures).
-    auto signature_algorithm = SignatureAlgorithm::Create(
-        der::Input(&signature_algorithm_tlv_), nullptr);
-    ASSERT_TRUE(signature_algorithm);
-    ASSERT_EQ(SignatureAlgorithmId::RsaPkcs1, signature_algorithm->algorithm());
-    const EVP_MD* md = nullptr;
-
-    switch (signature_algorithm->digest()) {
-      case DigestAlgorithm::Sha256:
-        md = EVP_sha256();
-        break;
-
-      case DigestAlgorithm::Sha1:
-        md = EVP_sha1();
-        break;
-
-      default:
-        ASSERT_TRUE(false) << "Only rsaEncryptionWithSha256 or "
-                              "rsaEnryptionWithSha1 are supported";
-        break;
-    }
-
-    // Sign the TBSCertificate and write the entire certificate.
-    bssl::ScopedCBB cbb;
-    CBB cert, signature;
-    bssl::ScopedEVP_MD_CTX ctx;
-    uint8_t* sig_out;
-    size_t sig_len;
-
-    ASSERT_TRUE(CBB_init(cbb.get(), tbs_cert.size()));
-    ASSERT_TRUE(CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE));
-    ASSERT_TRUE(AddBytesToCBB(&cert, tbs_cert));
-    ASSERT_TRUE(AddSignatureAlgorithm(&cert));
-    ASSERT_TRUE(CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING));
-    ASSERT_TRUE(CBB_add_u8(&signature, 0 /* no unused bits */));
-    ASSERT_TRUE(
-        EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, issuer_->GetKey()));
-    ASSERT_TRUE(EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes,
-                               tbs_cert.size()));
-    ASSERT_TRUE(CBB_reserve(&signature, &sig_out, sig_len));
-    ASSERT_TRUE(EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes,
-                               tbs_cert.size()));
-    ASSERT_TRUE(CBB_did_write(&signature, sig_len));
-
-    auto cert_der = FinishCBB(cbb.get());
-    cert_ = x509_util::CreateCryptoBuffer(
-        reinterpret_cast<const uint8_t*>(cert_der.data()), cert_der.size());
-  }
-
-  struct ExtensionValue {
-    bool critical = false;
-    std::string value;
-  };
-
-  std::string validity_tlv_;
-  std::string subject_tlv_;
-  std::string signature_algorithm_tlv_;
-  uint64_t serial_number_ = 0;
-
-  std::map<std::string, ExtensionValue> extensions_;
-
-  bssl::UniquePtr<CRYPTO_BUFFER> cert_;
-  bssl::UniquePtr<EVP_PKEY> key_;
-
-  CertBuilder* issuer_ = nullptr;
-};
-
 INSTANTIATE_TEST_CASE_P(,
                         CertVerifyProcInternalWithNetFetchingTest,
                         testing::ValuesIn(kAllCertVerifiers),
@@ -2784,39 +2822,23 @@
 TEST_P(CertVerifyProcInternalWithNetFetchingTest, MAYBE_IntermediateFromAia404) {
   const char kHostname[] = "www.example.com";
 
-  base::FilePath certs_dir =
-      GetTestNetDataDirectory()
-          .AppendASCII("verify_certificate_chain_unittest")
-          .AppendASCII("target-and-intermediate");
+  // Create a chain where the leaf has an AIA that points to test server.
+  scoped_refptr<X509Certificate> leaf;
+  std::string ca_issuers_path;
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
+  scoped_refptr<X509Certificate> root;
+  CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
 
-  CertificateList orig_certs = CreateCertificateListFromFile(
-      certs_dir, "chain.pem", X509Certificate::FORMAT_AUTO);
-  ASSERT_EQ(3U, orig_certs.size());
-
-  // Build a slightly modified variant of |orig_certs|, in which the leaf points
-  // to an AIA for obtaining the missing intermediate. This URL responds
-  // with a 404 (and a non-certificate response body and MIME).
-  CertBuilder root(orig_certs[2]->cert_buffer(), nullptr);
-  CertBuilder intermediate(orig_certs[1]->cert_buffer(), &root);
-  CertBuilder leaf(orig_certs[0]->cert_buffer(), &intermediate);
-
-  std::string ca_issuers_path = MakeRandomPath(".cer");
-  GURL ca_issuers_url = GetTestServerAbsoluteUrl(ca_issuers_path);
-  leaf.SetCaIssuersUrl(ca_issuers_url);
-  leaf.SetSubjectAltName(kHostname);
-
+  // Serve a 404 for the AIA url.
   RegisterSimpleTestServerHandler(ca_issuers_path, HTTP_NOT_FOUND, "text/plain",
                                   "Not Found");
 
   // Trust the root certificate.
-  auto root_cert = root.GetX509Certificate();
-  ScopedTestRoot scoped_root(root_cert.get());
+  ScopedTestRoot scoped_root(root.get());
 
   // The chain being verified is solely the leaf certificate (missing the
   // intermediate and root).
-  scoped_refptr<X509Certificate> chain = leaf.GetX509Certificate();
-  ASSERT_TRUE(chain.get());
-  ASSERT_EQ(0u, chain->intermediate_buffers().size());
+  ASSERT_EQ(0u, leaf->intermediate_buffers().size());
 
   const int flags = 0;
   int error;
@@ -2824,7 +2846,7 @@
 
   // Verifying the chain should fail as the intermediate is missing, and
   // cannot be fetched via AIA.
-  error = Verify(chain.get(), kHostname, flags, nullptr, CertificateList(),
+  error = Verify(leaf.get(), kHostname, flags, nullptr, CertificateList(),
                  &verify_result);
   EXPECT_NE(OK, error);
 
@@ -2843,49 +2865,32 @@
 // intermediate is available via AIA.
 // TODO(crbug.com/860189): Failing on iOS
 #if defined(OS_IOS)
-#define MAYBE_IntermediateFromAia200 DISABLED_IntermediateFromAia200
+#define MAYBE_IntermediateFromAia200Der DISABLED_IntermediateFromAia200Der
 #else
-#define MAYBE_IntermediateFromAia200 IntermediateFromAia200
+#define MAYBE_IntermediateFromAia200Der IntermediateFromAia200Der
 #endif
 TEST_P(CertVerifyProcInternalWithNetFetchingTest,
-       MAYBE_IntermediateFromAia200) {
+       MAYBE_IntermediateFromAia200Der) {
   const char kHostname[] = "www.example.com";
 
-  base::FilePath certs_dir =
-      GetTestNetDataDirectory()
-          .AppendASCII("verify_certificate_chain_unittest")
-          .AppendASCII("target-and-intermediate");
-
-  CertificateList orig_certs = CreateCertificateListFromFile(
-      certs_dir, "chain.pem", X509Certificate::FORMAT_AUTO);
-  ASSERT_EQ(3U, orig_certs.size());
-
-  // Build a slightly modified variant of |orig_certs|.
-  CertBuilder root(orig_certs[2]->cert_buffer(), nullptr);
-  CertBuilder intermediate(orig_certs[1]->cert_buffer(), &root);
-  CertBuilder leaf(orig_certs[0]->cert_buffer(), &intermediate);
-
-  // Make the leaf certificate have an AIA (CA Issuers) that points to the
-  // embedded test server. This uses a random URL for predictable behavior in
-  // the presence of global caching.
-  std::string ca_issuers_path = MakeRandomPath(".cer");
-  GURL ca_issuers_url = GetTestServerAbsoluteUrl(ca_issuers_path);
-  leaf.SetCaIssuersUrl(ca_issuers_url);
-  leaf.SetSubjectAltName(kHostname);
+  // Create a chain where the leaf has an AIA that points to test server.
+  scoped_refptr<X509Certificate> leaf;
+  std::string ca_issuers_path;
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
+  scoped_refptr<X509Certificate> root;
+  CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
 
   // Setup the test server to reply with the correct intermediate.
   RegisterSimpleTestServerHandler(
-      ca_issuers_path, HTTP_OK, "application/pkix-cert", intermediate.GetDER());
+      ca_issuers_path, HTTP_OK, "application/pkix-cert",
+      x509_util::CryptoBufferAsStringPiece(intermediate.get()).as_string());
 
   // Trust the root certificate.
-  auto root_cert = root.GetX509Certificate();
-  ScopedTestRoot scoped_root(root_cert.get());
+  ScopedTestRoot scoped_root(root.get());
 
   // The chain being verified is solely the leaf certificate (missing the
   // intermediate and root).
-  scoped_refptr<X509Certificate> chain = leaf.GetX509Certificate();
-  ASSERT_TRUE(chain.get());
-  ASSERT_EQ(0u, chain->intermediate_buffers().size());
+  ASSERT_EQ(0u, leaf->intermediate_buffers().size());
 
   const int flags = 0;
   int error;
@@ -2893,11 +2898,119 @@
 
   // Verifying the chain should succeed as the missing intermediate can be
   // fetched via AIA.
-  error = Verify(chain.get(), kHostname, flags, nullptr, CertificateList(),
+  error = Verify(leaf.get(), kHostname, flags, nullptr, CertificateList(),
                  &verify_result);
   EXPECT_THAT(error, IsOk());
 }
 
+// This test is the same as IntermediateFromAia200Der, except the certificate is
+// served as PEM rather than DER.
+//
+// Tries verifying a certificate chain that is missing an intermediate. The
+// intermediate is available via AIA, however is served as a PEM file rather
+// than DER.
+// TODO(crbug.com/860189): Failing on iOS
+#if defined(OS_IOS)
+#define MAYBE_IntermediateFromAia200Pem DISABLED_IntermediateFromAia200Pem
+#else
+#define MAYBE_IntermediateFromAia200Pem IntermediateFromAia200Pem
+#endif
+TEST_P(CertVerifyProcInternalWithNetFetchingTest,
+       MAYBE_IntermediateFromAia200Pem) {
+  const char kHostname[] = "www.example.com";
+
+  // Create a chain where the leaf has an AIA that points to test server.
+  scoped_refptr<X509Certificate> leaf;
+  std::string ca_issuers_path;
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
+  scoped_refptr<X509Certificate> root;
+  CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
+
+  std::string intermediate_pem;
+  ASSERT_TRUE(
+      X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem));
+
+  // Setup the test server to reply with the correct intermediate.
+  RegisterSimpleTestServerHandler(
+      ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem);
+
+  // Trust the root certificate.
+  ScopedTestRoot scoped_root(root.get());
+
+  // The chain being verified is solely the leaf certificate (missing the
+  // intermediate and root).
+  ASSERT_EQ(0u, leaf->intermediate_buffers().size());
+
+  const int flags = 0;
+  int error;
+  CertVerifyResult verify_result;
+
+  // Verifying the chain should succeed as the missing intermediate can be
+  // fetched via AIA.
+  error = Verify(leaf.get(), kHostname, flags, nullptr, CertificateList(),
+                 &verify_result);
+
+  if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
+    // Android doesn't support PEM - https://crbug.com/725180
+    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
+  } else {
+    EXPECT_THAT(error, IsOk());
+  }
+}
+
+// This test is the same as IntermediateFromAia200Pem, but with a different
+// formatting on the PEM data.
+//
+// TODO(crbug.com/860189): Failing on iOS
+#if defined(OS_IOS)
+#define MAYBE_IntermediateFromAia200Pem2 DISABLED_IntermediateFromAia200Pem2
+#else
+#define MAYBE_IntermediateFromAia200Pem2 IntermediateFromAia200Pem2
+#endif
+TEST_P(CertVerifyProcInternalWithNetFetchingTest,
+       MAYBE_IntermediateFromAia200Pem2) {
+  const char kHostname[] = "www.example.com";
+
+  // Create a chain where the leaf has an AIA that points to test server.
+  scoped_refptr<X509Certificate> leaf;
+  std::string ca_issuers_path;
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate;
+  scoped_refptr<X509Certificate> root;
+  CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root);
+
+  std::string intermediate_pem;
+  ASSERT_TRUE(
+      X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem));
+  intermediate_pem = "Text at start of file\n" + intermediate_pem;
+
+  // Setup the test server to reply with the correct intermediate.
+  RegisterSimpleTestServerHandler(
+      ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem);
+
+  // Trust the root certificate.
+  ScopedTestRoot scoped_root(root.get());
+
+  // The chain being verified is solely the leaf certificate (missing the
+  // intermediate and root).
+  ASSERT_EQ(0u, leaf->intermediate_buffers().size());
+
+  const int flags = 0;
+  int error;
+  CertVerifyResult verify_result;
+
+  // Verifying the chain should succeed as the missing intermediate can be
+  // fetched via AIA.
+  error = Verify(leaf.get(), kHostname, flags, nullptr, CertificateList(),
+                 &verify_result);
+
+  if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) {
+    // Android doesn't support PEM - https://crbug.com/725180
+    EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID));
+  } else {
+    EXPECT_THAT(error, IsOk());
+  }
+}
+
 // Tries verifying a certificate chain that uses a SHA1 intermediate,
 // however, chasing the AIA can discover a SHA256 version of the intermediate.
 //
diff --git a/net/cert/internal/cert_issuer_source_aia.cc b/net/cert/internal/cert_issuer_source_aia.cc
index 2e22bfe7..564beed1 100644
--- a/net/cert/internal/cert_issuer_source_aia.cc
+++ b/net/cert/internal/cert_issuer_source_aia.cc
@@ -4,8 +4,10 @@
 
 #include "net/cert/internal/cert_issuer_source_aia.h"
 
+#include "base/strings/string_piece.h"
 #include "net/cert/cert_net_fetcher.h"
 #include "net/cert/internal/cert_errors.h"
+#include "net/cert/pem_tokenizer.h"
 #include "net/cert/x509_util.h"
 #include "url/gurl.h"
 
@@ -18,6 +20,37 @@
 const int kMaxResponseBytes = 65536;
 const int kMaxFetchesPerCert = 5;
 
+bool ParseCertFromDer(const uint8_t* data,
+                      size_t length,
+                      ParsedCertificateList* results) {
+  CertErrors errors;
+  if (!ParsedCertificate::CreateAndAddToVector(
+          x509_util::CreateCryptoBuffer(data, length),
+          x509_util::DefaultParseCertificateOptions(), results, &errors)) {
+    // TODO(crbug.com/634443): propagate error info.
+    LOG(ERROR) << "Error parsing cert retrieved from AIA (as DER):\n"
+               << errors.ToDebugString();
+
+    return false;
+  }
+
+  return true;
+}
+
+bool ParseCertFromPem(const uint8_t* data,
+                      size_t length,
+                      ParsedCertificateList* results) {
+  base::StringPiece data_strpiece(reinterpret_cast<const char*>(data), length);
+
+  PEMTokenizer pem_tokenizer(data_strpiece, {"CERTIFICATE"});
+  if (!pem_tokenizer.GetNext())
+    return false;
+
+  return ParseCertFromDer(
+      reinterpret_cast<const uint8_t*>(pem_tokenizer.data().data()),
+      pem_tokenizer.data().size(), results);
+}
+
 class AiaRequest : public CertIssuerSource::Request {
  public:
   AiaRequest() = default;
@@ -70,6 +103,7 @@
     LOG(ERROR) << "AiaRequest::OnFetchCompleted got error " << error;
     return false;
   }
+
   // RFC 5280 section 4.2.2.1:
   //
   //    Conforming applications that support HTTP or FTP for accessing
@@ -77,20 +111,12 @@
   //    certificates and SHOULD be able to accept "certs-only" CMS messages.
   //
   // TODO(mattm): Is supporting CMS message format important?
-  //
-  // TODO(eroman): Avoid copying bytes in the certificate?
-  CertErrors errors;
-  if (!ParsedCertificate::CreateAndAddToVector(
-          x509_util::CreateCryptoBuffer(fetched_bytes.data(),
-                                        fetched_bytes.size()),
-          x509_util::DefaultParseCertificateOptions(), results, &errors)) {
-    // TODO(crbug.com/634443): propagate error info.
-    LOG(ERROR) << "Error parsing cert retrieved from AIA:\n"
-               << errors.ToDebugString();
-    return false;
-  }
 
-  return true;
+  // TODO(https://crbug.com/870359): Some AIA responses are served as PEM, which
+  // is not part of RFC 5280's profile.
+  return ParseCertFromDer(fetched_bytes.data(), fetched_bytes.size(),
+                          results) ||
+         ParseCertFromPem(fetched_bytes.data(), fetched_bytes.size(), results);
 }
 
 }  // namespace
diff --git a/net/dns/address_sorter.h b/net/dns/address_sorter.h
index 14a7d23..2af2ee2 100644
--- a/net/dns/address_sorter.h
+++ b/net/dns/address_sorter.h
@@ -21,16 +21,15 @@
 // AddressSorter does not necessarily preserve port numbers on the sorted list.
 class NET_EXPORT AddressSorter {
  public:
-  typedef base::Callback<void(bool success,
-                              const AddressList& list)> CallbackType;
+  using CallbackType =
+      base::OnceCallback<void(bool success, const AddressList& list)>;
 
   virtual ~AddressSorter() {}
 
   // Sorts |list|, which must include at least one IPv6 address.
   // Calls |callback| upon completion. Could complete synchronously. Could
   // complete after this AddressSorter is destroyed.
-  virtual void Sort(const AddressList& list,
-                    const CallbackType& callback) const = 0;
+  virtual void Sort(const AddressList& list, CallbackType callback) const = 0;
 
   // Creates platform-dependent AddressSorter.
   static std::unique_ptr<AddressSorter> CreateAddressSorter();
diff --git a/net/dns/address_sorter_posix.cc b/net/dns/address_sorter_posix.cc
index 37af8da..4379643c 100644
--- a/net/dns/address_sorter_posix.cc
+++ b/net/dns/address_sorter_posix.cc
@@ -266,7 +266,7 @@
 }
 
 void AddressSorterPosix::Sort(const AddressList& list,
-                              const CallbackType& callback) const {
+                              CallbackType callback) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   std::vector<std::unique_ptr<DestinationInfo>> sort_list;
 
@@ -322,7 +322,7 @@
   for (size_t i = 0; i < sort_list.size(); ++i)
     result.push_back(IPEndPoint(sort_list[i]->address, 0 /* port */));
 
-  callback.Run(true, result);
+  std::move(callback).Run(true, result);
 }
 
 void AddressSorterPosix::OnIPAddressChanged() {
diff --git a/net/dns/address_sorter_posix.h b/net/dns/address_sorter_posix.h
index b8f40d1..48ec585 100644
--- a/net/dns/address_sorter_posix.h
+++ b/net/dns/address_sorter_posix.h
@@ -63,8 +63,7 @@
   ~AddressSorterPosix() override;
 
   // AddressSorter:
-  void Sort(const AddressList& list,
-            const CallbackType& callback) const override;
+  void Sort(const AddressList& list, CallbackType callback) const override;
 
  private:
   friend class AddressSorterPosixTest;
diff --git a/net/dns/address_sorter_posix_unittest.cc b/net/dns/address_sorter_posix_unittest.cc
index 678132b..da5ba524 100644
--- a/net/dns/address_sorter_posix_unittest.cc
+++ b/net/dns/address_sorter_posix_unittest.cc
@@ -227,8 +227,8 @@
 
     AddressList result;
     TestCompletionCallback callback;
-    sorter_.Sort(list, base::Bind(&OnSortComplete, &result,
-                                  callback.callback()));
+    sorter_.Sort(list,
+                 base::BindOnce(&OnSortComplete, &result, callback.callback()));
     callback.WaitForResult();
 
     for (size_t i = 0; (i < result.size()) || (order[i] >= 0); ++i) {
diff --git a/net/dns/address_sorter_unittest.cc b/net/dns/address_sorter_unittest.cc
index 1fcb5ea..df4739d 100644
--- a/net/dns/address_sorter_unittest.cc
+++ b/net/dns/address_sorter_unittest.cc
@@ -62,8 +62,8 @@
 
   AddressList result;
   TestCompletionCallback callback;
-  sorter->Sort(list, base::Bind(&OnSortComplete, &result,
-                                callback.callback()));
+  sorter->Sort(list,
+               base::BindOnce(&OnSortComplete, &result, callback.callback()));
   EXPECT_EQ(expected_result, callback.WaitForResult());
 }
 
diff --git a/net/dns/address_sorter_win.cc b/net/dns/address_sorter_win.cc
index 5e3ace6..7a47ff5 100644
--- a/net/dns/address_sorter_win.cc
+++ b/net/dns/address_sorter_win.cc
@@ -32,10 +32,9 @@
   ~AddressSorterWin() override {}
 
   // AddressSorter:
-  void Sort(const AddressList& list,
-            const CallbackType& callback) const override {
+  void Sort(const AddressList& list, CallbackType callback) const override {
     DCHECK(!list.empty());
-    Job::Start(list, callback);
+    Job::Start(list, std::move(callback));
   }
 
  private:
@@ -43,8 +42,8 @@
   // performs the necessary conversions to/from AddressList.
   class Job : public base::RefCountedThreadSafe<Job> {
    public:
-    static void Start(const AddressList& list, const CallbackType& callback) {
-      auto job = base::WrapRefCounted(new Job(list, callback));
+    static void Start(const AddressList& list, CallbackType callback) {
+      auto job = base::WrapRefCounted(new Job(list, std::move(callback)));
       base::PostTaskWithTraitsAndReply(
           FROM_HERE,
           {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
@@ -55,15 +54,15 @@
    private:
     friend class base::RefCountedThreadSafe<Job>;
 
-    Job(const AddressList& list, const CallbackType& callback)
-        : callback_(callback),
+    Job(const AddressList& list, CallbackType callback)
+        : callback_(std::move(callback)),
           buffer_size_(sizeof(SOCKET_ADDRESS_LIST) +
-                       list.size() * (sizeof(SOCKET_ADDRESS) +
-                                      sizeof(SOCKADDR_STORAGE))),
-          input_buffer_(reinterpret_cast<SOCKET_ADDRESS_LIST*>(
-              malloc(buffer_size_))),
-          output_buffer_(reinterpret_cast<SOCKET_ADDRESS_LIST*>(
-              malloc(buffer_size_))),
+                       list.size() *
+                           (sizeof(SOCKET_ADDRESS) + sizeof(SOCKADDR_STORAGE))),
+          input_buffer_(
+              reinterpret_cast<SOCKET_ADDRESS_LIST*>(malloc(buffer_size_))),
+          output_buffer_(
+              reinterpret_cast<SOCKET_ADDRESS_LIST*>(malloc(buffer_size_))),
           success_(false) {
       input_buffer_->iAddressCount = list.size();
       SOCKADDR_STORAGE* storage = reinterpret_cast<SOCKADDR_STORAGE*>(
@@ -125,10 +124,10 @@
           list.push_back(ipe);
         }
       }
-      callback_.Run(success_, list);
+      std::move(callback_).Run(success_, list);
     }
 
-    const CallbackType callback_;
+    CallbackType callback_;
     const size_t buffer_size_;
     std::unique_ptr<SOCKET_ADDRESS_LIST, base::FreeDeleter> input_buffer_;
     std::unique_ptr<SOCKET_ADDRESS_LIST, base::FreeDeleter> output_buffer_;
diff --git a/net/dns/dns_test_util.cc b/net/dns/dns_test_util.cc
index 6919300..ac98c63 100644
--- a/net/dns/dns_test_util.cc
+++ b/net/dns/dns_test_util.cc
@@ -28,10 +28,9 @@
 class MockAddressSorter : public AddressSorter {
  public:
   ~MockAddressSorter() override = default;
-  void Sort(const AddressList& list,
-            const CallbackType& callback) const override {
+  void Sort(const AddressList& list, CallbackType callback) const override {
     // Do nothing.
-    callback.Run(true, list);
+    std::move(callback).Run(true, list);
   }
 };
 
diff --git a/net/dns/host_resolver_impl.cc b/net/dns/host_resolver_impl.cc
index 0d61e10..2ac81d6f 100644
--- a/net/dns/host_resolver_impl.cc
+++ b/net/dns/host_resolver_impl.cc
@@ -1288,8 +1288,8 @@
         addr_list_[0].GetFamily() == ADDRESS_FAMILY_IPV6) {
       // Sort addresses if needed.  Sort could complete synchronously.
       client_->GetAddressSorter()->Sort(
-          addr_list_, base::Bind(&DnsTask::OnSortComplete, AsWeakPtr(),
-                                 tick_clock_->NowTicks()));
+          addr_list_, base::BindOnce(&DnsTask::OnSortComplete, AsWeakPtr(),
+                                     tick_clock_->NowTicks()));
     } else {
       OnSuccess(addr_list_);
     }
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index d47261b..99dcf7b6 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -4936,7 +4936,6 @@
     { "name": "dibiphp.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "disruptivelabs.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "disruptivelabs.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "divingwithnic.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "dovetailnow.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "dukun.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "elpo.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -54577,6 +54576,7 @@
     { "name": "www.techrepublic.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "aka.ms", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "go.microsoft.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
+    { "name": "typewritten.net", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     // IP Address
     { "name": "1.0.0.1", "policy": "custom", "mode": "force-https", "include_subdomains": false },
     // No subdomains
@@ -54780,6 +54780,7 @@
     { "name": "ccac.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "apprenticeship.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "apprenticeships.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ai.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "bmoattachments.org", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     // END OF ETLD-OWNER REQUESTED ENTRIES
 
diff --git a/net/proxy_resolution/proxy_resolver_mac.cc b/net/proxy_resolution/proxy_resolver_mac.cc
index 66aaf2a..e0ab817 100644
--- a/net/proxy_resolution/proxy_resolver_mac.cc
+++ b/net/proxy_resolution/proxy_resolver_mac.cc
@@ -18,6 +18,7 @@
 #include "net/base/proxy_server.h"
 #include "net/proxy_resolution/proxy_info.h"
 #include "net/proxy_resolution/proxy_resolver.h"
+#include "url/gurl.h"
 
 #if defined(OS_IOS)
 #include <CFNetwork/CFProxySupport.h>
@@ -210,8 +211,18 @@
                                      CompletionOnceCallback /*callback*/,
                                      std::unique_ptr<Request>* /*request*/,
                                      const NetLogWithSource& net_log) {
+  // OS X's system resolver does not support WebSocket URLs in proxy.pac, as of
+  // version 10.13.5. See https://crbug.com/862121.
+  GURL mutable_query_url = query_url;
+  if (query_url.SchemeIsWSOrWSS()) {
+    GURL::Replacements replacements;
+    replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https"
+                                                                : "http");
+    mutable_query_url = query_url.ReplaceComponents(replacements);
+  }
+
   base::ScopedCFTypeRef<CFStringRef> query_ref(
-      base::SysUTF8ToCFStringRef(query_url.spec()));
+      base::SysUTF8ToCFStringRef(mutable_query_url.spec()));
   base::ScopedCFTypeRef<CFURLRef> query_url_ref(
       CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), NULL));
   if (!query_url_ref.get())
diff --git a/net/proxy_resolution/proxy_resolver_winhttp.cc b/net/proxy_resolution/proxy_resolver_winhttp.cc
index 82ccdb18..ccd4815 100644
--- a/net/proxy_resolution/proxy_resolver_winhttp.cc
+++ b/net/proxy_resolution/proxy_resolver_winhttp.cc
@@ -94,6 +94,20 @@
   if (!session_handle_ && !OpenWinHttpSession())
     return ERR_FAILED;
 
+  // Windows' system resolver does not support WebSocket URLs in proxy.pac. This
+  // was tested in version 10.0.16299, and is also implied by the description of
+  // the ERROR_WINHTTP_UNRECOGNIZED_SCHEME error code in the Microsoft
+  // documentation at
+  // https://docs.microsoft.com/en-us/windows/desktop/api/winhttp/nf-winhttp-winhttpgetproxyforurl.
+  // See https://crbug.com/862121.
+  GURL mutable_query_url = query_url;
+  if (query_url.SchemeIsWSOrWSS()) {
+    GURL::Replacements replacements;
+    replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https"
+                                                                : "http");
+    mutable_query_url = query_url.ReplaceComponents(replacements);
+  }
+
   // If we have been given an empty PAC url, then use auto-detection.
   //
   // NOTE: We just use DNS-based auto-detection here like Firefox.  We do this
@@ -114,14 +128,14 @@
   // Otherwise, we fail over to trying it with a value of true.  This way we
   // get good performance in the case where WinHTTP uses an out-of-process
   // resolver.  This is important for Vista and Win2k3.
-  BOOL ok = WinHttpGetProxyForUrl(session_handle_,
-                                  base::ASCIIToUTF16(query_url.spec()).c_str(),
-                                  &options, &info);
+  BOOL ok = WinHttpGetProxyForUrl(
+      session_handle_, base::ASCIIToUTF16(mutable_query_url.spec()).c_str(),
+      &options, &info);
   if (!ok) {
     if (ERROR_WINHTTP_LOGIN_FAILURE == GetLastError()) {
       options.fAutoLogonIfChallenged = TRUE;
       ok = WinHttpGetProxyForUrl(
-          session_handle_, base::ASCIIToUTF16(query_url.spec()).c_str(),
+          session_handle_, base::ASCIIToUTF16(mutable_query_url.spec()).c_str(),
           &options, &info);
     }
     if (!ok) {
diff --git a/net/quic/mock_crypto_client_stream.cc b/net/quic/mock_crypto_client_stream.cc
index 57ae56a..ce94f88f 100644
--- a/net/quic/mock_crypto_client_stream.cc
+++ b/net/quic/mock_crypto_client_stream.cc
@@ -43,6 +43,7 @@
 using quic::QuicSession;
 using quic::QuicSpdyClientSessionBase;
 using quic::QuicString;
+using quic::QuicStringPiece;
 using quic::QuicTagVector;
 using quic::QuicTime;
 
@@ -163,8 +164,10 @@
       break;
     }
 
-    case USE_DEFAULT_CRYPTO_STREAM: {
-      NOTREACHED();
+    case COLD_START_WITH_CHLO_SENT: {
+      handshake_confirmed_ = false;
+      encryption_established_ = false;
+      SendHandshakeMessage(GetDummyCHLOMessage());
       break;
     }
   }
@@ -216,6 +219,13 @@
   session()->OnCryptoHandshakeEvent(event);
 }
 
+// static
+CryptoHandshakeMessage MockCryptoClientStream::GetDummyCHLOMessage() {
+  CryptoHandshakeMessage message;
+  message.set_tag(quic::kCHLO);
+  return message;
+}
+
 void MockCryptoClientStream::SetConfigNegotiated() {
   ASSERT_FALSE(session()->config()->negotiated());
   QuicTagVector cgst;
diff --git a/net/quic/mock_crypto_client_stream.h b/net/quic/mock_crypto_client_stream.h
index 382a797..4a35f72 100644
--- a/net/quic/mock_crypto_client_stream.h
+++ b/net/quic/mock_crypto_client_stream.h
@@ -21,8 +21,6 @@
 class MockCryptoClientStream : public quic::QuicCryptoClientStream,
                                public quic::QuicCryptoHandshaker {
  public:
-  // TODO(zhongyi): might consider move HandshakeMode up to
-  // MockCryptoClientStreamFactory.
   // HandshakeMode enumerates the handshake mode MockCryptoClientStream should
   // mock in CryptoConnect.
   enum HandshakeMode {
@@ -36,13 +34,14 @@
     ZERO_RTT,
 
     // COLD_START indicates that CryptoConnect will neither establish encryption
-    // nor confirm the handshake
+    // nor confirm the handshake.
     COLD_START,
 
-    // USE_DEFAULT_CRYPTO_STREAM indicates that MockCryptoClientStreamFactory
-    // will create a QuicCryptoClientStream instead of a
-    // MockCryptoClientStream.
-    USE_DEFAULT_CRYPTO_STREAM,
+    // COLD_START_WITH_CHLO_SENT indicates that CryptoConnection will attempt to
+    // establish encryption by sending the initial CHLO packet on wire, which
+    // contains an empty CryptoHandshakeMessage. It will not confirm the
+    // hanshake though.
+    COLD_START_WITH_CHLO_SENT,
   };
 
   MockCryptoClientStream(
@@ -72,13 +71,15 @@
   void SendOnCryptoHandshakeEvent(
       quic::QuicSession::CryptoHandshakeEvent event);
 
-  HandshakeMode handshake_mode_;
+  static quic::CryptoHandshakeMessage GetDummyCHLOMessage();
 
  protected:
   using quic::QuicCryptoClientStream::session;
 
  private:
   void SetConfigNegotiated();
+
+  HandshakeMode handshake_mode_;
   bool encryption_established_;
   bool handshake_confirmed_;
   quic::QuicReferenceCountedPointer<quic::QuicCryptoNegotiatedParameters>
diff --git a/net/quic/mock_crypto_client_stream_factory.cc b/net/quic/mock_crypto_client_stream_factory.cc
index c8a415a..8fb80c9 100644
--- a/net/quic/mock_crypto_client_stream_factory.cc
+++ b/net/quic/mock_crypto_client_stream_factory.cc
@@ -30,11 +30,6 @@
     QuicChromiumClientSession* session,
     std::unique_ptr<quic::ProofVerifyContext> /*proof_verify_context*/,
     quic::QuicCryptoClientConfig* crypto_config) {
-  if (handshake_mode_ == MockCryptoClientStream::USE_DEFAULT_CRYPTO_STREAM) {
-    return new quic::QuicCryptoClientStream(server_id, session, nullptr,
-                                            crypto_config, session);
-  }
-
   const ProofVerifyDetailsChromium* proof_verify_details = nullptr;
   if (!proof_verify_details_queue_.empty()) {
     proof_verify_details = proof_verify_details_queue_.front();
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index da7c531b..eaeacf28 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -82,6 +82,8 @@
 
   void OverrideChannelIDSent() { force_channel_id_sent_ = true; }
 
+  MOCK_METHOD0(OnPathDegrading, void());
+
  private:
   bool force_channel_id_sent_ = false;
 };
@@ -1430,6 +1432,65 @@
   EXPECT_TRUE(new_socket_data.AllWriteDataConsumed());
 }
 
+TEST_P(QuicChromiumClientSessionTest, DetectPathDegradingDuringHandshake) {
+  migrate_session_early_v2_ = true;
+
+  MockQuicData quic_data;
+  quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read
+  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeDummyCHLOPacket(1));
+  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeDummyCHLOPacket(2));
+  quic_data.AddSocketDataToFactory(&socket_factory_);
+
+  // Set the crypto handshake mode to cold start and send CHLO packets.
+  crypto_client_stream_factory_.set_handshake_mode(
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT);
+  Initialize();
+
+  session_->CryptoConnect(callback_.callback());
+
+  // Check retransmission alarm is set after sending the initial CHLO packet.
+  quic::QuicAlarm* retransmission_alarm =
+      quic::test::QuicConnectionPeer::GetRetransmissionAlarm(
+          session_->connection());
+  EXPECT_TRUE(retransmission_alarm->IsSet());
+  quic::QuicTime retransmission_time = retransmission_alarm->deadline();
+
+  // Check path degrading alarm is set after sending the initial CHLO packet.
+  quic::QuicAlarm* path_degrading_alarm =
+      quic::test::QuicConnectionPeer::GetPathDegradingAlarm(
+          session_->connection());
+  EXPECT_TRUE(path_degrading_alarm->IsSet());
+  quic::QuicTime path_degrading_time = path_degrading_alarm->deadline();
+  EXPECT_LE(retransmission_time, path_degrading_time);
+
+  // Do not create outgoing stream since encryption is not established.
+  std::unique_ptr<QuicChromiumClientSession::Handle> handle =
+      session_->CreateHandle(destination_);
+  TestCompletionCallback callback;
+  EXPECT_TRUE(handle->IsConnected());
+  EXPECT_FALSE(handle->IsCryptoHandshakeConfirmed());
+  EXPECT_EQ(
+      ERR_IO_PENDING,
+      handle->RequestStream(/*require_handshake_confirmation=*/true,
+                            callback.callback(), TRAFFIC_ANNOTATION_FOR_TESTS));
+
+  // Fire the retransmission alarm to retransmit the crypto packet.
+  quic::QuicTime::Delta delay = retransmission_time - clock_.ApproximateNow();
+  clock_.AdvanceTime(delay);
+  alarm_factory_.FireAlarm(retransmission_alarm);
+
+  // Fire the path degrading alarm to notify session that path is degrading
+  // during crypto handshake.
+  delay = path_degrading_time - clock_.ApproximateNow();
+  clock_.AdvanceTime(delay);
+  EXPECT_CALL(*session_.get(), OnPathDegrading());
+  alarm_factory_.FireAlarm(path_degrading_alarm);
+
+  EXPECT_TRUE(session_->connection()->IsPathDegrading());
+  EXPECT_TRUE(quic_data.AllReadDataConsumed());
+  EXPECT_TRUE(quic_data.AllWriteDataConsumed());
+}
+
 TEST_P(QuicChromiumClientSessionTest, RetransmittableOnWireTimeout) {
   migrate_session_early_v2_ = true;
 
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index a2fd432..33fc61e 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -1802,7 +1802,7 @@
   Initialize();
   // Use unmocked crypto stream to do crypto connect.
   crypto_client_stream_factory_.set_handshake_mode(
-      MockCryptoClientStream::USE_DEFAULT_CRYPTO_STREAM);
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT);
 
   MockQuicData socket_data;
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
@@ -1863,7 +1863,7 @@
   Initialize();
   // Use unmocked crypto stream to do crypto connect.
   crypto_client_stream_factory_.set_handshake_mode(
-      MockCryptoClientStream::USE_DEFAULT_CRYPTO_STREAM);
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT);
   host_resolver_.set_synchronous_mode(true);
   host_resolver_.rules()->AddIPLiteralRule(host_port_pair_.host(),
                                            "192.168.0.1", "");
@@ -2188,12 +2188,12 @@
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2;
   // Connectivity probe to be sent on the new path.
-  quic_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(3, true, 1338));
+  quic_data2.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(3, true));
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data2.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   // Ping packet to send after migration is completed.
   quic_data2.AddWrite(ASYNC,
                       client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
@@ -2360,16 +2360,16 @@
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2;
   // First connectivity probe to be sent on the new path.
-  quic_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(3, true, 1338));
+  quic_data2.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(3, true));
   quic_data2.AddRead(ASYNC,
                      ERR_IO_PENDING);  // Pause so that we can control time.
   // Connectivity probe to receive from the server.
-  quic_data2.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   // Second connectivity probe which will complete asynchronously.
-  quic_data2.AddWrite(
-      ASYNC, client_maker_.MakeConnectivityProbingPacket(4, true, 1338));
+  quic_data2.AddWrite(ASYNC,
+                      client_maker_.MakeConnectivityProbingPacket(4, true));
   quic_data2.AddRead(
       ASYNC, ConstructOkResponsePacket(2, GetNthClientInitiatedStreamId(0),
                                        false, false));
@@ -2596,12 +2596,12 @@
   // Set up the second socket data provider that is used for probing.
   MockQuicData quic_data1;
   // Connectivity probe to be sent on the new path.
-  quic_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(2, true, 1338));
+  quic_data1.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(2, true));
   quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data1.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data1.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -3199,11 +3199,11 @@
   MockQuicData quic_data2;
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
-                                       packet_number++, true, 1338));
+                                       packet_number++, true));
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data2.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   // Ping packet to send after migration is completed.
   quic_data2.AddWrite(ASYNC, client_maker_.MakeAckAndPingPacket(
                                  packet_number++, false, 1, 1, 1));
@@ -3476,11 +3476,11 @@
   MockQuicData quic_data2;
   // Connectivity probe to be sent on the new path.
   quic_data2.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(
-                                       packet_number++, false, 1338));
+                                       packet_number++, false));
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data2.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(3, false, 1338));
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(3, false));
   // Ping packet to send after migration is completed.
   quic_data2.AddWrite(
       write_mode_for_queued_packet,
@@ -3632,12 +3632,12 @@
   // The response to the earlier request is read on the new socket.
   MockQuicData quic_data2;
   // Connectivity probe to be sent on the new path.
-  quic_data2.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(3, true, 1338));
+  quic_data2.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(3, true));
   quic_data2.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data2.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data2.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   // Ping packet to send after migration is completed.
   quic_data2.AddWrite(ASYNC,
                       client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
@@ -3982,12 +3982,12 @@
   // Set up the second socket data provider that is used for probing.
   MockQuicData quic_data1;
   // Connectivity probe to be sent on the new path.
-  quic_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(2, true, 1338));
+  quic_data1.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(2, true));
   quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Connectivity probe to receive from the server.
-  quic_data1.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(1, false, 1338));
+  quic_data1.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(1, false));
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4344,11 +4344,11 @@
   // Set up the third socket data provider for migrate back to default network.
   MockQuicData quic_data3;
   // Connectivity probe to be sent on the new path.
-  quic_data3.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeConnectivityProbingPacket(3, false, 1338));
+  quic_data3.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeConnectivityProbingPacket(3, false));
   // Connectivity probe to receive from the server.
-  quic_data3.AddRead(
-      ASYNC, server_maker_.MakeConnectivityProbingPacket(2, false, 1338));
+  quic_data3.AddRead(ASYNC,
+                     server_maker_.MakeConnectivityProbingPacket(2, false));
   quic_data3.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   quic_data3.AddWrite(ASYNC, client_maker_.MakeAckPacket(4, 1, 2, 1, 1, true));
   quic_data3.AddWrite(
@@ -4391,7 +4391,7 @@
 
   // Use unmocked crypto stream to do crypto connect.
   crypto_client_stream_factory_.set_handshake_mode(
-      MockCryptoClientStream::USE_DEFAULT_CRYPTO_STREAM);
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT);
 
   MockQuicData socket_data;
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
@@ -5327,15 +5327,13 @@
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
 
-  // Use unmocked crypto stream to do crypto connect.
+  // Use cold start mode to do crypto connect, and send CHLO packet on wire.
   crypto_client_stream_factory_.set_handshake_mode(
-      MockCryptoClientStream::USE_DEFAULT_CRYPTO_STREAM);
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT);
 
-  // Add hanging socket data so that handshake is not confirmed when
-  // OnNetworkDisconnected is delivered.
   MockQuicData socket_data;
-  socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);   // Hanging read.
-  socket_data.AddWrite(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging write.
+  socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+  socket_data.AddWrite(ASYNC, client_maker_.MakeDummyCHLOPacket(1));
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index 0b7e6ade..248bd1dc 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -7,6 +7,7 @@
 #include <list>
 #include <utility>
 
+#include "net/quic/mock_crypto_client_stream.h"
 #include "net/quic/quic_http_utils.h"
 #include "net/third_party/quic/core/quic_framer.h"
 #include "net/third_party/quic/core/quic_utils.h"
@@ -54,10 +55,8 @@
 }
 
 std::unique_ptr<quic::QuicReceivedPacket>
-QuicTestPacketMaker::MakeConnectivityProbingPacket(
-    quic::QuicPacketNumber num,
-    bool include_version,
-    quic::QuicByteCount packet_length) {
+QuicTestPacketMaker::MakeConnectivityProbingPacket(quic::QuicPacketNumber num,
+                                                   bool include_version) {
   quic::QuicPacketHeader header;
   header.destination_connection_id = connection_id_;
   header.destination_connection_id_length = GetDestinationConnectionIdLength();
@@ -72,15 +71,16 @@
   quic::QuicFramer framer(quic::test::SupportedVersions(quic::ParsedQuicVersion(
                               quic::PROTOCOL_QUIC_CRYPTO, version_)),
                           clock_->Now(), perspective_);
-
-  char buffer[quic::kMaxPacketSize];
+  size_t max_plaintext_size =
+      framer.GetMaxPlaintextSize(quic::kDefaultMaxPacketSize);
+  char buffer[quic::kDefaultMaxPacketSize];
   size_t length =
-      framer.BuildConnectivityProbingPacket(header, buffer, packet_length);
+      framer.BuildConnectivityProbingPacket(header, buffer, max_plaintext_size);
   size_t encrypted_size = framer.EncryptInPlace(
       quic::ENCRYPTION_NONE, header.packet_number,
       GetStartOfEncryptedData(framer.transport_version(), header), length,
-      quic::kMaxPacketSize, buffer);
-  EXPECT_NE(0u, encrypted_size);
+      quic::kDefaultMaxPacketSize, buffer);
+  EXPECT_EQ(quic::kDefaultMaxPacketSize, encrypted_size);
   quic::QuicReceivedPacket encrypted(buffer, encrypted_size, clock_->Now(),
                                      false);
   return encrypted.Clone();
@@ -105,6 +105,45 @@
 }
 
 std::unique_ptr<quic::QuicReceivedPacket>
+QuicTestPacketMaker::MakeDummyCHLOPacket(quic::QuicPacketNumber packet_num) {
+  encryption_level_ = quic::ENCRYPTION_NONE;
+  SetLongHeaderType(quic::INITIAL);
+  InitializeHeader(packet_num, /*include_version=*/true);
+
+  quic::CryptoHandshakeMessage message =
+      MockCryptoClientStream::GetDummyCHLOMessage();
+  const quic::QuicData& data =
+      message.GetSerialized(quic::Perspective::IS_CLIENT);
+
+  quic::QuicFrames frames;
+  quic::QuicStreamFrame frame(
+      quic::kCryptoStreamId, /*fin=*/false, /*offset=*/0,
+      quic::QuicStringPiece(data.data(), data.length()));
+  frames.push_back(quic::QuicFrame(&frame));
+  DVLOG(1) << "Adding frame: " << frames.back();
+  quic::QuicPaddingFrame padding;
+  frames.push_back(quic::QuicFrame(padding));
+  DVLOG(1) << "Adding frame: " << frames.back();
+
+  quic::QuicFramer framer(quic::test::SupportedVersions(quic::ParsedQuicVersion(
+                              quic::PROTOCOL_QUIC_CRYPTO, version_)),
+                          clock_->Now(), perspective_);
+  size_t max_plaintext_size =
+      framer.GetMaxPlaintextSize(quic::kDefaultMaxPacketSize);
+  std::unique_ptr<quic::QuicPacket> packet(quic::test::BuildUnsizedDataPacket(
+      &framer, header_, frames, max_plaintext_size));
+
+  char buffer[quic::kDefaultMaxPacketSize];
+  size_t encrypted_size =
+      framer.EncryptPayload(quic::ENCRYPTION_NONE, header_.packet_number,
+                            *packet, buffer, quic::kDefaultMaxPacketSize);
+  EXPECT_EQ(quic::kDefaultMaxPacketSize, encrypted_size);
+  quic::QuicReceivedPacket encrypted(buffer, encrypted_size,
+                                     quic::QuicTime::Zero(), false);
+  return encrypted.Clone();
+}
+
+std::unique_ptr<quic::QuicReceivedPacket>
 QuicTestPacketMaker::MakeAckAndPingPacket(
     quic::QuicPacketNumber num,
     bool include_version,
diff --git a/net/quic/quic_test_packet_maker.h b/net/quic/quic_test_packet_maker.h
index 6553eb1..08e67f7 100644
--- a/net/quic/quic_test_packet_maker.h
+++ b/net/quic/quic_test_packet_maker.h
@@ -50,11 +50,12 @@
   void set_hostname(const std::string& host);
   std::unique_ptr<quic::QuicReceivedPacket> MakeConnectivityProbingPacket(
       quic::QuicPacketNumber num,
-      bool include_version,
-      quic::QuicByteCount packet_length);
+      bool include_version);
   std::unique_ptr<quic::QuicReceivedPacket> MakePingPacket(
       quic::QuicPacketNumber num,
       bool include_version);
+  std::unique_ptr<quic::QuicReceivedPacket> MakeDummyCHLOPacket(
+      quic::QuicPacketNumber packet_num);
   std::unique_ptr<quic::QuicReceivedPacket> MakeAckAndPingPacket(
       quic::QuicPacketNumber num,
       bool include_version,
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
index b0c7dd5a..fb1163e 100644
--- a/net/test/spawned_test_server/base_test_server.cc
+++ b/net/test/spawned_test_server/base_test_server.cc
@@ -571,7 +571,7 @@
   }
 
   if (redirect_connect_to_localhost_) {
-    DCHECK_EQ(TYPE_BASIC_AUTH_PROXY, type_);
+    DCHECK(type_ == TYPE_BASIC_AUTH_PROXY || type_ == TYPE_PROXY);
     arguments->Set("redirect-connect-to-localhost",
                    std::make_unique<base::Value>());
   }
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index 56b6a7bd..c5b71a5 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -47,6 +47,7 @@
     TYPE_WSS,
     TYPE_TCP_ECHO,
     TYPE_UDP_ECHO,
+    TYPE_PROXY,
   };
 
   // Container for various options to control how the HTTPS or WSS server is
diff --git a/net/test/spawned_test_server/local_test_server.cc b/net/test/spawned_test_server/local_test_server.cc
index 8946456..f031cb50 100644
--- a/net/test/spawned_test_server/local_test_server.cc
+++ b/net/test/spawned_test_server/local_test_server.cc
@@ -234,6 +234,9 @@
     case TYPE_BASIC_AUTH_PROXY:
       command_line->AppendArg("--basic-auth-proxy");
       break;
+    case TYPE_PROXY:
+      command_line->AppendArg("--proxy");
+      break;
     default:
       NOTREACHED();
       return false;
diff --git a/net/tools/cert_verify_tool/cert_verify_tool.cc b/net/tools/cert_verify_tool/cert_verify_tool.cc
index 3b3904d..86975be 100644
--- a/net/tools/cert_verify_tool/cert_verify_tool.cc
+++ b/net/tools/cert_verify_tool/cert_verify_tool.cc
@@ -9,6 +9,7 @@
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/strings/string_split.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task_scheduler/task_scheduler.h"
 #include "base/threading/thread.h"
@@ -158,6 +159,25 @@
   }
 };
 
+// Creates an subclass of CertVerifyImpl based on its name, or returns nullptr.
+std::unique_ptr<CertVerifyImpl> CreateCertVerifyImplFromName(
+    base::StringPiece impl_name) {
+  if (impl_name == "platform")
+    return std::make_unique<CertVerifyImplUsingProc>(
+        "CertVerifyProc (default)", net::CertVerifyProc::CreateDefault());
+
+  if (impl_name == "builtin") {
+    return std::make_unique<CertVerifyImplUsingProc>(
+        "CertVerifyProcBuiltin", net::CreateCertVerifyProcBuiltin());
+  }
+
+  if (impl_name == "pathbuilder")
+    return std::make_unique<CertVerifyImplUsingPathBuilder>();
+
+  std::cerr << "WARNING: Unrecognized impl: " << impl_name << "\n";
+  return nullptr;
+}
+
 const char kUsage[] =
     " [flags] <target/chain>\n"
     "\n"
@@ -180,6 +200,12 @@
     "      <certs path> is a file containing certificates [1] for use when\n"
     "      path building is looking for intermediates.\n"
     "\n"
+    " --impls=<ordered list of implementations>\n"
+    "      Ordered list of the verifier implementations to run. If omitted,\n"
+    "      will default to: \"platform,builtin,pathbuilder\".\n"
+    "      Changing this can lead to different results in cases where the\n"
+    "      platform verifier affects global caches (as in the case of NSS).\n"
+    "\n"
     " --trust-last-cert\n"
     "      Removes the final intermediate from the chain and instead adds it\n"
     "      as a root. This is useful when providing a <target/chain>\n"
@@ -318,16 +344,25 @@
                                 &initialization_complete_event));
   initialization_complete_event.Wait();
 
-  // Sequentially run each of the certificate verifier implementations.
   std::vector<std::unique_ptr<CertVerifyImpl>> impls;
 
-  impls.push_back(
-      std::unique_ptr<CertVerifyImplUsingProc>(new CertVerifyImplUsingProc(
-          "CertVerifyProc (default)", net::CertVerifyProc::CreateDefault())));
-  impls.push_back(std::make_unique<CertVerifyImplUsingProc>(
-      "CertVerifyProcBuiltin", net::CreateCertVerifyProcBuiltin()));
-  impls.push_back(std::make_unique<CertVerifyImplUsingPathBuilder>());
+  // Parse the ordered list of CertVerifyImpl passed via command line flags into
+  // |impls|.
+  std::string impls_str = command_line.GetSwitchValueASCII("impls");
+  if (impls_str.empty())
+    impls_str = "platform,builtin,pathbuilder";  // Default value.
 
+  std::vector<std::string> impl_names = base::SplitString(
+      impls_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  for (const std::string& impl_name : impl_names) {
+    auto verify_impl = CreateCertVerifyImplFromName(impl_name);
+    if (verify_impl)
+      impls.push_back(std::move(verify_impl));
+  }
+
+  // Sequentially run the chain with each of the selected verifier
+  // implementations.
   bool all_impls_success = true;
 
   for (size_t i = 0; i < impls.size(); ++i) {
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 92ca87c..cf55990 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -3,8 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
-testing Chrome.
+"""This is a simple HTTP/FTP/TCP/UDP/PROXY/BASIC_AUTH_PROXY/WEBSOCKET server
+used for testing Chrome.
 
 It supports several test URLs, as specified by the handlers in TestPageHandler.
 By default, it listens on an ephemeral port and sends the port number back to
@@ -64,6 +64,7 @@
 SERVER_UDP_ECHO = 3
 SERVER_BASIC_AUTH_PROXY = 4
 SERVER_WEBSOCKET = 5
+SERVER_PROXY = 6
 
 # Default request queue size for WebSocketServer.
 _DEFAULT_REQUEST_QUEUE_SIZE = 128
@@ -1759,29 +1760,13 @@
     request_socket.sendto(return_data, self.client_address)
 
 
-class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
-  """A request handler that behaves as a proxy server which requires
-  basic authentication. Only CONNECT, GET and HEAD is supported for now.
+class ProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+  """A request handler that behaves as a proxy server. Only CONNECT, GET and
+  HEAD methods are supported.
   """
 
-  _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
   redirect_connect_to_localhost = False;
 
-  def parse_request(self):
-    """Overrides parse_request to check credential."""
-
-    if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
-      return False
-
-    auth = self.headers.getheader('Proxy-Authorization')
-    if auth != self._AUTH_CREDENTIAL:
-      self.send_response(407)
-      self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
-      self.end_headers()
-      return False
-
-    return True
-
   def _start_read_write(self, sock):
     sock.setblocking(0)
     self.request.setblocking(0)
@@ -1860,7 +1845,7 @@
       self.send_response(400)
       self.end_headers()
 
-    if BasicAuthProxyRequestHandler.redirect_connect_to_localhost:
+    if ProxyRequestHandler.redirect_connect_to_localhost:
       host = "127.0.0.1"
 
     sock = None
@@ -1883,6 +1868,28 @@
   def do_HEAD(self):
     self._do_common_method()
 
+class BasicAuthProxyRequestHandler(ProxyRequestHandler):
+  """A request handler that behaves as a proxy server which requires
+  basic authentication.
+  """
+
+  _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
+
+  def parse_request(self):
+    """Overrides parse_request to check credential."""
+
+    if not ProxyRequestHandler.parse_request(self):
+      return False
+
+    auth = self.headers.getheader('Proxy-Authorization')
+    if auth != self._AUTH_CREDENTIAL:
+      self.send_response(407)
+      self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
+      self.end_headers()
+      return False
+
+    return True
+
 
 class ServerRunner(testserver_base.TestServerRunner):
   """TestServerRunner for the net test servers."""
@@ -2172,8 +2179,14 @@
       server = UDPEchoServer((host, port), UDPEchoHandler)
       print 'Echo UDP server started on port %d...' % server.server_port
       server_data['port'] = server.server_port
+    elif self.options.server_type == SERVER_PROXY:
+      ProxyRequestHandler.redirect_connect_to_localhost = \
+          self.options.redirect_connect_to_localhost
+      server = ThreadingHTTPServer((host, port), ProxyRequestHandler)
+      print 'Proxy server started on port %d...' % server.server_port
+      server_data['port'] = server.server_port
     elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
-      BasicAuthProxyRequestHandler.redirect_connect_to_localhost = \
+      ProxyRequestHandler.redirect_connect_to_localhost = \
           self.options.redirect_connect_to_localhost
       server = ThreadingHTTPServer((host, port), BasicAuthProxyRequestHandler)
       print 'BasicAuthProxy server started on port %d...' % server.server_port
@@ -2232,6 +2245,10 @@
                                   const=SERVER_UDP_ECHO, default=SERVER_HTTP,
                                   dest='server_type',
                                   help='start up a udp echo server.')
+    self.option_parser.add_option('--proxy', action='store_const',
+                                  const=SERVER_PROXY,
+                                  default=SERVER_HTTP, dest='server_type',
+                                  help='start up a proxy server.')
     self.option_parser.add_option('--basic-auth-proxy', action='store_const',
                                   const=SERVER_BASIC_AUTH_PROXY,
                                   default=SERVER_HTTP, dest='server_type',
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 066647ca..aea2424 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -12,6 +12,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
@@ -22,22 +23,37 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
 #include "net/base/proxy_delegate.h"
+#include "net/base/url_util.h"
 #include "net/http/http_request_headers.h"
+#include "net/log/net_log.h"
+#include "net/proxy_resolution/proxy_config.h"
+#include "net/proxy_resolution/proxy_config_service.h"
+#include "net/proxy_resolution/proxy_config_service_fixed.h"
+#include "net/proxy_resolution/proxy_config_with_annotation.h"
+#include "net/proxy_resolution/proxy_info.h"
 #include "net/proxy_resolution/proxy_resolution_service.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/test/test_data_directory.h"
 #include "net/test/test_with_scoped_task_environment.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_test_util.h"
 #include "net/websockets/websocket_channel.h"
 #include "net/websockets/websocket_event_interface.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
 #include "url/origin.h"
 
 namespace net {
@@ -46,6 +62,10 @@
 
 namespace {
 
+using test_server::BasicHttpResponse;
+using test_server::HttpRequest;
+using test_server::HttpResponse;
+
 static const char kEchoServer[] = "echo-with-no-extension";
 
 // Simplify changing URL schemes.
@@ -259,7 +279,7 @@
     }
     url::Origin origin = url::Origin::Create(GURL("http://localhost"));
     GURL site_for_cookies("http://localhost/");
-    event_interface_ = new ConnectTestingEventInterface;
+    event_interface_ = new ConnectTestingEventInterface();
     channel_ = std::make_unique<WebSocketChannel>(
         base::WrapUnique(event_interface_), &context_);
     channel_->SendAddChannelRequest(GURL(socket_url), sub_protocols_, origin,
@@ -342,7 +362,7 @@
 // Regression test for crbug/426736 "WebSocket connections not using configured
 // system HTTPS Proxy".
 TEST_F(WebSocketEndToEndTest, MAYBE_HttpsProxyUsed) {
-  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
                                  base::FilePath());
   SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
                               GetWebSocketTestDataDirectory());
@@ -359,23 +379,6 @@
   context_.set_proxy_resolution_service(proxy_resolution_service.get());
   InitialiseContext();
 
-  // The test server doesn't have an unauthenticated proxy mode. WebSockets
-  // cannot provide auth information that isn't already cached, so it's
-  // necessary to preflight an HTTP request to authenticate against the proxy.
-  // It doesn't matter what the URL is, as long as it is an HTTP navigation.
-  GURL http_page =
-      ReplaceUrlScheme(ws_server.GetURL("connect_check.html"), "http");
-  TestDelegate delegate;
-  delegate.set_credentials(
-      AuthCredentials(base::ASCIIToUTF16("foo"), base::ASCIIToUTF16("bar")));
-  {
-    std::unique_ptr<URLRequest> request(context_.CreateRequest(
-        http_page, DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
-    request->Start();
-    delegate.RunUntilComplete();
-    EXPECT_TRUE(delegate.auth_required_called());
-  }
-
   GURL ws_url = ws_server.GetURL(kEchoServer);
   EXPECT_TRUE(ConnectAndWait(ws_url));
   const TestProxyDelegateWithProxyInfo::ResolvedProxyInfo& info =
@@ -384,6 +387,81 @@
   EXPECT_TRUE(info.proxy_info.is_http());
 }
 
+std::unique_ptr<HttpResponse> ProxyPacHandler(const HttpRequest& request) {
+  GURL url = request.GetURL();
+  EXPECT_EQ(url.path_piece(), "/proxy.pac");
+  EXPECT_TRUE(url.has_query());
+  std::string proxy;
+  EXPECT_TRUE(GetValueForKeyInQuery(url, "proxy", &proxy));
+  auto response = std::make_unique<BasicHttpResponse>();
+  response->set_content_type("application/x-ns-proxy-autoconfig");
+  response->set_content(
+      base::StringPrintf("function FindProxyForURL(url, host) {\n"
+                         "  return 'PROXY %s';\n"
+                         "}\n",
+                         proxy.c_str()));
+  return response;
+}
+
+// This tests the proxy.pac resolver that is built into the system. This is not
+// the one that Chrome normally uses. Chrome's normal implementation is defined
+// as a mojo service. It is outside //net and we can't use it from here. This
+// tests the alternative implementations that are selected when the
+// --winhttp-proxy-resolver flag is provided to Chrome. These only exist on OS X
+// and Windows.
+// TODO(ricea): Remove this test if --winhttp-proxy-resolver flag is removed.
+// See crbug.com/644030.
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+#define MAYBE_ProxyPacUsed ProxyPacUsed
+#else
+#define MAYBE_ProxyPacUsed DISABLED_ProxyPacUsed
+#endif
+
+TEST_F(WebSocketEndToEndTest, MAYBE_ProxyPacUsed) {
+  EmbeddedTestServer proxy_pac_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
+  SpawnedTestServer proxy_server(SpawnedTestServer::TYPE_PROXY,
+                                 base::FilePath());
+  SpawnedTestServer ws_server(SpawnedTestServer::TYPE_WS,
+                              GetWebSocketTestDataDirectory());
+  proxy_pac_server.RegisterRequestHandler(base::BindRepeating(ProxyPacHandler));
+  proxy_server.set_redirect_connect_to_localhost(true);
+
+  ASSERT_TRUE(proxy_pac_server.Start());
+  ASSERT_TRUE(proxy_server.StartInBackground());
+  ASSERT_TRUE(ws_server.StartInBackground());
+  ASSERT_TRUE(proxy_server.BlockUntilStarted());
+  ASSERT_TRUE(ws_server.BlockUntilStarted());
+
+  ProxyConfig proxy_config =
+      ProxyConfig::CreateFromCustomPacURL(proxy_pac_server.GetURL(base::StrCat(
+          {"/proxy.pac?proxy=", proxy_server.host_port_pair().ToString()})));
+  proxy_config.set_pac_mandatory(true);
+  auto proxy_config_service = std::make_unique<ProxyConfigServiceFixed>(
+      ProxyConfigWithAnnotation(proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
+  NetLog net_log;
+  std::unique_ptr<ProxyResolutionService> proxy_resolution_service(
+      ProxyResolutionService::CreateUsingSystemProxyResolver(
+          std::move(proxy_config_service), &net_log));
+  ASSERT_EQ(ws_server.host_port_pair().host(), "127.0.0.1");
+  context_.set_proxy_resolution_service(proxy_resolution_service.get());
+  InitialiseContext();
+
+  // We need to use something that doesn't look like localhost, or Windows'
+  // resolver will send us direct regardless of what proxy.pac says.
+  HostPortPair fake_ws_host_port_pair("stealth-localhost",
+                                      ws_server.host_port_pair().port());
+
+  GURL ws_url(base::StrCat(
+      {"ws://", fake_ws_host_port_pair.ToString(), "/", kEchoServer}));
+  EXPECT_TRUE(ConnectAndWait(ws_url));
+  const auto& info = proxy_delegate_->resolved_proxy_info();
+  EXPECT_EQ(ws_url, info.url);
+  EXPECT_TRUE(info.proxy_info.is_http());
+  EXPECT_EQ(info.proxy_info.ToPacString(),
+            base::StrCat({"PROXY ", proxy_server.host_port_pair().ToString()}));
+}
+
 // This is a regression test for crbug.com/408061 Crash in
 // net::WebSocketBasicHandshakeStream::Upgrade.
 TEST_F(WebSocketEndToEndTest, TruncatedResponse) {
diff --git a/remoting/resources/remoting_strings_cs.xtb b/remoting/resources/remoting_strings_cs.xtb
index c094131e..0d1d452 100644
--- a/remoting/resources/remoting_strings_cs.xtb
+++ b/remoting/resources/remoting_strings_cs.xtb
@@ -136,6 +136,7 @@
 <translation id="405887016757208221">Vzdálený počítač relaci neinicializoval. Pokud problém přetrvává, zkuste hostitele nakonfigurovat znovu.</translation>
 <translation id="4068946408131579958">Všechna připojení</translation>
 <translation id="409800995205263688">POZNÁMKA: Nastavení zásad umožňují spojení pouze mezi počítači v rámci vaší sítě.</translation>
+<translation id="4126409073460786861">Po dokončení nastavení tuto stránku obnovte. K počítači pak budete moci získat přístup tak, že vyberete požadované zařízení a zadáte PIN.</translation>
 <translation id="4145029455188493639">Jste přihlášeni pomocí účtu <ph name="EMAIL_ADDRESS" />.</translation>
 <translation id="4155497795971509630">Chybí některé povinné komponenty. Zkontrolujte, zda máte nainstalovanou nejnovější verzi softwaru, a zkuste to znovu.</translation>
 <translation id="4156740505453712750">Pokud chcete přístup k tomuto počítači zabezpečit, zvolte prosím kód PIN v délce <ph name="BOLD_START" />alespoň šesti číslic<ph name="BOLD_END" />. Tento kód PIN bude vyžadován při připojení z jiného místa.</translation>
@@ -270,6 +271,7 @@
 <translation id="6998989275928107238">Komu</translation>
 <translation id="7019153418965365059">Neznámá chyba hostitele: <ph name="HOST_OFFLINE_REASON" />.</translation>
 <translation id="701976023053394610">Vzdálená pomoc</translation>
+<translation id="7026930240735156896">Postupujte podle pokynů k nastavení počítače pro vzdálený přístup</translation>
 <translation id="7038683108611689168">Pomozte nám funkci Chromoting vylepšit tím, že nám povolíte shromažďovat statistiky využití a zprávy o selhání.</translation>
 <translation id="7067321367069083429">Obrazovka se chová jako dotyková obrazovka</translation>
 <translation id="7116737094673640201">Vítá vás Vzdálená plocha Chrome</translation>
diff --git a/remoting/resources/remoting_strings_en-GB.xtb b/remoting/resources/remoting_strings_en-GB.xtb
index cd75f9d..9ce902f4 100644
--- a/remoting/resources/remoting_strings_en-GB.xtb
+++ b/remoting/resources/remoting_strings_en-GB.xtb
@@ -136,6 +136,7 @@
 <translation id="405887016757208221">The remote computer has failed to initialise the session. If problem persists please try to configure the host again.</translation>
 <translation id="4068946408131579958">All connections</translation>
 <translation id="409800995205263688">NOTE: Policy settings permit connections only between computers within your network.</translation>
+<translation id="4126409073460786861">After setup is complete, refresh this page, then you'll be able to access the computer by choosing your device and entering the PIN</translation>
 <translation id="4145029455188493639">Signed in as <ph name="EMAIL_ADDRESS" />.</translation>
 <translation id="4155497795971509630">Some required components are missing. Please make sure that you have installed the latest version of the software and try again.</translation>
 <translation id="4156740505453712750">To protect access to this computer, please choose a PIN of <ph name="BOLD_START" />at least six digits<ph name="BOLD_END" />. This PIN will be required when connecting from another location.</translation>
@@ -270,6 +271,7 @@
 <translation id="6998989275928107238">To</translation>
 <translation id="7019153418965365059">Unrecognised host error: <ph name="HOST_OFFLINE_REASON" />.</translation>
 <translation id="701976023053394610">Remote Assistance</translation>
+<translation id="7026930240735156896">Follow the instructions to set up your computer for remote access</translation>
 <translation id="7038683108611689168">Help us improve Chromoting by allowing us to collect usage statistics and crash reports.</translation>
 <translation id="7067321367069083429">Screen acts like a touch screen</translation>
 <translation id="7116737094673640201">Welcome to Chrome Remote Desktop</translation>
diff --git a/remoting/resources/remoting_strings_lt.xtb b/remoting/resources/remoting_strings_lt.xtb
index 171d84e..b493364 100644
--- a/remoting/resources/remoting_strings_lt.xtb
+++ b/remoting/resources/remoting_strings_lt.xtb
@@ -136,6 +136,7 @@
 <translation id="405887016757208221">Nuotoliniam kompiuteriui nepavyko inicijuoti sesijos. Jei problema išlieka, pabandykite dar kartą sukonfigūruoti prieglobą.</translation>
 <translation id="4068946408131579958">Visi ryšiai</translation>
 <translation id="409800995205263688">PASTABA: pagal politikos nustatymus leidžiama užmegzti ryšį tik tarp tinkle esančių kompiuterių.</translation>
+<translation id="4126409073460786861">Kai sąranka bus baigta, atnaujinkite šį puslapį, tada galėsite pasiekti kompiuterį pasirinkę įrenginį ir įvedę PIN kodą</translation>
 <translation id="4145029455188493639">Prisijungta kaip <ph name="EMAIL_ADDRESS" />.</translation>
 <translation id="4155497795971509630">Trūksta kai kurių būtinų komponentų. Įsitikinkite, kad įdiegėte naujausios versijos programinę įrangą, ir bandykite dar kartą.</translation>
 <translation id="4156740505453712750">Jei norite apsaugoti prieigą prie šio kompiuterio, pasirinkite <ph name="BOLD_START" />bent šešių skaitmenų<ph name="BOLD_END" /> PIN kodą. Šio PIN kodo reikės prisijungiant iš kitos vietos.</translation>
@@ -270,6 +271,7 @@
 <translation id="6998989275928107238">Kam</translation>
 <translation id="7019153418965365059">Neatpažinta prieglobos klaida: <ph name="HOST_OFFLINE_REASON" />.</translation>
 <translation id="701976023053394610">Nuotolinė pagalba</translation>
+<translation id="7026930240735156896">Vadovaudamiesi instrukcijomis nustatykite nuotolinę kompiuterio prieigą</translation>
 <translation id="7038683108611689168">Leidę mums kaupti naudojimo statistiką ir strigčių ataskaitas, padėsite tobulinti „Chrome“ nuotolinį ryšį.</translation>
 <translation id="7067321367069083429">Ekranas veikia kaip jutiklinis ekranas</translation>
 <translation id="7116737094673640201">Sveiki, tai – „Chrome“ nuotolinis kompiuterio valdymas!</translation>
diff --git a/remoting/resources/remoting_strings_mr.xtb b/remoting/resources/remoting_strings_mr.xtb
index 292cef1..8cb51bc 100644
--- a/remoting/resources/remoting_strings_mr.xtb
+++ b/remoting/resources/remoting_strings_mr.xtb
@@ -136,6 +136,7 @@
 <translation id="405887016757208221">सत्र आरंभ करण्यात दूरस्थ कॉंप्युटर अयशस्वी झाले आहे. समस्या कायम राहिल्यास कृपया होस्ट पुन्हा कॉन्फिगर करण्‍याचा प्रयत्न करा.</translation>
 <translation id="4068946408131579958">सर्व कनेक्शन</translation>
 <translation id="409800995205263688">टीप: धोरण सेटिंग्ज केवळ आपल्या नेटवर्कमधील संगणकांच्या कनेक्शनना परवानगी देतात.</translation>
+<translation id="4126409073460786861">सेटअप करणे पूर्ण झाल्यावर, पेज रिफ्रेश करा, त्यानंतर तुम्ही तुमचे डिव्हाइस निवडून आणि पिन एंटर करून कॉंप्युटर अॅक्सेस करू शकाल</translation>
 <translation id="4145029455188493639"><ph name="EMAIL_ADDRESS" /> म्हणून साइन इन केले.</translation>
 <translation id="4155497795971509630">काही आवश्यक घटक गहाळ आहेत. कृपया आपण सॉफ्टवेअरची नवीनतम आवृत्ती इंस्टॉल केली असल्याचे सुनिश्चित करा आणि पुन्हा प्रयत्न करा.</translation>
 <translation id="4156740505453712750">या संगणकावर प्रवेश संरक्षित करण्यासाठी, कृपया <ph name="BOLD_START" />कमीत कमी सहा अंकांचा<ph name="BOLD_END" /> एक पिन निवडा. दुसर्‍या स्थानावरून कनेक्ट करताना हा पिन आवश्यक असेल.</translation>
@@ -270,6 +271,7 @@
 <translation id="6998989275928107238">प्रति</translation>
 <translation id="7019153418965365059">न ओळखलेली होस्ट एरर: <ph name="HOST_OFFLINE_REASON" />.</translation>
 <translation id="701976023053394610">दूरस्‍थ सहाय्य</translation>
+<translation id="7026930240735156896">रिमोट अॅक्सेससाठी तुमचा कॉंप्युटर सेट करण्यासाठी सूचना फॉलो करा</translation>
 <translation id="7038683108611689168">आम्हाला वापर आकडेवारी आणि क्रॅश अहवाल संकलित करण्याची अनुमती देऊन Chromoting सुधारण्यात आम्हाला मदत करा.</translation>
 <translation id="7067321367069083429">स्क्रीन टच स्क्रीनसारखा काम करत आहे</translation>
 <translation id="7116737094673640201">Chrome रिमोट डेस्कटॉपमध्ये तुमचे स्वागत आहे</translation>
diff --git a/remoting/resources/remoting_strings_sk.xtb b/remoting/resources/remoting_strings_sk.xtb
index 3cfe3f50..19b6b51 100644
--- a/remoting/resources/remoting_strings_sk.xtb
+++ b/remoting/resources/remoting_strings_sk.xtb
@@ -136,6 +136,7 @@
 <translation id="405887016757208221">Inicializácia relácie vzdialeným počítačom sa nepodarila. Ak problém pretrváva, skúste opätovne nakonfigurovať hostiteľa.</translation>
 <translation id="4068946408131579958">Všetky pripojenia</translation>
 <translation id="409800995205263688">Poznámka: Nastavenia pravidiel umožňujú pripojenie len medzi počítačmi v rámci vašej siete.</translation>
+<translation id="4126409073460786861">Po dokončení nastavenia túto stránku obnovte a budete môcť používať daný počítač výberom zariadenia alebo zadaním kódu PIN</translation>
 <translation id="4145029455188493639">Prihlásený/-á ako <ph name="EMAIL_ADDRESS" />.</translation>
 <translation id="4155497795971509630">Niektoré potrebné komponenty chýbajú. Uistite sa, že máte nainštalovanú najnovšiu verziu príslušného softvéru a skúste to znova.</translation>
 <translation id="4156740505453712750">Ak chcete ochrániť tento počítač pred neautorizovaným prístupom, nastavte si kód PIN obsahujúci <ph name="BOLD_START" />najmenej šesť číslic<ph name="BOLD_END" />. Tento kód PIN budete musieť zadať pri pokuse o pripojenie z iného miesta.</translation>
@@ -270,6 +271,7 @@
 <translation id="6998989275928107238">Komu</translation>
 <translation id="7019153418965365059">Nerozpoznaná chyba hostiteľa: <ph name="HOST_OFFLINE_REASON" />.</translation>
 <translation id="701976023053394610">Pomoc na diaľku</translation>
+<translation id="7026930240735156896">Postupujte podľa pokynov a nastavte v počítači vzdialený prístup</translation>
 <translation id="7038683108611689168">Pomôžte nám zlepšiť aplikáciu Chromoting tým, že nám umožníte zhromažďovať štatistiky používania a správy o zlyhaní.</translation>
 <translation id="7067321367069083429">Obrazovka funguje ako dotyková obrazovka</translation>
 <translation id="7116737094673640201">Predstavujeme Vzdialenú plochu Chrome</translation>
diff --git a/services/identity/public/cpp/DEPS b/services/identity/public/cpp/DEPS
index 6bb1884..6efe08b 100644
--- a/services/identity/public/cpp/DEPS
+++ b/services/identity/public/cpp/DEPS
@@ -4,6 +4,8 @@
   "+components/signin/core/browser/fake_gaia_cookie_manager_service.h",
   "+components/signin/core/browser/gaia_cookie_manager_service.h",
   "+components/signin/core/browser/profile_management_switches.h",
+  "+components/signin/core/browser/signin_metrics.h",
+  "+components/signin/core/browser/signin_switches.h",
   "+google_apis/gaia/gaia_auth_util.h",
   "+google_apis/gaia/google_service_auth_error.h",
   "+google_apis/gaia/oauth2_token_service.h",
diff --git a/services/identity/public/cpp/identity_manager.cc b/services/identity/public/cpp/identity_manager.cc
index 197558c9..fe234cd 100644
--- a/services/identity/public/cpp/identity_manager.cc
+++ b/services/identity/public/cpp/identity_manager.cc
@@ -154,6 +154,33 @@
   return !primary_account_info_.account_id.empty();
 }
 
+#if !defined(OS_CHROMEOS)
+void IdentityManager::ClearPrimaryAccount(
+    ClearAccountTokensAction token_action,
+    signin_metrics::ProfileSignout signout_source_metric,
+    signin_metrics::SignoutDelete signout_delete_metric) {
+  SigninManager* signin_manager =
+      SigninManager::FromSigninManagerBase(signin_manager_);
+
+  switch (token_action) {
+    case IdentityManager::ClearAccountTokensAction::kDefault:
+      signin_manager->SignOut(signout_source_metric, signout_delete_metric);
+      break;
+    case IdentityManager::ClearAccountTokensAction::kKeepAll:
+      signin_manager->SignOutAndKeepAllAccounts(signout_source_metric,
+                                                signout_delete_metric);
+      break;
+    case IdentityManager::ClearAccountTokensAction::kRemoveAll:
+      signin_manager->SignOutAndRemoveAllAccounts(signout_source_metric,
+                                                  signout_delete_metric);
+      break;
+  }
+
+  // NOTE: |primary_account_| member is cleared in WillFireGoogleSignedOut()
+  // and IdentityManager::Observers are notified in GoogleSignedOut();
+}
+#endif  // defined(OS_CHROMEOS)
+
 std::vector<AccountInfo> IdentityManager::GetAccountsWithRefreshTokens() const {
   // TODO(blundell): It seems wasteful to construct this vector every time this
   // method is called, but it also seems bad to maintain the vector as an ivar
@@ -289,7 +316,6 @@
     const std::string& account_id,
     bool is_valid) {
   DCHECK(!pending_token_available_state_);
-
   AccountInfo account_info =
       account_tracker_service_->GetAccountInfo(account_id);
 
diff --git a/services/identity/public/cpp/identity_manager.h b/services/identity/public/cpp/identity_manager.h
index 12d49c6..cb9d0c35 100644
--- a/services/identity/public/cpp/identity_manager.h
+++ b/services/identity/public/cpp/identity_manager.h
@@ -11,6 +11,7 @@
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_manager_base.h"
+#include "components/signin/core/browser/signin_metrics.h"
 #include "services/identity/public/cpp/access_token_fetcher.h"
 
 #if !defined(OS_CHROMEOS)
@@ -68,8 +69,8 @@
     virtual void OnPrimaryAccountCleared(
         const AccountInfo& previous_primary_account_info) {}
 
-    // TODO(blundell): Eventually we might need a callback for failure to log in
-    // to the primary account.
+    // TODO(https://crbug/869418): Eventually we might need a callback for
+    // failure to log in to the primary account.
 
     // Called when a new refresh token is associated with |account_info|.
     // |is_valid| indicates whether the new refresh token is valid.
@@ -128,6 +129,29 @@
   // primary account info has a valid account ID.
   bool HasPrimaryAccount() const;
 
+// For ChromeOS, mutation of primary account state is not managed externally.
+#if !defined(OS_CHROMEOS)
+  // Describes options for handling of tokens upon calling
+  // ClearPrimaryAccount().
+  enum class ClearAccountTokensAction{
+      // Default action (keep or remove tokens) based on internal policy.
+      kDefault,
+      // Keeps all account tokens for all accounts.
+      kKeepAll,
+      // Removes all accounts tokens for all accounts.
+      kRemoveAll,
+  };
+
+  // Clears the primary account, removing the preferences, and canceling all
+  // auth in progress. May optionally remove account tokens - see
+  // ClearAccountTokensAction. See definitions of signin_metrics::ProfileSignout
+  // and signin_metrics::SignoutDelete for usage. Observers will be notified via
+  // OnPrimaryAccountCleared() when complete.
+  void ClearPrimaryAccount(ClearAccountTokensAction token_action,
+                           signin_metrics::ProfileSignout signout_source_metric,
+                           signin_metrics::SignoutDelete signout_delete_metric);
+#endif  // defined(OS_CHROMEOS)
+
   // Provides access to the latest cached information of all accounts that have
   // refresh tokens.
   // NOTE: The accounts should not be assumed to be in any particular order; in
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index dc25869..33b1706 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -3,16 +3,21 @@
 // found in the LICENSE file.
 
 #include "services/identity/public/cpp/identity_manager.h"
+#include "base/command_line.h"
+#include "base/containers/flat_set.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/stl_util.h"
 #include "build/build_config.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/fake_gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/fake_signin_manager.h"
 #include "components/signin/core/browser/profile_management_switches.h"
+#include "components/signin/core/browser/signin_switches.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "google_apis/gaia/google_service_auth_error.h"
 #include "services/identity/public/cpp/identity_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -96,14 +101,20 @@
   void set_on_google_signin_succeeded_callback(base::OnceClosure callback) {
     on_google_signin_succeeded_callback_ = std::move(callback);
   }
+  void set_on_google_signin_failed_callback(base::OnceClosure callback) {
+    on_google_signin_failed_callback_ = std::move(callback);
+  }
   void set_on_google_signed_out_callback(base::OnceClosure callback) {
     on_google_signed_out_callback_ = std::move(callback);
   }
 
-  const AccountInfo& primary_account_from_signin_callback() {
+  const AccountInfo& primary_account_from_signin_callback() const {
     return primary_account_from_signin_callback_;
   }
-  const AccountInfo& primary_account_from_signout_callback() {
+  const GoogleServiceAuthError& error_from_signin_failed_callback() const {
+    return google_signin_failed_error_;
+  }
+  const AccountInfo& primary_account_from_signout_callback() const {
     return primary_account_from_signout_callback_;
   }
 
@@ -116,6 +127,11 @@
     if (on_google_signin_succeeded_callback_)
       std::move(on_google_signin_succeeded_callback_).Run();
   }
+  void GoogleSigninFailed(const GoogleServiceAuthError& error) override {
+    google_signin_failed_error_ = error;
+    if (on_google_signin_failed_callback_)
+      std::move(on_google_signin_failed_callback_).Run();
+  }
   void GoogleSignedOut(const AccountInfo& account_info) override {
     ASSERT_TRUE(identity_manager_);
     primary_account_from_signout_callback_ =
@@ -127,9 +143,11 @@
   SigninManagerBase* signin_manager_;
   IdentityManager* identity_manager_;
   base::OnceClosure on_google_signin_succeeded_callback_;
+  base::OnceClosure on_google_signin_failed_callback_;
   base::OnceClosure on_google_signed_out_callback_;
   AccountInfo primary_account_from_signin_callback_;
   AccountInfo primary_account_from_signout_callback_;
+  GoogleServiceAuthError google_signin_failed_error_;
 };
 
 // Class that observes updates from ProfileOAuth2TokenService and and verifies
@@ -205,7 +223,10 @@
   void set_on_refresh_token_updated_callback(base::OnceClosure callback) {
     on_refresh_token_updated_callback_ = std::move(callback);
   }
-  void set_on_refresh_token_removed_callback(base::OnceClosure callback) {
+  // This method uses a RepeatingCallback to simplify verification of multiple
+  // removed tokens.
+  void set_on_refresh_token_removed_callback(
+      base::RepeatingCallback<void(const AccountInfo&)> callback) {
     on_refresh_token_removed_callback_ = std::move(callback);
   }
 
@@ -251,7 +272,7 @@
       const AccountInfo& account_info) override {
     account_from_refresh_token_removed_callback_ = account_info;
     if (on_refresh_token_removed_callback_)
-      std::move(on_refresh_token_removed_callback_).Run();
+      on_refresh_token_removed_callback_.Run(account_info);
   }
   void OnAccountsInCookieUpdated(
       const std::vector<AccountInfo>& accounts) override {
@@ -264,7 +285,8 @@
   base::OnceClosure on_primary_account_set_callback_;
   base::OnceClosure on_primary_account_cleared_callback_;
   base::OnceClosure on_refresh_token_updated_callback_;
-  base::OnceClosure on_refresh_token_removed_callback_;
+  base::RepeatingCallback<void(const AccountInfo&)>
+      on_refresh_token_removed_callback_;
   base::OnceClosure on_accounts_in_cookie_updated_callback_;
   AccountInfo primary_account_from_set_callback_;
   AccountInfo primary_account_from_cleared_callback_;
@@ -327,14 +349,6 @@
  public:
   IdentityManagerTest()
       : signin_client_(&pref_service_),
-#if defined(OS_CHROMEOS)
-        signin_manager_(&signin_client_, &account_tracker_),
-#else
-        signin_manager_(&signin_client_,
-                        &token_service_,
-                        &account_tracker_,
-                        nullptr),
-#endif
         gaia_cookie_manager_service_(&token_service_,
                                      "identity_manager_unittest",
                                      &signin_client_) {
@@ -344,9 +358,9 @@
 
     account_tracker_.Initialize(&signin_client_);
 
-    signin_manager()->SetAuthenticatedAccountInfo(kTestGaiaId, kTestEmail);
-
-    RecreateIdentityManager();
+    RecreateSigninAndIdentityManager(
+        signin::AccountConsistencyMethod::kDisabled,
+        SigninManagerSetup::kWithAuthenticatedAccout);
   }
 
   IdentityManager* identity_manager() { return identity_manager_.get(); }
@@ -358,7 +372,7 @@
     return identity_manager_diagnostics_observer_.get();
   }
   AccountTrackerServiceForTest* account_tracker() { return &account_tracker_; }
-  SigninManagerForTest* signin_manager() { return &signin_manager_; }
+  SigninManagerForTest* signin_manager() { return signin_manager_.get(); }
   CustomFakeProfileOAuth2TokenService* token_service() {
     return &token_service_;
   }
@@ -366,9 +380,58 @@
     return &gaia_cookie_manager_service_;
   }
 
+  // See RecreateSigninAndIdentityManager.
+  enum class SigninManagerSetup {
+    kWithAuthenticatedAccout,
+    kNoAuthenticatedAccount
+  };
+
+  // Recreates SigninManager and IdentityManager with given
+  // |account_consistency| and optionally seeds with an authenticated account
+  // depending on |singin_manager_setup|. This process destroys any existing
+  // IdentityManager and its dependencies, then remakes them. Dependencies that
+  // outlive SigninManager (e.g. SigninClient) will be reused.
+  void RecreateSigninAndIdentityManager(
+      signin::AccountConsistencyMethod account_consistency,
+      SigninManagerSetup signin_manager_setup) {
+    // Reset dependents to null first to ensure that they're destroyed, as
+    // otherwise destructors of SigninManager and dependents will DCHECK because
+    // they still having living observers.
+    identity_manager_observer_.reset();
+    identity_manager_diagnostics_observer_.reset();
+    identity_manager_.reset();
+
+#if defined(OS_CHROMEOS)
+    DCHECK_EQ(account_consistency, signin::AccountConsistencyMethod::kDisabled)
+        << "AccountConsistency is not used by SigninManagerBase";
+    signin_manager_ = std::make_unique<FakeSigninManagerBase>(
+        &signin_client_, &account_tracker_);
+#else
+    signin_manager_ = std::make_unique<FakeSigninManager>(
+        &signin_client_, &token_service_, &account_tracker_,
+        &gaia_cookie_manager_service_, nullptr, account_consistency);
+#endif
+
+    // Passing this switch ensures that the new SigninManager starts with a
+    // clean slate. Otherwise SigninManagerBase::Initialize will use the account
+    // id stored in prefs::kGoogleServicesAccountId.
+    base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+    cmd_line->AppendSwitch(switches::kClearTokenService);
+
+    signin_manager_->Initialize(&pref_service_);
+
+    if (signin_manager_setup == SigninManagerSetup::kWithAuthenticatedAccout) {
+      signin_manager()->SetAuthenticatedAccountInfo(kTestGaiaId, kTestEmail);
+    }
+
+    RecreateIdentityManager();
+  }
+
   // Used by some tests that need to re-instantiate IdentityManager after
   // performing some other setup.
   void RecreateIdentityManager() {
+    DCHECK(signin_manager_) << "Create signin_manager_ first";
+
     // Reset them all to null first to ensure that they're destroyed, as
     // otherwise SigninManager ends up getting a new DiagnosticsObserver added
     // before the old one is removed.
@@ -377,7 +440,7 @@
     identity_manager_.reset();
 
     identity_manager_.reset(
-        new IdentityManager(&signin_manager_, &token_service_,
+        new IdentityManager(signin_manager_.get(), &token_service_,
                             &account_tracker_, &gaia_cookie_manager_service_));
     identity_manager_observer_.reset(
         new TestIdentityManagerObserver(identity_manager_.get()));
@@ -385,14 +448,94 @@
         new TestIdentityManagerDiagnosticsObserver(identity_manager_.get()));
   }
 
+#if !defined(OS_CHROMEOS)
+  enum class RemoveTokenExpectation{kKeepAll, kRemovePrimary, kRemoveAll};
+
+  // Helper for testing of ClearPrimaryAccount(). This method requires lots
+  // of tests due to having different behaviors based on its arguments. But the
+  // setup and execution of these test is all the boiler plate you see here:
+  // 1) Ensure you have 2 accounts, both with refresh tokens
+  // 2) Clear the primary account
+  // 3) Assert clearing succeeds and refresh tokens are optionally removed based
+  //    on arguments.
+  void RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction token_action,
+      RemoveTokenExpectation token_expectation) {
+    EXPECT_TRUE(identity_manager()->HasPrimaryAccount());
+    EXPECT_EQ(identity_manager()->GetPrimaryAccountInfo().email, kTestEmail);
+
+    // Ensure primary and secondary emails are set up with refresh tokens. Some
+    // tests may do this externally for the primary account.
+    if (!identity_manager()->HasPrimaryAccountWithRefreshToken())
+      SetRefreshTokenForPrimaryAccount(token_service(), identity_manager());
+    EXPECT_TRUE(identity_manager()->HasPrimaryAccountWithRefreshToken());
+    AccountInfo secondary_account_info = MakeAccountAvailable(
+        account_tracker(), token_service(), identity_manager(), kTestEmail2);
+    EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+        secondary_account_info.account_id));
+
+    // Track Observer token removal notifications.
+    base::flat_set<std::string> observed_removals;
+    identity_manager_observer()->set_on_refresh_token_removed_callback(
+        base::BindRepeating(
+            [](base::flat_set<std::string>* observed_removals,
+               const AccountInfo& removed_account) {
+              observed_removals->insert(removed_account.email);
+            },
+            &observed_removals));
+
+    // Grab this before clearing for token checks below.
+    auto former_primary_account = identity_manager()->GetPrimaryAccountInfo();
+
+    base::RunLoop run_loop;
+    identity_manager_observer()->set_on_primary_account_cleared_callback(
+        run_loop.QuitClosure());
+    identity_manager()->ClearPrimaryAccount(
+        token_action, signin_metrics::SIGNOUT_TEST,
+        signin_metrics::SignoutDelete::IGNORE_METRIC);
+    run_loop.Run();
+
+    EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
+    // NOTE: IdentityManager _may_ still possess this token (see switch below),
+    // but it is no longer considered part of the primary account.
+    EXPECT_FALSE(identity_manager()->HasPrimaryAccountWithRefreshToken());
+
+    switch (token_expectation) {
+      case RemoveTokenExpectation::kKeepAll:
+        EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+            former_primary_account.account_id));
+        EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+            secondary_account_info.account_id));
+        EXPECT_TRUE(observed_removals.empty());
+        break;
+      case RemoveTokenExpectation::kRemovePrimary:
+        EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(
+            former_primary_account.account_id));
+        EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+            secondary_account_info.account_id));
+        EXPECT_TRUE(base::ContainsKey(observed_removals, kTestEmail));
+        EXPECT_FALSE(base::ContainsKey(observed_removals, kTestEmail2));
+        break;
+      case RemoveTokenExpectation::kRemoveAll:
+        EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(
+            former_primary_account.account_id));
+        EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(
+            secondary_account_info.account_id));
+        EXPECT_TRUE(base::ContainsKey(observed_removals, kTestEmail));
+        EXPECT_TRUE(base::ContainsKey(observed_removals, kTestEmail2));
+        break;
+    }
+  }
+#endif  // !defined(OS_CHROMEOS)
+
  private:
   base::MessageLoop message_loop_;
   sync_preferences::TestingPrefServiceSyncable pref_service_;
   AccountTrackerServiceForTest account_tracker_;
   TestSigninClient signin_client_;
-  SigninManagerForTest signin_manager_;
   CustomFakeProfileOAuth2TokenService token_service_;
   FakeGaiaCookieManagerService gaia_cookie_manager_service_;
+  std::unique_ptr<SigninManagerForTest> signin_manager_;
   std::unique_ptr<IdentityManager> identity_manager_;
   std::unique_ptr<TestIdentityManagerObserver> identity_manager_observer_;
   std::unique_ptr<TestIdentityManagerDiagnosticsObserver>
@@ -433,6 +576,143 @@
   EXPECT_EQ(kTestEmail, primary_account_info.email);
 }
 
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_RemoveAll) {
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kRemoveAll,
+      RemoveTokenExpectation::kRemoveAll);
+}
+
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_KeepAll) {
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kKeepAll,
+      RemoveTokenExpectation::kKeepAll);
+}
+
+// Test that ClearPrimaryAccount(...) with ClearAccountTokensAction::kDefault
+// and AccountConsistencyMethod::kDisabled (notably != kDice) removes all
+// tokens.
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_Default_DisabledConsistency) {
+  // Tests default to kDisabled, so this is just being explicit.
+  RecreateSigninAndIdentityManager(
+      signin::AccountConsistencyMethod::kDisabled,
+      SigninManagerSetup::kWithAuthenticatedAccout);
+
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kDefault,
+      RemoveTokenExpectation::kRemoveAll);
+}
+
+// Test that ClearPrimaryAccount(...) with ClearAccountTokensAction::kDefault
+// and AccountConsistencyMethod::kMirror (notably != kDice) removes all
+// tokens.
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_Default_MirrorConsistency) {
+  RecreateSigninAndIdentityManager(
+      signin::AccountConsistencyMethod::kMirror,
+      SigninManagerSetup::kWithAuthenticatedAccout);
+
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kDefault,
+      RemoveTokenExpectation::kRemoveAll);
+}
+
+// Test that ClearPrimaryAccount(...) with ClearAccountTokensAction::kDefault
+// and AccountConsistencyMethod::kDice keeps all accounts when the the primary
+// account does not have an authentication error (see *_AuthError test).
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_Default_DiceConsistency) {
+  RecreateSigninAndIdentityManager(
+      signin::AccountConsistencyMethod::kDice,
+      SigninManagerSetup::kWithAuthenticatedAccout);
+
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kDefault,
+      RemoveTokenExpectation::kKeepAll);
+}
+
+// Test that ClearPrimaryAccount(...) with ClearAccountTokensAction::kDefault
+// and AccountConsistencyMethod::kDice removes *only* the primary account
+// due to it authentication error.
+TEST_F(IdentityManagerTest,
+       ClearPrimaryAccount_Default_DiceConsistency_AuthError) {
+  RecreateSigninAndIdentityManager(
+      signin::AccountConsistencyMethod::kDice,
+      SigninManagerSetup::kWithAuthenticatedAccout);
+
+  // Set primary account to have authentication error.
+  SetRefreshTokenForPrimaryAccount(token_service(), identity_manager());
+  token_service()->UpdateAuthErrorForTesting(
+      identity_manager()->GetPrimaryAccountInfo().account_id,
+      GoogleServiceAuthError(
+          GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
+
+  RunClearPrimaryAccountTest(
+      IdentityManager::ClearAccountTokensAction::kDefault,
+      RemoveTokenExpectation::kRemovePrimary);
+}
+
+// Test that ClearPrimaryAccount(...) with authentication in progress notifies
+// Observers that sign-in is canceled and does not remove any tokens.
+TEST_F(IdentityManagerTest, ClearPrimaryAccount_AuthInProgress) {
+  // Recreate components without setting an authenticated (primary) account.
+  RecreateSigninAndIdentityManager(signin::AccountConsistencyMethod::kDisabled,
+                                   SigninManagerSetup::kNoAuthenticatedAccount);
+  EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
+
+  // Simulate authentication in progress (id value not important, treated as
+  // potentially invalid until authentication completes).
+  signin_manager()->set_auth_in_progress("bogus_id");
+  EXPECT_TRUE(signin_manager()->AuthInProgress());
+
+  // Add a secondary account to verify that its refresh token survives the
+  // call to ClearPrimaryAccount(...) below.
+  AccountInfo secondary_account_info = MakeAccountAvailable(
+      account_tracker(), token_service(), identity_manager(), kTestEmail2);
+  EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+      secondary_account_info.account_id));
+
+  // Observe that in-progress authentication is *canceled* and quit the RunLoop.
+  // TODO(https://crbug/869418): Determine if signin failed notifications should
+  // be part of the IdentityManager::Observer interface.
+  base::RunLoop run_loop;
+  GoogleServiceAuthError::State observed_error =
+      GoogleServiceAuthError::State::NONE;
+  TestSigninManagerObserver signin_manager_observer(signin_manager());
+  signin_manager_observer.set_identity_manager(identity_manager());
+  signin_manager_observer.set_on_google_signin_failed_callback(base::BindOnce(
+      [](TestSigninManagerObserver* observer,
+         GoogleServiceAuthError::State* error, base::OnceClosure callback) {
+        *error = observer->error_from_signin_failed_callback().state();
+        std::move(callback).Run();
+      },
+      &signin_manager_observer, &observed_error, run_loop.QuitClosure()));
+
+  // Observer should not be notified of any token removals.
+  identity_manager_observer()->set_on_refresh_token_removed_callback(
+      base::BindRepeating([](const AccountInfo&) { EXPECT_TRUE(false); }));
+
+  // No primary account to "clear", so no callback.
+  identity_manager_observer()->set_on_primary_account_cleared_callback(
+      base::BindOnce([] { EXPECT_TRUE(false); }));
+
+  identity_manager()->ClearPrimaryAccount(
+      IdentityManager::ClearAccountTokensAction::kRemoveAll,
+      signin_metrics::SIGNOUT_TEST,
+      signin_metrics::SignoutDelete::IGNORE_METRIC);
+  run_loop.Run();
+
+  // Verify in-progress authentication was canceled.
+  EXPECT_EQ(observed_error, GoogleServiceAuthError::State::REQUEST_CANCELED);
+  EXPECT_FALSE(signin_manager()->AuthInProgress());
+
+  // We didn't have a primary account to start with, we shouldn't have one now
+  // either.
+  EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
+  EXPECT_FALSE(identity_manager()->HasPrimaryAccountWithRefreshToken());
+
+  // Secondary account token still exists.
+  EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+      secondary_account_info.account_id));
+}
+
 // Test that the user signing out results in firing of the IdentityManager
 // observer callback and the IdentityManager's state being updated.
 TEST_F(IdentityManagerTest, PrimaryAccountInfoAfterSigninAndSignout) {
@@ -1185,9 +1465,16 @@
 
 TEST_F(IdentityManagerTest,
        CallbackNotSentOnRefreshTokenRemovalOfUnknownAccount) {
+  // RemoveCredentials expects (and DCHECKS) that either the caller passes a
+  // known account ID, or the account is unknown because the token service is
+  // still loading credentials. Our common test setup actually completes this
+  // loading, so use the *for_testing() method below to simulate the race
+  // condition.
+  token_service()->set_all_credentials_loaded_for_testing(false);
+
   base::RunLoop run_loop;
   identity_manager_observer()->set_on_refresh_token_removed_callback(
-      base::BindOnce([] { EXPECT_TRUE(false); }));
+      base::BindRepeating([](const AccountInfo&) { EXPECT_TRUE(false); }));
   token_service()->RevokeCredentials("dummy_account");
 
   run_loop.RunUntilIdle();
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index d5a655e..aaf4e64f 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -64,6 +64,8 @@
     "network_usage_accumulator.h",
     "proxy_config_service_mojo.cc",
     "proxy_config_service_mojo.h",
+    "proxy_lookup_request.cc",
+    "proxy_lookup_request.h",
     "proxy_resolving_client_socket.cc",
     "proxy_resolving_client_socket.h",
     "proxy_resolving_client_socket_factory.cc",
@@ -72,6 +74,8 @@
     "proxy_resolving_socket_factory_mojo.h",
     "proxy_resolving_socket_mojo.cc",
     "proxy_resolving_socket_mojo.h",
+    "resolve_host_request.cc",
+    "resolve_host_request.h",
     "resource_scheduler.cc",
     "resource_scheduler.h",
     "resource_scheduler_client.cc",
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 4472861..fc20d1c 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -73,9 +73,11 @@
 #include "services/network/network_service.h"
 #include "services/network/network_service_network_delegate.h"
 #include "services/network/proxy_config_service_mojo.h"
+#include "services/network/proxy_lookup_request.h"
 #include "services/network/proxy_resolving_socket_factory_mojo.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/network_switches.h"
+#include "services/network/resolve_host_request.h"
 #include "services/network/resource_scheduler_client.h"
 #include "services/network/restricted_cookie_manager.h"
 #include "services/network/session_cleanup_channel_id_store.h"
@@ -498,6 +500,13 @@
       std::move(request));
 }
 
+void NetworkContext::OnProxyLookupComplete(
+    ProxyLookupRequest* proxy_lookup_request) {
+  auto it = proxy_lookup_requests_.find(proxy_lookup_request);
+  DCHECK(it != proxy_lookup_requests_.end());
+  proxy_lookup_requests_.erase(it);
+}
+
 void NetworkContext::DisableQuic() {
   url_request_context_->http_transaction_factory()->GetSession()->DisableQuic();
 }
@@ -509,6 +518,10 @@
   url_loader_factories_.erase(it);
 }
 
+size_t NetworkContext::GetNumOutstandingResolveHostRequestsForTesting() const {
+  return resolve_host_requests_.size();
+}
+
 void NetworkContext::ClearNetworkingHistorySince(
     base::Time time,
     base::OnceClosure completion_callback) {
@@ -768,12 +781,44 @@
 #endif  // !defined(OS_IOS)
 }
 
+void NetworkContext::LookUpProxyForURL(
+    const GURL& url,
+    mojom::ProxyLookupClientPtr proxy_lookup_client) {
+  DCHECK(proxy_lookup_client);
+  std::unique_ptr<ProxyLookupRequest> proxy_lookup_request(
+      std::make_unique<ProxyLookupRequest>(std::move(proxy_lookup_client),
+                                           this));
+  ProxyLookupRequest* proxy_lookup_request_ptr = proxy_lookup_request.get();
+  proxy_lookup_requests_.insert(std::move(proxy_lookup_request));
+  proxy_lookup_request_ptr->Start(url);
+}
+
 void NetworkContext::CreateNetLogExporter(
     mojom::NetLogExporterRequest request) {
   net_log_exporter_bindings_.AddBinding(std::make_unique<NetLogExporter>(this),
                                         std::move(request));
 }
 
+void NetworkContext::ResolveHost(const net::HostPortPair& host,
+                                 mojom::ResolveHostHandleRequest control_handle,
+                                 mojom::ResolveHostClientPtr response_client) {
+  auto request = std::make_unique<ResolveHostRequest>(
+      url_request_context_->host_resolver(), host, network_service_->net_log());
+
+  int rv =
+      request->Start(std::move(control_handle), std::move(response_client),
+                     base::BindOnce(&NetworkContext::OnResolveHostComplete,
+                                    base::Unretained(this), request.get()));
+  if (rv != net::ERR_IO_PENDING)
+    return;
+
+  // Store the request with the context so it can be cancelled on context
+  // shutdown.
+  bool insertion_result =
+      resolve_host_requests_.insert(std::move(request)).second;
+  DCHECK(insertion_result);
+}
+
 void NetworkContext::AddHSTSForTesting(const std::string& host,
                                        base::Time expiry,
                                        bool include_subdomains,
@@ -1186,6 +1231,15 @@
   std::move(callback).Run();
 }
 
+void NetworkContext::OnResolveHostComplete(ResolveHostRequest* request,
+                                           int error) {
+  DCHECK_NE(net::ERR_IO_PENDING, error);
+
+  auto found_request = resolve_host_requests_.find(request);
+  DCHECK(found_request != resolve_host_requests_.end());
+  resolve_host_requests_.erase(found_request);
+}
+
 void NetworkContext::OnHttpCacheSizeComputed(
     ComputeHttpCacheSizeCallback callback,
     HttpCacheDataCounter* counter,
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 7ae758df..a134f845 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -23,7 +23,9 @@
 #include "mojo/public/cpp/bindings/strong_binding_set.h"
 #include "services/network/http_cache_data_counter.h"
 #include "services/network/http_cache_data_remover.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
 #include "services/network/public/mojom/proxy_resolving_socket.mojom.h"
 #include "services/network/public/mojom/restricted_cookie_manager.mojom.h"
 #include "services/network/public/mojom/tcp_socket.mojom.h"
@@ -39,6 +41,7 @@
 
 namespace net {
 class CertVerifier;
+class HostPortPair;
 class ReportSender;
 class StaticHttpUserAgentSettings;
 class URLRequestContext;
@@ -53,6 +56,8 @@
 class CookieManager;
 class ExpectCTReporter;
 class NetworkService;
+class ProxyLookupRequest;
+class ResolveHostRequest;
 class ResourceScheduler;
 class ResourceSchedulerClient;
 class URLRequestContextBuilderMojo;
@@ -196,7 +201,13 @@
                        int32_t render_frame_id,
                        const url::Origin& origin,
                        mojom::AuthenticationHandlerPtr auth_handler) override;
+  void LookUpProxyForURL(
+      const GURL& url,
+      mojom::ProxyLookupClientPtr proxy_lookup_client) override;
   void CreateNetLogExporter(mojom::NetLogExporterRequest request) override;
+  void ResolveHost(const net::HostPortPair& host,
+                   mojom::ResolveHostHandleRequest control_handle,
+                   mojom::ResolveHostClientPtr response_client) override;
   void AddHSTSForTesting(const std::string& host,
                          base::Time expiry,
                          bool include_subdomains,
@@ -210,6 +221,9 @@
                          bool privacy_mode_enabled) override;
   void ResetURLLoaderFactories() override;
 
+  // Destroys |request| when a proxy lookup completes.
+  void OnProxyLookupComplete(ProxyLookupRequest* proxy_lookup_request);
+
   // Disables use of QUIC by the NetworkContext.
   void DisableQuic();
 
@@ -217,6 +231,12 @@
   // no open pipes.
   void DestroyURLLoaderFactory(cors::CORSURLLoaderFactory* url_loader_factory);
 
+  size_t GetNumOutstandingResolveHostRequestsForTesting() const;
+
+  size_t pending_proxy_lookup_requests_for_testing() const {
+    return proxy_lookup_requests_.size();
+  }
+
  private:
   class ContextNetworkDelegate;
 
@@ -229,6 +249,8 @@
   void OnHttpCacheCleared(ClearHttpCacheCallback callback,
                           HttpCacheDataRemover* remover);
 
+  void OnResolveHostComplete(ResolveHostRequest* request, int error);
+
   // Invoked when the computation for ComputeHttpCacheSize() has been completed,
   // to report result to user via |callback| and clean things up.
   void OnHttpCacheSizeComputed(ComputeHttpCacheSizeCallback callback,
@@ -275,8 +297,12 @@
   std::unique_ptr<WebSocketFactory> websocket_factory_;
 #endif  // !defined(OS_IOS)
 
+  // These must be below the URLRequestContext, so they're destroyed before it
+  // is.
   std::vector<std::unique_ptr<HttpCacheDataRemover>> http_cache_data_removers_;
   std::vector<std::unique_ptr<HttpCacheDataCounter>> http_cache_data_counters_;
+  std::set<std::unique_ptr<ProxyLookupRequest>, base::UniquePtrComparator>
+      proxy_lookup_requests_;
 
   // This must be below |url_request_context_| so that the URLRequestContext
   // outlives all the URLLoaderFactories and URLLoaders that depend on it.
@@ -307,6 +333,9 @@
       require_ct_delegate_;
   std::unique_ptr<certificate_transparency::TreeStateTracker> ct_tree_tracker_;
 
+  std::set<std::unique_ptr<ResolveHostRequest>, base::UniquePtrComparator>
+      resolve_host_requests_;
+
   DISALLOW_COPY_AND_ASSIGN(NetworkContext);
 };
 
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index dba4e4a..c73804f8 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/location.h"
 #include "base/metrics/field_trial.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/stl_util.h"
 #include "base/strings/string_split.h"
@@ -49,6 +50,7 @@
 #include "net/cookies/cookie_options.h"
 #include "net/cookies/cookie_store.h"
 #include "net/disk_cache/disk_cache.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/http/http_auth.h"
 #include "net/http/http_cache.h"
 #include "net/http/http_network_session.h"
@@ -153,6 +155,53 @@
                                    false)});
 }
 
+// ProxyLookupClient that drives proxy lookups and can wait for the responses to
+// be received.
+class TestProxyLookupClient : public mojom::ProxyLookupClient {
+ public:
+  TestProxyLookupClient() : binding_(this) {}
+  ~TestProxyLookupClient() override = default;
+
+  void StartLookUpProxyForURL(const GURL& url,
+                              mojom::NetworkContext* network_context) {
+    // Make sure this method is called at most once.
+    EXPECT_FALSE(binding_.is_bound());
+
+    mojom::ProxyLookupClientPtr proxy_lookup_client;
+    binding_.Bind(mojo::MakeRequest(&proxy_lookup_client));
+    network_context->LookUpProxyForURL(url, std::move(proxy_lookup_client));
+  }
+
+  void WaitForResult() { run_loop_.Run(); }
+
+  // mojom::ProxyLookupClient implementation:
+  void OnProxyLookupComplete(
+      const base::Optional<net::ProxyInfo>& proxy_info) override {
+    EXPECT_FALSE(is_done_);
+    EXPECT_FALSE(proxy_info_);
+
+    is_done_ = true;
+    proxy_info_ = proxy_info;
+    binding_.Close();
+    run_loop_.Quit();
+  }
+
+  const base::Optional<net::ProxyInfo>& proxy_info() const {
+    return proxy_info_;
+  }
+  bool is_done() const { return is_done_; }
+
+ private:
+  mojo::Binding<mojom::ProxyLookupClient> binding_;
+
+  bool is_done_ = false;
+  base::Optional<net::ProxyInfo> proxy_info_;
+
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestProxyLookupClient);
+};
+
 class NetworkContextTest : public testing::Test,
                            public net::SSLConfigService::Observer {
  public:
@@ -1970,27 +2019,46 @@
 }
 
 TEST_F(NetworkContextTest, ProxyConfig) {
-  // Create a bunch of proxy rules to switch between. All that matters is that
-  // they're all different. It's important that none of these configs require
+  // Each ProxyConfigSet consists of a net::ProxyConfig, and the net::ProxyInfos
+  // that it will result in for http and ftp URLs. All that matters is that each
+  // ProxyConfig is different. It's important that none of these configs require
   // fetching a PAC scripts, as this test checks
   // ProxyResolutionService::config(), which is only updated after fetching PAC
   // scripts (if applicable).
-  net::ProxyConfig proxy_configs[3];
-  proxy_configs[0].proxy_rules().ParseFromString("http=foopy:80");
-  proxy_configs[1].proxy_rules().ParseFromString("http=foopy:80;ftp=foopy2");
-  proxy_configs[2] = net::ProxyConfig::CreateDirect();
+  struct ProxyConfigSet {
+    net::ProxyConfig proxy_config;
+    net::ProxyInfo http_proxy_info;
+    net::ProxyInfo ftp_proxy_info;
+  } proxy_config_sets[3];
+
+  proxy_config_sets[0].proxy_config.proxy_rules().ParseFromString(
+      "http=foopy:80");
+  proxy_config_sets[0].http_proxy_info.UsePacString("PROXY foopy:80");
+  proxy_config_sets[0].ftp_proxy_info.UseDirect();
+
+  proxy_config_sets[1].proxy_config.proxy_rules().ParseFromString(
+      "http=foopy:80;ftp=foopy2");
+  proxy_config_sets[1].http_proxy_info.UsePacString("PROXY foopy:80");
+  proxy_config_sets[1].ftp_proxy_info.UsePacString("PROXY foopy2");
+
+  proxy_config_sets[2].proxy_config = net::ProxyConfig::CreateDirect();
+  proxy_config_sets[2].http_proxy_info.UseDirect();
+  proxy_config_sets[2].ftp_proxy_info.UseDirect();
 
   // Sanity check.
-  EXPECT_FALSE(proxy_configs[0].Equals(proxy_configs[1]));
-  EXPECT_FALSE(proxy_configs[0].Equals(proxy_configs[2]));
-  EXPECT_FALSE(proxy_configs[1].Equals(proxy_configs[2]));
+  EXPECT_FALSE(proxy_config_sets[0].proxy_config.Equals(
+      proxy_config_sets[1].proxy_config));
+  EXPECT_FALSE(proxy_config_sets[0].proxy_config.Equals(
+      proxy_config_sets[2].proxy_config));
+  EXPECT_FALSE(proxy_config_sets[1].proxy_config.Equals(
+      proxy_config_sets[2].proxy_config));
 
   // Try each proxy config as the initial config, to make sure setting the
   // initial config works.
-  for (const auto& initial_proxy_config : proxy_configs) {
+  for (const auto& initial_proxy_config_set : proxy_config_sets) {
     mojom::NetworkContextParamsPtr context_params = CreateContextParams();
     context_params->initial_proxy_config = net::ProxyConfigWithAnnotation(
-        initial_proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS);
+        initial_proxy_config_set.proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS);
     mojom::ProxyConfigClientPtr config_client;
     context_params->proxy_config_client_request =
         mojo::MakeRequest(&config_client);
@@ -1999,23 +2067,56 @@
 
     net::ProxyResolutionService* proxy_resolution_service =
         network_context->url_request_context()->proxy_resolution_service();
-    // Kick the ProxyResolutionService into action, as it doesn't start updating
-    // its config until it's first used.
-    proxy_resolution_service->ForceReloadProxyConfig();
+    // Need to do proxy resolutions before can check the ProxyConfig, as the
+    // ProxyService doesn't start updating its config until it's first used.
+    // This also gives some test coverage of LookUpProxyForURL.
+    TestProxyLookupClient http_proxy_lookup_client;
+    http_proxy_lookup_client.StartLookUpProxyForURL(GURL("http://foo"),
+                                                    network_context.get());
+    http_proxy_lookup_client.WaitForResult();
+    ASSERT_TRUE(http_proxy_lookup_client.proxy_info());
+    EXPECT_EQ(initial_proxy_config_set.http_proxy_info.ToPacString(),
+              http_proxy_lookup_client.proxy_info()->ToPacString());
+
+    TestProxyLookupClient ftp_proxy_lookup_client;
+    ftp_proxy_lookup_client.StartLookUpProxyForURL(GURL("ftp://foo"),
+                                                   network_context.get());
+    ftp_proxy_lookup_client.WaitForResult();
+    ASSERT_TRUE(ftp_proxy_lookup_client.proxy_info());
+    EXPECT_EQ(initial_proxy_config_set.ftp_proxy_info.ToPacString(),
+              ftp_proxy_lookup_client.proxy_info()->ToPacString());
+
     EXPECT_TRUE(proxy_resolution_service->config());
     EXPECT_TRUE(proxy_resolution_service->config()->value().Equals(
-        initial_proxy_config));
+        initial_proxy_config_set.proxy_config));
 
     // Always go through the other configs in the same order. This has the
     // advantage of testing the case where there's no change, for
     // proxy_config[0].
-    for (const auto& proxy_config : proxy_configs) {
+    for (const auto& proxy_config_set : proxy_config_sets) {
       config_client->OnProxyConfigUpdated(net::ProxyConfigWithAnnotation(
-          proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
+          proxy_config_set.proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
       scoped_task_environment_.RunUntilIdle();
+
+      TestProxyLookupClient http_proxy_lookup_client2;
+      http_proxy_lookup_client2.StartLookUpProxyForURL(GURL("http://foo"),
+                                                       network_context.get());
+      http_proxy_lookup_client2.WaitForResult();
+      ASSERT_TRUE(http_proxy_lookup_client2.proxy_info());
+      EXPECT_EQ(proxy_config_set.http_proxy_info.ToPacString(),
+                http_proxy_lookup_client2.proxy_info()->ToPacString());
+
+      TestProxyLookupClient ftp_proxy_lookup_client2;
+      ftp_proxy_lookup_client2.StartLookUpProxyForURL(GURL("ftp://foo"),
+                                                      network_context.get());
+      ftp_proxy_lookup_client2.WaitForResult();
+      ASSERT_TRUE(ftp_proxy_lookup_client2.proxy_info());
+      EXPECT_EQ(proxy_config_set.ftp_proxy_info.ToPacString(),
+                ftp_proxy_lookup_client2.proxy_info()->ToPacString());
+
       EXPECT_TRUE(proxy_resolution_service->config());
-      EXPECT_TRUE(
-          proxy_resolution_service->config()->value().Equals(proxy_config));
+      EXPECT_TRUE(proxy_resolution_service->config()->value().Equals(
+          proxy_config_set.proxy_config));
     }
   }
 }
@@ -2055,26 +2156,88 @@
   EXPECT_FALSE(proxy_resolution_service->fetched_config());
 
   // Before there's a proxy configuration, proxy requests should hang.
-  net::ProxyInfo proxy_info;
-  net::TestCompletionCallback test_callback;
-  std::unique_ptr<net::ProxyResolutionService::Request> request = nullptr;
-  ASSERT_EQ(net::ERR_IO_PENDING, proxy_resolution_service->ResolveProxy(
-                                     GURL("http://bar/"), "GET", &proxy_info,
-                                     test_callback.callback(), &request,
-                                     nullptr, net::NetLogWithSource()));
+  // Create two lookups, to make sure two simultaneous lookups can be handled at
+  // once.
+  TestProxyLookupClient http_proxy_lookup_client;
+  http_proxy_lookup_client.StartLookUpProxyForURL(GURL("http://foo/"),
+                                                  network_context.get());
+  TestProxyLookupClient ftp_proxy_lookup_client;
+  ftp_proxy_lookup_client.StartLookUpProxyForURL(GURL("ftp://foo/"),
+                                                 network_context.get());
   scoped_task_environment_.RunUntilIdle();
   EXPECT_FALSE(proxy_resolution_service->config());
   EXPECT_FALSE(proxy_resolution_service->fetched_config());
-  ASSERT_FALSE(test_callback.have_result());
+  EXPECT_FALSE(http_proxy_lookup_client.is_done());
+  EXPECT_FALSE(ftp_proxy_lookup_client.is_done());
+  EXPECT_EQ(2u, network_context->pending_proxy_lookup_requests_for_testing());
 
   net::ProxyConfig proxy_config;
   proxy_config.proxy_rules().ParseFromString("http=foopy:80");
   config_client->OnProxyConfigUpdated(net::ProxyConfigWithAnnotation(
       proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS));
-  ASSERT_EQ(net::OK, test_callback.WaitForResult());
 
-  EXPECT_TRUE(proxy_info.is_http());
-  EXPECT_EQ("foopy", proxy_info.proxy_server().host_port_pair().host());
+  http_proxy_lookup_client.WaitForResult();
+  ASSERT_TRUE(http_proxy_lookup_client.proxy_info());
+  EXPECT_EQ("PROXY foopy:80",
+            http_proxy_lookup_client.proxy_info()->ToPacString());
+
+  ftp_proxy_lookup_client.WaitForResult();
+  ASSERT_TRUE(ftp_proxy_lookup_client.proxy_info());
+  EXPECT_EQ("DIRECT", ftp_proxy_lookup_client.proxy_info()->ToPacString());
+
+  EXPECT_EQ(0u, network_context->pending_proxy_lookup_requests_for_testing());
+}
+
+TEST_F(NetworkContextTest, DestroyedWithoutProxyConfig) {
+  // Create a NetworkContext without an initial proxy configuration.
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->initial_proxy_config.reset();
+  mojom::ProxyConfigClientPtr config_client;
+  context_params->proxy_config_client_request =
+      mojo::MakeRequest(&config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  // Proxy requests should hang.
+  TestProxyLookupClient proxy_lookup_client;
+  proxy_lookup_client.StartLookUpProxyForURL(GURL("http://foo/"),
+                                             network_context.get());
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_EQ(1u, network_context->pending_proxy_lookup_requests_for_testing());
+  EXPECT_FALSE(proxy_lookup_client.is_done());
+
+  // Destroying the NetworkContext should cause the pending lookup to fail with
+  // ERR_ABORTED.
+  network_context.reset();
+  proxy_lookup_client.WaitForResult();
+  EXPECT_FALSE(proxy_lookup_client.proxy_info());
+}
+
+TEST_F(NetworkContextTest, CancelPendingProxyLookup) {
+  // Create a NetworkContext without an initial proxy configuration.
+  mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+  context_params->initial_proxy_config.reset();
+  mojom::ProxyConfigClientPtr config_client;
+  context_params->proxy_config_client_request =
+      mojo::MakeRequest(&config_client);
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(std::move(context_params));
+
+  // Proxy requests should hang.
+  std::unique_ptr<TestProxyLookupClient> proxy_lookup_client =
+      std::make_unique<TestProxyLookupClient>();
+  proxy_lookup_client->StartLookUpProxyForURL(GURL("http://foo/"),
+                                              network_context.get());
+  scoped_task_environment_.RunUntilIdle();
+  EXPECT_FALSE(proxy_lookup_client->is_done());
+  EXPECT_EQ(1u, network_context->pending_proxy_lookup_requests_for_testing());
+
+  // Cancelling the proxy lookup should cause the proxy lookup request objects
+  // to be deleted.
+  proxy_lookup_client.reset();
+  scoped_task_environment_.RunUntilIdle();
+
+  EXPECT_EQ(0u, network_context->pending_proxy_lookup_requests_for_testing());
 }
 
 TEST_F(NetworkContextTest, PacQuickCheck) {
@@ -2396,6 +2559,327 @@
   base::DeleteFile(temp_path, false);
 }
 
+net::IPEndPoint CreateExpectedEndPoint(const std::string& address,
+                                       uint16_t port) {
+  net::IPAddress ip_address;
+  CHECK(ip_address.AssignFromIPLiteral(address));
+  return net::IPEndPoint(ip_address, port);
+}
+
+class TestResolveHostClient : public mojom::ResolveHostClient {
+ public:
+  TestResolveHostClient(mojom::ResolveHostClientPtr* interface_ptr,
+                        base::RunLoop* run_loop)
+      : binding_(this, mojo::MakeRequest(interface_ptr)),
+        complete_(false),
+        run_loop_(run_loop) {
+    DCHECK(run_loop_);
+  }
+
+  void CloseBinding() { binding_.Close(); }
+
+  void OnComplete(int error,
+                  const base::Optional<net::AddressList>& addresses) override {
+    DCHECK(!complete_);
+
+    complete_ = true;
+    result_error_ = error;
+    result_addresses_ = addresses;
+    run_loop_->Quit();
+  }
+
+  bool complete() const { return complete_; }
+
+  int result_error() const {
+    DCHECK(complete_);
+    return result_error_;
+  }
+
+  const base::Optional<net::AddressList>& result_addresses() const {
+    DCHECK(complete_);
+    return result_addresses_;
+  }
+
+ private:
+  mojo::Binding<mojom::ResolveHostClient> binding_;
+
+  bool complete_;
+  int result_error_;
+  base::Optional<net::AddressList> result_addresses_;
+  base::RunLoop* const run_loop_;
+};
+
+TEST_F(NetworkContextTest, ResolveHost_Sync) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  auto resolver = std::make_unique<net::MockHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+  resolver->set_synchronous_mode(true);
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 160),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  run_loop.Run();
+
+  EXPECT_EQ(net::OK, response_client.result_error());
+  EXPECT_THAT(
+      response_client.result_addresses().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpectedEndPoint("127.0.0.1", 160)));
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_Async) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  auto resolver = std::make_unique<net::MockHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+  resolver->set_synchronous_mode(false);
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 160),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+
+  bool control_handle_closed = false;
+  auto connection_error_callback =
+      base::BindLambdaForTesting([&]() { control_handle_closed = true; });
+  control_handle.set_connection_error_handler(connection_error_callback);
+  run_loop.Run();
+
+  EXPECT_EQ(net::OK, response_client.result_error());
+  EXPECT_THAT(
+      response_client.result_addresses().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpectedEndPoint("127.0.0.1", 160)));
+  EXPECT_TRUE(control_handle_closed);
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_Failure_Sync) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  auto resolver = std::make_unique<net::MockHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+  resolver->rules()->AddSimulatedFailure("example.com");
+  resolver->set_synchronous_mode(true);
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("example.com", 160),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  run_loop.Run();
+
+  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, response_client.result_error());
+  EXPECT_FALSE(response_client.result_addresses());
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_Failure_Async) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  auto resolver = std::make_unique<net::MockHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+  resolver->rules()->AddSimulatedFailure("example.com");
+  resolver->set_synchronous_mode(false);
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("example.com", 160),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+
+  bool control_handle_closed = false;
+  auto connection_error_callback =
+      base::BindLambdaForTesting([&]() { control_handle_closed = true; });
+  control_handle.set_connection_error_handler(connection_error_callback);
+  run_loop.Run();
+
+  EXPECT_EQ(net::ERR_NAME_NOT_RESOLVED, response_client.result_error());
+  EXPECT_FALSE(response_client.result_addresses());
+  EXPECT_TRUE(control_handle_closed);
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_NoControlHandle) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  // Resolve "localhost" because it should always resolve fast and locally, even
+  // when using a real HostResolver.
+  network_context->ResolveHost(net::HostPortPair("localhost", 80), nullptr,
+                               std::move(response_client_ptr));
+  run_loop.Run();
+
+  EXPECT_EQ(net::OK, response_client.result_error());
+  EXPECT_THAT(
+      response_client.result_addresses().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpectedEndPoint("127.0.0.1", 80),
+                                    CreateExpectedEndPoint("::1", 80)));
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_CloseControlHandle) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  // Resolve "localhost" because it should always resolve fast and locally, even
+  // when using a real HostResolver.
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 160),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  control_handle = nullptr;
+  run_loop.Run();
+
+  EXPECT_EQ(net::OK, response_client.result_error());
+  EXPECT_THAT(
+      response_client.result_addresses().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpectedEndPoint("127.0.0.1", 160),
+                                    CreateExpectedEndPoint("::1", 160)));
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_Cancellation) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  // Override the HostResolver with a hanging one, so the test can ensure the
+  // request won't be completed before the cancellation arrives.
+  auto resolver = std::make_unique<net::HangingHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+
+  ASSERT_EQ(0, resolver->num_cancellations());
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 80),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  bool control_handle_closed = false;
+  auto connection_error_callback =
+      base::BindLambdaForTesting([&]() { control_handle_closed = true; });
+  control_handle.set_connection_error_handler(connection_error_callback);
+
+  control_handle->Cancel(net::ERR_ABORTED);
+  run_loop.Run();
+
+  // On cancellation, should receive an ERR_FAILED result, and the internal
+  // resolver request should have been cancelled.
+  EXPECT_EQ(net::ERR_ABORTED, response_client.result_error());
+  EXPECT_FALSE(response_client.result_addresses());
+  EXPECT_EQ(1, resolver->num_cancellations());
+  EXPECT_TRUE(control_handle_closed);
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
+TEST_F(NetworkContextTest, ResolveHost_DestroyContext) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  // Override the HostResolver with a hanging one, so the test can ensure the
+  // request won't be completed before the cancellation arrives.
+  auto resolver = std::make_unique<net::HangingHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+
+  ASSERT_EQ(0, resolver->num_cancellations());
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 80),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  bool control_handle_closed = false;
+  auto connection_error_callback =
+      base::BindLambdaForTesting([&]() { control_handle_closed = true; });
+  control_handle.set_connection_error_handler(connection_error_callback);
+
+  network_context = nullptr;
+  run_loop.Run();
+
+  // On context destruction, should receive an ERR_FAILED result, and the
+  // internal resolver request should have been cancelled.
+  EXPECT_EQ(net::ERR_FAILED, response_client.result_error());
+  EXPECT_FALSE(response_client.result_addresses());
+  EXPECT_EQ(1, resolver->num_cancellations());
+  EXPECT_TRUE(control_handle_closed);
+}
+
+TEST_F(NetworkContextTest, ResolveHost_CloseClient) {
+  std::unique_ptr<NetworkContext> network_context =
+      CreateContextWithParams(CreateContextParams());
+
+  // Override the HostResolver with a hanging one, so the test can ensure the
+  // request won't be completed before the cancellation arrives.
+  auto resolver = std::make_unique<net::HangingHostResolver>();
+  network_context->url_request_context()->set_host_resolver(resolver.get());
+
+  ASSERT_EQ(0, resolver->num_cancellations());
+
+  base::RunLoop run_loop;
+  mojom::ResolveHostClientPtr response_client_ptr;
+  TestResolveHostClient response_client(&response_client_ptr, &run_loop);
+
+  mojom::ResolveHostHandlePtr control_handle;
+  network_context->ResolveHost(net::HostPortPair("localhost", 80),
+                               mojo::MakeRequest(&control_handle),
+                               std::move(response_client_ptr));
+  bool control_handle_closed = false;
+  auto connection_error_callback =
+      base::BindLambdaForTesting([&]() { control_handle_closed = true; });
+  control_handle.set_connection_error_handler(connection_error_callback);
+
+  response_client.CloseBinding();
+  run_loop.RunUntilIdle();
+
+  // Response pipe is closed, so no results to check. Internal request should be
+  // cancelled.
+  EXPECT_FALSE(response_client.complete());
+  EXPECT_EQ(1, resolver->num_cancellations());
+  EXPECT_TRUE(control_handle_closed);
+  EXPECT_EQ(0u,
+            network_context->GetNumOutstandingResolveHostRequestsForTesting());
+}
+
 TEST_F(NetworkContextTest, PrivacyModeDisabledByDefault) {
   std::unique_ptr<NetworkContext> network_context =
       CreateContextWithParams(CreateContextParams());
diff --git a/services/network/proxy_lookup_request.cc b/services/network/proxy_lookup_request.cc
new file mode 100644
index 0000000..fd0dde81
--- /dev/null
+++ b/services/network/proxy_lookup_request.cc
@@ -0,0 +1,69 @@
+// Copyright 2018 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 "services/network/proxy_lookup_request.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/optional.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/log/net_log_with_source.h"
+#include "net/url_request/url_request_context.h"
+#include "services/network/network_context.h"
+#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h"
+#include "url/gurl.h"
+
+namespace network {
+
+ProxyLookupRequest::ProxyLookupRequest(
+    mojom::ProxyLookupClientPtr proxy_lookup_client,
+    NetworkContext* network_context)
+    : network_context_(network_context),
+      proxy_lookup_client_(std::move(proxy_lookup_client)) {
+  DCHECK(proxy_lookup_client_);
+}
+
+ProxyLookupRequest::~ProxyLookupRequest() {
+  // |request_| should be non-null only when the network service is being torn
+  // down.
+  if (request_)
+    proxy_lookup_client_->OnProxyLookupComplete(base::nullopt);
+}
+
+void ProxyLookupRequest::Start(const GURL& url) {
+  proxy_lookup_client_.set_connection_error_handler(
+      base::BindOnce(&ProxyLookupRequest::DestroySelf, base::Unretained(this)));
+  net::ProxyDelegate* proxy_delegate = network_context_->url_request_context()
+                                           ->http_transaction_factory()
+                                           ->GetSession()
+                                           ->context()
+                                           .proxy_delegate;
+  // TODO(mmenke): The NetLogWithSource() means nothing is logged. Fix that.
+  int result =
+      network_context_->url_request_context()
+          ->proxy_resolution_service()
+          ->ResolveProxy(url, std::string(), &proxy_info_,
+                         base::BindOnce(&ProxyLookupRequest::OnResolveComplete,
+                                        base::Unretained(this)),
+                         &request_, proxy_delegate, net::NetLogWithSource());
+  if (result != net::ERR_IO_PENDING)
+    OnResolveComplete(result);
+}
+
+void ProxyLookupRequest::OnResolveComplete(int result) {
+  proxy_lookup_client_->OnProxyLookupComplete(
+      result == net::OK ? base::Optional<net::ProxyInfo>(std::move(proxy_info_))
+                        : base::nullopt);
+  DestroySelf();
+}
+
+void ProxyLookupRequest::DestroySelf() {
+  request_.reset();
+  network_context_->OnProxyLookupComplete(this);
+}
+
+}  // namespace network
diff --git a/services/network/proxy_lookup_request.h b/services/network/proxy_lookup_request.h
new file mode 100644
index 0000000..6f6a5be
--- /dev/null
+++ b/services/network/proxy_lookup_request.h
@@ -0,0 +1,54 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_PROXY_LOOKUP_REQUEST_H_
+#define SERVICES_NETWORK_PROXY_LOOKUP_REQUEST_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "base/macros.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "net/proxy_resolution/proxy_resolution_service.h"
+#include "services/network/public/mojom/proxy_lookup_client.mojom.h"
+#include "url/gurl.h"
+
+namespace network {
+
+class NetworkContext;
+
+// Single-use object to manage a proxy lookup.
+class COMPONENT_EXPORT(NETWORK_SERVICE) ProxyLookupRequest {
+ public:
+  ProxyLookupRequest(mojom::ProxyLookupClientPtr proxy_lookup_client,
+                     NetworkContext* network_context);
+  ~ProxyLookupRequest();
+
+  // Starts looking up what proxy to use for |url|. On completion, will inform
+  // both the ProxyLookupClient and the NetworkContext that it has completed.
+  // On synchronous completion, will inform the NetworkContext of completion
+  // re-entrantly. If destroyed before the lookup completes, informs the client
+  // that the lookup was aborted.
+  void Start(const GURL& url);
+
+ private:
+  void OnResolveComplete(int result);
+
+  // Cancels |request_| and tells |network_context_| to delete |this|.
+  void DestroySelf();
+
+  NetworkContext* const network_context_;
+  mojom::ProxyLookupClientPtr proxy_lookup_client_;
+
+  net::ProxyInfo proxy_info_;
+  std::unique_ptr<net::ProxyResolutionService::Request> request_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxyLookupRequest);
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_PROXY_LOOKUP_REQUEST_H_
diff --git a/services/network/public/cpp/network_quality_tracker.cc b/services/network/public/cpp/network_quality_tracker.cc
index 40e0d36..042616c 100644
--- a/services/network/public/cpp/network_quality_tracker.cc
+++ b/services/network/public/cpp/network_quality_tracker.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/public/cpp/network_quality_tracker.h"
 
+#include <limits>
 #include <utility>
 
 #include "base/logging.h"
@@ -14,7 +15,7 @@
     base::RepeatingCallback<network::mojom::NetworkService*()> callback)
     : get_network_service_callback_(callback),
       effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
-      downlink_bandwidth_kbps_(INT32_MAX),
+      downlink_bandwidth_kbps_(std::numeric_limits<int32_t>::max()),
       binding_(this) {
   InitializeMojoChannel();
   DCHECK(binding_.is_bound());
@@ -57,6 +58,20 @@
   effective_connection_type_observer_list_.RemoveObserver(observer);
 }
 
+void NetworkQualityTracker::AddRTTAndThroughputEstimatesObserver(
+    RTTAndThroughputEstimatesObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  rtt_and_throughput_observer_list_.AddObserver(observer);
+  observer->OnRTTOrThroughputEstimatesComputed(http_rtt_, transport_rtt_,
+                                               downlink_bandwidth_kbps_);
+}
+
+void NetworkQualityTracker::RemoveRTTAndThroughputEstimatesObserver(
+    RTTAndThroughputEstimatesObserver* observer) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  rtt_and_throughput_observer_list_.RemoveObserver(observer);
+}
+
 void NetworkQualityTracker::OnNetworkQualityChanged(
     net::EffectiveConnectionType effective_connection_type,
     base::TimeDelta http_rtt,
@@ -64,15 +79,23 @@
     int32_t bandwidth_kbps) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  http_rtt_ = http_rtt;
-  transport_rtt_ = transport_rtt;
-  downlink_bandwidth_kbps_ = bandwidth_kbps;
+  if (http_rtt_ != http_rtt || transport_rtt_ != transport_rtt ||
+      downlink_bandwidth_kbps_ != bandwidth_kbps) {
+    http_rtt_ = http_rtt;
+    transport_rtt_ = transport_rtt;
+    downlink_bandwidth_kbps_ = bandwidth_kbps;
 
-  if (effective_connection_type == effective_connection_type_)
-    return;
-  effective_connection_type_ = effective_connection_type;
-  for (auto& observer : effective_connection_type_observer_list_)
-    observer.OnEffectiveConnectionTypeChanged(effective_connection_type_);
+    for (auto& observer : rtt_and_throughput_observer_list_) {
+      observer.OnRTTOrThroughputEstimatesComputed(http_rtt_, transport_rtt_,
+                                                  downlink_bandwidth_kbps_);
+    }
+  }
+
+  if (effective_connection_type != effective_connection_type_) {
+    effective_connection_type_ = effective_connection_type;
+    for (auto& observer : effective_connection_type_observer_list_)
+      observer.OnEffectiveConnectionTypeChanged(effective_connection_type_);
+  }
 }
 
 void NetworkQualityTracker::InitializeMojoChannel() {
diff --git a/services/network/public/cpp/network_quality_tracker.h b/services/network/public/cpp/network_quality_tracker.h
index 80b0799a..fd57acd 100644
--- a/services/network/public/cpp/network_quality_tracker.h
+++ b/services/network/public/cpp/network_quality_tracker.h
@@ -41,6 +41,28 @@
     DISALLOW_COPY_AND_ASSIGN(EffectiveConnectionTypeObserver);
   };
 
+  // Observes changes in the HTTP RTT, transport RTT or downstream throughput
+  // estimates.
+  class COMPONENT_EXPORT(NETWORK_CPP) RTTAndThroughputEstimatesObserver {
+   public:
+    // Called when there is a substantial change in either HTTP RTT, transport
+    // RTT or downstream estimate. If either of the RTT estimates are
+    // unavailable, then the value of that estimate is set to base::TimeDelta().
+    // If downstream estimate is unavailable, its value is set to INT32_MAX.
+    virtual void OnRTTOrThroughputEstimatesComputed(
+        base::TimeDelta http_rtt,
+        base::TimeDelta transport_rtt,
+        int32_t downstream_throughput_kbps) = 0;
+
+    virtual ~RTTAndThroughputEstimatesObserver() {}
+
+   protected:
+    RTTAndThroughputEstimatesObserver() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(RTTAndThroughputEstimatesObserver);
+  };
+
   // Running the |callback| returns the network service in use.
   // NetworkQualityTracker does not need to be destroyed before the network
   // service.
@@ -76,12 +98,30 @@
   void AddEffectiveConnectionTypeObserver(
       EffectiveConnectionTypeObserver* observer);
 
-  // Unregisters |observer| from receiving notifications.  This must be called
+  // Unregisters |observer| from receiving notifications. This must be called
   // on the same thread on which AddObserver() was called.
   // All observers must be unregistered before |this| is destroyed.
   void RemoveEffectiveConnectionTypeObserver(
       EffectiveConnectionTypeObserver* observer);
 
+  // Registers |observer| to receive notifications of RTT or throughput changes.
+  // This should be called on the same thread as the thread on which |this| is
+  // created. The |observer| would be called back with notifications on that
+  // same thread. The |observer| is notified of the current HTTP RTT, transport
+  // RTT and downstrean bandwidth estimates on the same thread. If either of the
+  // RTT estimate are unavailable, then the value of that estimate is set to
+  // base::TimeDelta(). If downstream estimate is unavailable, its value is set
+  // to INT32_MAX. The |observer| is notified of the current RTT and
+  // throughput estimates synchronously when this method is invoked.
+  void AddRTTAndThroughputEstimatesObserver(
+      RTTAndThroughputEstimatesObserver* observer);
+
+  // Unregisters |observer| from receiving notifications. This must be called
+  // on the same thread on which AddObserver() was called.
+  // All observers must be unregistered before |this| is destroyed.
+  void RemoveRTTAndThroughputEstimatesObserver(
+      RTTAndThroughputEstimatesObserver* observer);
+
  protected:
   // NetworkQualityEstimatorManagerClient implementation. Protected for testing.
   void OnNetworkQualityChanged(
@@ -112,6 +152,9 @@
   base::ObserverList<EffectiveConnectionTypeObserver>
       effective_connection_type_observer_list_;
 
+  base::ObserverList<RTTAndThroughputEstimatesObserver>
+      rtt_and_throughput_observer_list_;
+
   mojo::Binding<network::mojom::NetworkQualityEstimatorManagerClient> binding_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/services/network/public/cpp/network_quality_tracker_unittest.cc b/services/network/public/cpp/network_quality_tracker_unittest.cc
index 7265e84..90e3b6a3 100644
--- a/services/network/public/cpp/network_quality_tracker_unittest.cc
+++ b/services/network/public/cpp/network_quality_tracker_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "services/network/public/cpp/network_quality_tracker.h"
 
+#include <limits>
+
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
@@ -25,7 +27,8 @@
   explicit TestEffectiveConnectionTypeObserver(NetworkQualityTracker* tracker)
       : num_notifications_(0),
         tracker_(tracker),
-        run_loop_(std::make_unique<base::RunLoop>()),
+        expected_effective_connection_type_(
+            net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
         effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
     tracker_->AddEffectiveConnectionTypeObserver(this);
   }
@@ -46,14 +49,23 @@
     EXPECT_EQ(type, GetEffectiveConnectionTypeSync());
     num_notifications_++;
     effective_connection_type_ = type;
-    run_loop_->Quit();
+    if (run_loop_ && type == expected_effective_connection_type_)
+      run_loop_->Quit();
   }
 
   size_t num_notifications() const { return num_notifications_; }
 
-  void WaitForNotification() {
+  void WaitForNotification(
+      net::EffectiveConnectionType expected_effective_connection_type) {
+    if (expected_effective_connection_type == effective_connection_type_)
+      return;
+
+    expected_effective_connection_type_ = expected_effective_connection_type;
+    // WaitForNotification should not be called twice.
+    EXPECT_EQ(nullptr, run_loop_);
+    run_loop_ = std::make_unique<base::RunLoop>();
     run_loop_->Run();
-    run_loop_.reset(new base::RunLoop());
+    run_loop_.reset();
   }
 
   net::EffectiveConnectionType effective_connection_type() const {
@@ -61,22 +73,94 @@
   }
 
  private:
-  static void GetEffectiveConnectionTypeCallback(
-      base::RunLoop* run_loop,
-      net::EffectiveConnectionType* out,
-      net::EffectiveConnectionType type) {
-    *out = type;
-    run_loop->Quit();
-  }
-
   size_t num_notifications_;
   NetworkQualityTracker* tracker_;
+  // May be null.
   std::unique_ptr<base::RunLoop> run_loop_;
+  net::EffectiveConnectionType expected_effective_connection_type_;
   net::EffectiveConnectionType effective_connection_type_;
 
   DISALLOW_COPY_AND_ASSIGN(TestEffectiveConnectionTypeObserver);
 };
 
+class TestRTTAndThroughputEstimatesObserver
+    : public NetworkQualityTracker::RTTAndThroughputEstimatesObserver {
+ public:
+  explicit TestRTTAndThroughputEstimatesObserver(NetworkQualityTracker* tracker)
+      : num_notifications_(0),
+        tracker_(tracker),
+        downstream_throughput_kbps_(std::numeric_limits<int32_t>::max()) {
+    tracker_->AddRTTAndThroughputEstimatesObserver(this);
+  }
+
+  ~TestRTTAndThroughputEstimatesObserver() override {
+    tracker_->RemoveRTTAndThroughputEstimatesObserver(this);
+  }
+
+  // RTTAndThroughputEstimatesObserver implementation:
+  void OnRTTOrThroughputEstimatesComputed(
+      base::TimeDelta http_rtt,
+      base::TimeDelta transport_rtt,
+      int32_t downstream_throughput_kbps) override {
+    EXPECT_EQ(http_rtt, tracker_->GetHttpRTT());
+    EXPECT_EQ(transport_rtt, tracker_->GetTransportRTT());
+    EXPECT_EQ(downstream_throughput_kbps,
+              tracker_->GetDownstreamThroughputKbps());
+
+    num_notifications_++;
+    http_rtt_ = http_rtt;
+    transport_rtt_ = transport_rtt;
+    downstream_throughput_kbps_ = downstream_throughput_kbps;
+
+    if (run_loop_ && http_rtt == http_rtt_notification_wait_)
+      run_loop_->Quit();
+  }
+
+  size_t num_notifications() const { return num_notifications_; }
+
+  void WaitForNotification(base::TimeDelta expected_http_rtt) {
+    // It's not meaningful to wait for notification with RTT set to
+    // base::TimeDelta() since that value implies that the network quality
+    // estimate was unavailable.
+    EXPECT_NE(base::TimeDelta(), expected_http_rtt);
+    http_rtt_notification_wait_ = expected_http_rtt;
+    if (http_rtt_notification_wait_ == http_rtt_)
+      return;
+
+    // WaitForNotification should not be called twice.
+    EXPECT_EQ(nullptr, run_loop_);
+    run_loop_ = std::make_unique<base::RunLoop>();
+    run_loop_->Run();
+    EXPECT_EQ(expected_http_rtt, http_rtt_);
+    run_loop_.reset();
+  }
+
+  void VerifyNetworkQualityMatchesWithTracker() const {
+    EXPECT_EQ(tracker_->GetHttpRTT(), http_rtt_);
+    EXPECT_EQ(tracker_->GetTransportRTT(), transport_rtt_);
+    EXPECT_EQ(tracker_->GetDownstreamThroughputKbps(),
+              downstream_throughput_kbps_);
+  }
+
+  base::TimeDelta http_rtt() const { return http_rtt_; }
+  base::TimeDelta transport_rtt() const { return transport_rtt_; }
+  int32_t downstream_throughput_kbps() const {
+    return downstream_throughput_kbps_;
+  }
+
+ private:
+  size_t num_notifications_;
+  NetworkQualityTracker* tracker_;
+  // May be null.
+  std::unique_ptr<base::RunLoop> run_loop_;
+  base::TimeDelta http_rtt_;
+  base::TimeDelta transport_rtt_;
+  int32_t downstream_throughput_kbps_;
+  base::TimeDelta http_rtt_notification_wait_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestRTTAndThroughputEstimatesObserver);
+};
+
 }  // namespace
 
 class NetworkQualityTrackerTest : public testing::Test {
@@ -91,8 +175,10 @@
     tracker_ = std::make_unique<NetworkQualityTracker>(
         base::BindRepeating(&NetworkQualityTrackerTest::mojom_network_service,
                             base::Unretained(this)));
-    observer_ =
+    ect_observer_ =
         std::make_unique<TestEffectiveConnectionTypeObserver>(tracker_.get());
+    rtt_throughput_observer_ =
+        std::make_unique<TestRTTAndThroughputEstimatesObserver>(tracker_.get());
   }
 
   ~NetworkQualityTrackerTest() override {}
@@ -102,7 +188,11 @@
   NetworkQualityTracker* network_quality_tracker() { return tracker_.get(); }
 
   TestEffectiveConnectionTypeObserver* effective_connection_type_observer() {
-    return observer_.get();
+    return ect_observer_.get();
+  }
+
+  TestRTTAndThroughputEstimatesObserver* rtt_throughput_observer() {
+    return rtt_throughput_observer_.get();
   }
 
   // Simulates a connection type change and broadcast it to observers.
@@ -121,18 +211,20 @@
   base::test::ScopedTaskEnvironment scoped_task_environment_;
   std::unique_ptr<network::NetworkService> network_service_;
   std::unique_ptr<NetworkQualityTracker> tracker_;
-  std::unique_ptr<TestEffectiveConnectionTypeObserver> observer_;
+  std::unique_ptr<TestEffectiveConnectionTypeObserver> ect_observer_;
+  std::unique_ptr<TestRTTAndThroughputEstimatesObserver>
+      rtt_throughput_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkQualityTrackerTest);
 };
 
-TEST_F(NetworkQualityTrackerTest, ObserverNotified) {
+TEST_F(NetworkQualityTrackerTest, ECTObserverNotified) {
   EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
             effective_connection_type_observer()->effective_connection_type());
 
   SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
-
-  effective_connection_type_observer()->WaitForNotification();
+  effective_connection_type_observer()->WaitForNotification(
+      net::EFFECTIVE_CONNECTION_TYPE_3G);
   EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
             effective_connection_type_observer()->effective_connection_type());
   // Typical RTT and downlink values when effective connection type is 3G. Taken
@@ -144,20 +236,62 @@
   EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1u, effective_connection_type_observer()->num_notifications());
+
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+  effective_connection_type_observer()->WaitForNotification(
+      net::EFFECTIVE_CONNECTION_TYPE_2G);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            effective_connection_type_observer()->effective_connection_type());
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(75, network_quality_tracker()->GetDownstreamThroughputKbps());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(2u, effective_connection_type_observer()->num_notifications());
 }
 
-TEST_F(NetworkQualityTrackerTest, UnregisteredObserverNotNotified) {
-  auto network_quality_observer2 =
-      std::make_unique<TestEffectiveConnectionTypeObserver>(
-          network_quality_tracker());
+TEST_F(NetworkQualityTrackerTest, RttThroughputObserverNotified) {
+  // One notification must be received by rtt_throughput_observer() as soon as
+  // it is registered as an observer.
+  EXPECT_EQ(1u, rtt_throughput_observer()->num_notifications());
 
-  // Simulate a network quality change.
   SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(450));
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(2u, rtt_throughput_observer()->num_notifications());
 
-  network_quality_observer2->WaitForNotification();
-  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
-            network_quality_observer2->effective_connection_type());
-  effective_connection_type_observer()->WaitForNotification();
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(1800));
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(75, network_quality_tracker()->GetDownstreamThroughputKbps());
+  EXPECT_EQ(3u, rtt_throughput_observer()->num_notifications());
+}
+
+TEST_F(NetworkQualityTrackerTest, ECTObserverNotifiedOnAddition) {
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            effective_connection_type_observer()->effective_connection_type());
+
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+  effective_connection_type_observer()->WaitForNotification(
+      net::EFFECTIVE_CONNECTION_TYPE_3G);
   EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
             effective_connection_type_observer()->effective_connection_type());
   // Typical RTT and downlink values when effective connection type is 3G. Taken
@@ -167,13 +301,82 @@
   EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
             network_quality_tracker()->GetTransportRTT());
   EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
-  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, effective_connection_type_observer()->num_notifications());
 
-  network_quality_observer2.reset();
+  auto ect_observer_2 = std::make_unique<TestEffectiveConnectionTypeObserver>(
+      network_quality_tracker());
+  ect_observer_2->WaitForNotification(net::EFFECTIVE_CONNECTION_TYPE_3G);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
+            ect_observer_2->effective_connection_type());
+}
+
+TEST_F(NetworkQualityTrackerTest, RttThroughputObserverNotifiedOnAddition) {
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(450));
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(2u, rtt_throughput_observer()->num_notifications());
+
+  auto rtt_throughput_observer_2 =
+      std::make_unique<TestRTTAndThroughputEstimatesObserver>(
+          network_quality_tracker());
+  rtt_throughput_observer_2->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(1u, rtt_throughput_observer_2->num_notifications());
+
+  // Simulate a network quality change.
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(1800));
+  // Typical RTT and downlink values when effective connection type is 2G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(75, network_quality_tracker()->GetDownstreamThroughputKbps());
+
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(3u, rtt_throughput_observer()->num_notifications());
+  rtt_throughput_observer_2->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(2u, rtt_throughput_observer_2->num_notifications());
+}
+
+TEST_F(NetworkQualityTrackerTest, UnregisteredECTObserverNotNotified) {
+  auto ect_observer_2 = std::make_unique<TestEffectiveConnectionTypeObserver>(
+      network_quality_tracker());
+
+  // Simulate a network quality change.
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+
+  ect_observer_2->WaitForNotification(net::EFFECTIVE_CONNECTION_TYPE_3G);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
+            ect_observer_2->effective_connection_type());
+  effective_connection_type_observer()->WaitForNotification(
+      net::EFFECTIVE_CONNECTION_TYPE_3G);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_3G,
+            effective_connection_type_observer()->effective_connection_type());
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
+
+  ect_observer_2.reset();
 
   // Simulate an another network quality change.
   SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
-  effective_connection_type_observer()->WaitForNotification();
+  effective_connection_type_observer()->WaitForNotification(
+      net::EFFECTIVE_CONNECTION_TYPE_2G);
   EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
             effective_connection_type_observer()->effective_connection_type());
   EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
@@ -184,4 +387,42 @@
   EXPECT_EQ(2u, effective_connection_type_observer()->num_notifications());
 }
 
+TEST_F(NetworkQualityTrackerTest, UnregisteredRttThroughputbserverNotNotified) {
+  auto rtt_throughput_observer_2 =
+      std::make_unique<TestRTTAndThroughputEstimatesObserver>(
+          network_quality_tracker());
+
+  // Simulate a network quality change.
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_3G);
+
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(450));
+  // Typical RTT and downlink values when effective connection type is 3G. Taken
+  // from net::NetworkQualityEstimatorParams.
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(450),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(400),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(400, network_quality_tracker()->GetDownstreamThroughputKbps());
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  rtt_throughput_observer_2->VerifyNetworkQualityMatchesWithTracker();
+
+  // Destroying |rtt_throughput_observer_2| should unregister it as an observer.
+  // Verify that doing this causes network quality tracker to remove it as an
+  // observer from the list of registered observers.
+  rtt_throughput_observer_2.reset();
+
+  // Simulate an another network quality change.
+  SimulateEffectiveConnectionTypeChange(net::EFFECTIVE_CONNECTION_TYPE_2G);
+  rtt_throughput_observer()->WaitForNotification(
+      base::TimeDelta::FromMilliseconds(1800));
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1800),
+            network_quality_tracker()->GetHttpRTT());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1500),
+            network_quality_tracker()->GetTransportRTT());
+  EXPECT_EQ(75, network_quality_tracker()->GetDownstreamThroughputKbps());
+  rtt_throughput_observer()->VerifyNetworkQualityMatchesWithTracker();
+  EXPECT_EQ(3u, rtt_throughput_observer()->num_notifications());
+}
+
 }  // namespace network
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index e9d7bfe..62e538f 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -79,6 +79,7 @@
     "ct_log_info.mojom",
     "digitally_signed.mojom",
     "fetch_api.mojom",
+    "host_resolver.mojom",
     "http_request_headers.mojom",
     "network_change_manager.mojom",
     "network_context.mojom",
@@ -88,6 +89,7 @@
     "network_types.mojom",
     "proxy_config.mojom",
     "proxy_config_with_annotation.mojom",
+    "proxy_lookup_client.mojom",
     "proxy_resolving_socket.mojom",
     "request_context_frame_type.mojom",
     "restricted_cookie_manager.mojom",
diff --git a/services/network/public/mojom/host_resolver.mojom b/services/network/public/mojom/host_resolver.mojom
new file mode 100644
index 0000000..93bda9ae
--- /dev/null
+++ b/services/network/public/mojom/host_resolver.mojom
@@ -0,0 +1,34 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.

+// Use of this source code is governed by a BSD-style license that can be

+// found in the LICENSE file.

+

+module network.mojom;

+

+import "net/interfaces/address_list.mojom";

+

+// Control handle used to control outstanding NetworkContext::ResolveHost
+// requests. Handle is optional for all requests, and may be closed at any time
+// without affecting the request.
+//
+// TODO(crbug.com/821021): Add support to change priority.
+interface ResolveHostHandle {
+  // Requests cancellation of the resolution request. Doing so may have no
+  // effect if a result was already sent. If successful, cancellation results in
+  // ResolveHostClient::OnComplete being invoked with |result|, which should be
+  // a non-OK network error code.
+  Cancel(int32 result);
+};
+
+// Response interface used to receive results of NetworkContext::ResolveHost
+// requests.
+//
+// TODO(crbug.com/821021): Add additional methods to receive non-address
+// results (eg TXT DNS responses).
+interface ResolveHostClient {
+  // Called on completion of a resolve host request. Results are a network error
+  // code, and on success (network error code OK), an AddressList.
+  OnComplete(int32 result, net.interfaces.AddressList? resolved_addresses);
+};
+
+// TODO(crbug.com/821021): Create a separate HostResolver interface, so host
+// resolution can be done without direct access to NetworkContext.
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 9dfc9a96..65034a6b 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -13,9 +13,12 @@
 import "net/interfaces/ip_endpoint.mojom";
 import "services/network/public/mojom/cookie_manager.mojom";
 import "services/network/public/mojom/ct_log_info.mojom";
+import "services/network/public/mojom/host_resolver.mojom";
 import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom";
+import "services/network/public/mojom/network_param.mojom";
 import "services/network/public/mojom/proxy_config.mojom";
 import "services/network/public/mojom/proxy_config_with_annotation.mojom";
+import "services/network/public/mojom/proxy_lookup_client.mojom";
 import "services/network/public/mojom/proxy_resolving_socket.mojom";
 import "services/network/public/mojom/restricted_cookie_manager.mojom";
 import "services/network/public/mojom/ssl_config.mojom";
@@ -507,6 +510,10 @@
                   url.mojom.Origin origin,
                   AuthenticationHandler? auth_handler);
 
+  // Looks up what proxy to use for a particular URL.
+  LookUpProxyForURL(url.mojom.Url url,
+                    ProxyLookupClient proxy_lookup_client);
+
   // Create a NetLogExporter, which helps export NetLog to an existing file.
   // Note that the log is generally global, including all NetworkContexts
   // managed by the same NetworkService. The particular NetworkContext this is
@@ -531,6 +538,29 @@
   // used on URLLoaders.
   ResetURLLoaderFactories();
 
+  // Resolves the given hostname (or IP address literal). All results are sent
+  // via the passed |response_client|. Results are a network error code, and on
+  // success (network error code OK), an AddressList.
+  //
+  // Results in ERR_NAME_NOT_RESOLVED if hostname is invalid, or if it is an
+  // incompatible IP literal (e.g. IPv6 is disabled and it is an IPv6 literal).
+  //
+  // If passed an optional |control_handle|, the outstanding request can be
+  // controlled, eg cancelled, via the handle.
+  //
+  // All outstanding requests are cancelled if the NetworkContext is destroyed.
+  // Such requests will receive ERR_FAILED via |response_client|.
+  //
+  // TODO(crbug.com/821021): Implement more complex functionality to meet full
+  // capabilities of Resolve() and DnsClient/MDnsClient functionality.
+  //
+  // TODO(crbug.com/821021): Create (or move) a version of this API in some sort
+  // of separate (and possibly restrictable) HostResolver mojo interface that
+  // can be shared with processes without access to NetworkContext.
+  ResolveHost(HostPortPair host,

+              ResolveHostHandle&? control_handle,

+              ResolveHostClient response_client);

+
   [Sync]
   // Adds explicitly-specified data as if it was processed from an
   // HSTS header.
diff --git a/services/network/public/mojom/proxy_lookup_client.mojom b/services/network/public/mojom/proxy_lookup_client.mojom
new file mode 100644
index 0000000..b030a5b
--- /dev/null
+++ b/services/network/public/mojom/proxy_lookup_client.mojom
@@ -0,0 +1,22 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module network.mojom;
+
+import "url/mojom/url.mojom";
+import "services/proxy_resolver/public/mojom/proxy_resolver.mojom";
+
+// Interface for getting the result of a proxy lookup from a NetworkContext.
+// Not to be confused with ProxyResolverRequestClient, which is the interface
+// between the ProxyResolver service, which executes PAC scripts, and the
+// NetworkService.
+//
+// Closing the ProxyLookUpClient pipe before the request is complete will cancel
+// the proxy lookup.
+interface ProxyLookupClient {
+  // Called with the results of a proxy lookup when it is complete. Invoked only
+  // once per proxy lookup. |proxy_info| is null if the lookup failed, or if the
+  // NetworkContext was destroyed.
+  OnProxyLookupComplete(proxy_resolver.mojom.ProxyInfo? proxy_info);
+};
diff --git a/services/network/resolve_host_request.cc b/services/network/resolve_host_request.cc
new file mode 100644
index 0000000..04f58f09
--- /dev/null
+++ b/services/network/resolve_host_request.cc
@@ -0,0 +1,105 @@
+// Copyright 2018 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 "services/network/resolve_host_request.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/optional.h"
+#include "net/base/net_errors.h"
+#include "net/log/net_log.h"
+#include "net/log/net_log_with_source.h"
+
+namespace network {
+
+ResolveHostRequest::ResolveHostRequest(net::HostResolver* resolver,
+                                       const net::HostPortPair& host,
+                                       net::NetLog* net_log) {
+  DCHECK(resolver);
+  DCHECK(net_log);
+
+  internal_request_ = resolver->CreateRequest(
+      host, net::NetLogWithSource::Make(net_log, net::NetLogSourceType::NONE));
+}
+
+ResolveHostRequest::~ResolveHostRequest() {
+  if (control_handle_binding_.is_bound())
+    control_handle_binding_.Close();
+
+  if (response_client_.is_bound()) {
+    response_client_->OnComplete(net::ERR_FAILED, base::nullopt);
+    response_client_ = nullptr;
+  }
+}
+
+int ResolveHostRequest::Start(
+    mojom::ResolveHostHandleRequest control_handle_request,
+    mojom::ResolveHostClientPtr response_client,
+    net::CompletionOnceCallback callback) {
+  DCHECK(internal_request_);
+  DCHECK(!control_handle_binding_.is_bound());
+  DCHECK(!response_client_.is_bound());
+
+  // Unretained |this| reference is safe because if |internal_request_| goes out
+  // of scope, it will cancel the request and ResolveHost() will not call the
+  // callback.
+  int rv = internal_request_->Start(
+      base::BindOnce(&ResolveHostRequest::OnComplete, base::Unretained(this)));
+  if (rv != net::ERR_IO_PENDING) {
+    response_client->OnComplete(rv, GetAddressResults());
+    return rv;
+  }
+
+  if (control_handle_request)
+    control_handle_binding_.Bind(std::move(control_handle_request));
+
+  response_client_ = std::move(response_client);
+  // Unretained |this| reference is safe because connection error cannot occur
+  // if |response_client_| goes out of scope.
+  response_client_.set_connection_error_handler(base::BindOnce(
+      &ResolveHostRequest::Cancel, base::Unretained(this), net::ERR_FAILED));
+
+  callback_ = std::move(callback);
+
+  return net::ERR_IO_PENDING;
+}
+
+void ResolveHostRequest::Cancel(int error) {
+  DCHECK_NE(net::OK, error);
+
+  if (cancelled_)
+    return;
+
+  internal_request_ = nullptr;
+  cancelled_ = true;
+  OnComplete(error);
+}
+
+void ResolveHostRequest::OnComplete(int error) {
+  DCHECK(response_client_.is_bound());
+  DCHECK(callback_);
+
+  control_handle_binding_.Close();
+  response_client_->OnComplete(error, GetAddressResults());
+  response_client_ = nullptr;
+
+  // Invoke completion callback last as it may delete |this|.
+  std::move(callback_).Run(error);
+}
+
+const base::Optional<net::AddressList>& ResolveHostRequest::GetAddressResults()
+    const {
+  if (cancelled_) {
+    static base::NoDestructor<base::Optional<net::AddressList>>
+        cancelled_result(base::nullopt);
+    return *cancelled_result;
+  }
+
+  DCHECK(internal_request_);
+  return internal_request_->GetAddressResults();
+}
+
+}  // namespace network
diff --git a/services/network/resolve_host_request.h b/services/network/resolve_host_request.h
new file mode 100644
index 0000000..24bbef5e
--- /dev/null
+++ b/services/network/resolve_host_request.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_RESOLVE_HOST_REQUEST_H_
+#define SERVICES_NETWORK_RESOLVE_HOST_REQUEST_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "net/base/completion_once_callback.h"
+#include "net/dns/host_resolver.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
+
+namespace net {
+class HostPortPair;
+class NetLog;
+}  // namespace net
+
+namespace network {
+
+// Manager of a single Mojo request to NetworkContext::ResolveHost(). Binds
+// itself as the implementation of the control handle, and manages request
+// lifetime and cancellation.
+class ResolveHostRequest : public mojom::ResolveHostHandle {
+ public:
+  ResolveHostRequest(net::HostResolver* resolver,
+                     const net::HostPortPair& host,
+                     net::NetLog* net_log);
+  ~ResolveHostRequest() override;
+
+  int Start(mojom::ResolveHostHandleRequest control_handle_request,
+            mojom::ResolveHostClientPtr response_client,
+            net::CompletionOnceCallback callback);
+
+  // ResolveHostHandle overrides.
+  void Cancel(int error) override;
+
+ private:
+  void OnComplete(int error);
+  const base::Optional<net::AddressList>& GetAddressResults() const;
+
+  std::unique_ptr<net::HostResolver::ResolveHostRequest> internal_request_;
+
+  mojo::Binding<mojom::ResolveHostHandle> control_handle_binding_{this};
+  mojom::ResolveHostClientPtr response_client_;
+  net::CompletionOnceCallback callback_;
+  bool cancelled_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(ResolveHostRequest);
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_RESOLVE_HOST_REQUEST_H_
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index ddd510a..84db6a0 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -100,7 +100,13 @@
                        int32_t render_frame_id,
                        const url::Origin& origin,
                        mojom::AuthenticationHandlerPtr auth_handler) override {}
+  void LookUpProxyForURL(
+      const GURL& url,
+      ::network::mojom::ProxyLookupClientPtr proxy_lookup_client) override {}
   void CreateNetLogExporter(mojom::NetLogExporterRequest exporter) override {}
+  void ResolveHost(const net::HostPortPair& host,
+                   mojom::ResolveHostHandleRequest control_handle,
+                   mojom::ResolveHostClientPtr response_client) override {}
   void AddHSTSForTesting(const std::string& host,
                          base::Time expiry,
                          bool include_subdomains,
diff --git a/services/resource_coordinator/observers/page_signal_generator_impl.cc b/services/resource_coordinator/observers/page_signal_generator_impl.cc
index 5c8f731..ea9c4bd 100644
--- a/services/resource_coordinator/observers/page_signal_generator_impl.cc
+++ b/services/resource_coordinator/observers/page_signal_generator_impl.cc
@@ -234,7 +234,9 @@
       PageData* data = &entry.second;
       // TODO(siggi): Figure "recency" here, to avoid firing a measurement event
       //     for state transitions that happened "too long" before a
-      //     measurement started.
+      //     measurement started. Alternatively perhaps this bit of policy is
+      //     better done in the observer, in which case it needs the time stamps
+      //     involved.
       if (data->GetLoadIdleState() == kLoadedAndIdle &&
           !data->performance_estimate_issued &&
           data->last_state_change < measurement_start) {
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index 6599c9cf..239cdf4 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -4,6 +4,10 @@
 
 #include "services/tracing/perfetto/json_trace_exporter.h"
 
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "base/json/string_escape.h"
 #include "base/logging.h"
 
@@ -178,7 +182,7 @@
 
 JSONTraceExporter::JSONTraceExporter(const std::string& config,
                                      perfetto::TracingService* service)
-    : config_(config) {
+    : config_(config), metadata_(std::make_unique<base::DictionaryValue>()) {
   consumer_endpoint_ = service->ConnectConsumer(this);
 }
 
@@ -188,23 +192,35 @@
   // Start tracing.
   perfetto::TraceConfig trace_config;
   trace_config.add_buffers()->set_size_kb(4096 * 100);
-  auto* ds_config = trace_config.add_data_sources()->mutable_config();
-  ds_config->set_name(mojom::kTraceEventDataSourceName);
-  ds_config->set_target_buffer(0);
-  auto* chrome_config = ds_config->mutable_chrome_config();
+
+  auto* trace_event_config = trace_config.add_data_sources()->mutable_config();
+  trace_event_config->set_name(mojom::kTraceEventDataSourceName);
+  trace_event_config->set_target_buffer(0);
+  auto* chrome_config = trace_event_config->mutable_chrome_config();
   chrome_config->set_trace_config(config_);
 
+  auto* trace_metadata_config =
+      trace_config.add_data_sources()->mutable_config();
+  trace_metadata_config->set_name(mojom::kMetaDataSourceName);
+  trace_metadata_config->set_target_buffer(0);
+
   consumer_endpoint_->EnableTracing(trace_config);
 }
 
 void JSONTraceExporter::OnDisconnect() {}
 
+void JSONTraceExporter::OnTracingDisabled() {
+  consumer_endpoint_->ReadBuffers();
+}
+
+// This is called by the Coordinator interface, mainly used by the
+// TracingController which in turn is used by the tracing UI etc
+// to start/stop tracing.
 void JSONTraceExporter::StopAndFlush(OnTraceEventJSONCallback callback) {
   DCHECK(!json_callback_ && callback);
   json_callback_ = callback;
 
   consumer_endpoint_->DisableTracing();
-  consumer_endpoint_->ReadBuffers();
 }
 
 void JSONTraceExporter::OnTraceData(std::vector<perfetto::TracePacket> packets,
@@ -239,13 +255,37 @@
 
       OutputJSONFromTraceEventProto(event, &out);
     }
+
+    for (const perfetto::protos::ChromeMetadata& metadata : bundle.metadata()) {
+      if (metadata.has_string_value()) {
+        metadata_->SetString(metadata.name(), metadata.string_value());
+      } else if (metadata.has_int_value()) {
+        metadata_->SetInteger(metadata.name(), metadata.int_value());
+      } else if (metadata.has_bool_value()) {
+        metadata_->SetBoolean(metadata.name(), metadata.bool_value());
+      } else if (metadata.has_json_value()) {
+        std::unique_ptr<base::Value> value(
+            base::JSONReader::Read(metadata.json_value()));
+        metadata_->Set(metadata.name(), std::move(value));
+      } else {
+        NOTREACHED();
+      }
+    }
   }
 
   if (!has_more) {
-    out += "]}";
+    out += "]";
+    if (!metadata_->empty()) {
+      out += ",\"metadata\":";
+      std::string json_value;
+      base::JSONWriter::Write(*metadata_, &json_value);
+      out += json_value;
+    }
+
+    out += "}";
   }
 
-  json_callback_.Run(out, has_more);
+  json_callback_.Run(out, metadata_.get(), has_more);
 }
 
 }  // namespace tracing
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index 782396a..be55410 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -11,6 +11,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/values.h"
 
 #include "third_party/perfetto/include/perfetto/tracing/core/consumer.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/tracing_service.h"
@@ -31,7 +32,9 @@
   ~JSONTraceExporter() override;
 
   using OnTraceEventJSONCallback =
-      base::RepeatingCallback<void(const std::string& json, bool has_more)>;
+      base::RepeatingCallback<void(const std::string& json,
+                                   base::DictionaryValue* metadata,
+                                   bool has_more)>;
   void StopAndFlush(OnTraceEventJSONCallback callback);
 
   // perfetto::Consumer implementation.
@@ -39,7 +42,7 @@
   // and to send finished protobufs over.
   void OnConnect() override;
   void OnDisconnect() override;
-  void OnTracingDisabled() override{};
+  void OnTracingDisabled() override;
   void OnTraceData(std::vector<perfetto::TracePacket> packets,
                    bool has_more) override;
 
@@ -48,6 +51,7 @@
   bool has_output_json_preamble_ = false;
   bool has_output_first_event_ = false;
   std::string config_;
+  std::unique_ptr<base::DictionaryValue> metadata_;
 
   // Keep last to avoid edge-cases where its callbacks come in mid-destruction.
   std::unique_ptr<perfetto::TracingService::ConsumerEndpoint>
diff --git a/services/tracing/perfetto/json_trace_exporter_unittest.cc b/services/tracing/perfetto/json_trace_exporter_unittest.cc
index 3af401df..4792316 100644
--- a/services/tracing/perfetto/json_trace_exporter_unittest.cc
+++ b/services/tracing/perfetto/json_trace_exporter_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/trace_event/trace_event.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#include "services/tracing/public/mojom/perfetto_service.mojom.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_packet.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
@@ -64,7 +65,8 @@
   void EnableTracing(
       const perfetto::TraceConfig& config,
       perfetto::base::ScopedFile = perfetto::base::ScopedFile()) override {
-    EXPECT_EQ(1, config.data_sources_size());
+    EXPECT_EQ(mojom::kTraceEventDataSourceName,
+              config.data_sources()[0].config().name());
     mock_service_->OnTracingEnabled(
         config.data_sources()[0].config().chrome_config().trace_config());
   }
@@ -72,7 +74,9 @@
   void DisableTracing() override { mock_service_->OnTracingDisabled(); }
   void ReadBuffers() override {}
   void FreeBuffers() override {}
-  void Flush(uint32_t timeout_ms, FlushCallback) override {}
+  void Flush(uint32_t timeout_ms, FlushCallback callback) override {
+    callback(true);
+  }
 
  private:
   MockService* mock_service_;
@@ -141,7 +145,10 @@
         &JSONTraceExporterTest::OnTraceEventJSON, base::Unretained(this)));
   }
 
-  void OnTraceEventJSON(const std::string& json, bool has_more) {
+  void OnTraceEventJSON(const std::string& json,
+                        base::DictionaryValue* metadata,
+                        bool has_more) {
+    CHECK(!has_more);
     // The TraceAnalyzer expects the raw trace output, without the
     // wrapping root-node.
     static const size_t kTracingPreambleLength = strlen("\"{traceEvents\":");
@@ -151,7 +158,10 @@
         json.length() - kTracingPreambleLength - kTracingEpilogueLength);
 
     trace_analyzer_.reset(trace_analyzer::TraceAnalyzer::Create(raw_events));
-    EXPECT_TRUE(trace_analyzer_);
+
+    parsed_trace_data_ =
+        base::DictionaryValue::From(base::JSONReader::Read(json));
+    EXPECT_TRUE(parsed_trace_data_);
   }
 
   void SetTestPacketBasicData(
@@ -215,12 +225,16 @@
     return trace_analyzer_.get();
   }
   MockService* service() { return service_.get(); }
+  const base::DictionaryValue* parsed_trace_data() const {
+    return parsed_trace_data_.get();
+  }
 
  private:
   std::unique_ptr<MockService> service_;
   std::unique_ptr<JSONTraceExporter> json_trace_exporter_;
   std::unique_ptr<base::MessageLoop> message_loop_;
   std::unique_ptr<trace_analyzer::TraceAnalyzer> trace_analyzer_;
+  std::unique_ptr<base::DictionaryValue> parsed_trace_data_;
 };
 
 TEST_F(JSONTraceExporterTest, EnableTracingWithGivenConfig) {
@@ -230,6 +244,54 @@
   EXPECT_EQ(kDummyTraceConfig, service()->tracing_enabled_with_config());
 }
 
+TEST_F(JSONTraceExporterTest, TestMetadata) {
+  CreateJSONTraceExporter("foo");
+  service()->WaitForTracingEnabled();
+  StopAndFlush();
+
+  perfetto::protos::TracePacket trace_packet_proto;
+
+  {
+    auto* new_metadata =
+        trace_packet_proto.mutable_chrome_events()->add_metadata();
+    new_metadata->set_name("int_metadata");
+    new_metadata->set_int_value(42);
+  }
+
+  {
+    auto* new_metadata =
+        trace_packet_proto.mutable_chrome_events()->add_metadata();
+    new_metadata->set_name("string_metadata");
+    new_metadata->set_string_value("met_val");
+  }
+
+  {
+    auto* new_metadata =
+        trace_packet_proto.mutable_chrome_events()->add_metadata();
+    new_metadata->set_name("bool_metadata");
+    new_metadata->set_bool_value(true);
+  }
+
+  {
+    auto* new_metadata =
+        trace_packet_proto.mutable_chrome_events()->add_metadata();
+    new_metadata->set_name("dict_metadata");
+    new_metadata->set_json_value("{\"child_dict\": \"foo\"}");
+  }
+
+  FinalizePacket(trace_packet_proto);
+
+  service()->WaitForTracingDisabled();
+  auto* metadata = parsed_trace_data()->FindKey("metadata");
+  EXPECT_TRUE(metadata);
+  EXPECT_EQ(metadata->FindKey("int_metadata")->GetInt(), 42);
+  EXPECT_EQ(metadata->FindKey("string_metadata")->GetString(), "met_val");
+  EXPECT_EQ(metadata->FindKey("bool_metadata")->GetBool(), true);
+  EXPECT_EQ(
+      metadata->FindKey("dict_metadata")->FindKey("child_dict")->GetString(),
+      "foo");
+}
+
 TEST_F(JSONTraceExporterTest, TestBasicEvent) {
   CreateJSONTraceExporter("foo");
   service()->WaitForTracingEnabled();
diff --git a/services/tracing/perfetto/perfetto_integration_unittest.cc b/services/tracing/perfetto/perfetto_integration_unittest.cc
index fa6b998..102aa2f2 100644
--- a/services/tracing/perfetto/perfetto_integration_unittest.cc
+++ b/services/tracing/perfetto/perfetto_integration_unittest.cc
@@ -121,12 +121,16 @@
     }
   }
 
-  void TearDownDataSourceInstance(uint64_t id) override {
+  void TearDownDataSourceInstance(
+      uint64_t id,
+      TearDownDataSourceInstanceCallback callback) override {
     enabled_data_source_.reset();
 
     if (client_disabled_callback_) {
       std::move(client_disabled_callback_).Run();
     }
+
+    std::move(callback).Run();
   }
 
   void CommitData(const perfetto::CommitDataRequest& commit,
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index 8a76774..06b301b 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -42,7 +42,9 @@
         &TracingSession::OnJSONTraceEventCallback, base::Unretained(this)));
   }
 
-  void OnJSONTraceEventCallback(const std::string& json, bool has_more) {
+  void OnJSONTraceEventCallback(const std::string& json,
+                                base::DictionaryValue* metadata,
+                                bool has_more) {
     if (stream_.is_valid()) {
       mojo::BlockingCopyFromString(json, stream_);
     }
@@ -50,7 +52,7 @@
     if (!has_more) {
       DCHECK(!stop_and_flush_callback_.is_null());
       base::ResetAndReturn(&stop_and_flush_callback_)
-          .Run(/*metadata=*/base::Value(base::Value::Type::DICTIONARY));
+          .Run(/*metadata=*/std::move(*metadata));
 
       base::SequencedTaskRunnerHandle::Get()->PostTask(
           FROM_HERE, std::move(tracing_over_callback_));
diff --git a/services/tracing/perfetto/producer_host.cc b/services/tracing/perfetto/producer_host.cc
index 2349fe70..2fbe67a 100644
--- a/services/tracing/perfetto/producer_host.cc
+++ b/services/tracing/perfetto/producer_host.cc
@@ -53,11 +53,6 @@
 }
 
 void ProducerHost::OnConnect() {
-  // Register data sources with Perfetto here.
-
-  perfetto::DataSourceDescriptor descriptor;
-  descriptor.set_name(mojom::kTraceEventDataSourceName);
-  producer_endpoint_->RegisterDataSource(descriptor);
 }
 
 void ProducerHost::OnDisconnect() {
@@ -92,7 +87,13 @@
 void ProducerHost::TearDownDataSourceInstance(
     perfetto::DataSourceInstanceID id) {
   if (producer_client_) {
-    producer_client_->TearDownDataSourceInstance(id);
+    producer_client_->TearDownDataSourceInstance(
+        id,
+        base::BindOnce(
+            [](ProducerHost* producer_host, perfetto::DataSourceInstanceID id) {
+              producer_host->producer_endpoint_->NotifyDataSourceStopped(id);
+            },
+            base::Unretained(this), id));
   }
 }
 
@@ -113,7 +114,10 @@
 // inputs.
 void ProducerHost::CommitData(mojom::CommitDataRequestPtr data_request) {
   perfetto::CommitDataRequest native_data_request;
+
   // TODO(oysteine): Set up a TypeTrait for this instead of manual conversion.
+  native_data_request.set_flush_request_id(data_request->flush_request_id);
+
   for (auto& chunk : data_request->chunks_to_move) {
     auto* new_chunk = native_data_request.add_chunks_to_move();
     new_chunk->set_page(chunk->page);
@@ -145,4 +149,16 @@
   producer_endpoint_->CommitData(native_data_request);
 }
 
+void ProducerHost::RegisterDataSource(
+    mojom::DataSourceRegistrationPtr registration_info) {
+  perfetto::DataSourceDescriptor descriptor;
+  descriptor.set_name(registration_info->name);
+  descriptor.set_will_notify_on_stop(registration_info->will_notify_on_stop);
+  producer_endpoint_->RegisterDataSource(descriptor);
+}
+
+void ProducerHost::NotifyFlushComplete(uint64_t flush_request_id) {
+  producer_endpoint_->NotifyFlushComplete(flush_request_id);
+}
+
 }  // namespace tracing
diff --git a/services/tracing/perfetto/producer_host.h b/services/tracing/perfetto/producer_host.h
index ae4228ce..00efaef 100644
--- a/services/tracing/perfetto/producer_host.h
+++ b/services/tracing/perfetto/producer_host.h
@@ -69,12 +69,22 @@
   void Flush(perfetto::FlushRequestID,
              const perfetto::DataSourceInstanceID* raw_data_source_ids,
              size_t num_data_sources) override;
+
   // mojom::ProducerHost implementation.
   // This interface gets called by the per-process ProducerClients
   // to signal that there's changes to be committed to the
   // Shared Memory buffer (like finished chunks).
   void CommitData(mojom::CommitDataRequestPtr data_request) override;
 
+  // Called by the ProducerClient to signal the Host that it can
+  // provide a specific data source.
+  void RegisterDataSource(
+      mojom::DataSourceRegistrationPtr registration_info) override;
+
+  // Called to signal the Host that a specific flush request
+  // is finished.
+  void NotifyFlushComplete(uint64_t flush_request_id) override;
+
  protected:
   void OnConnectionError();
 
diff --git a/services/tracing/public/cpp/perfetto/producer_client.cc b/services/tracing/public/cpp/perfetto/producer_client.cc
index af17180..21bef496 100644
--- a/services/tracing/public/cpp/perfetto/producer_client.cc
+++ b/services/tracing/public/cpp/perfetto/producer_client.cc
@@ -9,7 +9,6 @@
 #include "base/no_destructor.h"
 #include "base/task_scheduler/post_task.h"
 #include "services/tracing/public/cpp/perfetto/shared_memory.h"
-#include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/commit_data_request.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/shared_memory_arbiter.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_writer.h"
@@ -33,7 +32,22 @@
 
 }  // namespace
 
-ProducerClient::ProducerClient() {
+ProducerClient::DataSourceBase::DataSourceBase(const std::string& name)
+    : name_(name) {
+  DCHECK(!name.empty());
+}
+
+ProducerClient::DataSourceBase::~DataSourceBase() = default;
+
+void ProducerClient::DataSourceBase::StartTracingWithID(
+    uint64_t data_source_id,
+    ProducerClient* producer_client,
+    const mojom::DataSourceConfig& data_source_config) {
+  data_source_id_ = data_source_id;
+  StartTracing(producer_client, data_source_config);
+}
+
+ProducerClient::ProducerClient() : weak_ptr_factory_(this) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
@@ -88,6 +102,21 @@
                                 mojo::MakeRequest(&producer_host_)));
 }
 
+void ProducerClient::AddDataSource(DataSourceBase* data_source) {
+  GetTaskRunner()->PostTask(
+      FROM_HERE, base::BindOnce(&ProducerClient::AddDataSourceOnSequence,
+                                base::Unretained(this), data_source));
+}
+
+void ProducerClient::AddDataSourceOnSequence(DataSourceBase* data_source) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  data_sources_.insert(data_source);
+  auto new_registration = mojom::DataSourceRegistration::New();
+  new_registration->name = data_source->name();
+  new_registration->will_notify_on_stop = true;
+  producer_host_->RegisterDataSource(std::move(new_registration));
+}
+
 void ProducerClient::OnTracingStart(
     mojo::ScopedSharedBufferHandle shared_memory) {
   // TODO(oysteine): In next CLs plumb this through the service.
@@ -116,23 +145,47 @@
   DCHECK(data_source_config);
 
   // TODO(oysteine): Support concurrent tracing sessions.
-  TraceEventDataSource::GetInstance()->StartTracing(this, *data_source_config);
+  for (auto* data_source : data_sources_) {
+    if (data_source->name() == data_source_config->name) {
+      data_source->StartTracingWithID(id, this, *data_source_config);
+      return;
+    }
+  }
 }
 
-void ProducerClient::TearDownDataSourceInstance(uint64_t id) {
+void ProducerClient::TearDownDataSourceInstance(
+    uint64_t id,
+    TearDownDataSourceInstanceCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  TraceEventDataSource::GetInstance()->StopTracing();
+  for (auto* data_source : data_sources_) {
+    if (data_source->data_source_id() == id) {
+      data_source->StopTracing(std::move(callback));
+      return;
+    }
+  }
 
-  // TODO(oysteine): Yak shave: Can only destroy these once the TraceWriters
-  // are all cleaned up; have to figure out the TLS bits.
-  // shared_memory_arbiter_ = nullptr;
-  // shared_memory_ = nullptr;
+  LOG(FATAL) << "Invalid data source ID.";
 }
 
 void ProducerClient::Flush(uint64_t flush_request_id,
                            const std::vector<uint64_t>& data_source_ids) {
-  NOTREACHED();
+  pending_replies_for_latest_flush_ = {flush_request_id,
+                                       data_source_ids.size()};
+
+  // N^2, optimize once there's more than a couple of possible data sources.
+  for (auto* data_source : data_sources_) {
+    if (std::find(data_source_ids.begin(), data_source_ids.end(),
+                  data_source->data_source_id()) != data_source_ids.end()) {
+      data_source->Flush(base::BindRepeating(
+          [](base::WeakPtr<ProducerClient> weak_ptr, uint64_t id) {
+            if (weak_ptr) {
+              weak_ptr->NotifyFlushComplete(id);
+            }
+          },
+          weak_ptr_factory_.GetWeakPtr(), flush_request_id));
+    }
+  }
 }
 
 void ProducerClient::RegisterDataSource(const perfetto::DataSourceDescriptor&) {
@@ -143,6 +196,11 @@
   NOTREACHED();
 }
 
+void ProducerClient::NotifyDataSourceStopped(
+    perfetto::DataSourceInstanceID id) {
+  NOTREACHED();
+}
+
 void ProducerClient::CommitData(const perfetto::CommitDataRequest& commit,
                                 CommitDataCallback callback) {
   // The CommitDataRequest which the SharedMemoryArbiter uses to
@@ -152,6 +210,7 @@
   // service-side.
   auto new_data_request = mojom::CommitDataRequest::New();
 
+  new_data_request->flush_request_id = commit.flush_request_id();
   for (auto& chunk : commit.chunks_to_move()) {
     auto new_chunk = mojom::ChunksToMove::New();
     new_chunk->page = chunk.page();
@@ -207,12 +266,17 @@
   return 0;
 }
 
-void ProducerClient::NotifyFlushComplete(perfetto::FlushRequestID) {
-  NOTREACHED();
-}
+void ProducerClient::NotifyFlushComplete(perfetto::FlushRequestID id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (pending_replies_for_latest_flush_.first != id) {
+    // Ignore; completed flush was for an earlier request.
+    return;
+  }
 
-void ProducerClient::NotifyDataSourceStopped(perfetto::DataSourceInstanceID) {
-  NOTREACHED();
+  DCHECK_NE(pending_replies_for_latest_flush_.second, 0u);
+  if (--pending_replies_for_latest_flush_.second == 0) {
+    producer_host_->NotifyFlushComplete(id);
+  }
 }
 
 std::unique_ptr<perfetto::TraceWriter> ProducerClient::CreateTraceWriter(
diff --git a/services/tracing/public/cpp/perfetto/producer_client.h b/services/tracing/public/cpp/perfetto/producer_client.h
index 654a6c1..ca7757c 100644
--- a/services/tracing/public/cpp/perfetto/producer_client.h
+++ b/services/tracing/public/cpp/perfetto/producer_client.h
@@ -6,12 +6,15 @@
 #define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_PRODUCER_CLIENT_H_
 
 #include <memory>
+#include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/atomicops.h"
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/tracing/public/cpp/perfetto/task_runner.h"
@@ -41,6 +44,32 @@
     : public mojom::ProducerClient,
       public perfetto::TracingService::ProducerEndpoint {
  public:
+  class DataSourceBase {
+   public:
+    explicit DataSourceBase(const std::string& name);
+    virtual ~DataSourceBase();
+
+    void StartTracingWithID(uint64_t data_source_id,
+                            ProducerClient* producer_client,
+                            const mojom::DataSourceConfig& data_source_config);
+
+    virtual void StartTracing(
+        ProducerClient* producer_client,
+        const mojom::DataSourceConfig& data_source_config) = 0;
+    virtual void StopTracing(
+        base::OnceClosure stop_complete_callback = base::OnceClosure()) = 0;
+
+    // Flush the data source.
+    virtual void Flush(base::RepeatingClosure flush_complete_callback) = 0;
+
+    const std::string& name() const { return name_; }
+    uint64_t data_source_id() const { return data_source_id_; }
+
+   private:
+    uint64_t data_source_id_ = 0;
+    std::string name_;
+  };
+
   ProducerClient();
   ~ProducerClient() override;
 
@@ -58,6 +87,11 @@
                               mojom::ProducerHostRequest)>;
   void CreateMojoMessagepipes(MessagepipesReadyCallback);
 
+  // Add a new data source to the ProducerClient; the caller
+  // retains ownership and is responsible for making sure
+  // the data source outlives the ProducerClient.
+  void AddDataSource(DataSourceBase*);
+
   // mojom::ProducerClient implementation.
   // Called through Mojo by the ProducerHost on the service-side to control
   // tracing and toggle specific DataSources.
@@ -66,7 +100,9 @@
       uint64_t id,
       mojom::DataSourceConfigPtr data_source_config) override;
 
-  void TearDownDataSourceInstance(uint64_t id) override;
+  void TearDownDataSourceInstance(
+      uint64_t id,
+      TearDownDataSourceInstanceCallback callback) override;
   void Flush(uint64_t flush_request_id,
              const std::vector<uint64_t>& data_source_ids) override;
 
@@ -77,23 +113,25 @@
   void CommitData(const perfetto::CommitDataRequest& commit,
                   CommitDataCallback callback) override;
   // Used by the DataSource implementations to create TraceWriters
-  // for writing their protobufs
+  // for writing their protobufs, and respond to flushes.
   std::unique_ptr<perfetto::TraceWriter> CreateTraceWriter(
       perfetto::BufferID target_buffer) override;
+  void NotifyFlushComplete(perfetto::FlushRequestID) override;
   perfetto::SharedMemory* shared_memory() const override;
 
   // These ProducerEndpoint functions are only used on the service
   // side and should not be called on the clients.
   void RegisterDataSource(const perfetto::DataSourceDescriptor&) override;
   void UnregisterDataSource(const std::string& name) override;
-  size_t shared_buffer_page_size_kb() const override;
-  void NotifyFlushComplete(perfetto::FlushRequestID) override;
   void NotifyDataSourceStopped(perfetto::DataSourceInstanceID) override;
+  size_t shared_buffer_page_size_kb() const override;
 
   static void ResetTaskRunnerForTesting();
 
  private:
   void CommitDataOnSequence(mojom::CommitDataRequestPtr request);
+  void AddDataSourceOnSequence(DataSourceBase*);
+
   // The callback will be run on the |origin_task_runner|, meaning
   // the same sequence as CreateMojoMessagePipes() got called on.
   void CreateMojoMessagepipesOnSequence(
@@ -106,8 +144,15 @@
   std::unique_ptr<perfetto::SharedMemoryArbiter> shared_memory_arbiter_;
   mojom::ProducerHostPtr producer_host_;
   std::unique_ptr<MojoSharedMemory> shared_memory_;
+  std::set<DataSourceBase*> data_sources_;
+  // First value is the flush ID, the second is the number of
+  // replies we're still waiting for.
+  std::pair<uint64_t, size_t> pending_replies_for_latest_flush_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
+  // NOTE: Weak pointers must be invalidated before all other member variables.
+  base::WeakPtrFactory<ProducerClient> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(ProducerClient);
 };
 
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index f32b67e..1a288ce 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "base/json/json_writer.h"
+#include "base/memory/ref_counted_memory.h"
 #include "base/no_destructor.h"
 #include "base/process/process_handle.h"
 #include "base/trace_event/trace_event.h"
@@ -21,6 +23,78 @@
 
 namespace tracing {
 
+TraceEventMetadataSource::TraceEventMetadataSource()
+    : DataSourceBase(mojom::kMetaDataSourceName),
+      origin_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+
+TraceEventMetadataSource::~TraceEventMetadataSource() = default;
+
+void TraceEventMetadataSource::AddGeneratorFunction(
+    MetadataGeneratorFunction generator) {
+  DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+  base::AutoLock lock(lock_);
+  generator_functions_.push_back(generator);
+}
+
+void TraceEventMetadataSource::GenerateMetadata(
+    std::unique_ptr<perfetto::TraceWriter> trace_writer) {
+  DCHECK(origin_task_runner_->RunsTasksInCurrentSequence());
+
+  auto trace_packet = trace_writer->NewTracePacket();
+  protozero::MessageHandle<perfetto::protos::pbzero::ChromeEventBundle>
+      event_bundle(trace_packet->set_chrome_events());
+
+  base::AutoLock lock(lock_);
+  for (auto& generator : generator_functions_) {
+    std::unique_ptr<base::DictionaryValue> metadata_dict = generator.Run();
+    if (!metadata_dict) {
+      continue;
+    }
+
+    for (const auto& it : metadata_dict->DictItems()) {
+      auto* new_metadata = event_bundle->add_metadata();
+      new_metadata->set_name(it.first.c_str());
+
+      if (it.second.is_int()) {
+        new_metadata->set_int_value(it.second.GetInt());
+      } else if (it.second.is_bool()) {
+        new_metadata->set_bool_value(it.second.GetBool());
+      } else if (it.second.is_string()) {
+        new_metadata->set_string_value(it.second.GetString().c_str());
+      } else {
+        std::string json_value;
+        base::JSONWriter::Write(it.second, &json_value);
+        new_metadata->set_json_value(json_value.c_str());
+      }
+    }
+  }
+}
+
+void TraceEventMetadataSource::StartTracing(
+    ProducerClient* producer_client,
+    const mojom::DataSourceConfig& data_source_config) {
+  origin_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&TraceEventMetadataSource::GenerateMetadata,
+                                base::Unretained(this),
+                                producer_client->CreateTraceWriter(
+                                    data_source_config.target_buffer)));
+}
+
+void TraceEventMetadataSource::StopTracing(
+    base::OnceClosure stop_complete_callback) {
+  // We bounce a task off the origin_task_runner_ that the generator
+  // callbacks are run from, to make sure that GenerateMetaData() has finished
+  // running.
+  origin_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+                                        std::move(stop_complete_callback));
+}
+
+void TraceEventMetadataSource::Flush(
+    base::RepeatingClosure flush_complete_callback) {
+  origin_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(),
+                                        std::move(flush_complete_callback));
+}
+
 class TraceEventDataSource::ThreadLocalEventSink {
  public:
   explicit ThreadLocalEventSink(
@@ -169,6 +243,8 @@
     }
   }
 
+  void Flush() { trace_writer_->Flush(); }
+
  private:
   std::unique_ptr<perfetto::TraceWriter> trace_writer_;
 };
@@ -193,49 +269,86 @@
   return instance.get();
 }
 
-TraceEventDataSource::TraceEventDataSource() = default;
+TraceEventDataSource::TraceEventDataSource()
+    : DataSourceBase(mojom::kTraceEventDataSourceName) {}
 
 TraceEventDataSource::~TraceEventDataSource() = default;
 
 void TraceEventDataSource::StartTracing(
     ProducerClient* producer_client,
     const mojom::DataSourceConfig& data_source_config) {
-  base::AutoLock lock(lock_);
+  {
+    base::AutoLock lock(lock_);
 
-  DCHECK(!producer_client_);
-  producer_client_ = producer_client;
-  target_buffer_ = data_source_config.target_buffer;
+    DCHECK(!producer_client_);
+    producer_client_ = producer_client;
+    target_buffer_ = data_source_config.target_buffer;
+  }
 
   TraceLog::GetInstance()->SetAddTraceEventOverride(
-      &TraceEventDataSource::OnAddTraceEvent);
+      &TraceEventDataSource::OnAddTraceEvent,
+      &TraceEventDataSource::FlushCurrentThread);
 
   TraceLog::GetInstance()->SetEnabled(
       TraceConfig(data_source_config.trace_config), TraceLog::RECORDING_MODE);
 }
 
 void TraceEventDataSource::StopTracing(
-    base::RepeatingClosure stop_complete_callback) {
-  DCHECK(producer_client_);
+    base::OnceClosure stop_complete_callback) {
+  stop_complete_callback_ = std::move(stop_complete_callback);
 
-  {
-    base::AutoLock lock(lock_);
+  auto on_tracing_stopped_callback =
+      [](TraceEventDataSource* data_source,
+         const scoped_refptr<base::RefCountedString>&, bool has_more_events) {
+        if (has_more_events) {
+          return;
+        }
 
-    producer_client_ = nullptr;
-    target_buffer_ = 0;
+        TraceLog::GetInstance()->SetAddTraceEventOverride(nullptr, nullptr);
+
+        // TraceLog::CancelTracing will cause metadata events to be written;
+        // make sure we flush the TraceWriter for this thread (TraceLog will
+        // only call TraceEventDataSource::FlushCurrentThread for threads with
+        // a MessageLoop).
+        // TODO(oysteine): The perfetto service itself should be able to recover
+        // unreturned chunks so technically this can go away
+        // at some point, but seems needed for now.
+        FlushCurrentThread();
+
+        if (data_source->stop_complete_callback_) {
+          std::move(data_source->stop_complete_callback_).Run();
+        }
+      };
+
+  if (TraceLog::GetInstance()->IsEnabled()) {
+    // We call CancelTracing because we don't want/need TraceLog to do any of
+    // its own JSON serialization on its own.
+    TraceLog::GetInstance()->CancelTracing(base::BindRepeating(
+        on_tracing_stopped_callback, base::Unretained(this)));
+  } else {
+    on_tracing_stopped_callback(this, scoped_refptr<base::RefCountedString>(),
+                                false);
   }
 
-  TraceLog::GetInstance()->SetAddTraceEventOverride(nullptr);
+  base::AutoLock lock(lock_);
+  DCHECK(producer_client_);
+  producer_client_ = nullptr;
+  target_buffer_ = 0;
+}
 
-  // We call CancelTracing because we don't want/need TraceLog to do any of
-  // its own JSON serialization on its own
-  TraceLog::GetInstance()->CancelTracing(base::BindRepeating(
-      [](base::RepeatingClosure stop_complete_callback,
+void TraceEventDataSource::Flush(
+    base::RepeatingClosure flush_complete_callback) {
+  DCHECK(TraceLog::GetInstance()->IsEnabled());
+  TraceLog::GetInstance()->Flush(base::BindRepeating(
+      [](base::RepeatingClosure flush_complete_callback,
          const scoped_refptr<base::RefCountedString>&, bool has_more_events) {
-        if (!has_more_events && stop_complete_callback) {
-          stop_complete_callback.Run();
+        if (has_more_events) {
+          return;
         }
+
+        flush_complete_callback.Run();
       },
-      std::move(stop_complete_callback)));
+      std::move(flush_complete_callback)));
 }
 
 TraceEventDataSource::ThreadLocalEventSink*
@@ -266,10 +379,11 @@
 }
 
 // static
-void TraceEventDataSource::ResetCurrentThreadForTesting() {
-  ThreadLocalEventSink* thread_local_event_sink =
+void TraceEventDataSource::FlushCurrentThread() {
+  auto* thread_local_event_sink =
       static_cast<ThreadLocalEventSink*>(ThreadLocalEventSinkSlot()->Get());
   if (thread_local_event_sink) {
+    thread_local_event_sink->Flush();
     delete thread_local_event_sink;
     ThreadLocalEventSinkSlot()->Set(nullptr);
   }
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.h b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
index 4e0741a..efefbd8 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.h
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
@@ -7,55 +7,89 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/threading/thread_local.h"
 #include "services/tracing/public/cpp/perfetto/producer_client.h"
 
+namespace perfetto {
+class TraceWriter;
+}
+
 namespace tracing {
 
-class ProducerClient;
+// This class is a data source that clients can use to provide
+// global metadata in dictionary form, by registering callbacks.
+class COMPONENT_EXPORT(TRACING_CPP) TraceEventMetadataSource
+    : public ProducerClient::DataSourceBase {
+ public:
+  TraceEventMetadataSource();
+  ~TraceEventMetadataSource() override;
+
+  using MetadataGeneratorFunction =
+      base::RepeatingCallback<std::unique_ptr<base::DictionaryValue>()>;
+  // Any callbacks passed here will be called when tracing starts.
+  void AddGeneratorFunction(MetadataGeneratorFunction generator);
+
+  // ProducerClient::DataSourceBase implementation, called by
+  // ProducerClent.
+  void StartTracing(ProducerClient* producer_client,
+                    const mojom::DataSourceConfig& data_source_config) override;
+  void StopTracing(base::OnceClosure stop_complete_callback) override;
+  void Flush(base::RepeatingClosure flush_complete_callback) override;
+
+ private:
+  void GenerateMetadata(std::unique_ptr<perfetto::TraceWriter> trace_writer);
+
+  std::vector<MetadataGeneratorFunction> generator_functions_;
+  scoped_refptr<base::SequencedTaskRunner> origin_task_runner_;
+  base::Lock lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(TraceEventMetadataSource);
+};
 
 // This class acts as a bridge between the TraceLog and
 // the Perfetto ProducerClient. It converts incoming
 // trace events to ChromeTraceEvent protos and writes
 // them into the Perfetto shared memory.
-class COMPONENT_EXPORT(TRACING_CPP) TraceEventDataSource {
+class COMPONENT_EXPORT(TRACING_CPP) TraceEventDataSource
+    : public ProducerClient::DataSourceBase {
  public:
   class ThreadLocalEventSink;
 
   static TraceEventDataSource* GetInstance();
 
-  // Deletes the TraceWriter for the current thread, if any.
-  static void ResetCurrentThreadForTesting();
+  // Flushes and deletes the TraceWriter for the current thread, if any.
+  static void FlushCurrentThread();
 
-  // The ProducerClient is responsible for calling RequestStop
+  // The ProducerClient is responsible for calling StopTracing
   // which will clear the stored pointer to it, before it
   // gets destroyed. ProducerClient::CreateTraceWriter can be
   // called by the TraceEventDataSource on any thread.
   void StartTracing(ProducerClient* producer_client,
-                    const mojom::DataSourceConfig& data_source_config);
+                    const mojom::DataSourceConfig& data_source_config) override;
 
   // Called from the ProducerClient.
-  void StopTracing(
-      base::RepeatingClosure stop_complete_callback = base::RepeatingClosure());
+  void StopTracing(base::OnceClosure stop_complete_callback) override;
+  void Flush(base::RepeatingClosure flush_complete_callback) override;
 
  private:
   friend class base::NoDestructor<TraceEventDataSource>;
 
   TraceEventDataSource();
-  ~TraceEventDataSource();
+  ~TraceEventDataSource() override;
 
   ThreadLocalEventSink* CreateThreadLocalEventSink();
 
-  // Callback from TraceLog on any added trace events, can be called from
-  // any thread.
+  // Callback from TraceLog, can be called from any thread.
   static void OnAddTraceEvent(const base::trace_event::TraceEvent& trace_event);
 
   base::Lock lock_;
   uint32_t target_buffer_ = 0;
   ProducerClient* producer_client_ = nullptr;
+  base::OnceClosure stop_complete_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(TraceEventDataSource);
 };
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
index 5ceb0b5..a43ea2b 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/debug/leak_annotations.h"
+#include "base/json/json_reader.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
@@ -30,10 +31,12 @@
 class MockProducerClient : public ProducerClient {
  public:
   explicit MockProducerClient(
-      scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner)
+      scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner,
+      const char* wanted_event_category)
       : delegate_(perfetto::base::kPageSize),
         stream_(&delegate_),
-        main_thread_task_runner_(std::move(main_thread_task_runner)) {
+        main_thread_task_runner_(std::move(main_thread_task_runner)),
+        wanted_event_category_(wanted_event_category) {
     trace_packet_.Reset(&stream_);
   }
 
@@ -55,8 +58,11 @@
       if (proto->has_chrome_events() &&
           proto->chrome_events().trace_events().size() > 0 &&
           proto->chrome_events().trace_events()[0].category_group_name() ==
-              kCategoryGroup) {
+              wanted_event_category_) {
         finalized_packets_.push_back(std::move(proto));
+      } else if (proto->has_chrome_events() &&
+                 proto->chrome_events().metadata().size() > 0) {
+        metadata_packets_.push_back(std::move(proto));
       }
     }
 
@@ -84,13 +90,24 @@
     return event_bundle.trace_events();
   }
 
+  const google::protobuf::RepeatedPtrField<perfetto::protos::ChromeMetadata>
+  GetChromeMetadata(size_t packet_index = 0) {
+    FlushPacketIfPossible();
+    EXPECT_GT(metadata_packets_.size(), packet_index);
+
+    auto event_bundle = metadata_packets_[packet_index]->chrome_events();
+    return event_bundle.metadata();
+  }
+
  private:
   std::vector<std::unique_ptr<perfetto::protos::TracePacket>>
       finalized_packets_;
+  std::vector<std::unique_ptr<perfetto::protos::TracePacket>> metadata_packets_;
   perfetto::protos::pbzero::TracePacket trace_packet_;
   protozero::ScatteredStreamWriterNullDelegate delegate_;
   protozero::ScatteredStreamWriter stream_;
   scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
+  const char* wanted_event_category_;
 };
 
 // For sequences/threads other than our own, we just want to ignore
@@ -157,27 +174,34 @@
  public:
   void SetUp() override {
     ProducerClient::ResetTaskRunnerForTesting();
-    producer_client_ = std::make_unique<MockProducerClient>(
-        scoped_task_environment_.GetMainThreadTaskRunner());
   }
 
   void TearDown() override {
-    base::RunLoop wait_for_tracelog_flush;
+    if (base::trace_event::TraceLog::GetInstance()->IsEnabled()) {
+      base::RunLoop wait_for_tracelog_flush;
 
-    TraceEventDataSource::GetInstance()->StopTracing(base::BindRepeating(
-        [](const base::RepeatingClosure& quit_closure) { quit_closure.Run(); },
-        wait_for_tracelog_flush.QuitClosure()));
+      TraceEventDataSource::GetInstance()->StopTracing(base::BindRepeating(
+          [](const base::RepeatingClosure& quit_closure) {
+            quit_closure.Run();
+          },
+          wait_for_tracelog_flush.QuitClosure()));
 
-    wait_for_tracelog_flush.Run();
+      wait_for_tracelog_flush.Run();
+    }
 
     // As MockTraceWriter keeps a pointer to our MockProducerClient,
     // we need to make sure to clean it up from TLS. The other sequences
     // get DummyTraceWriters that we don't care about.
-    TraceEventDataSource::GetInstance()->ResetCurrentThreadForTesting();
+    TraceEventDataSource::GetInstance()->FlushCurrentThread();
     producer_client_.reset();
   }
 
-  void CreateTraceEventDataSource() {
+  void CreateTraceEventDataSource(
+      const char* wanted_event_category = kCategoryGroup) {
+    producer_client_ = std::make_unique<MockProducerClient>(
+        scoped_task_environment_.GetMainThreadTaskRunner(),
+        wanted_event_category);
+
     auto data_source_config = mojom::DataSourceConfig::New();
     TraceEventDataSource::GetInstance()->StartTracing(producer_client(),
                                                       *data_source_config);
@@ -190,6 +214,105 @@
   base::test::ScopedTaskEnvironment scoped_task_environment_;
 };
 
+void HasMetadataValue(const perfetto::protos::ChromeMetadata& entry,
+                      const char* value) {
+  EXPECT_TRUE(entry.has_string_value());
+  EXPECT_EQ(entry.string_value(), value);
+}
+
+void HasMetadataValue(const perfetto::protos::ChromeMetadata& entry,
+                      int value) {
+  EXPECT_TRUE(entry.has_int_value());
+  EXPECT_EQ(entry.int_value(), value);
+}
+
+void HasMetadataValue(const perfetto::protos::ChromeMetadata& entry,
+                      bool value) {
+  EXPECT_TRUE(entry.has_bool_value());
+  EXPECT_EQ(entry.bool_value(), value);
+}
+
+void HasMetadataValue(const perfetto::protos::ChromeMetadata& entry,
+                      const base::DictionaryValue& value) {
+  EXPECT_TRUE(entry.has_json_value());
+
+  std::unique_ptr<base::Value> child_dict =
+      base::JSONReader::Read(entry.json_value());
+  EXPECT_EQ(*child_dict, value);
+}
+
+template <typename T>
+void MetadataHasNamedValue(const google::protobuf::RepeatedPtrField<
+                               perfetto::protos::ChromeMetadata>& metadata,
+                           const char* name,
+                           const T& value) {
+  for (int i = 0; i < metadata.size(); i++) {
+    auto& entry = metadata[i];
+    if (entry.name() == name) {
+      HasMetadataValue(entry, value);
+      return;
+    }
+  }
+
+  NOTREACHED();
+}
+
+TEST_F(TraceEventDataSourceTest, MetadataSourceBasicTypes) {
+  auto metadata_source = std::make_unique<TraceEventMetadataSource>();
+  metadata_source->AddGeneratorFunction(base::BindRepeating([]() {
+    auto metadata = std::make_unique<base::DictionaryValue>();
+    metadata->SetInteger("foo_int", 42);
+    metadata->SetString("foo_str", "bar");
+    metadata->SetBoolean("foo_bool", true);
+
+    auto child_dict = std::make_unique<base::DictionaryValue>();
+    child_dict->SetString("child_str", "child_val");
+    metadata->Set("child_dict", std::move(child_dict));
+    return metadata;
+  }));
+
+  CreateTraceEventDataSource();
+
+  auto data_source_config = mojom::DataSourceConfig::New();
+  metadata_source->StartTracing(producer_client(), *data_source_config);
+
+  base::RunLoop wait_for_flush;
+  metadata_source->Flush(wait_for_flush.QuitClosure());
+  wait_for_flush.Run();
+
+  auto metadata = producer_client()->GetChromeMetadata();
+  EXPECT_EQ(4, metadata.size());
+  MetadataHasNamedValue(metadata, "foo_int", 42);
+  MetadataHasNamedValue(metadata, "foo_str", "bar");
+  MetadataHasNamedValue(metadata, "foo_bool", true);
+
+  auto child_dict = std::make_unique<base::DictionaryValue>();
+  child_dict->SetString("child_str", "child_val");
+  MetadataHasNamedValue(metadata, "child_dict", *child_dict);
+}
+
+TEST_F(TraceEventDataSourceTest, TraceLogMetadataEvents) {
+  CreateTraceEventDataSource("__metadata");
+
+  base::RunLoop wait_for_flush;
+  TraceEventDataSource::GetInstance()->StopTracing(
+      wait_for_flush.QuitClosure());
+  wait_for_flush.Run();
+
+  bool has_process_uptime_event = false;
+  for (size_t i = 0; i < producer_client()->GetFinalizedPacketCount(); ++i) {
+    auto trace_events = producer_client()->GetChromeTraceEvents(i);
+    for (auto& event : trace_events) {
+      if (event.name() == "process_uptime_seconds") {
+        has_process_uptime_event = true;
+        break;
+      }
+    }
+  }
+
+  EXPECT_TRUE(has_process_uptime_event);
+}
+
 TEST_F(TraceEventDataSourceTest, BasicTraceEvent) {
   CreateTraceEventDataSource();
 
diff --git a/services/tracing/public/cpp/trace_event_agent.cc b/services/tracing/public/cpp/trace_event_agent.cc
index 6750fe4..2623a650 100644
--- a/services/tracing/public/cpp/trace_event_agent.cc
+++ b/services/tracing/public/cpp/trace_event_agent.cc
@@ -26,6 +26,10 @@
     defined(OS_WIN)
 #define PERFETTO_AVAILABLE
 #include "services/tracing/public/cpp/perfetto/producer_client.h"
+#include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
+#include "third_party/perfetto/include/perfetto/tracing/core/trace_writer.h"
+#include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
+#include "third_party/perfetto/protos/perfetto/trace/trace_packet.pbzero.h"
 #endif
 
 namespace {
@@ -63,6 +67,21 @@
               std::move(producer_client_pipe), std::move(producer_host_pipe));
         },
         std::move(perfetto_service)));
+
+    GetProducerClient()->AddDataSource(TraceEventDataSource::GetInstance());
+  }
+
+  void AddMetadataGeneratorFunction(
+      MetadataGeneratorFunction generator) override {
+    // Instantiate and register the metadata data source on the first
+    // call.
+    static TraceEventMetadataSource* metadata_source = []() {
+      static base::NoDestructor<TraceEventMetadataSource> instance;
+      GetProducerClient()->AddDataSource(instance.get());
+      return instance.get();
+    }();
+
+    metadata_source->AddGeneratorFunction(generator);
   }
 };
 #endif
diff --git a/services/tracing/public/cpp/trace_event_agent.h b/services/tracing/public/cpp/trace_event_agent.h
index 610fbf07..0e011d3 100644
--- a/services/tracing/public/cpp/trace_event_agent.h
+++ b/services/tracing/public/cpp/trace_event_agent.h
@@ -48,7 +48,7 @@
   void GetCategories(GetCategoriesCallback callback) override;
 
   virtual void AddMetadataGeneratorFunction(
-      MetadataGeneratorFunction generator) {}
+      MetadataGeneratorFunction generator) = 0;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TraceEventAgent);
diff --git a/services/tracing/public/mojom/perfetto_service.mojom b/services/tracing/public/mojom/perfetto_service.mojom
index da6fb0a..056a94e3 100644
--- a/services/tracing/public/mojom/perfetto_service.mojom
+++ b/services/tracing/public/mojom/perfetto_service.mojom
@@ -5,6 +5,7 @@
 module tracing.mojom;
 
 const string kTraceEventDataSourceName = "org.chromium.trace_event";
+const string kMetaDataSourceName = "org.chromium.trace_metadata";
 
 // Brief description of the flow: There's a per-process ProducerClient
 // which connects to the central PerfettoService and establishes a two-way
@@ -57,6 +58,7 @@
 struct CommitDataRequest {
   array<ChunksToMove> chunks_to_move;
   array<ChunksToPatch> chunks_to_patch;
+  uint64 flush_request_id;
 };
 
 struct DataSourceConfig {
@@ -65,25 +67,39 @@
   uint32 target_buffer;
 };
 
+struct DataSourceRegistration {
+  string name;
+  bool will_notify_on_stop;
+};
+
 interface ProducerHost {
   // Called by a ProducerClient to asks the service to:
-  // 1) Move data from the shared memory buffer into the final tracing buffer owned by the service
-  //    (through the |chunks_to_move|).
-  // 2) Patch data (i.e. apply diff) that has been previously copied into the tracing buffer
-  //    (if it's not been overwritten).
-  // The service is robust in terms of tolerating malformed or malicious requests.
+  // 1) Move data from the shared memory buffer into the final tracing buffer
+  //    owned by the service (through the |chunks_to_move|).
+  // 2) Patch data (i.e. apply diff) that has been previously copied into the
+  //    tracing buffer (if it's not been overwritten).
+  // The service is robust in terms of tolerating malformed or malicious
+  // requests.
   CommitData(CommitDataRequest data_request);
+
+  // Called by a ProducerClient to let the Host know it can provide a
+  // specific datasource.
+  RegisterDataSource(DataSourceRegistration registration_info);
+
+  // Called to let the Service know that a flush is complete.
+  NotifyFlushComplete(uint64 flush_request_id);
 };
 
 interface ProducerClient {
     OnTracingStart(handle<shared_buffer> shared_memory);
 
     // TODO(oysteine): Make a TypeTrait for sending the full DataSourceConfig.
-    // Called by Perfetto (via ProducerHost) to request a data source to start logging.
+    // Called by Perfetto (via ProducerHost) to request a data source to start
+    // logging.
     CreateDataSourceInstance(uint64 id, DataSourceConfig data_source_config);
-    // Requesting a data source to stop logging again, with the id previously sent
-    // in the CreateDataSourceInstance call.
-    TearDownDataSourceInstance(uint64 id);
+    // Requesting a data source to stop logging again, with the id previously
+    // sent in the CreateDataSourceInstance call.
+    TearDownDataSourceInstance(uint64 id) => ();
     Flush(uint64 flush_request_id, array<uint64> data_source_ids);
 };
 
diff --git a/services/ui/ws2/server_window.cc b/services/ui/ws2/server_window.cc
index f1a4e1c..00ad453b 100644
--- a/services/ui/ws2/server_window.cc
+++ b/services/ui/ws2/server_window.cc
@@ -182,6 +182,11 @@
  protected:
   // Returns true if the event should be ignored (not forwarded to the client).
   bool ShouldIgnoreEvent(const ui::Event& event) {
+    // It's assumed clients do their own gesture recognizition, which means
+    // GestureEvents should not be forwarded to clients.
+    if (event.IsGestureEvent())
+      return true;
+
     if (static_cast<aura::Window*>(event.target()) != window()) {
       // As ServerWindow is a EP_PRETARGET EventHandler it gets events *before*
       // descendants. Ignore all such events, and only process when
@@ -323,8 +328,7 @@
       return;
     }
 
-    // Gestures are always handled locally.
-    if (ShouldIgnoreEvent(*event) || event->IsGestureEvent())
+    if (ShouldIgnoreEvent(*event))
       return;
 
     // If there is capture, send the event to the client that owns it. A null
@@ -339,8 +343,8 @@
       return;
     }
 
-    // This code does has two specific behaviors. It's used to ensure events
-    // go to the right target (either local, or the remote client).
+    // This code has two specific behaviors. It's used to ensure events go to
+    // the right target (either local, or the remote client).
     // . a press-release sequence targets only one. If in non-client area then
     //   local, otherwise remote client.
     // . mouse-moves (not drags) go to both targets.
diff --git a/services/ui/ws2/window_tree.cc b/services/ui/ws2/window_tree.cc
index a3e5c23..e94f371 100644
--- a/services/ui/ws2/window_tree.cc
+++ b/services/ui/ws2/window_tree.cc
@@ -144,6 +144,11 @@
 }
 
 void WindowTree::SendEventToClient(aura::Window* window, const Event& event) {
+  // As gesture recognition runs in the client, GestureEvents should not be
+  // forwarded. ServerWindow's event processing should ensure no GestureEvents
+  // are sent.
+  DCHECK(!event.IsGestureEvent());
+
   const uint32_t event_id = GenerateEventAckId();
   std::unique_ptr<InFlightEvent> in_flight_event =
       std::make_unique<InFlightEvent>();
diff --git a/services/ui/ws2/window_tree_unittest.cc b/services/ui/ws2/window_tree_unittest.cc
index 11fef922..13b8454 100644
--- a/services/ui/ws2/window_tree_unittest.cc
+++ b/services/ui/ws2/window_tree_unittest.cc
@@ -1698,6 +1698,36 @@
   EXPECT_NE(*top_level_server_window->local_surface_id(), *initial_surface_id);
 }
 
+TEST(WindowTreeTest, DontSendGestures) {
+  // Create a top-level and a child window.
+  WindowServiceTestSetup setup;
+  aura::Window* top_level =
+      setup.window_tree_test_helper()->NewTopLevelWindow();
+  ASSERT_TRUE(top_level);
+  top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
+  top_level->Show();
+  aura::Window* child_window = setup.window_tree_test_helper()->NewWindow();
+  ASSERT_TRUE(child_window);
+  top_level->AddChild(child_window);
+  child_window->SetBounds(gfx::Rect(0, 0, 100, 100));
+  child_window->Show();
+
+  test::EventGenerator event_generator(setup.root());
+  // GestureTapAt() generates a touch down/up, and should not generate a gesture
+  // because the Window Service consumes touch events (consuming touch events
+  // results in no GestureEvents being generated). Additionally, gestures should
+  // never be forwared to the client, as it's assumed the client runs its own
+  // gesture recognizer.
+  event_generator.GestureTapAt(gfx::Point(10, 10));
+  EXPECT_EQ("POINTER_DOWN",
+            EventToEventType(
+                setup.window_tree_client()->PopInputEvent().event.get()));
+  EXPECT_EQ("POINTER_UP",
+            EventToEventType(
+                setup.window_tree_client()->PopInputEvent().event.get()));
+  EXPECT_TRUE(setup.window_tree_client()->input_events().empty());
+}
+
 }  // namespace
 }  // namespace ws2
 }  // namespace ui
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 453133c2..674a7d0 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -20,6 +20,7 @@
 
 skia_support_gpu = !is_ios
 skia_support_pdf = !is_ios && enable_basic_printing
+skia_support_skottie = true
 
 declare_args() {
   skia_whitelist_serialized_typefaces = false
@@ -112,6 +113,10 @@
   if (skia_whitelist_serialized_typefaces) {
     defines += [ "SK_WHITELIST_SERIALIZED_TYPEFACES" ]
   }
+
+  if (skia_support_skottie) {
+    include_dirs += [ "//third_party/skia/modules/skottie/include" ]
+  }
 }
 
 # Internal-facing config for Skia library code.
@@ -226,6 +231,10 @@
       "/wd4800",  # forcing value to bool 'true/false'(assigning int to bool).
     ]
   }
+
+  if (skia_support_skottie) {
+    include_dirs += [ "//third_party/skia/modules/sksg/include" ]
+  }
 }
 
 import("//third_party/skia/third_party/skcms/skcms.gni")
@@ -393,7 +402,6 @@
     "//third_party/skia/src/utils/SkCamera.cpp",
     "//third_party/skia/src/utils/SkFrontBufferedStream.cpp",
     "//third_party/skia/src/utils/SkInterpolator.cpp",
-    "//third_party/skia/src/utils/SkOSPath.cpp",
     "//third_party/skia/src/utils/SkParsePath.cpp",
   ]
 
@@ -564,6 +572,12 @@
     sources += [ "//third_party/skia/src/pdf/SkDocument_PDF_None.cpp" ]
   }
 
+  if (skia_support_skottie) {
+    import("//third_party/skia/modules/skottie/skottie.gni")
+    import("//third_party/skia/modules/sksg/sksg.gni")
+    sources += skia_skottie_sources + skia_skottie_public + skia_sksg_sources
+  }
+
   if (!is_debug) {
     configs -= [ "//build/config/compiler:default_optimization" ]
     configs += [ "//build/config/compiler:optimize_max" ]
@@ -819,6 +833,10 @@
       "//third_party/test_fonts",
     ]
   }
+
+  if (skia_support_skottie) {
+    sources += [ "ext/skottie_unittest.cc" ]
+  }
 }
 
 if (!is_ios) {
diff --git a/skia/ext/skottie_unittest.cc b/skia/ext/skottie_unittest.cc
new file mode 100644
index 0000000..1ee873f
--- /dev/null
+++ b/skia/ext/skottie_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 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 "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+#include "third_party/skia/include/core/SkStream.h"
+#include "third_party/skia/include/core/SkSurface.h"
+#include "third_party/skia/modules/skottie/include/Skottie.h"
+
+TEST(Skottie, Basic) {
+  // Just a solid green layer.
+  static constexpr char anim_data[] =
+      "{"
+      "  \"v\" : \"4.12.0\","
+      "  \"fr\": 30,"
+      "  \"w\" : 400,"
+      "  \"h\" : 200,"
+      "  \"ip\": 0,"
+      "  \"op\": 150,"
+      "  \"assets\": [],"
+
+      "  \"layers\": ["
+      "    {"
+      "      \"ty\": 1,"
+      "      \"sw\": 400,"
+      "      \"sh\": 200,"
+      "      \"sc\": \"#00ff00\","
+      "      \"ip\": 0,"
+      "      \"op\": 150"
+      "    }"
+      "  ]"
+      "}";
+
+  SkMemoryStream stream(anim_data, strlen(anim_data));
+  auto anim = skottie::Animation::Make(&stream);
+
+  ASSERT_TRUE(anim);
+  EXPECT_EQ(strcmp(anim->version().c_str(), "4.12.0"), 0);
+  EXPECT_EQ(anim->size().width(), 400.0f);
+  EXPECT_EQ(anim->size().height(), 200.0f);
+  EXPECT_EQ(anim->duration(), 5.0f);
+
+  auto surface = SkSurface::MakeRasterN32Premul(400, 200);
+  anim->seek(0);
+  anim->render(surface->getCanvas());
+
+  SkPixmap pixmap;
+  ASSERT_TRUE(surface->peekPixels(&pixmap));
+
+  for (int i = 0; i < pixmap.width(); ++i) {
+    for (int j = 0; j < pixmap.height(); ++j) {
+      EXPECT_EQ(pixmap.getColor(i, j), 0xff00ff00);
+    }
+  }
+}
diff --git a/storage/common/BUILD.gn b/storage/common/BUILD.gn
index 56863a2..a7f41a35 100644
--- a/storage/common/BUILD.gn
+++ b/storage/common/BUILD.gn
@@ -18,6 +18,7 @@
     "fileapi/file_system_info.cc",
     "fileapi/file_system_info.h",
     "fileapi/file_system_mount_option.h",
+    "fileapi/file_system_type_converters.h",
     "fileapi/file_system_types.h",
     "fileapi/file_system_util.cc",
     "fileapi/file_system_util.h",
diff --git a/storage/common/fileapi/OWNERS b/storage/common/fileapi/OWNERS
index 3cf9e69..aaa27af5 100644
--- a/storage/common/fileapi/OWNERS
+++ b/storage/common/fileapi/OWNERS
@@ -1,5 +1,8 @@
 tzik@chromium.org
 nhiroki@chromium.org
 
+per-file *_type_converter*.*=set noparent
+per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
+
 # TEAM: storage-dev@chromium.org
 # COMPONENT: Blink>Storage>FileSystem
diff --git a/storage/common/fileapi/file_system_type_converters.h b/storage/common/fileapi/file_system_type_converters.h
new file mode 100644
index 0000000..b0adc21
--- /dev/null
+++ b/storage/common/fileapi/file_system_type_converters.h
@@ -0,0 +1,96 @@
+// Copyright 2018 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 STORAGE_COMMON_FILEAPI_FILE_SYSTEM_TYPE_CONVERTERS_H_
+#define STORAGE_COMMON_FILEAPI_FILE_SYSTEM_TYPE_CONVERTERS_H_
+
+#include "storage/common/fileapi/file_system_types.h"
+#include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
+
+namespace mojo {
+
+template <>
+struct TypeConverter<blink::mojom::FileSystemType, storage::FileSystemType> {
+  static blink::mojom::FileSystemType Convert(
+      const storage::FileSystemType& type) {
+    switch (type) {
+      case storage::FileSystemType::kFileSystemTypeTemporary:
+        return blink::mojom::FileSystemType::kTemporary;
+      case storage::FileSystemType::kFileSystemTypePersistent:
+        return blink::mojom::FileSystemType::kPersistent;
+      case storage::FileSystemType::kFileSystemTypeIsolated:
+        return blink::mojom::FileSystemType::kIsolated;
+      case storage::FileSystemType::kFileSystemTypeExternal:
+        return blink::mojom::FileSystemType::kExternal;
+      // Internal enum types
+      case storage::FileSystemType::kFileSystemTypeUnknown:
+      case storage::FileSystemType::kFileSystemInternalTypeEnumStart:
+      case storage::FileSystemType::kFileSystemTypeTest:
+      case storage::FileSystemType::kFileSystemTypeNativeLocal:
+      case storage::FileSystemType::kFileSystemTypeRestrictedNativeLocal:
+      case storage::FileSystemType::kFileSystemTypeDragged:
+      case storage::FileSystemType::kFileSystemTypeNativeMedia:
+      case storage::FileSystemType::kFileSystemTypeDeviceMedia:
+      case storage::FileSystemType::kFileSystemTypeDrive:
+      case storage::FileSystemType::kFileSystemTypeSyncable:
+      case storage::FileSystemType::kFileSystemTypeSyncableForInternalSync:
+      case storage::FileSystemType::kFileSystemTypeNativeForPlatformApp:
+      case storage::FileSystemType::kFileSystemTypeForTransientFile:
+      case storage::FileSystemType::kFileSystemTypePluginPrivate:
+      case storage::FileSystemType::kFileSystemTypeCloudDevice:
+      case storage::FileSystemType::kFileSystemTypeProvided:
+      case storage::FileSystemType::kFileSystemTypeDeviceMediaAsFileStorage:
+      case storage::FileSystemType::kFileSystemTypeArcContent:
+      case storage::FileSystemType::kFileSystemTypeArcDocumentsProvider:
+      case storage::FileSystemType::kFileSystemInternalTypeEnumEnd:
+        NOTREACHED();
+        return blink::mojom::FileSystemType::kTemporary;
+    }
+    NOTREACHED();
+    return blink::mojom::FileSystemType::kTemporary;
+  }
+};
+
+template <>
+struct TypeConverter<storage::FileSystemType, blink::mojom::FileSystemType> {
+  static storage::FileSystemType Convert(
+      const blink::mojom::FileSystemType& type) {
+    switch (type) {
+      case blink::mojom::FileSystemType::kTemporary:
+        return storage::FileSystemType::kFileSystemTypeTemporary;
+      case blink::mojom::FileSystemType::kPersistent:
+        return storage::FileSystemType::kFileSystemTypePersistent;
+      case blink::mojom::FileSystemType::kIsolated:
+        return storage::FileSystemType::kFileSystemTypeIsolated;
+      case blink::mojom::FileSystemType::kExternal:
+        return storage::FileSystemType::kFileSystemTypeExternal;
+    }
+    NOTREACHED();
+    return storage::FileSystemType::kFileSystemTypeTemporary;
+  }
+};
+
+template <>
+struct TypeConverter<blink::mojom::FileSystemInfoPtr, storage::FileSystemInfo> {
+  static blink::mojom::FileSystemInfoPtr Convert(
+      const storage::FileSystemInfo& info) {
+    return blink::mojom::FileSystemInfo::New(
+        info.name, info.root_url,
+        mojo::ConvertTo<blink::mojom::FileSystemType>(info.mount_type));
+  }
+};
+
+template <>
+struct TypeConverter<storage::FileSystemInfo, blink::mojom::FileSystemInfoPtr> {
+  static storage::FileSystemInfo Convert(
+      const blink::mojom::FileSystemInfoPtr& info) {
+    return storage::FileSystemInfo(
+        info->name, info->root_url,
+        mojo::ConvertTo<storage::FileSystemType>(info->mount_type));
+  }
+};
+
+}  // namespace mojo
+
+#endif  // STORAGE_COMMON_FILEAPI_FILE_SYSTEM_TYPE_CONVERTERS_H_
diff --git a/testing/buildbot/filters/mash.browser_tests.filter b/testing/buildbot/filters/mash.browser_tests.filter
index 41199cf..a8b2d01a 100644
--- a/testing/buildbot/filters/mash.browser_tests.filter
+++ b/testing/buildbot/filters/mash.browser_tests.filter
@@ -36,12 +36,9 @@
 
 # The browser frame is a work in progress.
 -BrowserNonClientFrameViewAshBackButtonTest.*
--BrowserNonClientFrameViewAshTest.ActiveStateOfButtonMatchesWidget/*
 -BrowserNonClientFrameViewAshTest.AvatarDisplayOnTeleportedWindow/*
 -BrowserNonClientFrameViewAshTest.HeaderHeightForSnappedBrowserInSplitView/*
 -BrowserNonClientFrameViewAshTest.ImmersiveModeTopViewInset/*
--BrowserNonClientFrameViewAshTest.IncognitoAvatar/*
--BrowserNonClientFrameViewAshTest.IncognitoMarkedAsAssistantBlocked/*
 -BrowserNonClientFrameViewAshTest.RestoreMinimizedBrowserUpdatesCaption/*
 -BrowserNonClientFrameViewAshTest.ToggleTabletModeOnMinimizedWindow/*
 -BrowserNonClientFrameViewAshTest.ToggleTabletModeRelayout/*
@@ -292,6 +289,7 @@
 # https://crbug.com/827327
 -PictureInPictureWindowControllerBrowserTest.*
 -ControlPictureInPictureWindowControllerBrowserTest.*
+-PlatformAppBrowserTest.PictureInPicture
 
 # These started failing with the switch to ws2.
 # https:://crbug.com/855767
diff --git a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
index a6c50c43..962cd9b 100644
--- a/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
+++ b/testing/buildbot/filters/mojo.fyi.network_browser_tests.filter
@@ -18,21 +18,9 @@
 -SubresourceFilterBrowserTest.FailedProvisionalLoadInMainframe
 -WebViewTest.WebViewInBackgroundPage
 
-# A large number of Cloud Print files use URLFetchers.
-# A few of the bugs:
-# https://crbug.com/844950
-# https://crbug.com/844951
-# https://crbug.com/844952
--ComponentCloudPolicyTest.FetchExtensionPolicy
--ComponentCloudPolicyTest.InstallNewExtension
--ComponentCloudPolicyTest.SignOutAndBackIn
--ComponentCloudPolicyTest.UpdateExtensionPolicy
--KeyRotationComponentCloudPolicyTest.Basic
-
 # These tests uses net::URLRequestFilter (through TestRequestInterceptor) to
 # mock out network responses. Unclear if the code being tested uses
 # SimpleURLLoader or URLFetcher.
--ComponentUpdaterPolicyTest.EnabledComponentUpdates
 -PolicyTest.ExtensionInstallBlacklistSharedModules
 -PolicyTest.ExtensionInstallForcelist
 -PolicyTest.ExtensionInstallForcelist_DefaultedUpdateUrl
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 428563c..e04172f 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2246,6 +2246,19 @@
             ],
             "experiments": [
                 {
+                    "//0": "This experiment is used for M69 release rollouts.",
+                    "//1": "The name change is required to update Canary, Dev",
+                    "//2": "and Beta Finch studies that previously used ",
+                    "//3": "NewTabPageAllBirthday_Enabled.",
+                    "name": "NewTabPageM69AllBirthday_Enabled",
+                    "enable_features": [
+                        "NewTabPageBackgrounds",
+                        "NewTabPageCustomLinks",
+                        "NewTabPageIcons",
+                        "NewTabPageUIMd"
+                    ]
+                },
+                {
                     "name": "NewTabPageM68AllBirthday_Enabled",
                     "enable_features": [
                         "NewTabPageBackgrounds",
@@ -2253,6 +2266,7 @@
                     ]
                 },
                 {
+                    "//0": "Deprecated.",
                     "name": "NewTabPageAllBirthday_Enabled",
                     "enable_features": [
                         "NewTabPageBackgrounds",
@@ -4052,25 +4066,6 @@
             ]
         }
     ],
-    "SupervisedUserCommittedInterstitials": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "SupervisedUserCommittedInterstitialsEnabled",
-                    "enable_features": [
-                        "SupervisedUserCommittedInterstitials"
-                    ]
-                }
-            ]
-        }
-    ],
     "TLS13Variant": [
         {
             "platforms": [
@@ -4863,6 +4858,26 @@
             ]
         }
     ],
+    "WebSocketHandshakeReuseConnection": [
+        {
+            "platforms": [
+                "windows",
+                "mac",
+                "chromeos",
+                "linux",
+                "android",
+                "android_webview"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "WebSocketHandshakeReuseConnection"
+                    ]
+                }
+            ]
+        }
+    ],
     "WheelScrollLatchingAndAsyncWheelEvents": [
         {
             "platforms": [
diff --git a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
index ab3e916..32a3b3a 100644
--- a/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/WebKit/LayoutTests/FlagExpectations/enable-blink-features=LayoutNG
@@ -95,60 +95,22 @@
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-001e.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-001h.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-002e.xht [ Pass ]
-crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-insert-002f.xht [ Failure ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-nested-002.xht [ Pass ]
 crbug.com/591099 external/wpt/css/CSS2/normal-flow/block-in-inline-remove-006.xht [ Pass ]
-crbug.com/591099 external/wpt/css/CSS2/normal-flow/max-width-applies-to-005.xht [ Failure ]
-crbug.com/591099 external/wpt/css/CSS2/normal-flow/min-width-applies-to-005.xht [ Failure ]
-crbug.com/591099 external/wpt/css/CSS2/normal-flow/replaced-intrinsic-001.xht [ Failure ]
 crbug.com/591099 external/wpt/css/CSS2/text/white-space-mixed-003.xht [ Pass ]
-crbug.com/591099 external/wpt/css/css-animations/CSSAnimation-getComputedTiming.tentative.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-backgrounds/box-shadow-syntax-001.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-contain/counter-scoping-001.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-contain/counter-scoping-003.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-display/display-contents-fieldset-nested-legend.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-flexbox/flex-minimum-height-flex-items-005.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-flexbox/flex-minimum-height-flex-items-007.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-flexbox/flexbox_visibility-collapse-line-wrapping.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-1.html [ Pass ]
 crbug.com/714962 external/wpt/css/css-fonts/font-features-across-space-3.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-fonts/font-synthesis-01.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-fonts/font-variant-alternates-02.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-fonts/font-variant-alternates-08.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-grid/alignment/grid-gutters-009.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-grid/grid-layout-properties.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-images/tiled-gradients.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-masking/clip-path-svg-content/mask-objectboundingbox-content-clip-transform.svg [ Failure ]
-crbug.com/591099 external/wpt/css/css-masking/clip-path-svg-content/mask-userspaceonuse-content-clip.svg [ Failure ]
-crbug.com/591099 external/wpt/css/css-masking/clip-path/clip-path-polygon-006.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-masking/clip-path/clip-path-polygon-010.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-multicol/multicol-span-all-margin-nested-002.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-position/position-relative-table-tfoot-left.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-position/position-relative-table-thead-top.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-position/position-relative-table-tr-top-absolute-child.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-position/position-sticky-writing-modes.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-rhythm/ [ Skip ]
 crbug.com/591099 external/wpt/css/css-scroll-anchoring/clipped-scrollers-skipped.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-scroll-anchoring/opt-out.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-scroll-anchoring/position-change-heuristic.html [ Failure ]
 crbug.com/714962 external/wpt/css/css-scroll-anchoring/wrapped-text.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-008.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-010.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-011.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/gradients/shape-outside-linear-gradient-013.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/shape-image/shape-image-010.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-shapes/shape-outside/supported-shapes/polygon/shape-outside-polygon-017.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-shapes/shape-outside/values/shape-outside-circle-004.html [ Failure ]
 crbug.com/845902 external/wpt/css/css-sizing/whitespace-and-break.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-position-below-left-001.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-text-decor/text-emphasis-style-002.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-jazh-136.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-jazh-142.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-opclns-255.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/i18n/css3-text-line-break-opclns-259.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-009.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-011.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-ic-002.html [ Pass ]
@@ -158,17 +120,10 @@
 crbug.com/591099 external/wpt/css/css-text/white-space/pre-wrap-002.html [ Pass ]
 crbug.com/40634 external/wpt/css/css-text/white-space/trailing-space-before-br-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-008.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-011.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-transforms/transform-box/view-box-mutation.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-transforms/transform-transformed-tr-percent-height-child.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-transitions/properties-value-003.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-transitions/properties-value-implicit-001.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-transitions/transitions-animatable-properties-01.html [ Timeout ]
-crbug.com/591099 external/wpt/css/css-ui/box-sizing-015.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-ui/box-sizing-019.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-ui/outline-006.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-ui/outline-014.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-010.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-015.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/abs-pos-non-replaced-icb-vlr-003.xht [ Pass ]
@@ -249,12 +204,9 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/available-size-010.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/available-size-011.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/available-size-012.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/available-size-015.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/available-size-016.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/available-size-017.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/available-size-018.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/block-flow-direction-vrl-009.xht [ Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/clip-rect-vlr-009.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/line-box-direction-vrl-009.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/line-box-height-vlr-021.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/line-box-height-vlr-023.xht [ Pass ]
@@ -265,47 +217,24 @@
 crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001s.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/orthogonal-parent-shrink-to-fit-001t.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-htb-in-vlr-001.xht [ Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vlr-in-htb-008.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vrl-in-htb-004.xht [ Failure ]
 crbug.com/591099 external/wpt/css/css-writing-modes/sizing-orthog-vrl-in-htb-013.xht [ Failure ]
-crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-script-001a.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-script-001c.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-writing-modes/text-orientation-script-001j.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-003.xht [ Pass ]
 crbug.com/591099 external/wpt/css/css-writing-modes/vertical-alignment-009.xht [ Pass ]
 crbug.com/591099 external/wpt/css/cssom/cssstyledeclaration-mutationrecord-001.html [ Pass ]
-crbug.com/591099 external/wpt/css/selectors/focus-visible-007.html [ Failure ]
 crbug.com/591099 external/wpt/css/selectors/selector-placeholder-shown-type-change-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/selectors/selector-read-write-type-change-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/selectors/selector-required-type-change-002.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-fallback.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/descriptor-negative-invalid.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001b.xhtml [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-collapsed-item-baseline-001.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-collapsed-item-horiz-001.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-column-row-gap-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-004.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003v.html [ Failure Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-005v.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-006.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-011.html [ Crash ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-writing-mode-012.html [ Crash ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-1.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/emptyspan-4.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/remove-from-split-inline-6.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/split-inner-inline-2.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/float-retry-push-image.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-border-box-border-radius-003.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-border-box-border-radius-004.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-border-box-border-radius-006.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-border-box-border-radius-008.html [ Failure ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-border-box-border-radius-009.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-042.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-044.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-circle-050.html [ Pass ]
@@ -323,7 +252,6 @@
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-margin-box-border-radius-004.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-margin-box-border-radius-008.html [ Pass ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-padding-box-border-radius-002.html [ Pass ]
-crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-021.html [ Failure ]
 crbug.com/591099 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/shapes1/shape-outside-polygon-022.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/bold.html [ Pass ]
 crbug.com/591099 external/wpt/editing/run/fontname.html [ Pass ]
@@ -500,7 +428,6 @@
 crbug.com/591099 fast/css/transform-inline-style-remove.html [ Failure ]
 crbug.com/591099 fast/css3-text/css3-text-decoration/text-underline-position/text-underline-position-under.html [ Failure ]
 crbug.com/591099 fast/dom/nodesFromRect/nodesFromRect-basic.html [ Failure ]
-crbug.com/591099 fast/dynamic/first-letter-after-list-marker.html [ Failure ]
 crbug.com/591099 fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
 crbug.com/591099 fast/events/wheel/mainthread-touchpad-fling-latching.html [ Pass ]
 crbug.com/591099 fast/events/wheel/wheel-scroll-latching-on-scrollbar.html [ Pass ]
diff --git a/third_party/WebKit/LayoutTests/SlowTests b/third_party/WebKit/LayoutTests/SlowTests
index 2cd7b78a..15b2d5a 100644
--- a/third_party/WebKit/LayoutTests/SlowTests
+++ b/third_party/WebKit/LayoutTests/SlowTests
@@ -402,6 +402,7 @@
 crbug.com/864887 [ Mac ] fast/scroll-snap/snaps-after-scrollbar-scrolling.html [ Slow ]
 
 crbug.com/865262 [ Mac ] media/controls/text-track-menu-pointer-selection.html [ Slow ]
+crbug.com/865262 [ Mac ] virtual/new-remote-playback-pipeline/media/controls/text-track-menu-pointer-selection.html [ Slow ]
 
 ### virtual/gpu/fast/canvas/ blending layout tests are slow
 crbug.com/866850 [ Linux Mac ] virtual/gpu/fast/canvas/canvas-blend-image.html [ Slow ]
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations
index e7aef7d..1123d270 100644
--- a/third_party/WebKit/LayoutTests/TestExpectations
+++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1829,6 +1829,7 @@
 crbug.com/567837 virtual/scalefactor200withzoom/fast/hidpi/static/popup-menu-with-scrollbar-appearance.html [ Skip ]
 
 crbug.com/853912 virtual/paint-touchaction-rects/fast/events/touch/compositor-touch-hit-rects.html [ Failure ]
+crbug.com/870595 [ Mac ] virtual/paint-touchaction-rects/fast/events/touch/touch-latched-scroll-node-removed.html [ Pass Failure ]
 
 # TODO(ojan): These tests aren't flaky. See crbug.com/517144.
 # Release trybots run asserts, but the main waterfall ones don't. So, even
@@ -2730,7 +2731,11 @@
 crbug.com/849859 external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html [ Failure ]
 crbug.com/849859 external/wpt/web-animations/timing-model/animations/pausing-an-animation.html [ Failure ]
 
+# wptserve raises exceptions when running this test:
+crbug.com/870526 external/wpt/payment-handler/idlharness.https.any.serviceworker.html [ Skip ]
+
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/clear-site-data/executionContexts.sub.html [ Timeout ]
 crbug.com/626703 [ Win10 ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-column-row-gap-004.html [ Failure ]
 crbug.com/626703 [ Linux Mac Win7 ] external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-column-row-gap-004.html [ Failure ]
 crbug.com/626703 [ Mac10.12 Mac10.13 ] external/wpt/service-workers/service-worker/clients-matchall-order.https.html [ Timeout ]
@@ -4863,3 +4868,6 @@
 crbug.com/869773 [ Mac Linux ] http/tests/misc/window-dot-stop.html [ Pass Failure ]
 crbug.com/869829 [ Mac Release ] http/tests/media/video-cancel-load.html [ Pass Failure ]
 crbug.com/862806 [ Linux ] css3/filters/effect-drop-shadow-hw.html [ Pass Timeout ]
+
+# Sheriff 2018-08-02
+crbug.com/870259 [ Mac ] http/tests/media/video-throttled-load-metadata.html [ Pass Timeout ]
diff --git a/third_party/WebKit/LayoutTests/app_banner/app-banner-event-prompt.html b/third_party/WebKit/LayoutTests/app_banner/app-banner-event-prompt.html
index 56ea1fdc1..dcdc444f 100644
--- a/third_party/WebKit/LayoutTests/app_banner/app-banner-event-prompt.html
+++ b/third_party/WebKit/LayoutTests/app_banner/app-banner-event-prompt.html
@@ -3,6 +3,23 @@
 <script src="../resources/testharnessreport.js"></script>
 <script>
 
+// Copied from resources/bluetooth/bluetooth-helpers.js.
+function callWithKeyDown(functionCalledOnKeyPress) {
+  return new Promise((resolve, reject) => {
+    function onKeyPress() {
+      document.removeEventListener('keypress', onKeyPress, false);
+      try {
+        resolve(functionCalledOnKeyPress());
+      } catch (e) {
+        reject(e);
+      }
+    }
+    document.addEventListener('keypress', onKeyPress, false);
+
+    eventSender.keyDown(' ', []);
+  });
+}
+
 var prompt_test_cases = [
     {
         name: 'prompt-accept-access-userchoice-first',
@@ -92,7 +109,8 @@
 ];
 
 function verify_prompt_resolve(e, t) {
-    e.prompt().then(function() { }, t.unreached_func("prompt() promise should resolve."));
+    callWithKeyDown(() => e.prompt().then(
+        function() { }, t.unreached_func("prompt() promise should resolve.")));
 }
 
 function verify_prompt_reject(e, t) {
@@ -104,7 +122,7 @@
             );
             assert_equals(
                 error.message,
-                "The prompt() method may only be called once.",
+                "The prompt() method must be called with a user gesture",
                 "Rejected promise does not provide expected message."
             );
         })
@@ -116,8 +134,6 @@
         assert_equals(result.platform, test_case.platform, 'Resolved platform');
         assert_equals(result.outcome, test_case.outcome, 'Outcome');
     })).then(t.step_func(function() {
-        verify_prompt_reject(e, t);
-    })).then(t.step_func(function() {
         prompt_test(index + 1);
         t.done();
     }), t.unreached_func("userChoice promise should resolve."));
@@ -161,6 +177,9 @@
                 return;
             }
 
+            // Ensure prompt rejects without a user gesture.
+            verify_prompt_reject(e, t);
+
             // Call prompt() to restart the pipeline.
             verify_prompt_resolve(e, t);
 
diff --git a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
index 285c02d..e046fc34 100644
--- a/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
+++ b/third_party/WebKit/LayoutTests/external/WPT_BASE_MANIFEST.json
@@ -104021,6 +104021,11 @@
      {}
     ]
    ],
+   "clear-site-data/support/iframe_executionContexts.html": [
+    [
+     {}
+    ]
+   ],
    "clear-site-data/support/page_with_resource.sub.html": [
     [
      {}
@@ -159186,6 +159191,11 @@
      {}
     ]
    ],
+   "interfaces/notifications.idl": [
+    [
+     {}
+    ]
+   ],
    "interfaces/orientation-event.idl": [
     [
      {}
@@ -159416,6 +159426,11 @@
      {}
     ]
    ],
+   "interfaces/webrtc-stats.idl": [
+    [
+     {}
+    ]
+   ],
    "interfaces/webusb.idl": [
     [
      {}
@@ -160251,6 +160266,11 @@
      {}
     ]
    ],
+   "mediacapture-streams/MediaStream-removetrack.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https-expected.txt": [
     [
      {}
@@ -161081,6 +161101,26 @@
      {}
     ]
    ],
+   "notifications/idlharness.any-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "notifications/idlharness.any.sharedworker-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "notifications/idlharness.any.worker-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "notifications/idlharness.https.any.serviceworker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "notifications/instance-expected.txt": [
     [
      {}
@@ -161471,6 +161511,11 @@
      {}
     ]
    ],
+   "payment-handler/idlharness.https.any.sharedworker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "payment-handler/idlharness.https.any.worker-expected.txt": [
     [
      {}
@@ -162096,6 +162141,21 @@
      {}
     ]
    ],
+   "push-api/idlharness.any-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "push-api/idlharness.any.sharedworker-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "push-api/idlharness.any.worker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "quirks/META.yml": [
     [
      {}
@@ -166241,6 +166301,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/serviceworker/cache-keys.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/serviceworker/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -166256,6 +166321,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/window/cache-keys.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/window/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -166276,6 +166346,11 @@
      {}
     ]
    ],
+   "service-workers/cache-storage/worker/cache-keys.https-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "service-workers/cache-storage/worker/cache-matchAll.https-expected.txt": [
     [
      {}
@@ -169901,11 +169976,31 @@
      {}
     ]
    ],
+   "url/url-tojson.any-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "url/url-tojson.any.worker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "url/urlencoded-parser-expected.txt": [
     [
      {}
     ]
    ],
+   "url/urlencoded-parser.any-expected.txt": [
+    [
+     {}
+    ]
+   ],
+   "url/urlencoded-parser.any.worker-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "user-timing/META.yml": [
     [
      {}
@@ -171056,6 +171151,11 @@
      {}
     ]
    ],
+   "webrtc-stats/idlharness.window-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webrtc/META.yml": [
     [
      {}
@@ -174176,6 +174276,11 @@
      {}
     ]
    ],
+   "webxr/idlharness.https.window-expected.txt": [
+    [
+     {}
+    ]
+   ],
    "webxr/interfaces.https-expected.txt": [
     [
      {}
@@ -185595,6 +185700,12 @@
      {}
     ]
    ],
+   "clear-site-data/executionContexts.sub.html": [
+    [
+     "/clear-site-data/executionContexts.sub.html",
+     {}
+    ]
+   ],
    "clear-site-data/navigation-insecure.html": [
     [
      "/clear-site-data/navigation-insecure.html",
@@ -228723,15 +228834,27 @@
      {}
     ]
    ],
-   "notifications/instance.html": [
+   "notifications/idlharness.any.js": [
     [
-     "/notifications/instance.html",
+     "/notifications/idlharness.any.html",
+     {}
+    ],
+    [
+     "/notifications/idlharness.any.sharedworker.html",
+     {}
+    ],
+    [
+     "/notifications/idlharness.any.worker.html",
+     {}
+    ],
+    [
+     "/notifications/idlharness.https.any.serviceworker.html",
      {}
     ]
    ],
-   "notifications/interfaces.html": [
+   "notifications/instance.html": [
     [
-     "/notifications/interfaces.html",
+     "/notifications/instance.html",
      {}
     ]
    ],
@@ -238347,17 +238470,15 @@
      {}
     ],
     [
-     "/payment-handler/idlharness.https.any.worker.html",
-     {}
-    ]
-   ],
-   "payment-handler/interfaces.https.any.js": [
-    [
-     "/payment-handler/interfaces.https.any.html",
+     "/payment-handler/idlharness.https.any.serviceworker.html",
      {}
     ],
     [
-     "/payment-handler/interfaces.https.any.worker.html",
+     "/payment-handler/idlharness.https.any.sharedworker.html",
+     {}
+    ],
+    [
+     "/payment-handler/idlharness.https.any.worker.html",
      {}
     ]
    ],
@@ -239135,22 +239256,22 @@
      {}
     ]
    ],
-   "push-api/idlharness.https.any.js": [
+   "push-api/idlharness.any.js": [
     [
-     "/push-api/idlharness.https.any.html",
+     "/push-api/idlharness.any.html",
+     {}
+    ],
+    [
+     "/push-api/idlharness.any.sharedworker.html",
+     {}
+    ],
+    [
+     "/push-api/idlharness.any.worker.html",
      {}
     ],
     [
      "/push-api/idlharness.https.any.serviceworker.html",
      {}
-    ],
-    [
-     "/push-api/idlharness.https.any.sharedworker.html",
-     {}
-    ],
-    [
-     "/push-api/idlharness.https.any.worker.html",
-     {}
     ]
    ],
    "quirks/blocks-ignore-line-height.html": [
@@ -253173,81 +253294,139 @@
      {}
     ]
    ],
+   "url/url-searchparams.any.js": [
+    [
+     "/url/url-searchparams.any.html",
+     {}
+    ],
+    [
+     "/url/url-searchparams.any.worker.html",
+     {}
+    ]
+   ],
    "url/url-setters.html": [
     [
      "/url/url-setters.html",
      {}
     ]
    ],
-   "url/url-tojson.html": [
+   "url/url-tojson.any.js": [
     [
-     "/url/url-tojson.html",
+     "/url/url-tojson.any.html",
+     {}
+    ],
+    [
+     "/url/url-tojson.any.worker.html",
      {}
     ]
    ],
-   "url/urlencoded-parser.html": [
+   "url/urlencoded-parser.any.js": [
     [
-     "/url/urlencoded-parser.html",
+     "/url/urlencoded-parser.any.html",
+     {}
+    ],
+    [
+     "/url/urlencoded-parser.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-append.html": [
+   "url/urlsearchparams-append.any.js": [
     [
-     "/url/urlsearchparams-append.html",
+     "/url/urlsearchparams-append.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-append.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-constructor.html": [
+   "url/urlsearchparams-constructor.any.js": [
     [
-     "/url/urlsearchparams-constructor.html",
+     "/url/urlsearchparams-constructor.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-constructor.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-delete.html": [
+   "url/urlsearchparams-delete.any.js": [
     [
-     "/url/urlsearchparams-delete.html",
+     "/url/urlsearchparams-delete.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-delete.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-foreach.html": [
+   "url/urlsearchparams-foreach.any.js": [
     [
-     "/url/urlsearchparams-foreach.html",
+     "/url/urlsearchparams-foreach.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-foreach.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-get.html": [
+   "url/urlsearchparams-get.any.js": [
     [
-     "/url/urlsearchparams-get.html",
+     "/url/urlsearchparams-get.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-get.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-getall.html": [
+   "url/urlsearchparams-getall.any.js": [
     [
-     "/url/urlsearchparams-getall.html",
+     "/url/urlsearchparams-getall.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-getall.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-has.html": [
+   "url/urlsearchparams-has.any.js": [
     [
-     "/url/urlsearchparams-has.html",
+     "/url/urlsearchparams-has.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-has.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-set.html": [
+   "url/urlsearchparams-set.any.js": [
     [
-     "/url/urlsearchparams-set.html",
+     "/url/urlsearchparams-set.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-set.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-sort.html": [
+   "url/urlsearchparams-sort.any.js": [
     [
-     "/url/urlsearchparams-sort.html",
+     "/url/urlsearchparams-sort.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-sort.any.worker.html",
      {}
     ]
    ],
-   "url/urlsearchparams-stringifier.html": [
+   "url/urlsearchparams-stringifier.any.js": [
     [
-     "/url/urlsearchparams-stringifier.html",
+     "/url/urlsearchparams-stringifier.any.html",
+     {}
+    ],
+    [
+     "/url/urlsearchparams-stringifier.any.worker.html",
      {}
     ]
    ],
@@ -256025,6 +256204,12 @@
      {}
     ]
    ],
+   "webrtc-stats/idlharness.window.js": [
+    [
+     "/webrtc-stats/idlharness.window.html",
+     {}
+    ]
+   ],
    "webrtc/RTCCertificate.html": [
     [
      "/webrtc/RTCCertificate.html",
@@ -259485,9 +259670,9 @@
      {}
     ]
    ],
-   "webxr/interfaces.https.html": [
+   "webxr/idlharness.https.window.js": [
     [
-     "/webxr/interfaces.https.html",
+     "/webxr/idlharness.https.window.html",
      {}
     ]
    ],
@@ -268189,7 +268374,7 @@
    "support"
   ],
   "./README.md": [
-   "3e4c2ec3696939abd1849dee0809e40c1ecb12a7",
+   "5ba8bf6a121329db78fa5ea6844e9ac987aa5b94",
    "support"
   ],
   "./lint.whitelist": [
@@ -275833,7 +276018,7 @@
    "support"
   ],
   "background-fetch/idlharness.https.any.serviceworker-expected.txt": [
-   "85f47963c646c7fdda4c8251fac18002aed6c5db",
+   "92d77448b72d6220ad7b2d3a6b23534c99bdc9e5",
    "support"
   ],
   "background-fetch/mixed-content-and-allowed-schemes.https.window.js": [
@@ -276860,6 +277045,10 @@
    "8c90c19ce29952d868460d36f7b20120b155992c",
    "support"
   ],
+  "clear-site-data/executionContexts.sub.html": [
+   "61bbbd4b509c5542648f29753df936b43d364a89",
+   "testharness"
+  ],
   "clear-site-data/navigation-insecure.html": [
    "97c069cf7c938e1ebdba3f243ad48369a7fb5542",
    "testharness"
@@ -276880,6 +277069,10 @@
    "6d6822db671d4591d9c9d8aecfc9d6d2dcf2f308",
    "support"
   ],
+  "clear-site-data/support/iframe_executionContexts.html": [
+   "d3fe0641c99d09c739f35a3e57d49cc225412d75",
+   "support"
+  ],
   "clear-site-data/support/page_with_resource.sub.html": [
    "246451c21ea53f7ac0868a5d8772aa4527dd5664",
    "support"
@@ -280081,7 +280274,7 @@
    "testharness"
   ],
   "cookie-store/httponly_cookies.https.window.js": [
-   "29d9c0bfaa9aa3e7115758731156dbeca9e736dc",
+   "23b7d469bd4e836cebd6791eec783ef699a78389",
    "testharness"
   ],
   "cookie-store/idlharness.tentative.https.html": [
@@ -331805,11 +331998,11 @@
    "testharness"
   ],
   "css/css-typed-om/interfaces-expected.txt": [
-   "d92e93f51740e4a4d6192638282949f96e6211ed",
+   "88189b4c2ee6e7b7ceb01a7bd0960c0b6e67b28a",
    "support"
   ],
   "css/css-typed-om/interfaces.html": [
-   "b594b1907b2ca95278b8fc643b97480d693bab9f",
+   "72799d69ff61fe2619530ee3c5577e70d4bffc9e",
    "testharness"
   ],
   "css/css-typed-om/resources/testhelper.js": [
@@ -352773,7 +352966,7 @@
    "testharness"
   ],
   "encoding/idlharness.any.js": [
-   "60a9348e7608a10af6091b0a157943ffc31a2e8a",
+   "d3f8cb917e97eefd6fd0d763780fc0bfea5ed0ac",
    "testharness"
   ],
   "encoding/iso-2022-jp-decoder-expected.txt": [
@@ -378965,7 +379158,7 @@
    "support"
   ],
   "interfaces/cookie-store.idl": [
-   "fe873252f8a58c66f736fbabd90d6d37a15df139",
+   "3cb64ada25e357702a029a424ef72bedefa80ceb",
    "support"
   ],
   "interfaces/cors-rfc1918.idl": [
@@ -379029,7 +379222,7 @@
    "support"
   ],
   "interfaces/css-typed-om.idl": [
-   "36526913c07a04f9fd329a5650430db82407d766",
+   "81881ca59f5cb7a287e66b861f18a17fd36c4beb",
    "support"
   ],
   "interfaces/cssom-view.idl": [
@@ -379133,7 +379326,7 @@
    "support"
   ],
   "interfaces/media-capabilities.idl": [
-   "dad6123e39feca39ec620de51307e56823cc5408",
+   "0bba702ce3c8683baca527f16132a5ac62dd656d",
    "support"
   ],
   "interfaces/media-source.idl": [
@@ -379161,13 +379354,17 @@
    "support"
   ],
   "interfaces/navigation-timing.idl": [
-   "c12830eb9f5f18eef60557f5a93ea68812f78193",
+   "6c021847c039e69e8a88fc1555f02b84a9280fbd",
    "support"
   ],
   "interfaces/netinfo.idl": [
    "c40e777040b92c90591f40a02a66440ecc228c5a",
    "support"
   ],
+  "interfaces/notifications.idl": [
+   "5043f859cc27314c5c36753506cdbfed694fc5a3",
+   "support"
+  ],
   "interfaces/orientation-event.idl": [
    "88dba12d08fe65b1474994cbc0d15a71504e6300",
    "support"
@@ -379185,7 +379382,7 @@
    "support"
   ],
   "interfaces/payment-handler.idl": [
-   "c47c18345c06d87b7b9fe318b22d51413603f626",
+   "eb54a6c25cb788f2e0590cb0d5d94aa87385ceb7",
    "support"
   ],
   "interfaces/payment-method-basic-card.idl": [
@@ -379229,7 +379426,7 @@
    "support"
   ],
   "interfaces/push-api.idl": [
-   "8157418c11faef0a36f0829f42b070daca8b57f5",
+   "be970887b4de77c05203fa16dc373d5ac36ad269",
    "support"
   ],
   "interfaces/referrer-policy.idl": [
@@ -379352,6 +379549,10 @@
    "a631e2e0ea0f451c64b5d5f74fe7cbeafc231b8e",
    "support"
   ],
+  "interfaces/webrtc-stats.idl": [
+   "a4a2e5152e3c96a5efa26893ed34856ebe5348c0",
+   "support"
+  ],
   "interfaces/webusb.idl": [
    "8b6b064f5dc45751da07476ef2c05390a23a2d79",
    "support"
@@ -379361,7 +379562,7 @@
    "support"
   ],
   "interfaces/webxr.idl": [
-   "cc9199634844a31a06e4d25c4095a7193d308c90",
+   "4e805a507c2ed25e68d49bb05354d51d9eec29e8",
    "support"
   ],
   "interfaces/worklets.idl": [
@@ -379785,15 +379986,15 @@
    "support"
   ],
   "media-capabilities/idlharness.any-expected.txt": [
-   "eab18157052b78209da8a04d40dbf95a8611d325",
+   "e6a818daebab604a5168893763b72ec763f5b5be",
    "support"
   ],
   "media-capabilities/idlharness.any.js": [
-   "23dff2a720b31aa5066a96a373ecc25e649719e7",
+   "593b7e0daf0e7e315baf573f6d777bcc1fd71b4b",
    "testharness"
   ],
   "media-capabilities/idlharness.any.worker-expected.txt": [
-   "119c59065c2d1e35c49726a90cbf5f6a6f7c0a88",
+   "0f52878fdd839b84c26dcde5ff2b05f0867ff2e0",
    "support"
   ],
   "media-source/META.yml": [
@@ -380624,8 +380825,12 @@
    "2cf280abdf5598a21000a26365f243fcc7ffdb6b",
    "testharness"
   ],
+  "mediacapture-streams/MediaStream-removetrack.https-expected.txt": [
+   "87065c67c9c3745c68dae5d5ef498c276ec20eda",
+   "support"
+  ],
   "mediacapture-streams/MediaStream-removetrack.https.html": [
-   "28bdf571b37412078199e127ecf3a9423770f0ce",
+   "54b35255d3db1c714c6bbf3cf1043a0efe754670",
    "testharness"
   ],
   "mediacapture-streams/MediaStream-video-only.https.html": [
@@ -382580,6 +382785,26 @@
    "444f20a7ce7348764efe1b7eeb33e9611c4857ac",
    "manual"
   ],
+  "notifications/idlharness.any-expected.txt": [
+   "5c3baf5da0b37791d9ae35033c04d0fcf5a1b318",
+   "support"
+  ],
+  "notifications/idlharness.any.js": [
+   "c140ff43dc93d45d611941edfdb92b42be11ba87",
+   "testharness"
+  ],
+  "notifications/idlharness.any.sharedworker-expected.txt": [
+   "cc3279f611cbe6e08909ef7f00e5ac151629699c",
+   "support"
+  ],
+  "notifications/idlharness.any.worker-expected.txt": [
+   "cc3279f611cbe6e08909ef7f00e5ac151629699c",
+   "support"
+  ],
+  "notifications/idlharness.https.any.serviceworker-expected.txt": [
+   "a0511ee3c37e878f62bf0c9e3893230210d275ac",
+   "support"
+  ],
   "notifications/instance-expected.txt": [
    "09b69a66ddfc87cacfa2fb2cf68eb147d6c7cba7",
    "support"
@@ -382588,10 +382813,6 @@
    "0d1c3d540163dc596080f7c09291d41a41f5c9b9",
    "testharness"
   ],
-  "notifications/interfaces.html": [
-   "bb54608283b8413d91f2fa020130b9f58f4d0c4a",
-   "testharness"
-  ],
   "notifications/lang-expected.txt": [
    "d7cdf784912c710774225f14e0d6b54cc7885e98",
    "support"
@@ -389437,25 +389658,25 @@
    "testharness"
   ],
   "payment-handler/idlharness.https.any-expected.txt": [
-   "7dacbac59fdf0ee85daa55f78325525093f8b6a1",
+   "c265929221af8fd61c9184a1536a94b004ef76d6",
    "support"
   ],
   "payment-handler/idlharness.https.any.js": [
-   "416308d5f1ef290fc8a8ab2343a47a89fa78d994",
+   "ecbdcd4f442d2e91b322a1a1d06a9b64d77f632d",
    "testharness"
   ],
+  "payment-handler/idlharness.https.any.sharedworker-expected.txt": [
+   "3b3352f4228ac4976ad14cad1b2ec9f9975250da",
+   "support"
+  ],
   "payment-handler/idlharness.https.any.worker-expected.txt": [
-   "8b7243315da04bd6fea0572ce09d2aeb94342326",
+   "3b3352f4228ac4976ad14cad1b2ec9f9975250da",
    "support"
   ],
   "payment-handler/interfaces.https.any-expected.txt": [
    "21994aad0e45909d41c0b8367d4a9e8c705930df",
    "support"
   ],
-  "payment-handler/interfaces.https.any.js": [
-   "4d1e2360e69d2c2f1664e73a79c1d893c6ee10fb",
-   "testharness"
-  ],
   "payment-handler/interfaces.https.any.worker-expected.txt": [
    "cdaa388b9505059d77e705b8bd3a1a8e337cb816",
    "support"
@@ -390912,10 +391133,22 @@
    "4d397edf0b610cfe0fcdf16f11f156d08f201e68",
    "support"
   ],
-  "push-api/idlharness.https.any.js": [
-   "5a5c1a88cd9314af18333123e3c6567b6969dbf2",
+  "push-api/idlharness.any-expected.txt": [
+   "d1559a52f07fcb9f06d6d70c140665463d17c215",
+   "support"
+  ],
+  "push-api/idlharness.any.js": [
+   "72864ef8db9789fc58ec0de4dfc84249f9368ff1",
    "testharness"
   ],
+  "push-api/idlharness.any.sharedworker-expected.txt": [
+   "7b4ac8ca15e6888027a19c5e5bdef7e0109968bf",
+   "support"
+  ],
+  "push-api/idlharness.any.worker-expected.txt": [
+   "7b4ac8ca15e6888027a19c5e5bdef7e0109968bf",
+   "support"
+  ],
   "quirks/META.yml": [
    "0214c11aec63acf1b5ae247cff64b440ff8b4bfe",
    "support"
@@ -400573,7 +400806,7 @@
    "support"
   ],
   "service-workers/cache-storage/script-tests/cache-keys.js": [
-   "b21b2dda441fcd8fd7e3ae3d60de91bfef0b5f15",
+   "c0405f7345f05d6ac6a561b9fddaf33c0ea8c5f5",
    "support"
   ],
   "service-workers/cache-storage/script-tests/cache-match.js": [
@@ -400624,6 +400857,10 @@
    "f1456bd2f56e8bd17bb9664960347c33decb927f",
    "testharness"
   ],
+  "service-workers/cache-storage/serviceworker/cache-keys.https-expected.txt": [
+   "c1951e14371ff31d03489bbf87da09a3d4b38517",
+   "support"
+  ],
   "service-workers/cache-storage/serviceworker/cache-keys.https.html": [
    "c1f638bea1f897b086ce403b7a87fe4fd7b77b25",
    "testharness"
@@ -400680,6 +400917,10 @@
    "8b02db13f96f2d9328e5a7cc4c55dd58bd23618c",
    "testharness"
   ],
+  "service-workers/cache-storage/window/cache-keys.https-expected.txt": [
+   "8401abd224a6e9d2d70996d419d76ba3834d6f3c",
+   "support"
+  ],
   "service-workers/cache-storage/window/cache-keys.https.html": [
    "be44bcee176090ffc0380cce2611a6e1350bbad7",
    "testharness"
@@ -400740,6 +400981,10 @@
    "6e75a546b1646e8a97aa43aa9cf5434402dca064",
    "testharness"
   ],
+  "service-workers/cache-storage/worker/cache-keys.https-expected.txt": [
+   "8401abd224a6e9d2d70996d419d76ba3834d6f3c",
+   "support"
+  ],
   "service-workers/cache-storage/worker/cache-keys.https.html": [
    "b1a90bfc9676e2cb20316a7a6852a5df016ef797",
    "testharness"
@@ -406833,7 +407078,7 @@
    "support"
   ],
   "url/README.md": [
-   "37bbc0fb70e865c45ff369a434de1a4a0a2bbc3e",
+   "38ce5daffb9cfe170d0f197b8cb1aa49e5e4611a",
    "support"
   ],
   "url/a-element-origin-xhtml.xhtml": [
@@ -406917,13 +407162,17 @@
    "testharness"
   ],
   "url/url-constructor.html": [
-   "e1cc6dfc07040b019201dd7695b5171572f2fb8f",
+   "ff4b13a1777a8ab3cda4244873fd4022f6b32fa4",
    "testharness"
   ],
   "url/url-origin.html": [
    "c68cfe55de2330dce6b9c4fa40c0983a14e5c871",
    "testharness"
   ],
+  "url/url-searchparams.any.js": [
+   "a35b7d7a0cadf9f6f75501a35172d430aa09dfb4",
+   "testharness"
+  ],
   "url/url-setters.html": [
    "46668877b10431168404c83af3e7c58c48940cfc",
    "testharness"
@@ -406932,56 +407181,72 @@
    "c5fd3011bd764eaa1e555d39bc155231b3c7e56b",
    "support"
   ],
-  "url/url-tojson.html": [
-   "d961e88b6976933599ec06b1a2b6da6f200c150b",
+  "url/url-tojson.any-expected.txt": [
+   "edfb08a1f70879ffc41dd442decef2837afa4077",
+   "support"
+  ],
+  "url/url-tojson.any.js": [
+   "b32cdbc2b4abf05046a8031e3f7d864280244ddc",
    "testharness"
   ],
+  "url/url-tojson.any.worker-expected.txt": [
+   "edfb08a1f70879ffc41dd442decef2837afa4077",
+   "support"
+  ],
   "url/urlencoded-parser-expected.txt": [
    "5d9391c8c9e53c7a7007ac44509f8791e47c7eff",
    "support"
   ],
-  "url/urlencoded-parser.html": [
-   "4eb385fec2e8cbd3e65dbf36edb47a6f2beb0de8",
+  "url/urlencoded-parser.any-expected.txt": [
+   "5d9391c8c9e53c7a7007ac44509f8791e47c7eff",
+   "support"
+  ],
+  "url/urlencoded-parser.any.js": [
+   "15e66116df7924897ef16843dd4f83159f3de054",
    "testharness"
   ],
-  "url/urlsearchparams-append.html": [
-   "df184eb089b0e639b96e901b10179cc4051f887e",
+  "url/urlencoded-parser.any.worker-expected.txt": [
+   "5d9391c8c9e53c7a7007ac44509f8791e47c7eff",
+   "support"
+  ],
+  "url/urlsearchparams-append.any.js": [
+   "13a70f382adc80bfe375cb6e4faee0f69583f361",
    "testharness"
   ],
-  "url/urlsearchparams-constructor.html": [
-   "e7d341e38acba7c7608da8567c20a3468ca522e2",
+  "url/urlsearchparams-constructor.any.js": [
+   "b73cb13af7d312177c9ae8219343a1fc42e80738",
    "testharness"
   ],
-  "url/urlsearchparams-delete.html": [
-   "4713dfec8b4277cc29520dbd25958bd74e440c19",
+  "url/urlsearchparams-delete.any.js": [
+   "9624118e0675321b46eea6f686b05c0dd9783de5",
    "testharness"
   ],
-  "url/urlsearchparams-foreach.html": [
-   "8f2adc3468f0f017253ed90fd68872ea0ad1d59f",
+  "url/urlsearchparams-foreach.any.js": [
+   "5de42ed629095ee278e93fa66443cfec1257ab91",
    "testharness"
   ],
-  "url/urlsearchparams-get.html": [
-   "33ae86cc4d7ae13385f3501dbe309562ecf965e8",
+  "url/urlsearchparams-get.any.js": [
+   "d12994535270260fe523ed5ca2fb0b4c7bac121c",
    "testharness"
   ],
-  "url/urlsearchparams-getall.html": [
-   "0d7b0ec7e984048ed87f2ea2967bac413a55858e",
+  "url/urlsearchparams-getall.any.js": [
+   "6a9b6530d75bec0ca35b703e77adfc5202cf545b",
    "testharness"
   ],
-  "url/urlsearchparams-has.html": [
-   "149f1a71e64135460f627bfc569f93a0dae18d73",
+  "url/urlsearchparams-has.any.js": [
+   "0254766d66a67a19560eabb123118324658513c2",
    "testharness"
   ],
-  "url/urlsearchparams-set.html": [
-   "2a540fed628b86c8a59766a83c57fbb3e73dd620",
+  "url/urlsearchparams-set.any.js": [
+   "afae80f5b1073b0da05bc8d13de011113ebe6c06",
    "testharness"
   ],
-  "url/urlsearchparams-sort.html": [
-   "dfc8f32339d084299318e9eb94fc47171247f2e1",
+  "url/urlsearchparams-sort.any.js": [
+   "be7f85a26e68598bcc30e4f7612149907aad5be4",
    "testharness"
   ],
-  "url/urlsearchparams-stringifier.html": [
-   "479e3fb9fc9eee208c492428c7032a96c70a5ae8",
+  "url/urlsearchparams-stringifier.any.js": [
+   "2fd2b07bccc39ba42682ec7db651f53940e169d6",
    "testharness"
   ],
   "user-timing/META.yml": [
@@ -409884,6 +410149,14 @@
    "927d32e9ebff034b34cf3bebde7955bbf3330e08",
    "support"
   ],
+  "webrtc-stats/idlharness.window-expected.txt": [
+   "8d38b82a73bdb391316426b69940cc598f73824f",
+   "support"
+  ],
+  "webrtc-stats/idlharness.window.js": [
+   "1a083b694f3cb495d37d4f2cba2d800cc567489b",
+   "testharness"
+  ],
   "webrtc/META.yml": [
    "8e81606efcc908d48a399b942fe8465b667ac0af",
    "support"
@@ -409937,11 +410210,11 @@
    "testharness"
   ],
   "webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt": [
-   "76c487323a27bb6e72b734122d07a00518495f3d",
+   "5305921e0659d9ca5e6bcce8da5a974a40575fb9",
    "support"
   ],
   "webrtc/RTCConfiguration-rtcpMuxPolicy.html": [
-   "a9beda60053916185106aedac98014123d6f7105",
+   "98a64f101f3cea18fec9d5585321e742ded4b13b",
    "testharness"
   ],
   "webrtc/RTCDTMFSender-helper.js": [
@@ -411937,15 +412210,15 @@
    "support"
   ],
   "webusb/idlharness.https.any-expected.txt": [
-   "33899351e3fd980d310680c9037c08a782347124",
+   "b972cd4079bd70c4c810ef66f6351fcb603cdd43",
    "support"
   ],
   "webusb/idlharness.https.any.js": [
-   "68976647ef1dfeed72c6888a85c8b98490953d4d",
+   "00e2a9412a84b726ce75b769aff6eb772e9c998c",
    "testharness"
   ],
   "webusb/idlharness.https.any.worker-expected.txt": [
-   "a7aa15a08dfa4b8f8cbc660179ea5b7647902fbf",
+   "348b39e06066d0e729bac79432855bb215945267",
    "support"
   ],
   "webusb/insecure-context.any.js": [
@@ -411969,7 +412242,7 @@
    "support"
   ],
   "webusb/resources/usb-helpers.js": [
-   "7a5878c9f4c472bafa8f3db026573feba8e746b0",
+   "e5e011aed2f4a9e4400f414d9514b50a40b6db00",
    "support"
   ],
   "webusb/usb-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html": [
@@ -414984,14 +415257,18 @@
    "7b93efb43b3ff46cab0cb4b7eb88b4b021003218",
    "support"
   ],
+  "webxr/idlharness.https.window-expected.txt": [
+   "b646c9ca97e7a2d4eb356dc5524e8a0eb2dfcf9c",
+   "support"
+  ],
+  "webxr/idlharness.https.window.js": [
+   "9444f29b752c878ad16517b866ed78419d0816d6",
+   "testharness"
+  ],
   "webxr/interfaces.https-expected.txt": [
    "58810d4be6ad2b4d087a06dc39a203bfc377b490",
    "support"
   ],
-  "webxr/interfaces.https.html": [
-   "aa283090f19dd7300d9a8860fe93737d1e1aab60",
-   "testharness"
-  ],
   "webxr/resources/webxr_check.html": [
    "3fa96e39a073bda2e813f0b2c2411c26b73d374e",
    "support"
diff --git a/third_party/WebKit/LayoutTests/external/wpt/README.md b/third_party/WebKit/LayoutTests/external/wpt/README.md
index c16793556..684f461 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/README.md
+++ b/third_party/WebKit/LayoutTests/external/wpt/README.md
@@ -133,11 +133,11 @@
 brew install nss
 ```
 
-On other platforms, download the firefox archive and common.tests.zip
+On other platforms, download the firefox archive and common.tests.tar.gz
 archive for your platform from
 [Mozilla CI](https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/).
 
-Then extract `certutil[.exe]` from the tests.zip package and
+Then extract `certutil[.exe]` from the tests.tar.gz package and
 `libnss3[.so|.dll|.dynlib]` and put the former on your path and the latter on
 your library path.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/background-fetch/idlharness.https.any.serviceworker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/background-fetch/idlharness.https.any.serviceworker-expected.txt
index b56910e..927b830 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/background-fetch/idlharness.https.any.serviceworker-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/background-fetch/idlharness.https.any.serviceworker-expected.txt
@@ -84,7 +84,7 @@
 PASS BackgroundFetchUpdateEvent interface: existence and properties of interface prototype object
 PASS BackgroundFetchUpdateEvent interface: existence and properties of interface prototype object's "constructor" property
 PASS BackgroundFetchUpdateEvent interface: existence and properties of interface prototype object's @@unscopables property
-PASS BackgroundFetchUpdateEvent interface: operation updateUI(DOMString)
+FAIL BackgroundFetchUpdateEvent interface: operation updateUI(DOMString) assert_equals: property has wrong .length expected 1 but got 0
 PASS BackgroundFetchClickEvent interface: existence and properties of interface object
 PASS BackgroundFetchClickEvent interface object length
 PASS BackgroundFetchClickEvent interface object name
diff --git a/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/executionContexts.sub.html b/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/executionContexts.sub.html
new file mode 100644
index 0000000..f09a3f7
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/executionContexts.sub.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+  </head>
+
+  <body>
+    <script>
+      function createAndLoadIframe() {
+        return new Promise(resolve => {
+          addEventListener("message", function() {
+            assert_true(true, "Iframe loaded");
+            resolve();
+          }, { once: true });
+
+          let ifr = document.createElement('iframe');
+          document.body.appendChild(ifr);
+          ifr.src = "https://{{domains[www2]}}:{{ports[https][0]}}/clear-site-data/support/iframe_executionContexts.html";
+        });
+      }
+
+      function loadClearSiteDataResource(what) {
+        return new Promise(resolve => {
+          addEventListener("message", function() {
+            assert_true(true, "Iframe re-loaded");
+            resolve();
+          }, { once: true });
+
+          let image = new Image();
+          image.src = "https://{{domains[www2]}}:{{ports[https][0]}}/clear-site-data/support/echo-clear-site-data.py?" + what;
+        });
+      }
+
+      promise_test(function(test) {
+        return createAndLoadIframe()
+          .then(() => loadClearSiteDataResource("executionContexts"))
+      }, "executionContexts triggers the reload of contexts");
+
+      promise_test(function(test) {
+        return createAndLoadIframe()
+          .then(() => loadClearSiteDataResource("*"));
+      }, "* triggers the reload of contexts");
+    </script>
+  </body>
+</html>
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/support/iframe_executionContexts.html b/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/support/iframe_executionContexts.html
new file mode 100644
index 0000000..9c20c9e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/clear-site-data/support/iframe_executionContexts.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <script>
+      parent.postMessage("Hello world!", "*");
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/httponly_cookies.https.window.js b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/httponly_cookies.https.window.js
index 01239ae..8a10e35 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/cookie-store/httponly_cookies.https.window.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/cookie-store/httponly_cookies.https.window.js
@@ -45,3 +45,25 @@
     eventPromise, {changed: [{name: 'TEST', value: 'dummy'}]},
     'HttpOnly cookie deletion was not observed');
 }, 'HttpOnly cookies are not observed');
+
+
+cookie_test(async t => {
+  document.cookie = 'cookie1=value1; path=/';
+  document.cookie = 'cookie2=value2; path=/; httponly';
+  document.cookie = 'cookie3=value3; path=/';
+  assert_equals(
+    await getCookieStringHttp(), 'cookie1=value1; cookie3=value3',
+    'Trying to store an HttpOnly cookie with document.cookie fails');
+}, 'HttpOnly cookies can not be set by document.cookie');
+
+
+// Historical: Early iterations of the proposal included an httpOnly option.
+cookie_test(async t => {
+  await cookieStore.set('cookie1', 'value1');
+  await cookieStore.set('cookie2', 'value2', {httpOnly: true});
+  await cookieStore.set('cookie3', 'value3');
+  assert_equals(
+    await getCookieStringHttp(),
+    'cookie1=value1; cookie2=value2; cookie3=value3',
+    'httpOnly is not an option for CookieStore.set()');
+}, 'HttpOnly cookies can not be set by CookieStore');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces-expected.txt
index b7d26bf..a1eac73 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces-expected.txt
@@ -1,9 +1,10 @@
 This is a testharness.js-based test.
-Found 259 tests; 249 PASS, 10 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 447 tests; 414 PASS, 33 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS CSS Typed OM IDL test
 PASS Partial interface Element: original interface defined
 PASS Partial interface CSSStyleRule: original interface defined
 PASS Partial interface ElementCSSInlineStyle: original interface defined
+PASS Partial namespace CSS: original namespace defined
 PASS CSSStyleValue interface: existence and properties of interface object
 PASS CSSStyleValue interface object length
 PASS CSSStyleValue interface object name
@@ -39,6 +40,27 @@
 PASS StylePropertyMap interface: operation append(USVString, [object Object],[object Object])
 PASS StylePropertyMap interface: operation delete(USVString)
 PASS StylePropertyMap interface: operation clear()
+PASS StylePropertyMap must be primary interface of styleMap
+PASS Stringification of styleMap
+PASS StylePropertyMap interface: styleMap must inherit property "set(USVString, [object Object],[object Object])" with the proper type
+PASS StylePropertyMap interface: calling set(USVString, [object Object],[object Object]) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "append(USVString, [object Object],[object Object])" with the proper type
+PASS StylePropertyMap interface: calling append(USVString, [object Object],[object Object]) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "delete(USVString)" with the proper type
+PASS StylePropertyMap interface: calling delete(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMap interface: styleMap must inherit property "clear()" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "get(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling get(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "getAll(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling getAll(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "has(USVString)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling has(USVString) on styleMap with too few arguments must throw TypeError
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "size" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "entries()" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "keys()" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "values()" with the proper type
+PASS StylePropertyMapReadOnly interface: styleMap must inherit property "forEach(function, any)" with the proper type
+PASS StylePropertyMapReadOnly interface: calling forEach(function, any) on styleMap with too few arguments must throw TypeError
 PASS CSSUnparsedValue interface: existence and properties of interface object
 PASS CSSUnparsedValue interface object length
 PASS CSSUnparsedValue interface object name
@@ -98,6 +120,35 @@
 PASS CSSUnitValue interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSUnitValue interface: attribute value
 PASS CSSUnitValue interface: attribute unit
+PASS CSSUnitValue must be primary interface of unitValue
+PASS Stringification of unitValue
+PASS CSSUnitValue interface: unitValue must inherit property "value" with the proper type
+PASS CSSUnitValue interface: unitValue must inherit property "unit" with the proper type
+PASS CSSNumericValue interface: unitValue must inherit property "add(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling add(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "sub(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling sub(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "mul(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling mul(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "div(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling div(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "min(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling min(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "max(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling max(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "equals(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling equals(CSSNumberish) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "to(USVString)" with the proper type
+PASS CSSNumericValue interface: calling to(USVString) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "toSum(USVString)" with the proper type
+PASS CSSNumericValue interface: calling toSum(USVString) on unitValue with too few arguments must throw TypeError
+PASS CSSNumericValue interface: unitValue must inherit property "type()" with the proper type
+PASS CSSNumericValue interface: unitValue must inherit property "parse(USVString)" with the proper type
+FAIL CSSNumericValue interface: calling parse(USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: unitValue must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: unitValue must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on unitValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
 PASS CSSMathValue interface: existence and properties of interface object
 PASS CSSMathValue interface object length
 PASS CSSMathValue interface object name
@@ -112,6 +163,35 @@
 PASS CSSMathSum interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSMathSum interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSMathSum interface: attribute values
+PASS CSSMathSum must be primary interface of mathSum
+PASS Stringification of mathSum
+PASS CSSMathSum interface: mathSum must inherit property "values" with the proper type
+PASS CSSMathValue interface: mathSum must inherit property "operator" with the proper type
+PASS CSSNumericValue interface: mathSum must inherit property "add(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling add(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "sub(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling sub(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "mul(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling mul(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "div(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling div(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "min(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling min(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "max(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling max(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "equals(CSSNumberish)" with the proper type
+PASS CSSNumericValue interface: calling equals(CSSNumberish) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "to(USVString)" with the proper type
+PASS CSSNumericValue interface: calling to(USVString) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "toSum(USVString)" with the proper type
+PASS CSSNumericValue interface: calling toSum(USVString) on mathSum with too few arguments must throw TypeError
+PASS CSSNumericValue interface: mathSum must inherit property "type()" with the proper type
+PASS CSSNumericValue interface: mathSum must inherit property "parse(USVString)" with the proper type
+FAIL CSSNumericValue interface: calling parse(USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: mathSum must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: mathSum must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on mathSum with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
 PASS CSSMathProduct interface: existence and properties of interface object
 PASS CSSMathProduct interface object length
 PASS CSSMathProduct interface object name
@@ -147,6 +227,15 @@
 PASS CSSMathMax interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSMathMax interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSMathMax interface: attribute values
+FAIL CSSMathClamp interface: existence and properties of interface object assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface object length assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface object name assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute min assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute val assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
+FAIL CSSMathClamp interface: attribute max assert_own_property: self does not have own property "CSSMathClamp" expected property "CSSMathClamp" missing
 PASS CSSNumericArray interface: existence and properties of interface object
 PASS CSSNumericArray interface object length
 PASS CSSNumericArray interface object name
@@ -187,6 +276,20 @@
             fn.apply(obj, args);
         }" did not throw
 PASS CSSTransformValue interface: operation forEach(function, any)
+PASS CSSTransformValue must be primary interface of transformValue
+PASS Stringification of transformValue
+PASS CSSTransformValue interface: transformValue must inherit property "length" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "is2D" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "toMatrix()" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "entries()" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "keys()" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "values()" with the proper type
+PASS CSSTransformValue interface: transformValue must inherit property "forEach(function, any)" with the proper type
+PASS CSSTransformValue interface: calling forEach(function, any) on transformValue with too few arguments must throw TypeError
+PASS CSSStyleValue interface: transformValue must inherit property "parse(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parse(USVString, USVString) on transformValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parse" missing
+PASS CSSStyleValue interface: transformValue must inherit property "parseAll(USVString, USVString)" with the proper type
+FAIL CSSStyleValue interface: calling parseAll(USVString, USVString) on transformValue with too few arguments must throw TypeError assert_own_property: interface object must have static operation as own property expected property "parseAll" missing
 PASS CSSTransformComponent interface: existence and properties of interface object
 PASS CSSTransformComponent interface object length
 PASS CSSTransformComponent interface object name
@@ -205,6 +308,13 @@
 PASS CSSTranslate interface: attribute x
 PASS CSSTranslate interface: attribute y
 PASS CSSTranslate interface: attribute z
+PASS CSSTranslate must be primary interface of transformValue[0]
+PASS Stringification of transformValue[0]
+PASS CSSTranslate interface: transformValue[0] must inherit property "x" with the proper type
+PASS CSSTranslate interface: transformValue[0] must inherit property "y" with the proper type
+PASS CSSTranslate interface: transformValue[0] must inherit property "z" with the proper type
+PASS CSSTransformComponent interface: transformValue[0] must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: transformValue[0] must inherit property "toMatrix()" with the proper type
 PASS CSSRotate interface: existence and properties of interface object
 PASS CSSRotate interface object length
 PASS CSSRotate interface object name
@@ -215,6 +325,14 @@
 PASS CSSRotate interface: attribute y
 PASS CSSRotate interface: attribute z
 PASS CSSRotate interface: attribute angle
+PASS CSSRotate must be primary interface of rotate
+PASS Stringification of rotate
+PASS CSSRotate interface: rotate must inherit property "x" with the proper type
+PASS CSSRotate interface: rotate must inherit property "y" with the proper type
+PASS CSSRotate interface: rotate must inherit property "z" with the proper type
+PASS CSSRotate interface: rotate must inherit property "angle" with the proper type
+PASS CSSTransformComponent interface: rotate must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: rotate must inherit property "toMatrix()" with the proper type
 PASS CSSScale interface: existence and properties of interface object
 PASS CSSScale interface object length
 PASS CSSScale interface object name
@@ -224,6 +342,13 @@
 PASS CSSScale interface: attribute x
 PASS CSSScale interface: attribute y
 PASS CSSScale interface: attribute z
+PASS CSSScale must be primary interface of scale
+PASS Stringification of scale
+PASS CSSScale interface: scale must inherit property "x" with the proper type
+PASS CSSScale interface: scale must inherit property "y" with the proper type
+PASS CSSScale interface: scale must inherit property "z" with the proper type
+PASS CSSTransformComponent interface: scale must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: scale must inherit property "toMatrix()" with the proper type
 PASS CSSSkew interface: existence and properties of interface object
 PASS CSSSkew interface object length
 PASS CSSSkew interface object name
@@ -232,6 +357,12 @@
 PASS CSSSkew interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSSkew interface: attribute ax
 PASS CSSSkew interface: attribute ay
+PASS CSSSkew must be primary interface of skew
+PASS Stringification of skew
+PASS CSSSkew interface: skew must inherit property "ax" with the proper type
+PASS CSSSkew interface: skew must inherit property "ay" with the proper type
+PASS CSSTransformComponent interface: skew must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skew must inherit property "toMatrix()" with the proper type
 PASS CSSSkewX interface: existence and properties of interface object
 PASS CSSSkewX interface object length
 PASS CSSSkewX interface object name
@@ -239,6 +370,11 @@
 PASS CSSSkewX interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSSkewX interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSSkewX interface: attribute ax
+PASS CSSSkewX must be primary interface of skewX
+PASS Stringification of skewX
+PASS CSSSkewX interface: skewX must inherit property "ax" with the proper type
+PASS CSSTransformComponent interface: skewX must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skewX must inherit property "toMatrix()" with the proper type
 PASS CSSSkewY interface: existence and properties of interface object
 PASS CSSSkewY interface object length
 PASS CSSSkewY interface object name
@@ -246,6 +382,11 @@
 PASS CSSSkewY interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSSkewY interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSSkewY interface: attribute ay
+PASS CSSSkewY must be primary interface of skewY
+PASS Stringification of skewY
+PASS CSSSkewY interface: skewY must inherit property "ay" with the proper type
+PASS CSSTransformComponent interface: skewY must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: skewY must inherit property "toMatrix()" with the proper type
 PASS CSSPerspective interface: existence and properties of interface object
 PASS CSSPerspective interface object length
 PASS CSSPerspective interface object name
@@ -253,6 +394,11 @@
 PASS CSSPerspective interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSPerspective interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSPerspective interface: attribute length
+PASS CSSPerspective must be primary interface of perspective
+PASS Stringification of perspective
+PASS CSSPerspective interface: perspective must inherit property "length" with the proper type
+PASS CSSTransformComponent interface: perspective must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: perspective must inherit property "toMatrix()" with the proper type
 PASS CSSMatrixComponent interface: existence and properties of interface object
 PASS CSSMatrixComponent interface object length
 PASS CSSMatrixComponent interface object name
@@ -260,6 +406,11 @@
 PASS CSSMatrixComponent interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSMatrixComponent interface: existence and properties of interface prototype object's @@unscopables property
 PASS CSSMatrixComponent interface: attribute matrix
+PASS CSSMatrixComponent must be primary interface of matrix
+PASS Stringification of matrix
+PASS CSSMatrixComponent interface: matrix must inherit property "matrix" with the proper type
+PASS CSSTransformComponent interface: matrix must inherit property "is2D" with the proper type
+PASS CSSTransformComponent interface: matrix must inherit property "toMatrix()" with the proper type
 PASS CSSPositionValue interface: existence and properties of interface object
 PASS CSSPositionValue interface object length
 PASS CSSPositionValue interface object name
@@ -274,8 +425,45 @@
 PASS CSSImageValue interface: existence and properties of interface prototype object
 PASS CSSImageValue interface: existence and properties of interface prototype object's "constructor" property
 PASS CSSImageValue interface: existence and properties of interface prototype object's @@unscopables property
-PASS Element interface: operation computedStyleMap()
 PASS CSSStyleRule interface: attribute styleMap
-FAIL ElementCSSInlineStyle interface: attribute attributeStyleMap assert_own_property: self does not have own property "ElementCSSInlineStyle" expected property "ElementCSSInlineStyle" missing
+PASS CSS namespace: operation escape(CSSOMString)
+PASS CSS namespace: operation number(double)
+PASS CSS namespace: operation percent(double)
+PASS CSS namespace: operation em(double)
+PASS CSS namespace: operation ex(double)
+PASS CSS namespace: operation ch(double)
+FAIL CSS namespace: operation ic(double) assert_own_property: namespace object missing operation "ic" expected property "ic" missing
+PASS CSS namespace: operation rem(double)
+FAIL CSS namespace: operation lh(double) assert_own_property: namespace object missing operation "lh" expected property "lh" missing
+FAIL CSS namespace: operation rlh(double) assert_own_property: namespace object missing operation "rlh" expected property "rlh" missing
+PASS CSS namespace: operation vw(double)
+PASS CSS namespace: operation vh(double)
+FAIL CSS namespace: operation vi(double) assert_own_property: namespace object missing operation "vi" expected property "vi" missing
+FAIL CSS namespace: operation vb(double) assert_own_property: namespace object missing operation "vb" expected property "vb" missing
+PASS CSS namespace: operation vmin(double)
+PASS CSS namespace: operation vmax(double)
+PASS CSS namespace: operation cm(double)
+PASS CSS namespace: operation mm(double)
+PASS CSS namespace: operation Q(double)
+PASS CSS namespace: operation in(double)
+PASS CSS namespace: operation pt(double)
+PASS CSS namespace: operation pc(double)
+PASS CSS namespace: operation px(double)
+PASS CSS namespace: operation deg(double)
+PASS CSS namespace: operation grad(double)
+PASS CSS namespace: operation rad(double)
+PASS CSS namespace: operation turn(double)
+PASS CSS namespace: operation s(double)
+PASS CSS namespace: operation ms(double)
+PASS CSS namespace: operation Hz(double)
+PASS CSS namespace: operation kHz(double)
+PASS CSS namespace: operation dpi(double)
+PASS CSS namespace: operation dpcm(double)
+PASS CSS namespace: operation dppx(double)
+PASS CSS namespace: operation fr(double)
+FAIL SVGElement interface: attribute attributeStyleMap assert_own_property: expected property "attributeStyleMap" missing
+FAIL HTMLElement interface: attribute attributeStyleMap assert_own_property: expected property "attributeStyleMap" missing
+PASS WorkerGlobalScope interface: existence and properties of interface object
+PASS Element interface: operation computedStyleMap()
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces.html b/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces.html
index e5a3638..d21f3f46 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/css/css-typed-om/interfaces.html
@@ -6,23 +6,73 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/WebIDLParser.js"></script>
 <script src="/resources/idlharness.js"></script>
+
+<style>
+  body {
+    font-size: normal;
+  }
+</style>
+
 <script>
 'use strict';
 
-promise_test(t => {
-  return fetch('/interfaces/css-typed-om.idl')
-    .then(response => response.text())
-    .then(idls => {
-      var idl_array = new IdlArray();
-      idl_array.add_idls(idls);
-      idl_array.add_untested_idls('interface Element {};');
-      idl_array.add_untested_idls('interface CSSStyleRule {};');
-      idl_array.add_untested_idls('interface ElementCSSInlineStyle {};');
-      idl_array.add_objects({
-        // TODO: Add more objects.
-      });
-      idl_array.test();
-      t.done();
+idl_test(
+  ['css-typed-om'],
+  ['cssom', 'SVG', 'geometry', 'html', 'dom'],
+  idl_array => {
+    try {
+      self.styleMap = document.styleSheets[0].rules[0].styleMap;
+    } catch (e) {}
+
+    try {
+      self.unitValue = CSSStyleValue.parse('height', '10px');
+    } catch (e) {}
+
+    try {
+      self.mathSum = CSSStyleValue.parse('width', 'calc(100% - 32px)');
+    } catch (e) {}
+
+    try {
+      self.transformValue = CSSStyleValue.parse('transform', 'translateX(0)');
+    } catch (e) {}
+
+    try {
+      self.rotate = CSSStyleValue.parse('transform', 'rotateX(0)')[0];
+    } catch (e) {}
+
+    try {
+      self.scale = CSSStyleValue.parse('transform', 'scale(1)')[0];
+    } catch (e) {}
+
+    try {
+      self.skew = CSSStyleValue.parse('transform', 'skew(0,0)')[0];
+      self.skewX = CSSStyleValue.parse('transform', 'skewX(0)')[0];
+      self.skewY = CSSStyleValue.parse('transform', 'skewY(0)')[0];
+    } catch (e) {}
+
+    try {
+      self.perspective = CSSStyleValue.parse('transform', 'perspective(0)')[0];
+    } catch (e) {}
+
+    try {
+      self.matrix = CSSStyleValue.parse('transform', 'matrix(0, 0, 0, 0, 0, 0)')[0];
+    } catch (e) {}
+
+    idl_array.add_objects({
+      StylePropertyMap: ['styleMap'],
+      CSSUnitValue: ['unitValue'],
+      CSSMathSum: ['mathSum'],
+      CSSTransformValue: ['transformValue'],
+      CSSTranslate: ['transformValue[0]'],
+      CSSRotate: ['rotate'],
+      CSSScale: ['scale'],
+      CSSSkew: ['skew'],
+      CSSSkewX: ['skewX'],
+      CSSSkewY: ['skewY'],
+      CSSPerspective: ['perspective'],
+      CSSMatrixComponent: ['matrix'],
     });
-}, 'CSS Typed OM IDL test');
+  },
+  'CSS Typed OM IDL test'
+);
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/encoding/idlharness.any.js b/third_party/WebKit/LayoutTests/external/wpt/encoding/idlharness.any.js
index 14335ba2..7a057f14 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/encoding/idlharness.any.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/encoding/idlharness.any.js
@@ -10,6 +10,5 @@
       TextEncoder: ['new TextEncoder()'],
       TextDecoder: ['new TextDecoder()']
     });
-  },
-  'Encoding Standard IDL'
+  }
 );
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
index 4f53dbf0..454da38 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/cookie-store.idl
@@ -69,7 +69,6 @@
   USVString? domain = null;
   USVString path = "/";
   boolean secure = true;
-  boolean httpOnly = false;
   CookieSameSite sameSite = "strict";
 };
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/css-typed-om.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/css-typed-om.idl
index 45df520..fa20d2d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/css-typed-om.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/css-typed-om.idl
@@ -1,6 +1,7 @@
 // GENERATED CONTENT - DO NOT EDIT
-// Content of this file was automatically extracted from the CSS Typed OM spec.
-// See https://drafts.css-houdini.org/css-typed-om/
+// Content of this file was automatically extracted from the
+// "CSS Typed OM Level 1" spec.
+// See: https://drafts.css-houdini.org/css-typed-om-1/
 
 [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
 interface CSSStyleValue {
@@ -153,6 +154,14 @@
     readonly attribute CSSNumericArray values;
 };
 
+[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
+ Constructor(CSSNumberish min, CSSNumberish val, CSSNumberish max)]
+interface CSSMathClamp : CSSMathValue {
+    readonly attribute CSSNumericValue min;
+    readonly attribute CSSNumericValue val;
+    readonly attribute CSSNumericValue max;
+};
+
 [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
 interface CSSNumericArray {
     iterable<CSSNumericValue>;
@@ -167,11 +176,9 @@
     "invert",
     "min",
     "max",
+    "clamp",
 };
 
-// FIXME: Uncomment this when IDLHarness supports CSS namespaces:
-// https://github.com/web-platform-tests/wpt/issues/7583
-/*
 partial namespace CSS {
     CSSUnitValue number(double value);
     CSSUnitValue percent(double value);
@@ -220,7 +227,6 @@
     // <flex>
     CSSUnitValue fr(double value);
 };
-*/
 
 [Exposed=(Window, Worker, PaintWorklet, LayoutWorklet),
  Constructor(sequence<CSSTransformComponent> transforms)]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/media-capabilities.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/media-capabilities.idl
index 7b7b078..05b505d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/media-capabilities.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/media-capabilities.idl
@@ -48,20 +48,20 @@
   readonly attribute boolean powerEfficient;
 };
 
-[Exposed=(Window)]
+[Exposed=Window]
 partial interface Navigator {
   [SameObject] readonly attribute MediaCapabilities mediaCapabilities;
 };
 
-[Exposed=(Worker)]
+[Exposed=Worker]
 partial interface WorkerNavigator {
   [SameObject] readonly attribute MediaCapabilities mediaCapabilities;
 };
 
 [Exposed=(Window, Worker)]
 interface MediaCapabilities {
-  Promise<MediaCapabilitiesInfo> decodingInfo(MediaDecodingConfiguration configuration);
-  Promise<MediaCapabilitiesInfo> encodingInfo(MediaEncodingConfiguration configuration);
+  [NewObject] Promise<MediaCapabilitiesInfo> decodingInfo(MediaDecodingConfiguration configuration);
+  [NewObject] Promise<MediaCapabilitiesInfo> encodingInfo(MediaEncodingConfiguration configuration);
 };
 
 interface ScreenLuminance {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/navigation-timing.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/navigation-timing.idl
index 59f29fbd..7b6f75d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/navigation-timing.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/navigation-timing.idl
@@ -5,63 +5,67 @@
 
 [Exposed=Window]
 interface PerformanceNavigationTiming : PerformanceResourceTiming {
-    readonly attribute DOMHighResTimeStamp unloadEventStart;
-    readonly attribute DOMHighResTimeStamp unloadEventEnd;
-    readonly attribute DOMHighResTimeStamp domInteractive;
-    readonly attribute DOMHighResTimeStamp domContentLoadedEventStart;
-    readonly attribute DOMHighResTimeStamp domContentLoadedEventEnd;
-    readonly attribute DOMHighResTimeStamp domComplete;
-    readonly attribute DOMHighResTimeStamp loadEventStart;
-    readonly attribute DOMHighResTimeStamp loadEventEnd;
-    readonly attribute NavigationType      type;
-    readonly attribute unsigned short      redirectCount;
+    readonly        attribute DOMHighResTimeStamp unloadEventStart;
+    readonly        attribute DOMHighResTimeStamp unloadEventEnd;
+    readonly        attribute DOMHighResTimeStamp domInteractive;
+    readonly        attribute DOMHighResTimeStamp domContentLoadedEventStart;
+    readonly        attribute DOMHighResTimeStamp domContentLoadedEventEnd;
+    readonly        attribute DOMHighResTimeStamp domComplete;
+    readonly        attribute DOMHighResTimeStamp loadEventStart;
+    readonly        attribute DOMHighResTimeStamp loadEventEnd;
+    readonly        attribute NavigationType type;
+    readonly        attribute unsigned short redirectCount;
     [Default] object toJSON();
 };
+
 enum NavigationType {
     "navigate",
     "reload",
     "back_forward",
     "prerender"
 };
+
 [Exposed=Window]
 interface PerformanceTiming {
-    readonly attribute unsigned long long navigationStart;
-    readonly attribute unsigned long long unloadEventStart;
-    readonly attribute unsigned long long unloadEventEnd;
-    readonly attribute unsigned long long redirectStart;
-    readonly attribute unsigned long long redirectEnd;
-    readonly attribute unsigned long long fetchStart;
-    readonly attribute unsigned long long domainLookupStart;
-    readonly attribute unsigned long long domainLookupEnd;
-    readonly attribute unsigned long long connectStart;
-    readonly attribute unsigned long long connectEnd;
-    readonly attribute unsigned long long secureConnectionStart;
-    readonly attribute unsigned long long requestStart;
-    readonly attribute unsigned long long responseStart;
-    readonly attribute unsigned long long responseEnd;
-    readonly attribute unsigned long long domLoading;
-    readonly attribute unsigned long long domInteractive;
-    readonly attribute unsigned long long domContentLoadedEventStart;
-    readonly attribute unsigned long long domContentLoadedEventEnd;
-    readonly attribute unsigned long long domComplete;
-    readonly attribute unsigned long long loadEventStart;
-    readonly attribute unsigned long long loadEventEnd;
-    [Default] object toJSON();
+  readonly attribute unsigned long long navigationStart;
+  readonly attribute unsigned long long unloadEventStart;
+  readonly attribute unsigned long long unloadEventEnd;
+  readonly attribute unsigned long long redirectStart;
+  readonly attribute unsigned long long redirectEnd;
+  readonly attribute unsigned long long fetchStart;
+  readonly attribute unsigned long long domainLookupStart;
+  readonly attribute unsigned long long domainLookupEnd;
+  readonly attribute unsigned long long connectStart;
+  readonly attribute unsigned long long connectEnd;
+  readonly attribute unsigned long long secureConnectionStart;
+  readonly attribute unsigned long long requestStart;
+  readonly attribute unsigned long long responseStart;
+  readonly attribute unsigned long long responseEnd;
+  readonly attribute unsigned long long domLoading;
+  readonly attribute unsigned long long domInteractive;
+  readonly attribute unsigned long long domContentLoadedEventStart;
+  readonly attribute unsigned long long domContentLoadedEventEnd;
+  readonly attribute unsigned long long domComplete;
+  readonly attribute unsigned long long loadEventStart;
+  readonly attribute unsigned long long loadEventEnd;
+  [Default] object toJSON();
 };
+
 [Exposed=Window]
 interface PerformanceNavigation {
-    const unsigned short TYPE_NAVIGATE = 0;
-    const unsigned short TYPE_RELOAD = 1;
-    const unsigned short TYPE_BACK_FORWARD = 2;
-    const unsigned short TYPE_RESERVED = 255;
-    readonly attribute unsigned short type;
-    readonly attribute unsigned short redirectCount;
-    [Default] object toJSON();
+  const unsigned short TYPE_NAVIGATE = 0;
+  const unsigned short TYPE_RELOAD = 1;
+  const unsigned short TYPE_BACK_FORWARD = 2;
+  const unsigned short TYPE_RESERVED = 255;
+  readonly attribute unsigned short type;
+  readonly attribute unsigned short redirectCount;
+  [Default] object toJSON();
 };
+
 [Exposed=Window]
 partial interface Performance {
-    [SameObject]
-    readonly attribute PerformanceTiming     timing;
-    [SameObject]
-    readonly attribute PerformanceNavigation navigation;
+  [SameObject]
+  readonly attribute PerformanceTiming timing;
+  [SameObject]
+  readonly attribute PerformanceNavigation navigation;
 };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/interfaces.html b/third_party/WebKit/LayoutTests/external/wpt/interfaces/notifications.idl
similarity index 61%
rename from third_party/WebKit/LayoutTests/external/wpt/notifications/interfaces.html
rename to third_party/WebKit/LayoutTests/external/wpt/interfaces/notifications.idl
index a348450..2524933d 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/notifications/interfaces.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/notifications.idl
@@ -1,22 +1,8 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>Notification interface IDL tests</title>
-<div id=log></div>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<script src=/resources/WebIDLParser.js></script>
-<script src=/resources/idlharness.js></script>
-<script type=text/plain class=untested>
-interface EventTarget {
-  void addEventListener(DOMString type, EventListener? callback, optional boolean capture /* = false */);
-  void removeEventListener(DOMString type, EventListener? callback, optional boolean capture /* = false */);
-  boolean dispatchEvent(Event event);
-};
-[TreatNonCallableAsNull]
-callback EventHandlerNonNull = any (Event event);
-typedef EventHandlerNonNull? EventHandler;
-</script>
-<script type=text/plain>
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the
+// "Notifications API Standard" spec.
+// See: https://notifications.spec.whatwg.org/
+
 [Constructor(DOMString title, optional NotificationOptions options),
  Exposed=(Window,Worker)]
 interface Notification : EventTarget {
@@ -85,19 +71,29 @@
 };
 
 callback NotificationPermissionCallback = void (NotificationPermission permission);
-</script>
-<script>
-"use strict";
-var idlArray = new IdlArray();
-[].forEach.call(document.querySelectorAll("script[type=text\\/plain]"), function(node) {
-  if (node.className == "untested") {
-    idlArray.add_untested_idls(node.textContent);
-  } else {
-    idlArray.add_idls(node.textContent);
-  }
-});
-idlArray.add_objects({
-  Notification: ['new Notification("Running idlharness.")'],
-});
-idlArray.test();
-</script>
+
+dictionary GetNotificationOptions {
+  DOMString tag = "";
+};
+
+partial interface ServiceWorkerRegistration {
+  Promise<void> showNotification(DOMString title, optional NotificationOptions options);
+  Promise<sequence<Notification>> getNotifications(optional GetNotificationOptions filter);
+};
+
+[Constructor(DOMString type, NotificationEventInit eventInitDict),
+ Exposed=ServiceWorker]
+interface NotificationEvent : ExtendableEvent {
+  readonly attribute Notification notification;
+  readonly attribute DOMString action;
+};
+
+dictionary NotificationEventInit : ExtendableEventInit {
+  required Notification notification;
+  DOMString action = "";
+};
+
+partial interface ServiceWorkerGlobalScope {
+  attribute EventHandler onnotificationclick;
+  attribute EventHandler onnotificationclose;
+};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/payment-handler.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/payment-handler.idl
index e14a54b..a9e3525 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/payment-handler.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/payment-handler.idl
@@ -4,83 +4,87 @@
 // See: https://w3c.github.io/payment-handler/
 
 partial interface ServiceWorkerRegistration {
-    [SameObject]
-    readonly attribute PaymentManager paymentManager;
+  [SameObject] readonly attribute PaymentManager paymentManager;
 };
-[SecureContext,
- Exposed=(Window,Worker)]
+
+[SecureContext, Exposed=(Window,Worker)]
 interface PaymentManager {
-    [SameObject]
-    readonly attribute PaymentInstruments instruments;
-    [Exposed=Window] static Promise<PermissionState> requestPermission();
-             attribute DOMString          userHint;
+  [SameObject] readonly attribute PaymentInstruments instruments;
+  [Exposed=Window] static Promise<PermissionState> requestPermission();
+  attribute DOMString userHint;
 };
-[SecureContext,
- Exposed=(Window,Worker)]
+
+[SecureContext, Exposed=(Window,Worker)]
 interface PaymentInstruments {
-    Promise<boolean>             delete(DOMString instrumentKey);
-    Promise<any>                 get(DOMString instrumentKey);
+    Promise<boolean> delete(DOMString instrumentKey);
+    Promise<any> get(DOMString instrumentKey);
     Promise<sequence<DOMString>> keys();
-    Promise<boolean>             has(DOMString instrumentKey);
-    Promise<void>                set(DOMString instrumentKey,
-                                     PaymentInstrument details);
-    Promise<void>                clear();
+    Promise<boolean> has(DOMString instrumentKey);
+    Promise<void> set(DOMString instrumentKey, PaymentInstrument details);
+    Promise<void> clear();
 };
+
 dictionary PaymentInstrument {
-    required DOMString             name;
-             sequence<ImageObject> icons;
-             DOMString             method;
-             object                capabilities;
+  required DOMString name;
+  sequence<ImageObject> icons;
+  DOMString method;
+  object capabilities;
 };
+
 dictionary ImageObject {
     required USVString src;
-             DOMString sizes;
-             DOMString type;
+    DOMString sizes;
+    DOMString type;
 };
+
 partial interface ServiceWorkerGlobalScope {
-    attribute EventHandler oncanmakepayment;
+  attribute EventHandler oncanmakepayment;
 };
-[Constructor(DOMString type, CanMakePaymentEventInit eventInitDict),
- Exposed=ServiceWorker]
+
+[Constructor(DOMString type, CanMakePaymentEventInit eventInitDict), Exposed=ServiceWorker]
 interface CanMakePaymentEvent : ExtendableEvent {
-    readonly attribute USVString                           topOrigin;
-    readonly attribute USVString                           paymentRequestOrigin;
-    readonly attribute FrozenArray<PaymentMethodData>      methodData;
-    readonly attribute FrozenArray<PaymentDetailsModifier> modifiers;
-    void respondWith(Promise<boolean> canMakePaymentResponse);
+  readonly attribute USVString topOrigin;
+  readonly attribute USVString paymentRequestOrigin;
+  readonly attribute FrozenArray<PaymentMethodData> methodData;
+  readonly attribute FrozenArray<PaymentDetailsModifier> modifiers;
+  void respondWith(Promise<boolean> canMakePaymentResponse);
 };
+
 dictionary CanMakePaymentEventInit : ExtendableEventInit {
-    USVString                        topOrigin;
-    USVString                        paymentRequestOrigin;
-    sequence<PaymentMethodData>      methodData;
-    sequence<PaymentDetailsModifier> modifiers;
+  USVString topOrigin;
+  USVString paymentRequestOrigin;
+  sequence<PaymentMethodData> methodData;
+  sequence<PaymentDetailsModifier> modifiers;
 };
+
 partial interface ServiceWorkerGlobalScope {
-    attribute EventHandler onpaymentrequest;
+  attribute EventHandler onpaymentrequest;
 };
-[Constructor(DOMString type, PaymentRequestEventInit eventInitDict),
- Exposed=ServiceWorker]
+
+[Constructor(DOMString type, PaymentRequestEventInit eventInitDict), Exposed=ServiceWorker]
 interface PaymentRequestEvent : ExtendableEvent {
-    readonly attribute USVString                           topOrigin;
-    readonly attribute USVString                           paymentRequestOrigin;
-    readonly attribute DOMString                           paymentRequestId;
-    readonly attribute FrozenArray<PaymentMethodData>      methodData;
-    readonly attribute object                              total;
-    readonly attribute FrozenArray<PaymentDetailsModifier> modifiers;
-    readonly attribute DOMString                           instrumentKey;
-    Promise<WindowClient?> openWindow(USVString url);
-    void                   respondWith(Promise<PaymentHandlerResponse> handlerResponsePromise);
+  readonly attribute USVString topOrigin;
+  readonly attribute USVString paymentRequestOrigin;
+  readonly attribute DOMString paymentRequestId;
+  readonly attribute FrozenArray<PaymentMethodData> methodData;
+  readonly attribute object total;
+  readonly attribute FrozenArray<PaymentDetailsModifier> modifiers;
+  readonly attribute DOMString instrumentKey;
+  Promise<WindowClient?> openWindow(USVString url);
+  void respondWith(Promise<PaymentHandlerResponse> handlerResponsePromise);
 };
+
 dictionary PaymentRequestEventInit : ExtendableEventInit {
-    USVString                        topOrigin;
-    USVString                        paymentRequestOrigin;
-    DOMString                        paymentRequestId;
-    sequence<PaymentMethodData>      methodData;
-    PaymentCurrencyAmount            total;
-    sequence<PaymentDetailsModifier> modifiers;
-    DOMString                        instrumentKey;
+  USVString topOrigin;
+  USVString paymentRequestOrigin;
+  DOMString paymentRequestId;
+  sequence<PaymentMethodData> methodData;
+  PaymentCurrencyAmount total;
+  sequence<PaymentDetailsModifier> modifiers;
+  DOMString instrumentKey;
 };
+
 dictionary PaymentHandlerResponse {
-    DOMString methodName;
-    object    details;
+DOMString methodName;
+object details;
 };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/push-api.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/push-api.idl
index 9047d978..fe7331d6 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/push-api.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/push-api.idl
@@ -5,92 +5,89 @@
 
 [SecureContext]
 partial interface ServiceWorkerRegistration {
-    readonly attribute PushManager pushManager;
+  readonly attribute PushManager pushManager;
 };
-[Exposed=(Window,Worker),
- SecureContext]
+
+[Exposed=(Window,Worker), SecureContext]
 interface PushManager {
-    [SameObject]
-    static readonly attribute FrozenArray<DOMString> supportedContentEncodings;
+  [SameObject] static readonly attribute FrozenArray<DOMString> supportedContentEncodings;
 
-    Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
-    Promise<PushSubscription?>   getSubscription();
-    Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
+  Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options);
+  Promise<PushSubscription?> getSubscription();
+  Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 };
+
 dictionary PushSubscriptionOptionsInit {
-    boolean                      userVisibleOnly = false;
-    (BufferSource or DOMString)? applicationServerKey = null;
+  boolean userVisibleOnly = false;
+  (BufferSource or DOMString)? applicationServerKey = null;
 };
 
-[Exposed=(Window,Worker),
- SecureContext]
+[Exposed=(Window,Worker), SecureContext]
 interface PushSubscriptionOptions {
-    readonly attribute boolean      userVisibleOnly;
-    [SameObject]
-    readonly attribute ArrayBuffer? applicationServerKey;
+  readonly attribute boolean userVisibleOnly;
+  [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
 };
-[Exposed=(Window,Worker),
- SecureContext]
-interface PushSubscription {
-    readonly attribute USVString               endpoint;
-    readonly attribute DOMTimeStamp?           expirationTime;
-    [SameObject]
-    readonly attribute PushSubscriptionOptions options;
-    ArrayBuffer?         getKey(PushEncryptionKeyName name);
-    Promise<boolean>     unsubscribe();
 
-    PushSubscriptionJSON toJSON();
+[Exposed=(Window,Worker), SecureContext]
+interface PushSubscription {
+  readonly attribute USVString endpoint;
+  readonly attribute DOMTimeStamp? expirationTime;
+  [SameObject] readonly attribute PushSubscriptionOptions options;
+  ArrayBuffer? getKey(PushEncryptionKeyName name);
+  Promise<boolean> unsubscribe();
+
+  PushSubscriptionJSON toJSON();
 };
 
 dictionary PushSubscriptionJSON {
-    USVString                    endpoint;
-    DOMTimeStamp?                expirationTime;
-    record<DOMString, USVString> keys;
+  USVString endpoint;
+  DOMTimeStamp? expirationTime;
+  record<DOMString, USVString> keys;
 };
+
 enum PushEncryptionKeyName {
-    "p256dh",
-    "auth"
+  "p256dh",
+  "auth"
 };
-[Exposed=ServiceWorker,
- SecureContext]
+
+[Exposed=ServiceWorker, SecureContext]
 interface PushMessageData {
-    ArrayBuffer arrayBuffer();
-    Blob        blob();
-    any         json();
-    USVString   text();
+  ArrayBuffer arrayBuffer();
+  Blob blob();
+  any json();
+  USVString text();
 };
-[Exposed=ServiceWorker,
- SecureContext]
+
+[Exposed=ServiceWorker, SecureContext]
 partial interface ServiceWorkerGlobalScope {
-    attribute EventHandler onpush;
-    attribute EventHandler onpushsubscriptionchange;
+  attribute EventHandler onpush;
+  attribute EventHandler onpushsubscriptionchange;
 };
+
 typedef (BufferSource or USVString) PushMessageDataInit;
 
 dictionary PushEventInit : ExtendableEventInit {
-    PushMessageDataInit data;
+  PushMessageDataInit data;
 };
 
-[Constructor(DOMString type, optional PushEventInit eventInitDict),
- Exposed=ServiceWorker,
- SecureContext]
+[Constructor(DOMString type, optional PushEventInit eventInitDict), Exposed=ServiceWorker, SecureContext]
 interface PushEvent : ExtendableEvent {
-    readonly attribute PushMessageData? data;
-};
-dictionary PushSubscriptionChangeInit : ExtendableEventInit {
-    PushSubscription newSubscription = null;
-    PushSubscription oldSubscription = null;
+  readonly attribute PushMessageData? data;
 };
 
-[Constructor(DOMString type, optional PushSubscriptionChangeInit eventInitDict),
- Exposed=ServiceWorker,
- SecureContext]
-interface PushSubscriptionChangeEvent : ExtendableEvent {
-    readonly attribute PushSubscription? newSubscription;
-    readonly attribute PushSubscription? oldSubscription;
+dictionary PushSubscriptionChangeInit : ExtendableEventInit {
+  PushSubscription newSubscription = null;
+  PushSubscription oldSubscription = null;
 };
+
+[Constructor(DOMString type, optional PushSubscriptionChangeInit eventInitDict), Exposed=ServiceWorker, SecureContext]
+interface PushSubscriptionChangeEvent : ExtendableEvent {
+  readonly attribute PushSubscription? newSubscription;
+  readonly attribute PushSubscription? oldSubscription;
+};
+
 enum PushPermissionState {
-    "denied",
-    "granted",
-    "prompt",
+  "denied",
+  "granted",
+  "prompt",
 };
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-stats.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-stats.idl
new file mode 100644
index 0000000..0b2474a5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webrtc-stats.idl
@@ -0,0 +1,318 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the
+// "Identifiers for WebRTC's Statistics API" spec.
+// See: https://w3c.github.io/webrtc-stats/
+
+dictionary RTCStats {
+DOMHighResTimeStamp timestamp;
+RTCStatsType type;
+DOMString id;
+    };
+
+enum RTCStatsType {
+"codec",
+"inbound-rtp",
+"outbound-rtp",
+"remote-inbound-rtp",
+"remote-outbound-rtp",
+"csrc",
+"peer-connection",
+"data-channel",
+"stream",
+"track",
+"sender",
+"receiver",
+"transport",
+"candidate-pair",
+"local-candidate",
+"remote-candidate",
+"certificate"
+};
+
+dictionary RTCRtpStreamStats : RTCStats {
+             unsigned long ssrc;
+             DOMString kind;
+             DOMString transportId;
+             DOMString codecId;
+             unsigned long firCount;
+             unsigned long pliCount;
+             unsigned long nackCount;
+             unsigned long sliCount;
+             unsigned long long qpSum;
+};
+
+dictionary RTCCodecStats : RTCStats {
+             unsigned long payloadType;
+             RTCCodecType codecType;
+             DOMString transportId;
+             DOMString mimeType;
+             unsigned long clockRate;
+             unsigned long channels;
+             DOMString sdpFmtpLine;
+             DOMString implementation;
+};
+
+enum RTCCodecType {
+    "encode",
+    "decode",
+};
+
+dictionary RTCReceivedRtpStreamStats : RTCRtpStreamStats {
+             unsigned long packetsReceived;
+             long packetsLost;
+             double jitter;
+             unsigned long packetsDiscarded;
+             unsigned long packetsRepaired;
+             unsigned long burstPacketsLost;
+             unsigned long burstPacketsDiscarded;
+             unsigned long burstLossCount;
+             unsigned long burstDiscardCount;
+             double burstLossRate;
+             double burstDiscardRate;
+             double gapLossRate;
+             double gapDiscardRate;
+};
+
+dictionary RTCInboundRtpStreamStats : RTCReceivedRtpStreamStats {
+             DOMString trackId;
+             DOMString receiverId;
+             DOMString remoteId;
+             unsigned long framesDecoded;
+             DOMHighResTimeStamp lastPacketReceivedTimestamp;
+             double averageRtcpInterval;
+             unsigned long fecPacketsReceived;
+             unsigned long long bytesReceived;
+             unsigned long packetsFailedDecryption;
+             unsigned long packetsDuplicated;
+             record<USVString, unsigned long> perDscpPacketsReceived;
+            };
+
+dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats {
+             DOMString localId;
+             double roundTripTime;
+             double fractionLost;
+};
+
+dictionary RTCSentRtpStreamStats : RTCRtpStreamStats {
+             unsigned long packetsSent;
+             unsigned long packetsDiscardedOnSend;
+             unsigned long fecPacketsSent;
+             unsigned long long bytesSent;
+             unsigned long long bytesDiscardedOnSend;
+};
+
+dictionary RTCOutboundRtpStreamStats : RTCSentRtpStreamStats {
+             DOMString trackId;
+             DOMString senderId;
+             DOMString remoteId;
+             DOMHighResTimeStamp lastPacketSentTimestamp;
+             double targetBitrate;
+             unsigned long framesEncoded;
+             double totalEncodeTime;
+             double averageRtcpInterval;
+             RTCQualityLimitationReason qualityLimitationReason;
+             record<DOMString, double> qualityLimitationDurations;
+             record<USVString, unsigned long> perDscpPacketsSent;
+};
+
+enum RTCQualityLimitationReason {
+            "none",
+            "cpu",
+            "bandwidth",
+            "other",
+          };
+
+dictionary RTCRemoteOutboundRtpStreamStats : RTCSentRtpStreamStats {
+             DOMString localId;
+             DOMHighResTimeStamp remoteTimestamp;
+};
+
+dictionary RTCRtpContributingSourceStats : RTCStats {
+             unsigned long contributorSsrc;
+             DOMString inboundRtpStreamId;
+             unsigned long packetsContributedTo;
+             double audioLevel;
+};
+
+dictionary RTCPeerConnectionStats : RTCStats {
+            unsigned long dataChannelsOpened;
+            unsigned long dataChannelsClosed;
+            unsigned long dataChannelsRequested;
+            unsigned long dataChannelsAccepted;
+};
+
+dictionary RTCMediaStreamStats : RTCStats {
+             DOMString streamIdentifier;
+             sequence<DOMString> trackIds;
+};
+
+dictionary RTCMediaHandlerStats : RTCStats {
+             DOMString trackIdentifier;
+             boolean remoteSource;
+             boolean ended;
+             DOMString kind;
+             RTCPriorityType priority;
+};
+
+dictionary RTCVideoHandlerStats : RTCMediaHandlerStats {
+             unsigned long frameWidth;
+             unsigned long frameHeight;
+             double framesPerSecond;
+};
+
+dictionary RTCVideoSenderStats : RTCVideoHandlerStats {
+             unsigned long framesCaptured;
+             unsigned long framesSent;
+             unsigned long hugeFramesSent;
+             unsigned long keyFramesSent;
+};
+
+dictionary RTCSenderVideoTrackAttachmentStats : RTCVideoSenderStats {
+};
+
+dictionary RTCVideoReceiverStats : RTCVideoHandlerStats {
+             DOMHighResTimeStamp estimatedPlayoutTimestamp;
+             double jitterBufferDelay;
+             unsigned long long jitterBufferEmittedCount;
+             unsigned long framesReceived;
+             unsigned long keyFramesReceived;
+             unsigned long framesDecoded;
+             unsigned long framesDropped;
+             unsigned long partialFramesLost;
+             unsigned long fullFramesLost;
+};
+
+dictionary RTCAudioHandlerStats : RTCMediaHandlerStats {
+             double audioLevel;
+             double totalAudioEnergy;
+             boolean voiceActivityFlag;
+             double totalSamplesDuration;
+};
+
+dictionary RTCAudioSenderStats : RTCAudioHandlerStats {
+             double echoReturnLoss;
+             double echoReturnLossEnhancement;
+             unsigned long long totalSamplesSent;
+};
+
+dictionary RTCSenderAudioTrackAttachmentStats : RTCAudioSenderStats {
+};
+
+dictionary RTCAudioReceiverStats : RTCAudioHandlerStats {
+             DOMHighResTimeStamp estimatedPlayoutTimestamp;
+             double jitterBufferDelay;
+             unsigned long long jitterBufferEmittedCount;
+             unsigned long long totalSamplesReceived;
+             unsigned long long concealedSamples;
+             unsigned long long concealmentEvents;
+};
+
+dictionary RTCDataChannelStats : RTCStats {
+             DOMString label;
+             DOMString protocol;
+             long dataChannelIdentifier;
+             DOMString transportId;
+             RTCDataChannelState state;
+             unsigned long messagesSent;
+             unsigned long long bytesSent;
+             unsigned long messagesReceived;
+             unsigned long long bytesReceived;
+};
+
+dictionary RTCTransportStats : RTCStats {
+             unsigned long packetsSent;
+             unsigned long packetsReceived;
+             unsigned long long bytesSent;
+             unsigned long long bytesReceived;
+             DOMString rtcpTransportStatsId;
+             RTCIceRole iceRole;
+             RTCDtlsTransportState dtlsState;
+             DOMString selectedCandidatePairId;
+             DOMString localCertificateId;
+             DOMString remoteCertificateId;
+             DOMString dtlsCipher;
+             DOMString srtpCipher;
+};
+
+dictionary RTCIceCandidateStats : RTCStats {
+             DOMString transportId;
+             RTCNetworkType networkType;
+             DOMString ip;
+             long port;
+             DOMString protocol;
+             RTCIceCandidateType candidateType;
+             long priority;
+             DOMString url;
+             DOMString relayProtocol;
+             boolean deleted = false;
+};
+
+enum RTCNetworkType {
+    "bluetooth",
+    "cellular",
+    "ethernet",
+    "wifi",
+    "wimax",
+    "vpn",
+    "unknown"
+};
+
+dictionary RTCIceCandidatePairStats : RTCStats {
+             DOMString transportId;
+             DOMString localCandidateId;
+             DOMString remoteCandidateId;
+             RTCStatsIceCandidatePairState state;
+             boolean nominated;
+             unsigned long packetsSent;
+             unsigned long packetsReceived;
+             unsigned long long bytesSent;
+             unsigned long long bytesReceived;
+             DOMHighResTimeStamp lastPacketSentTimestamp;
+             DOMHighResTimeStamp lastPacketReceivedTimestamp;
+             DOMHighResTimeStamp firstRequestTimestamp;
+             DOMHighResTimeStamp lastRequestTimestamp;
+             DOMHighResTimeStamp lastResponseTimestamp;
+             double totalRoundTripTime;
+             double currentRoundTripTime;
+             double availableOutgoingBitrate;
+             double availableIncomingBitrate;
+             unsigned long circuitBreakerTriggerCount;
+             unsigned long long requestsReceived;
+             unsigned long long requestsSent;
+             unsigned long long responsesReceived;
+             unsigned long long responsesSent;
+             unsigned long long retransmissionsReceived;
+             unsigned long long retransmissionsSent;
+             unsigned long long consentRequestsSent;
+             DOMHighResTimeStamp consentExpiredTimestamp;
+};
+
+enum RTCStatsIceCandidatePairState {
+    "frozen",
+    "waiting",
+    "in-progress",
+    "failed",
+    "succeeded"
+};
+
+dictionary RTCCertificateStats : RTCStats {
+             DOMString fingerprint;
+             DOMString fingerprintAlgorithm;
+             DOMString base64Certificate;
+             DOMString issuerCertificateId;
+};
+
+partial dictionary RTCIceCandidateStats {
+           boolean isRemote;
+        };
+
+partial dictionary RTCIceCandidatePairStats {
+          double totalRtt;
+          double currentRtt;
+          unsigned long long priority;
+          };
+
+partial dictionary RTCRTPStreamStats {
+             DOMString mediaType;
+             double averageRTCPInterval;
+};
diff --git a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webxr.idl b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webxr.idl
index ca6d3fc..2b90859 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/interfaces/webxr.idl
+++ b/third_party/WebKit/LayoutTests/external/wpt/interfaces/webxr.idl
@@ -1,3 +1,8 @@
+// GENERATED CONTENT - DO NOT EDIT
+// Content of this file was automatically extracted from the
+// "WebXR Device API" spec.
+// See: https://immersive-web.github.io/webxr/
+
 [SecureContext, Exposed=Window] interface XR : EventTarget {
   // Methods
   Promise<XRDevice?> requestDevice();
@@ -22,11 +27,18 @@
   XRPresentationContext outputContext;
 };
 
+enum XREnvironmentBlendMode {
+  "opaque",
+  "additive",
+  "alpha-blend",
+};
+
 [SecureContext, Exposed=Window] interface XRSession : EventTarget {
   // Attributes
   readonly attribute XRDevice device;
   readonly attribute boolean immersive;
   readonly attribute XRPresentationContext outputContext;
+  readonly attribute XREnvironmentBlendMode environmentBlendMode;
 
   attribute double depthNear;
   attribute double depthFar;
@@ -35,6 +47,8 @@
   // Methods
   Promise<XRFrameOfReference> requestFrameOfReference(XRFrameOfReferenceType type, optional XRFrameOfReferenceOptions options);
 
+  FrozenArray<XRInputSource> getInputSources();
+
   long requestAnimationFrame(XRFrameRequestCallback callback);
   void cancelAnimationFrame(long handle);
 
@@ -45,14 +59,19 @@
   attribute EventHandler onfocus;
   attribute EventHandler onresetpose;
   attribute EventHandler onend;
+  attribute EventHandler onselect;
+  attribute EventHandler onselectstart;
+  attribute EventHandler onselectend;
 };
 
 callback XRFrameRequestCallback = void (DOMHighResTimeStamp time, XRFrame frame);
 
 [SecureContext, Exposed=Window] interface XRFrame {
+  readonly attribute XRSession session;
   readonly attribute FrozenArray<XRView> views;
 
   XRDevicePose? getDevicePose(XRCoordinateSystem coordinateSystem);
+  XRInputPose? getInputPose(XRInputSource inputSource, XRCoordinateSystem coordinateSystem);
 };
 
 [SecureContext, Exposed=Window] interface XRCoordinateSystem : EventTarget {
@@ -109,6 +128,38 @@
   Float32Array getViewMatrix(XRView view);
 };
 
+enum XRHandedness {
+  "",
+  "left",
+  "right"
+};
+
+enum XRTargetRayMode {
+  "gaze",
+  "tracked-pointer",
+  "screen"
+};
+
+[SecureContext, Exposed=Window]
+interface XRInputSource {
+  readonly attribute XRHandedness handedness;
+  readonly attribute XRTargetRayMode targetRayMode;
+};
+
+[SecureContext, Exposed=Window]
+interface XRRay {
+  readonly attribute DOMPointReadOnly origin;
+  readonly attribute DOMPointReadOnly direction;
+  readonly attribute Float32Array transformMatrix;
+};
+
+[SecureContext, Exposed=Window]
+interface XRInputPose {
+  readonly attribute boolean emulatedPosition;
+  readonly attribute XRRay targetRay;
+  readonly attribute Float32Array? gripMatrix;
+};
+
 [SecureContext, Exposed=Window] interface XRLayer {};
 
 typedef (WebGLRenderingContext or
@@ -120,7 +171,7 @@
   boolean stencil = false;
   boolean alpha = true;
   boolean multiview = false;
-  double framebufferScaleFactor;
+  double framebufferScaleFactor = 1.0;
 };
 
 [SecureContext, Exposed=Window, Constructor(XRSession session,
@@ -143,6 +194,9 @@
   // Methods
   XRViewport? getViewport(XRView view);
   void requestViewportScaling(double viewportScaleFactor);
+
+  // Static Methods
+  static double getNativeFramebufferScaleFactor(XRSession session);
 };
 
 partial dictionary WebGLContextAttributes {
@@ -166,6 +220,17 @@
   required XRSession session;
 };
 
+[SecureContext, Exposed=Window, Constructor(DOMString type, XRInputSourceEventInit eventInitDict)]
+interface XRInputSourceEvent : Event {
+  readonly attribute XRFrame frame;
+  readonly attribute XRInputSource inputSource;
+};
+
+dictionary XRInputSourceEventInit : EventInit {
+  required XRFrame frame;
+  required XRInputSource inputSource;
+};
+
 [SecureContext, Exposed=Window, Constructor(DOMString type, XRCoordinateSystemEventInit eventInitDict)]
 interface XRCoordinateSystemEvent : Event {
   readonly attribute XRCoordinateSystem coordinateSystem;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any-expected.txt
index 739ee8d..386002af 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any-expected.txt
@@ -1,4 +1,6 @@
 This is a testharness.js-based test.
+Found 63 tests; 37 PASS, 26 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idlharness
 PASS Test IDL implementation of Media Capabilities
 PASS Partial interface Navigator: original interface defined
 PASS Partial interface Navigator: valid exposure set
@@ -14,6 +16,16 @@
 PASS MediaCapabilitiesInfo interface: attribute supported
 PASS MediaCapabilitiesInfo interface: attribute smooth
 PASS MediaCapabilitiesInfo interface: attribute powerEfficient
+PASS MediaCapabilitiesInfo must be primary interface of decodingInfo
+PASS Stringification of decodingInfo
+PASS MediaCapabilitiesInfo interface: decodingInfo must inherit property "supported" with the proper type
+PASS MediaCapabilitiesInfo interface: decodingInfo must inherit property "smooth" with the proper type
+PASS MediaCapabilitiesInfo interface: decodingInfo must inherit property "powerEfficient" with the proper type
+PASS MediaCapabilitiesInfo must be primary interface of encodingInfo
+PASS Stringification of encodingInfo
+PASS MediaCapabilitiesInfo interface: encodingInfo must inherit property "supported" with the proper type
+PASS MediaCapabilitiesInfo interface: encodingInfo must inherit property "smooth" with the proper type
+PASS MediaCapabilitiesInfo interface: encodingInfo must inherit property "powerEfficient" with the proper type
 PASS MediaCapabilities interface: existence and properties of interface object
 PASS MediaCapabilities interface object length
 PASS MediaCapabilities interface object name
@@ -22,6 +34,12 @@
 PASS MediaCapabilities interface: existence and properties of interface prototype object's @@unscopables property
 PASS MediaCapabilities interface: operation decodingInfo(MediaDecodingConfiguration)
 PASS MediaCapabilities interface: operation encodingInfo(MediaEncodingConfiguration)
+FAIL MediaCapabilities must be primary interface of navigatior.mediaCapabilities assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL Stringification of navigatior.mediaCapabilities assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "decodingInfo(MediaDecodingConfiguration)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: calling decodingInfo(MediaDecodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "encodingInfo(MediaEncodingConfiguration)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: calling encodingInfo(MediaEncodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
 FAIL ScreenLuminance interface: existence and properties of interface object assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
 FAIL ScreenLuminance interface object length assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
 FAIL ScreenLuminance interface object name assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
@@ -31,11 +49,19 @@
 FAIL ScreenLuminance interface: attribute min assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
 FAIL ScreenLuminance interface: attribute max assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
 FAIL ScreenLuminance interface: attribute maxAverage assert_own_property: self does not have own property "ScreenLuminance" expected property "ScreenLuminance" missing
+FAIL ScreenLuminance must be primary interface of screen.luminance assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of screen.luminance assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL ScreenLuminance interface: screen.luminance must inherit property "min" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL ScreenLuminance interface: screen.luminance must inherit property "max" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL ScreenLuminance interface: screen.luminance must inherit property "maxAverage" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
 PASS Navigator interface: attribute mediaCapabilities
 PASS Navigator interface: navigator must inherit property "mediaCapabilities" with the proper type
 PASS WorkerNavigator interface: existence and properties of interface object
 FAIL Screen interface: attribute colorGamut assert_true: The prototype object must have a property "colorGamut" expected true got false
 FAIL Screen interface: attribute luminance assert_true: The prototype object must have a property "luminance" expected true got false
 FAIL Screen interface: attribute onchange assert_true: The prototype object must have a property "onchange" expected true got false
+FAIL Screen interface: screen must inherit property "colorGamut" with the proper type assert_inherits: property "colorGamut" not found in prototype chain
+FAIL Screen interface: screen must inherit property "luminance" with the proper type assert_inherits: property "luminance" not found in prototype chain
+FAIL Screen interface: screen must inherit property "onchange" with the proper type assert_inherits: property "onchange" not found in prototype chain
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.js b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.js
index 64c3fb5..41ede3d0 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.js
@@ -6,18 +6,42 @@
 'use strict';
 
 promise_test(async () => {
-  const idl = await fetch('/interfaces/media-capabilities.idl').then(r => r.text());
-  const html = await fetch('/interfaces/html.idl').then(r => r.text());
-  const cssomView = await fetch('/interfaces/cssom-view.idl').then(r => r.text());
+  try {
+    const video = {
+      contentType: 'video/webm; codecs="vp09.00.10.08"',
+      width: 800,
+      height: 600,
+      bitrate: 3000,
+      framerate: 24,
+    };
+    self.decodingInfo = await navigator.mediaCapabilities.decodingInfo({
+      type: 'file',
+      video: video,
+    });
+    self.encodingInfo = await navigator.mediaCapabilities.encodingInfo({
+      type: 'record',
+      video: video
+    });
+  } catch (e) {
+    // Will be surfaced when encodingInfo/decodingInfo is undefined below.
+  }
 
-  var idl_array = new IdlArray();
-  idl_array.add_idls(idl);
-  idl_array.add_dependency_idls(html);
-  idl_array.add_dependency_idls(cssomView);
-
-  idl_array.add_objects({
-    Navigator: ['navigator']
-  });
-
-  idl_array.test();
-}, 'Test IDL implementation of Media Capabilities');
+  idl_test(
+    ['media-capabilities'],
+    ['html', 'cssom-view'],
+    idl_array => {
+      if (self.GLOBAL.isWorker()) {
+        idl_array.add_objects({ WorkerNavigator: ['navigator'] });
+      } else {
+        idl_array.add_objects({ Navigator: ['navigator'] });
+      }
+      idl_array.add_objects({
+        MediaCapabilities: ['navigatior.mediaCapabilities'],
+        MediaCapabilitiesInfo: ['decodingInfo', 'encodingInfo'],
+        Screen: ['screen'],
+        ScreenLuminance: ['screen.luminance'],
+      });
+    },
+    'Test IDL implementation of Media Capabilities'
+  );
+});
diff --git a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.worker-expected.txt
index b159583..f0e2b93 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/media-capabilities/idlharness.any.worker-expected.txt
@@ -1,4 +1,6 @@
 This is a testharness.js-based test.
+Found 53 tests; 10 PASS, 43 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idlharness
 PASS Test IDL implementation of Media Capabilities
 PASS Partial interface Navigator: original interface defined
 PASS Partial interface Navigator: valid exposure set
@@ -14,6 +16,16 @@
 FAIL MediaCapabilitiesInfo interface: attribute supported assert_own_property: self does not have own property "MediaCapabilitiesInfo" expected property "MediaCapabilitiesInfo" missing
 FAIL MediaCapabilitiesInfo interface: attribute smooth assert_own_property: self does not have own property "MediaCapabilitiesInfo" expected property "MediaCapabilitiesInfo" missing
 FAIL MediaCapabilitiesInfo interface: attribute powerEfficient assert_own_property: self does not have own property "MediaCapabilitiesInfo" expected property "MediaCapabilitiesInfo" missing
+FAIL MediaCapabilitiesInfo must be primary interface of decodingInfo assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: decodingInfo is not defined"
+FAIL Stringification of decodingInfo assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: decodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: decodingInfo must inherit property "supported" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: decodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: decodingInfo must inherit property "smooth" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: decodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: decodingInfo must inherit property "powerEfficient" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: decodingInfo is not defined"
+FAIL MediaCapabilitiesInfo must be primary interface of encodingInfo assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: encodingInfo is not defined"
+FAIL Stringification of encodingInfo assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: encodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: encodingInfo must inherit property "supported" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: encodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: encodingInfo must inherit property "smooth" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: encodingInfo is not defined"
+FAIL MediaCapabilitiesInfo interface: encodingInfo must inherit property "powerEfficient" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: encodingInfo is not defined"
 FAIL MediaCapabilities interface: existence and properties of interface object assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
 FAIL MediaCapabilities interface object length assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
 FAIL MediaCapabilities interface object name assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
@@ -22,10 +34,24 @@
 FAIL MediaCapabilities interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
 FAIL MediaCapabilities interface: operation decodingInfo(MediaDecodingConfiguration) assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
 FAIL MediaCapabilities interface: operation encodingInfo(MediaEncodingConfiguration) assert_own_property: self does not have own property "MediaCapabilities" expected property "MediaCapabilities" missing
+FAIL MediaCapabilities must be primary interface of navigatior.mediaCapabilities assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL Stringification of navigatior.mediaCapabilities assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "decodingInfo(MediaDecodingConfiguration)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: calling decodingInfo(MediaDecodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: navigatior.mediaCapabilities must inherit property "encodingInfo(MediaEncodingConfiguration)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
+FAIL MediaCapabilities interface: calling encodingInfo(MediaEncodingConfiguration) on navigatior.mediaCapabilities with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: navigatior is not defined"
 PASS ScreenLuminance interface: existence and properties of interface object
+FAIL ScreenLuminance must be primary interface of screen.luminance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL Stringification of screen.luminance assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL ScreenLuminance interface: screen.luminance must not have property "min" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL ScreenLuminance interface: screen.luminance must not have property "max" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL ScreenLuminance interface: screen.luminance must not have property "maxAverage" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
 PASS Navigator interface: existence and properties of interface object
-PASS Navigator interface: navigator must not have property "mediaCapabilities"
 FAIL WorkerNavigator interface: attribute mediaCapabilities assert_true: The prototype object must have a property "mediaCapabilities" expected true got false
+FAIL WorkerNavigator interface: navigator must inherit property "mediaCapabilities" with the proper type assert_inherits: property "mediaCapabilities" not found in prototype chain
 PASS Screen interface: existence and properties of interface object
+FAIL Screen interface: screen must not have property "colorGamut" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL Screen interface: screen must not have property "luminance" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
+FAIL Screen interface: screen must not have property "onchange" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: screen is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https-expected.txt
new file mode 100644
index 0000000..59a5126
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https-expected.txt
@@ -0,0 +1,6 @@
+This is a testharness.js-based test.
+PASS Tests that a removal from a MediaStream works as expected
+FAIL Test that removal from a MediaStream fires ended on media elements (video first) assert_equals: audio element ended because no more audio tracks expected true but got false
+FAIL Test that removal from a MediaStream fires ended on media elements (audio first) assert_equals: audio element ended because no more audio tracks expected true but got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https.html b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https.html
index 787af0b..b934397 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/mediacapture-streams/MediaStream-removetrack.https.html
@@ -10,36 +10,117 @@
 <p class="instructions">When prompted, accept to share your audio stream, then your video stream.</p>
 <h1 class="instructions">Description</h1>
 <p class="instructions">This test checks that removinging a track from a MediaStream works as expected.</p>
-
+<video id="video" height="120" width="160" autoplay muted></video>
+<audio id="audio" autoplay muted></audio>
 <div id='log'></div>
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script>
-var t = async_test("Tests that a removal from a MediaStream works as expected", {timeout:10000});
-t.step(function () {
-  var audio;
-  navigator.mediaDevices.getUserMedia({audio:true}).then(gotAudio);
-  function gotAudio(stream) {
-     audio = stream;
-     navigator.mediaDevices.getUserMedia({video:true}).then(gotVideo);
-  }
 
-  function gotVideo(stream) {
-    var video = stream;
-    video.onremovetrack = function () {
-       assert_unreached("onremovetrack is not triggered when removal of track is triggered by the script itself");
-    };
-    t.step(function () {
-       assert_equals(video.getVideoTracks().length, 1, "video mediastream starts with one video track");
-       video.removeTrack(video.getVideoTracks()[0]);
-       assert_equals(video.getVideoTracks().length, 0, "video mediastream has no video track left");
-       video.removeTrack(audio.getAudioTracks()[0]); // should not throw
-    });
-    t.step(function() {
-       t.done();
-   });
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(track => track.stop()));
+  const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+  tracks.push(...stream2.getTracks());
+
+  stream.onremovetrack = stream2.onremovetrack = t.step_func(() =>
+    assert_unreached("onremovetrack is not triggered by script itself"));
+
+  assert_equals(stream.getTracks().length, 2, "mediastream starts with 2 tracks");
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  assert_equals(stream.getTracks().length, 1, "mediastream has 1 track left");
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  assert_equals(stream.getTracks().length, 0, "mediastream has no tracks left");
+  stream.removeTrack(stream2.getTracks()[0]); // should not throw
+
+  // Allow time to verify no events fire.
+  await new Promise(r => t.step_timeout(r, 1));
+
+}, "Tests that a removal from a MediaStream works as expected");
+
+async function doesEventFire(t, target, name, ms = 1) {
+  const cookie = {};
+  const value = await Promise.race([
+    new Promise(r => target.addEventListener(name, r, {once: true})),
+    new Promise(r => t.step_timeout(r, ms)).then(() => cookie)
+  ]);
+  return value !== cookie;
+}
+
+const doEventsFire = (t, target1, target2, name, ms = 1) => Promise.all([
+  doesEventFire(t, target1, "ended", ms),
+  doesEventFire(t, target2, "ended", ms)
+]);
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(track => track.stop()));
+
+  audio.srcObject = video.srcObject = stream;
+  t.add_cleanup(() => audio.srcObject = video.srcObject = null);
+
+  await Promise.all([
+    new Promise(r => audio.onloadedmetadata = r),
+    new Promise(r => video.onloadedmetadata = r)
+  ]);
+
+  assert_equals(audio.ended, false, "audio element starts out not ended");
+  assert_equals(video.ended, false, "video element starts out not ended");
+
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, false, "audio element unaffected");
+    assert_equals(audioDidEnd, false, "no audio ended event should fire yet");
+    assert_equals(video.ended, false, "video element keeps going with audio track");
+    assert_equals(videoDidEnd, false, "no video ended event should fire yet");
   }
-});
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element ended because no more audio tracks");
+    assert_equals(audioDidEnd, true, "go audio ended event");
+    assert_equals(video.ended, true, "video element ended because no more tracks");
+    assert_equals(videoDidEnd, true, "got video ended event");
+  }
+}, "Test that removal from a MediaStream fires ended on media elements (video first)");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(track => track.stop()));
+
+  audio.srcObject = video.srcObject = stream;
+  t.add_cleanup(() => audio.srcObject = video.srcObject = null);
+
+  await Promise.all([
+    new Promise(r => audio.onloadedmetadata = r),
+    new Promise(r => video.onloadedmetadata = r)
+  ]);
+
+  assert_equals(audio.ended, false, "audio element starts out not ended");
+  assert_equals(video.ended, false, "video element starts out not ended");
+
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element ended because no more audio tracks");
+    assert_equals(audioDidEnd, true, "got audio ended event");
+    assert_equals(video.ended, false, "video element keeps going with video track");
+    assert_equals(videoDidEnd, false, "no video ended event should fire yet");
+  }
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element remains ended from before");
+    assert_equals(audioDidEnd, false, "no second audio ended event should fire");
+    assert_equals(video.ended, true, "video element ended because no more tracks");
+    assert_equals(videoDidEnd, true, "got video ended event");
+  }
+}, "Test that removal from a MediaStream fires ended on media elements (audio first)");
+
 </script>
 </body>
 </html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any-expected.txt
new file mode 100644
index 0000000..15bb34a
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any-expected.txt
@@ -0,0 +1,68 @@
+This is a testharness.js-based test.
+Found 64 tests; 62 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Notification interface: existence and properties of interface object
+PASS Notification interface object length
+PASS Notification interface object name
+PASS Notification interface: existence and properties of interface prototype object
+PASS Notification interface: existence and properties of interface prototype object's "constructor" property
+PASS Notification interface: existence and properties of interface prototype object's @@unscopables property
+PASS Notification interface: attribute permission
+PASS Notification interface: operation requestPermission(NotificationPermissionCallback)
+PASS Notification interface: attribute maxActions
+PASS Notification interface: attribute onclick
+PASS Notification interface: attribute onshow
+PASS Notification interface: attribute onerror
+PASS Notification interface: attribute onclose
+PASS Notification interface: attribute title
+PASS Notification interface: attribute dir
+PASS Notification interface: attribute lang
+PASS Notification interface: attribute body
+PASS Notification interface: attribute tag
+PASS Notification interface: attribute image
+PASS Notification interface: attribute icon
+PASS Notification interface: attribute badge
+PASS Notification interface: attribute vibrate
+PASS Notification interface: attribute timestamp
+PASS Notification interface: attribute renotify
+PASS Notification interface: attribute silent
+PASS Notification interface: attribute requireInteraction
+PASS Notification interface: attribute data
+PASS Notification interface: attribute actions
+PASS Notification interface: operation close()
+PASS Notification must be primary interface of notification
+PASS Stringification of notification
+PASS Notification interface: notification must inherit property "permission" with the proper type
+PASS Notification interface: notification must inherit property "requestPermission(NotificationPermissionCallback)" with the proper type
+PASS Notification interface: calling requestPermission(NotificationPermissionCallback) on notification with too few arguments must throw TypeError
+PASS Notification interface: notification must inherit property "maxActions" with the proper type
+PASS Notification interface: notification must inherit property "onclick" with the proper type
+PASS Notification interface: notification must inherit property "onshow" with the proper type
+PASS Notification interface: notification must inherit property "onerror" with the proper type
+PASS Notification interface: notification must inherit property "onclose" with the proper type
+PASS Notification interface: notification must inherit property "title" with the proper type
+PASS Notification interface: notification must inherit property "dir" with the proper type
+PASS Notification interface: notification must inherit property "lang" with the proper type
+PASS Notification interface: notification must inherit property "body" with the proper type
+PASS Notification interface: notification must inherit property "tag" with the proper type
+PASS Notification interface: notification must inherit property "image" with the proper type
+PASS Notification interface: notification must inherit property "icon" with the proper type
+PASS Notification interface: notification must inherit property "badge" with the proper type
+PASS Notification interface: notification must inherit property "vibrate" with the proper type
+PASS Notification interface: notification must inherit property "timestamp" with the proper type
+PASS Notification interface: notification must inherit property "renotify" with the proper type
+PASS Notification interface: notification must inherit property "silent" with the proper type
+PASS Notification interface: notification must inherit property "requireInteraction" with the proper type
+PASS Notification interface: notification must inherit property "data" with the proper type
+PASS Notification interface: notification must inherit property "actions" with the proper type
+PASS Notification interface: notification must inherit property "close()" with the proper type
+PASS NotificationEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: operation showNotification(DOMString, NotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+FAIL ServiceWorkerRegistration interface: operation getNotifications(GetNotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS WorkerGlobalScope interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.js b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.js
new file mode 100644
index 0000000..e2907f3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.js
@@ -0,0 +1,27 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://notifications.spec.whatwg.org/
+
+idl_test(
+  ['notifications'],
+  ['service-workers', 'html', 'dom'],
+  idl_array => {
+    idl_array.add_objects({
+      Notification: ['notification'],
+    });
+    if (self.ServiceWorkerGlobalScope) {
+      idl_array.add_objects({
+        NotificationEvent: ['notificationEvent'],
+        ServiceWorkerGlobalScope: ['self'],
+      });
+    }
+    self.notification = new Notification("Running idlharness.");
+    if (self.ServiceWorkerGlobalScope) {
+      self.notificationEvent = new NotificationEvent("type", { notification: notification });
+    }
+  }
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.sharedworker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.sharedworker-expected.txt
new file mode 100644
index 0000000..be77ff36
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.sharedworker-expected.txt
@@ -0,0 +1,78 @@
+This is a testharness.js-based test.
+Found 74 tests; 72 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Notification interface: existence and properties of interface object
+PASS Notification interface object length
+PASS Notification interface object name
+PASS Notification interface: existence and properties of interface prototype object
+PASS Notification interface: existence and properties of interface prototype object's "constructor" property
+PASS Notification interface: existence and properties of interface prototype object's @@unscopables property
+PASS Notification interface: attribute permission
+PASS Notification interface: member requestPermission
+PASS Notification interface: attribute maxActions
+PASS Notification interface: attribute onclick
+PASS Notification interface: attribute onshow
+PASS Notification interface: attribute onerror
+PASS Notification interface: attribute onclose
+PASS Notification interface: attribute title
+PASS Notification interface: attribute dir
+PASS Notification interface: attribute lang
+PASS Notification interface: attribute body
+PASS Notification interface: attribute tag
+PASS Notification interface: attribute image
+PASS Notification interface: attribute icon
+PASS Notification interface: attribute badge
+PASS Notification interface: attribute vibrate
+PASS Notification interface: attribute timestamp
+PASS Notification interface: attribute renotify
+PASS Notification interface: attribute silent
+PASS Notification interface: attribute requireInteraction
+PASS Notification interface: attribute data
+PASS Notification interface: attribute actions
+PASS Notification interface: operation close()
+PASS Notification must be primary interface of notification
+PASS Stringification of notification
+PASS Notification interface: notification must inherit property "permission" with the proper type
+PASS Notification interface: notification must not have property "requestPermission"
+PASS Notification interface: notification must inherit property "maxActions" with the proper type
+PASS Notification interface: notification must inherit property "onclick" with the proper type
+PASS Notification interface: notification must inherit property "onshow" with the proper type
+PASS Notification interface: notification must inherit property "onerror" with the proper type
+PASS Notification interface: notification must inherit property "onclose" with the proper type
+PASS Notification interface: notification must inherit property "title" with the proper type
+PASS Notification interface: notification must inherit property "dir" with the proper type
+PASS Notification interface: notification must inherit property "lang" with the proper type
+PASS Notification interface: notification must inherit property "body" with the proper type
+PASS Notification interface: notification must inherit property "tag" with the proper type
+PASS Notification interface: notification must inherit property "image" with the proper type
+PASS Notification interface: notification must inherit property "icon" with the proper type
+PASS Notification interface: notification must inherit property "badge" with the proper type
+PASS Notification interface: notification must inherit property "vibrate" with the proper type
+PASS Notification interface: notification must inherit property "timestamp" with the proper type
+PASS Notification interface: notification must inherit property "renotify" with the proper type
+PASS Notification interface: notification must inherit property "silent" with the proper type
+PASS Notification interface: notification must inherit property "requireInteraction" with the proper type
+PASS Notification interface: notification must inherit property "data" with the proper type
+PASS Notification interface: notification must inherit property "actions" with the proper type
+PASS Notification interface: notification must inherit property "close()" with the proper type
+PASS NotificationEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: operation showNotification(DOMString, NotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+FAIL ServiceWorkerRegistration interface: operation getNotifications(GetNotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS HTMLElement interface: existence and properties of interface object
+PASS HTMLBodyElement interface: existence and properties of interface object
+PASS Window interface: existence and properties of interface object
+PASS HTMLFrameSetElement interface: existence and properties of interface object
+PASS Node interface: existence and properties of interface object
+PASS Document interface: existence and properties of interface object
+PASS DocumentType interface: existence and properties of interface object
+PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
+PASS Element interface: existence and properties of interface object
+PASS CharacterData interface: existence and properties of interface object
+PASS Text interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.worker-expected.txt
new file mode 100644
index 0000000..be77ff36
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.any.worker-expected.txt
@@ -0,0 +1,78 @@
+This is a testharness.js-based test.
+Found 74 tests; 72 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Notification interface: existence and properties of interface object
+PASS Notification interface object length
+PASS Notification interface object name
+PASS Notification interface: existence and properties of interface prototype object
+PASS Notification interface: existence and properties of interface prototype object's "constructor" property
+PASS Notification interface: existence and properties of interface prototype object's @@unscopables property
+PASS Notification interface: attribute permission
+PASS Notification interface: member requestPermission
+PASS Notification interface: attribute maxActions
+PASS Notification interface: attribute onclick
+PASS Notification interface: attribute onshow
+PASS Notification interface: attribute onerror
+PASS Notification interface: attribute onclose
+PASS Notification interface: attribute title
+PASS Notification interface: attribute dir
+PASS Notification interface: attribute lang
+PASS Notification interface: attribute body
+PASS Notification interface: attribute tag
+PASS Notification interface: attribute image
+PASS Notification interface: attribute icon
+PASS Notification interface: attribute badge
+PASS Notification interface: attribute vibrate
+PASS Notification interface: attribute timestamp
+PASS Notification interface: attribute renotify
+PASS Notification interface: attribute silent
+PASS Notification interface: attribute requireInteraction
+PASS Notification interface: attribute data
+PASS Notification interface: attribute actions
+PASS Notification interface: operation close()
+PASS Notification must be primary interface of notification
+PASS Stringification of notification
+PASS Notification interface: notification must inherit property "permission" with the proper type
+PASS Notification interface: notification must not have property "requestPermission"
+PASS Notification interface: notification must inherit property "maxActions" with the proper type
+PASS Notification interface: notification must inherit property "onclick" with the proper type
+PASS Notification interface: notification must inherit property "onshow" with the proper type
+PASS Notification interface: notification must inherit property "onerror" with the proper type
+PASS Notification interface: notification must inherit property "onclose" with the proper type
+PASS Notification interface: notification must inherit property "title" with the proper type
+PASS Notification interface: notification must inherit property "dir" with the proper type
+PASS Notification interface: notification must inherit property "lang" with the proper type
+PASS Notification interface: notification must inherit property "body" with the proper type
+PASS Notification interface: notification must inherit property "tag" with the proper type
+PASS Notification interface: notification must inherit property "image" with the proper type
+PASS Notification interface: notification must inherit property "icon" with the proper type
+PASS Notification interface: notification must inherit property "badge" with the proper type
+PASS Notification interface: notification must inherit property "vibrate" with the proper type
+PASS Notification interface: notification must inherit property "timestamp" with the proper type
+PASS Notification interface: notification must inherit property "renotify" with the proper type
+PASS Notification interface: notification must inherit property "silent" with the proper type
+PASS Notification interface: notification must inherit property "requireInteraction" with the proper type
+PASS Notification interface: notification must inherit property "data" with the proper type
+PASS Notification interface: notification must inherit property "actions" with the proper type
+PASS Notification interface: notification must inherit property "close()" with the proper type
+PASS NotificationEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: operation showNotification(DOMString, NotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+FAIL ServiceWorkerRegistration interface: operation getNotifications(GetNotificationOptions) assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS HTMLElement interface: existence and properties of interface object
+PASS HTMLBodyElement interface: existence and properties of interface object
+PASS Window interface: existence and properties of interface object
+PASS HTMLFrameSetElement interface: existence and properties of interface object
+PASS Node interface: existence and properties of interface object
+PASS Document interface: existence and properties of interface object
+PASS DocumentType interface: existence and properties of interface object
+PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
+PASS Element interface: existence and properties of interface object
+PASS CharacterData interface: existence and properties of interface object
+PASS Text interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.https.any.serviceworker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.https.any.serviceworker-expected.txt
new file mode 100644
index 0000000..62dc9ea
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/notifications/idlharness.https.any.serviceworker-expected.txt
@@ -0,0 +1,90 @@
+This is a testharness.js-based test.
+FAIL idl_test setup promise_test: Unhandled rejection with value: object "TypeError: Failed to construct 'Notification': Illegal constructor."
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Notification interface: existence and properties of interface object
+PASS Notification interface object length
+PASS Notification interface object name
+PASS Notification interface: existence and properties of interface prototype object
+PASS Notification interface: existence and properties of interface prototype object's "constructor" property
+PASS Notification interface: existence and properties of interface prototype object's @@unscopables property
+PASS Notification interface: attribute permission
+PASS Notification interface: member requestPermission
+PASS Notification interface: attribute maxActions
+PASS Notification interface: attribute onclick
+PASS Notification interface: attribute onshow
+PASS Notification interface: attribute onerror
+PASS Notification interface: attribute onclose
+PASS Notification interface: attribute title
+PASS Notification interface: attribute dir
+PASS Notification interface: attribute lang
+PASS Notification interface: attribute body
+PASS Notification interface: attribute tag
+PASS Notification interface: attribute image
+PASS Notification interface: attribute icon
+PASS Notification interface: attribute badge
+PASS Notification interface: attribute vibrate
+PASS Notification interface: attribute timestamp
+PASS Notification interface: attribute renotify
+PASS Notification interface: attribute silent
+PASS Notification interface: attribute requireInteraction
+PASS Notification interface: attribute data
+PASS Notification interface: attribute actions
+PASS Notification interface: operation close()
+FAIL Notification must be primary interface of notification assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Stringification of notification assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "permission" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must not have property "requestPermission" assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "maxActions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "onclick" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "onshow" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "onerror" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "onclose" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "title" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "dir" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "lang" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "body" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "tag" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "image" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "icon" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "badge" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "vibrate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "timestamp" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "renotify" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "silent" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "requireInteraction" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "data" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "actions" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+FAIL Notification interface: notification must inherit property "close()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notification is not defined"
+PASS NotificationEvent interface: existence and properties of interface object
+PASS NotificationEvent interface object length
+PASS NotificationEvent interface object name
+PASS NotificationEvent interface: existence and properties of interface prototype object
+PASS NotificationEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS NotificationEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS NotificationEvent interface: attribute notification
+PASS NotificationEvent interface: attribute action
+FAIL NotificationEvent must be primary interface of notificationEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notificationEvent is not defined"
+FAIL Stringification of notificationEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notificationEvent is not defined"
+FAIL NotificationEvent interface: notificationEvent must inherit property "notification" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notificationEvent is not defined"
+FAIL NotificationEvent interface: notificationEvent must inherit property "action" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: notificationEvent is not defined"
+PASS ServiceWorkerRegistration interface: operation showNotification(DOMString, NotificationOptions)
+PASS ServiceWorkerRegistration interface: operation getNotifications(GetNotificationOptions)
+PASS ServiceWorkerGlobalScope interface: attribute onnotificationclick
+PASS ServiceWorkerGlobalScope interface: attribute onnotificationclose
+PASS ServiceWorkerGlobalScope interface: self must inherit property "onnotificationclick" with the proper type
+PASS ServiceWorkerGlobalScope interface: self must inherit property "onnotificationclose" with the proper type
+PASS HTMLElement interface: existence and properties of interface object
+PASS HTMLBodyElement interface: existence and properties of interface object
+PASS Window interface: existence and properties of interface object
+PASS HTMLFrameSetElement interface: existence and properties of interface object
+PASS Node interface: existence and properties of interface object
+PASS Document interface: existence and properties of interface object
+PASS DocumentType interface: existence and properties of interface object
+PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
+PASS Element interface: existence and properties of interface object
+PASS CharacterData interface: existence and properties of interface object
+PASS Text interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any-expected.txt
index 1a40439..a74a0fe7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-PASS payment-handler interfaces.
+PASS idl_test setup
 PASS Partial interface ServiceWorkerRegistration: original interface defined
 PASS Partial interface ServiceWorkerGlobalScope: original interface defined
 PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
@@ -12,6 +12,11 @@
 PASS PaymentManager interface: attribute instruments
 FAIL PaymentManager interface: operation requestPermission() assert_own_property: interface object missing static operation expected property "requestPermission" missing
 PASS PaymentManager interface: attribute userHint
+PASS PaymentManager must be primary interface of paymentManager
+PASS Stringification of paymentManager
+PASS PaymentManager interface: paymentManager must inherit property "instruments" with the proper type
+PASS PaymentManager interface: paymentManager must inherit property "requestPermission()" with the proper type
+PASS PaymentManager interface: paymentManager must inherit property "userHint" with the proper type
 PASS PaymentInstruments interface: existence and properties of interface object
 PASS PaymentInstruments interface object length
 PASS PaymentInstruments interface object name
@@ -24,9 +29,22 @@
 PASS PaymentInstruments interface: operation has(DOMString)
 PASS PaymentInstruments interface: operation set(DOMString, PaymentInstrument)
 PASS PaymentInstruments interface: operation clear()
+PASS PaymentInstruments must be primary interface of instruments
+PASS Stringification of instruments
+PASS PaymentInstruments interface: instruments must inherit property "delete(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling delete(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "get(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling get(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "keys()" with the proper type
+PASS PaymentInstruments interface: instruments must inherit property "has(DOMString)" with the proper type
+PASS PaymentInstruments interface: calling has(DOMString) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "set(DOMString, PaymentInstrument)" with the proper type
+PASS PaymentInstruments interface: calling set(DOMString, PaymentInstrument) on instruments with too few arguments must throw TypeError
+PASS PaymentInstruments interface: instruments must inherit property "clear()" with the proper type
 PASS CanMakePaymentEvent interface: existence and properties of interface object
 PASS PaymentRequestEvent interface: existence and properties of interface object
 PASS ServiceWorkerRegistration interface: attribute paymentManager
+PASS ServiceWorkerRegistration interface: registration must inherit property "paymentManager" with the proper type
 PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
 PASS ExtendableEvent interface: existence and properties of interface object
 PASS WorkerGlobalScope interface: existence and properties of interface object
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.js
index d039f66..6fbba53 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.js
@@ -1,24 +1,46 @@
+// META: global=window,worker
 // META: script=/resources/WebIDLParser.js
 // META: script=/resources/idlharness.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
 
 'use strict';
 
 // https://w3c.github.io/payment-handler/
 
-promise_test(async () => {
-  const srcs = [
-    'payment-handler',
-    'service-workers',
-    'dedicated-workers',
-    'dom'
-  ];
-  const [idl, sw, dw, dom] = await Promise.all(
-      srcs.map(i => fetch(`/interfaces/${i}.idl`).then(r => r.text())));
+idl_test(
+  ['payment-handler'],
+  ['service-workers', 'dedicated-workers', 'dom'],
+  async (idl_array, t) => {
+    const isWindow = self.GLOBAL.isWindow();
+    const isServiceWorker = 'ServiceWorkerGlobalScope' in self;
+    const hasRegistration = isServiceWorker || isWindow;
 
-  const idlArray = new IdlArray();
-  idlArray.add_idls(idl);
-  idlArray.add_dependency_idls(sw);
-  idlArray.add_dependency_idls(dw);
-  idlArray.add_dependency_idls(dom);
-  idlArray.test();
-}, 'payment-handler interfaces.');
+    if (hasRegistration) {
+      idl_array.add_objects({
+        ServiceWorkerRegistration: ['registration'],
+        PaymentManager: ['paymentManager'],
+        PaymentInstruments: ['instruments'],
+      });
+    }
+    if (isServiceWorker) {
+      idl_array.add_objects({
+        ServiceWorkerGlobalScope: ['self'],
+        CanMakePaymentEvent: ['new CanMakePaymentEvent("type")'],
+        PaymentRequestEvent: ['new PaymentRequestEvent("type")'],
+      })
+    }
+
+    if (isWindow) {
+      const scope = '/service-workers/service-worker/resources/';
+      const reg = await service_worker_unregister_and_register(
+        t, '/service-workers/service-worker/resources/empty-worker.js', scope);
+      self.registration = reg;
+      await wait_for_state(t, reg.installing, "activated");
+      add_completion_callback(() => reg.unregister());
+    }
+    if (hasRegistration) {
+      self.paymentManager = self.registration.paymentManager;
+      self.instruments = self.paymentManager.instruments;
+    }
+  }
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
new file mode 100644
index 0000000..942bea03
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.sharedworker-expected.txt
@@ -0,0 +1,33 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
+FAIL PaymentManager interface: existence and properties of interface object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object length assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface object name assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: attribute instruments assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+FAIL PaymentManager interface: member requestPermission Cannot use 'in' operator to search for 'requestPermission' in undefined
+FAIL PaymentManager interface: attribute userHint assert_own_property: self does not have own property "PaymentManager" expected property "PaymentManager" missing
+PASS PaymentInstruments interface: existence and properties of interface object
+PASS PaymentInstruments interface object length
+PASS PaymentInstruments interface object name
+PASS PaymentInstruments interface: existence and properties of interface prototype object
+PASS PaymentInstruments interface: existence and properties of interface prototype object's "constructor" property
+PASS PaymentInstruments interface: existence and properties of interface prototype object's @@unscopables property
+PASS PaymentInstruments interface: operation delete(DOMString)
+PASS PaymentInstruments interface: operation get(DOMString)
+PASS PaymentInstruments interface: operation keys()
+PASS PaymentInstruments interface: operation has(DOMString)
+PASS PaymentInstruments interface: operation set(DOMString, PaymentInstrument)
+PASS PaymentInstruments interface: operation clear()
+PASS CanMakePaymentEvent interface: existence and properties of interface object
+PASS PaymentRequestEvent interface: existence and properties of interface object
+PASS ServiceWorkerRegistration interface: attribute paymentManager
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
index da4f7d4a..942bea03 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/idlharness.https.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-PASS payment-handler interfaces.
+PASS idl_test setup
 PASS Partial interface ServiceWorkerRegistration: original interface defined
 PASS Partial interface ServiceWorkerGlobalScope: original interface defined
 PASS Partial interface ServiceWorkerGlobalScope[2]: original interface defined
diff --git a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/interfaces.https.any.js b/third_party/WebKit/LayoutTests/external/wpt/payment-handler/interfaces.https.any.js
deleted file mode 100644
index 7367930..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/payment-handler/interfaces.https.any.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// META: script=/resources/WebIDLParser.js
-// META: script=/resources/idlharness.js
-
-'use strict';
-
-// https://w3c.github.io/payment-handler/
-
-promise_test(async () => {
-  const idl = await fetch('/interfaces/payment-handler.idl').then(r => r.text());
-  const idlArray = new IdlArray();
-  idlArray.add_idls(idl);
-  idlArray.add_untested_idls('interface ExtendableEvent {};');
-  idlArray.add_untested_idls('dictionary ExtendableEventInit {};');
-  idlArray.test();
-  done();
-}, 'Payment handler interfaces.');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any-expected.txt
new file mode 100644
index 0000000..45e53938
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any-expected.txt
@@ -0,0 +1,54 @@
+This is a testharness.js-based test.
+Found 50 tests; 39 PASS, 11 FAIL, 0 TIMEOUT, 0 NOTRUN.
+FAIL idl_test setup promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'getRegistration' of undefined"
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: valid exposure set
+PASS PushManager interface: existence and properties of interface object
+PASS PushManager interface object length
+PASS PushManager interface object name
+PASS PushManager interface: existence and properties of interface prototype object
+PASS PushManager interface: existence and properties of interface prototype object's "constructor" property
+PASS PushManager interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushManager interface: attribute supportedContentEncodings
+PASS PushManager interface: operation subscribe(PushSubscriptionOptionsInit)
+PASS PushManager interface: operation getSubscription()
+PASS PushManager interface: operation permissionState(PushSubscriptionOptionsInit)
+FAIL PushManager must be primary interface of registration.pushManager assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL Stringification of registration.pushManager assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: registration.pushManager must inherit property "supportedContentEncodings" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: registration.pushManager must inherit property "subscribe(PushSubscriptionOptionsInit)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: calling subscribe(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: registration.pushManager must inherit property "getSubscription()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: registration.pushManager must inherit property "permissionState(PushSubscriptionOptionsInit)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+FAIL PushManager interface: calling permissionState(PushSubscriptionOptionsInit) on registration.pushManager with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+PASS PushSubscriptionOptions interface: existence and properties of interface object
+PASS PushSubscriptionOptions interface object length
+PASS PushSubscriptionOptions interface object name
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscriptionOptions interface: attribute userVisibleOnly
+PASS PushSubscriptionOptions interface: attribute applicationServerKey
+PASS PushSubscription interface: existence and properties of interface object
+PASS PushSubscription interface object length
+PASS PushSubscription interface object name
+PASS PushSubscription interface: existence and properties of interface prototype object
+PASS PushSubscription interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscription interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscription interface: attribute endpoint
+PASS PushSubscription interface: attribute expirationTime
+PASS PushSubscription interface: attribute options
+PASS PushSubscription interface: operation getKey(PushEncryptionKeyName)
+PASS PushSubscription interface: operation unsubscribe()
+PASS PushSubscription interface: operation toJSON()
+PASS PushMessageData interface: existence and properties of interface object
+PASS PushEvent interface: existence and properties of interface object
+PASS PushSubscriptionChangeEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: attribute pushManager assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+FAIL ServiceWorkerRegistration interface: registration must inherit property "pushManager" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: registration is not defined"
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS WorkerGlobalScope interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.js b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.js
new file mode 100644
index 0000000..16c0826
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.js
@@ -0,0 +1,42 @@
+// META: global=window,worker
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+
+// https://w3c.github.io/push-api/
+
+idl_test(
+  ['push-api'],
+  ['service-workers', 'html', 'dom'],
+  async (idl_array, t) => {
+    const isServiceWorker = 'ServiceWorkerGlobalScope' in self
+      && self instanceof ServiceWorkerGlobalScope;
+    if (isServiceWorker) {
+      idl_array.add_objects({
+        ServiceWorkerGlobalScope: ['self'],
+        PushEvent: ['new PushEvent("type")'],
+        PushSubscriptionChangeEvent: [
+          'new PushSubscriptionChangeEvent("pushsubscriptionchange")'
+        ],
+      })
+    }
+    if (GLOBAL.isWindow() || isServiceWorker) {
+      idl_array.add_objects({
+        // self.registration set for window below, and registration is already
+        // part of ServiceWorkerGlobalScope.
+        ServiceWorkerRegistration: ['registration'],
+        PushManager: ['registration.pushManager'],
+      });
+    }
+
+    if (GLOBAL.isWindow()) {
+      const scope = '/service-workers/service-worker/resources/';
+      const worker = `${scope}empty-worker.js`;
+      return service_worker_unregister_and_register(t, worker, scope)
+        .then(registration => {
+          self.registration = registration;
+          t.add_cleanup(function () { registration.unregister(); });
+        });
+    }
+  }
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.sharedworker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.sharedworker-expected.txt
new file mode 100644
index 0000000..5e397fb2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.sharedworker-expected.txt
@@ -0,0 +1,56 @@
+This is a testharness.js-based test.
+Found 52 tests; 51 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: valid exposure set
+PASS PushManager interface: existence and properties of interface object
+PASS PushManager interface object length
+PASS PushManager interface object name
+PASS PushManager interface: existence and properties of interface prototype object
+PASS PushManager interface: existence and properties of interface prototype object's "constructor" property
+PASS PushManager interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushManager interface: attribute supportedContentEncodings
+PASS PushManager interface: operation subscribe(PushSubscriptionOptionsInit)
+PASS PushManager interface: operation getSubscription()
+PASS PushManager interface: operation permissionState(PushSubscriptionOptionsInit)
+PASS PushSubscriptionOptions interface: existence and properties of interface object
+PASS PushSubscriptionOptions interface object length
+PASS PushSubscriptionOptions interface object name
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscriptionOptions interface: attribute userVisibleOnly
+PASS PushSubscriptionOptions interface: attribute applicationServerKey
+PASS PushSubscription interface: existence and properties of interface object
+PASS PushSubscription interface object length
+PASS PushSubscription interface object name
+PASS PushSubscription interface: existence and properties of interface prototype object
+PASS PushSubscription interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscription interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscription interface: attribute endpoint
+PASS PushSubscription interface: attribute expirationTime
+PASS PushSubscription interface: attribute options
+PASS PushSubscription interface: operation getKey(PushEncryptionKeyName)
+PASS PushSubscription interface: operation unsubscribe()
+PASS PushSubscription interface: operation toJSON()
+PASS PushMessageData interface: existence and properties of interface object
+PASS PushEvent interface: existence and properties of interface object
+PASS PushSubscriptionChangeEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: attribute pushManager assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS HTMLElement interface: existence and properties of interface object
+PASS HTMLBodyElement interface: existence and properties of interface object
+PASS Window interface: existence and properties of interface object
+PASS HTMLFrameSetElement interface: existence and properties of interface object
+PASS Node interface: existence and properties of interface object
+PASS Document interface: existence and properties of interface object
+PASS DocumentType interface: existence and properties of interface object
+PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
+PASS Element interface: existence and properties of interface object
+PASS CharacterData interface: existence and properties of interface object
+PASS Text interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.worker-expected.txt
new file mode 100644
index 0000000..5e397fb2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.any.worker-expected.txt
@@ -0,0 +1,56 @@
+This is a testharness.js-based test.
+Found 52 tests; 51 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS idl_test setup
+PASS Partial interface ServiceWorkerRegistration: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: original interface defined
+PASS Partial interface ServiceWorkerGlobalScope: valid exposure set
+PASS PushManager interface: existence and properties of interface object
+PASS PushManager interface object length
+PASS PushManager interface object name
+PASS PushManager interface: existence and properties of interface prototype object
+PASS PushManager interface: existence and properties of interface prototype object's "constructor" property
+PASS PushManager interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushManager interface: attribute supportedContentEncodings
+PASS PushManager interface: operation subscribe(PushSubscriptionOptionsInit)
+PASS PushManager interface: operation getSubscription()
+PASS PushManager interface: operation permissionState(PushSubscriptionOptionsInit)
+PASS PushSubscriptionOptions interface: existence and properties of interface object
+PASS PushSubscriptionOptions interface object length
+PASS PushSubscriptionOptions interface object name
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscriptionOptions interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscriptionOptions interface: attribute userVisibleOnly
+PASS PushSubscriptionOptions interface: attribute applicationServerKey
+PASS PushSubscription interface: existence and properties of interface object
+PASS PushSubscription interface object length
+PASS PushSubscription interface object name
+PASS PushSubscription interface: existence and properties of interface prototype object
+PASS PushSubscription interface: existence and properties of interface prototype object's "constructor" property
+PASS PushSubscription interface: existence and properties of interface prototype object's @@unscopables property
+PASS PushSubscription interface: attribute endpoint
+PASS PushSubscription interface: attribute expirationTime
+PASS PushSubscription interface: attribute options
+PASS PushSubscription interface: operation getKey(PushEncryptionKeyName)
+PASS PushSubscription interface: operation unsubscribe()
+PASS PushSubscription interface: operation toJSON()
+PASS PushMessageData interface: existence and properties of interface object
+PASS PushEvent interface: existence and properties of interface object
+PASS PushSubscriptionChangeEvent interface: existence and properties of interface object
+FAIL ServiceWorkerRegistration interface: attribute pushManager assert_own_property: self does not have own property "ServiceWorkerRegistration" expected property "ServiceWorkerRegistration" missing
+PASS ServiceWorkerGlobalScope interface: existence and properties of interface object
+PASS ExtendableEvent interface: existence and properties of interface object
+PASS HTMLElement interface: existence and properties of interface object
+PASS HTMLBodyElement interface: existence and properties of interface object
+PASS Window interface: existence and properties of interface object
+PASS HTMLFrameSetElement interface: existence and properties of interface object
+PASS Node interface: existence and properties of interface object
+PASS Document interface: existence and properties of interface object
+PASS DocumentType interface: existence and properties of interface object
+PASS DocumentFragment interface: existence and properties of interface object
+PASS ShadowRoot interface: existence and properties of interface object
+PASS Element interface: existence and properties of interface object
+PASS CharacterData interface: existence and properties of interface object
+PASS Text interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.https.any.js b/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.https.any.js
deleted file mode 100644
index 7ed5e11..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/push-api/idlharness.https.any.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// META: global=window,worker
-// META: script=/resources/WebIDLParser.js
-// META: script=/resources/idlharness.js
-
-// https://w3c.github.io/push-api/
-
-idl_test(
-  ['push-api'],
-  ['service-workers', 'html', 'dom'],
-  idl_array => {
-    // TODO: ServiceWorkerRegistration objects
-    if ('ServiceWorkerGlobalScope' in self
-        && self instanceof ServiceWorkerGlobalScope) {
-      idl_array.add_objects({
-        PushSubscriptionChangeEvent: [
-          'new PushSubscriptionChangeEvent("pushsubscriptionchange")'
-        ],
-      })
-    }
-  },
-  'push-api interfaces'
-);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/resources/idlharness.js b/third_party/WebKit/LayoutTests/external/wpt/resources/idlharness.js
index 7b01142..91818ef 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/resources/idlharness.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/resources/idlharness.js
@@ -2361,10 +2361,11 @@
 };
 
 IdlInterface.prototype.test_to_json_operation = function(memberHolderObject, member) {
-    var instanceName = memberHolderObject.constructor.name;
+    var instanceName = memberHolderObject && memberHolderObject.constructor.name
+        || member.name + " object";
     if (member.has_extended_attribute("Default")) {
-        var map = this.default_to_json_operation();
         subsetTestByKey(this.name, test, function() {
+            var map = this.default_to_json_operation();
             var json = memberHolderObject.toJSON();
             map.forEach(function(type, k) {
                 assert_true(k in json, "property " + JSON.stringify(k) + " should be present in the output of " + this.name + ".prototype.toJSON()");
@@ -3201,6 +3202,7 @@
         var idl_array = new IdlArray();
         srcs = (srcs instanceof Array) ? srcs : [srcs] || [];
         deps = (deps instanceof Array) ? deps : [deps] || [];
+        var setup_error = null;
         return Promise.all(
             srcs.concat(deps).map(function(spec) {
                 return fetch_spec(spec);
@@ -3218,18 +3220,21 @@
                     return idl_setup_func(idl_array, t);
                 }
             })
-            .then(function() { idl_array.test(); })
-            .catch(function (reason) {
+            .catch(function(e) { setup_error = e || 'IDL setup failed.'; })
+            .finally(function () {
+                var error = setup_error;
                 try {
                     idl_array.test(); // Test what we can.
                 } catch (e) {
                     // If testing fails hard here, the original setup error
                     // is more likely to be the real cause.
-                    reason = reason || e;
+                    error = error || e;
                 }
-                return Promise.reject(reason || 'IDL setup failed.');
+                if (error) {
+                    throw error;
+                }
             });
-    }, test_name);
+    }, test_name || 'idl_test setup');
 }
 
 /**
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/script-tests/cache-keys.js b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/script-tests/cache-keys.js
index 96bd43b..a94c18f87 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/script-tests/cache-keys.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/script-tests/cache-keys.js
@@ -179,6 +179,20 @@
         });
   }, 'Cache.keys without parameters');
 
+prepopulated_cache_test(vary_entries, function(cache, entries) {
+    return cache.keys()
+      .then(function(result) {
+          assert_request_array_equals(
+            result,
+            [
+              entries.vary_cookie_is_cookie.request,
+              entries.vary_cookie_is_good.request,
+              entries.vary_cookie_absent.request,
+            ],
+            'Cache.keys without parameters should match all entries.');
+        });
+  }, 'Cache.keys without parameters and VARY entries');
+
 prepopulated_cache_test(simple_entries, function(cache, entries) {
     return cache.keys(new Request(entries.cat.request.url, {method: 'HEAD'}))
       .then(function(result) {
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/serviceworker/cache-keys.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/serviceworker/cache-keys.https-expected.txt
new file mode 100644
index 0000000..a83c246
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/serviceworker/cache-keys.https-expected.txt
@@ -0,0 +1,18 @@
+This is a testharness.js-based test.
+PASS Cache.keys
+PASS Cache.keys() called on an empty cache
+PASS Cache.keys with no matching entries
+PASS Cache.keys with URL
+PASS Cache.keys with Request
+PASS Cache.keys with new Request
+PASS Cache.keys with ignoreSearch option (request with no search parameters)
+PASS Cache.keys with ignoreSearch option (request with search parameters)
+PASS Cache.keys supports ignoreMethod
+PASS Cache.keys supports ignoreVary
+PASS Cache.keys with URL containing fragment
+PASS Cache.keys with string fragment "http" as query
+PASS Cache.keys without parameters
+FAIL Cache.keys without parameters and VARY entries assert_equals: Cache.keys without parameters should match all entries. expected 3 but got 1
+PASS Cache.keys with a HEAD Request
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/window/cache-keys.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/window/cache-keys.https-expected.txt
new file mode 100644
index 0000000..88c5107
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/window/cache-keys.https-expected.txt
@@ -0,0 +1,17 @@
+This is a testharness.js-based test.
+PASS Cache.keys() called on an empty cache
+PASS Cache.keys with no matching entries
+PASS Cache.keys with URL
+PASS Cache.keys with Request
+PASS Cache.keys with new Request
+PASS Cache.keys with ignoreSearch option (request with no search parameters)
+PASS Cache.keys with ignoreSearch option (request with search parameters)
+PASS Cache.keys supports ignoreMethod
+PASS Cache.keys supports ignoreVary
+PASS Cache.keys with URL containing fragment
+PASS Cache.keys with string fragment "http" as query
+PASS Cache.keys without parameters
+FAIL Cache.keys without parameters and VARY entries assert_equals: Cache.keys without parameters should match all entries. expected 3 but got 1
+PASS Cache.keys with a HEAD Request
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/worker/cache-keys.https-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/worker/cache-keys.https-expected.txt
new file mode 100644
index 0000000..88c5107
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/service-workers/cache-storage/worker/cache-keys.https-expected.txt
@@ -0,0 +1,17 @@
+This is a testharness.js-based test.
+PASS Cache.keys() called on an empty cache
+PASS Cache.keys with no matching entries
+PASS Cache.keys with URL
+PASS Cache.keys with Request
+PASS Cache.keys with new Request
+PASS Cache.keys with ignoreSearch option (request with no search parameters)
+PASS Cache.keys with ignoreSearch option (request with search parameters)
+PASS Cache.keys supports ignoreMethod
+PASS Cache.keys supports ignoreVary
+PASS Cache.keys with URL containing fragment
+PASS Cache.keys with string fragment "http" as query
+PASS Cache.keys without parameters
+FAIL Cache.keys without parameters and VARY entries assert_equals: Cache.keys without parameters should match all entries. expected 3 but got 1
+PASS Cache.keys with a HEAD Request
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/README.md b/third_party/WebKit/LayoutTests/external/wpt/url/README.md
index 667951d..823a8eec 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/README.md
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/README.md
@@ -44,5 +44,10 @@
 Tests in `/encoding` and `/html/infrastructure/urls/resolving-urls/query-encoding/` cover the
 encoding argument to the URL parser.
 
+## Specification
+
+The tests in this directory assert conformance with [the URL Standard][URL].
+
 [parsing]: https://url.spec.whatwg.org/#concept-basic-url-parser
 [API]: https://url.spec.whatwg.org/#api
+[URL]: https://url.spec.whatwg.org/
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-constructor.html b/third_party/WebKit/LayoutTests/external/wpt/url/url-constructor.html
index 6d7c2d6..cb4c0db3 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/url-constructor.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/url-constructor.html
@@ -40,73 +40,5 @@
   }
 }
 
-function runURLSearchParamTests() {
-  test(function() {
-    var url = bURL('http://example.org/?a=b')
-    assert_true("searchParams" in url)
-    var searchParams = url.searchParams
-    assert_true(url.searchParams === searchParams, 'Object identity should hold.')
-  }, 'URL.searchParams getter')
-
-  test(function() {
-    var url = bURL('http://example.org/?a=b')
-    assert_true("searchParams" in url)
-    var searchParams = url.searchParams
-    assert_equals(searchParams.toString(), 'a=b')
-
-    searchParams.set('a', 'b')
-    assert_equals(url.searchParams.toString(), 'a=b')
-    assert_equals(url.search, '?a=b')
-    url.search = ''
-    assert_equals(url.searchParams.toString(), '')
-    assert_equals(url.search, '')
-    assert_equals(searchParams.toString(), '')
-  }, 'URL.searchParams updating, clearing')
-
-  test(function() {
-    'use strict'
-    var urlString = 'http://example.org'
-    var url = bURL(urlString)
-    assert_throws(TypeError(), function() { url.searchParams = new URLSearchParams(urlString) })
-  }, 'URL.searchParams setter, invalid values')
-
-  test(function() {
-    var url = bURL('http://example.org/file?a=b&c=d')
-    assert_true("searchParams" in url)
-    var searchParams = url.searchParams
-    assert_equals(url.search, '?a=b&c=d')
-    assert_equals(searchParams.toString(), 'a=b&c=d')
-
-    // Test that setting 'search' propagates to the URL object's query object.
-    url.search = 'e=f&g=h'
-    assert_equals(url.search, '?e=f&g=h')
-    assert_equals(searchParams.toString(), 'e=f&g=h')
-
-    // ..and same but with a leading '?'.
-    url.search = '?e=f&g=h'
-    assert_equals(url.search, '?e=f&g=h')
-    assert_equals(searchParams.toString(), 'e=f&g=h')
-
-    // And in the other direction, altering searchParams propagates
-    // back to 'search'.
-    searchParams.append('i', ' j ')
-    assert_equals(url.search, '?e=f&g=h&i=+j+')
-    assert_equals(url.searchParams.toString(), 'e=f&g=h&i=+j+')
-    assert_equals(searchParams.get('i'), ' j ')
-
-    searchParams.set('e', 'updated')
-    assert_equals(url.search, '?e=updated&g=h&i=+j+')
-    assert_equals(searchParams.get('e'), 'updated')
-
-    var url2 = bURL('http://example.org/file??a=b&c=d')
-    assert_equals(url2.search, '??a=b&c=d')
-    assert_equals(url2.searchParams.toString(), '%3Fa=b&c=d')
-
-    url2.href = 'http://example.org/file??a=b'
-    assert_equals(url2.search, '??a=b')
-    assert_equals(url2.searchParams.toString(), '%3Fa=b')
-  }, 'URL.searchParams and URL.search setters, update propagation')
-}
-runURLSearchParamTests()
 promise_test(() => fetch("resources/urltestdata.json").then(res => res.json()).then(runURLTests), "Loading data…");
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-searchparams.any.js b/third_party/WebKit/LayoutTests/external/wpt/url/url-searchparams.any.js
new file mode 100644
index 0000000..c55ae58
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/url-searchparams.any.js
@@ -0,0 +1,72 @@
+function bURL(url, base) {
+  return new URL(url, base || "about:blank")
+}
+
+function runURLSearchParamTests() {
+  test(function() {
+    var url = bURL('http://example.org/?a=b')
+    assert_true("searchParams" in url)
+    var searchParams = url.searchParams
+    assert_true(url.searchParams === searchParams, 'Object identity should hold.')
+  }, 'URL.searchParams getter')
+
+  test(function() {
+    var url = bURL('http://example.org/?a=b')
+    assert_true("searchParams" in url)
+    var searchParams = url.searchParams
+    assert_equals(searchParams.toString(), 'a=b')
+
+    searchParams.set('a', 'b')
+    assert_equals(url.searchParams.toString(), 'a=b')
+    assert_equals(url.search, '?a=b')
+    url.search = ''
+    assert_equals(url.searchParams.toString(), '')
+    assert_equals(url.search, '')
+    assert_equals(searchParams.toString(), '')
+  }, 'URL.searchParams updating, clearing')
+
+  test(function() {
+    'use strict'
+    var urlString = 'http://example.org'
+    var url = bURL(urlString)
+    assert_throws(TypeError(), function() { url.searchParams = new URLSearchParams(urlString) })
+  }, 'URL.searchParams setter, invalid values')
+
+  test(function() {
+    var url = bURL('http://example.org/file?a=b&c=d')
+    assert_true("searchParams" in url)
+    var searchParams = url.searchParams
+    assert_equals(url.search, '?a=b&c=d')
+    assert_equals(searchParams.toString(), 'a=b&c=d')
+
+    // Test that setting 'search' propagates to the URL object's query object.
+    url.search = 'e=f&g=h'
+    assert_equals(url.search, '?e=f&g=h')
+    assert_equals(searchParams.toString(), 'e=f&g=h')
+
+    // ..and same but with a leading '?'.
+    url.search = '?e=f&g=h'
+    assert_equals(url.search, '?e=f&g=h')
+    assert_equals(searchParams.toString(), 'e=f&g=h')
+
+    // And in the other direction, altering searchParams propagates
+    // back to 'search'.
+    searchParams.append('i', ' j ')
+    assert_equals(url.search, '?e=f&g=h&i=+j+')
+    assert_equals(url.searchParams.toString(), 'e=f&g=h&i=+j+')
+    assert_equals(searchParams.get('i'), ' j ')
+
+    searchParams.set('e', 'updated')
+    assert_equals(url.search, '?e=updated&g=h&i=+j+')
+    assert_equals(searchParams.get('e'), 'updated')
+
+    var url2 = bURL('http://example.org/file??a=b&c=d')
+    assert_equals(url2.search, '??a=b&c=d')
+    assert_equals(url2.searchParams.toString(), '%3Fa=b&c=d')
+
+    url2.href = 'http://example.org/file??a=b'
+    assert_equals(url2.search, '??a=b')
+    assert_equals(url2.searchParams.toString(), '%3Fa=b')
+  }, 'URL.searchParams and URL.search setters, update propagation')
+}
+runURLSearchParamTests()
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any-expected.txt
new file mode 100644
index 0000000..6fcce98
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL url-tojson assert_equals: expected "\"https://example.com/\"" but got "{}"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.js b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.js
new file mode 100644
index 0000000..65165f9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.js
@@ -0,0 +1,4 @@
+test(() => {
+  const a = new URL("https://example.com/")
+  assert_equals(JSON.stringify(a), "\"https://example.com/\"")
+})
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.worker-expected.txt
new file mode 100644
index 0000000..6fcce98
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.any.worker-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL url-tojson assert_equals: expected "\"https://example.com/\"" but got "{}"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.html b/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.html
deleted file mode 100644
index 3e8798c..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/url/url-tojson.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>URL's toJSON()</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<div id=log></div>
-<script>
-test(() => {
-  const a = new URL("https://example.com/")
-  assert_equals(JSON.stringify(a), "\"https://example.com/\"")
-})
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any-expected.txt
new file mode 100644
index 0000000..4e3b49f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any-expected.txt
@@ -0,0 +1,94 @@
+This is a testharness.js-based test.
+Found 90 tests; 73 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS URLSearchParams constructed with: test
+PASS request.formData() with input: test
+PASS response.formData() with input: test
+PASS URLSearchParams constructed with: test=
+FAIL request.formData() with input: test= assert_array_equals: property 0, expected "test" but got "test"
+FAIL response.formData() with input: test= assert_array_equals: property 0, expected "test" but got "test"
+PASS URLSearchParams constructed with: %EF%BB%BFtest=%EF%BB%BF
+PASS request.formData() with input: %EF%BB%BFtest=%EF%BB%BF
+PASS response.formData() with input: %EF%BB%BFtest=%EF%BB%BF
+FAIL URLSearchParams constructed with: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL request.formData() with input: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL response.formData() with input: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL URLSearchParams constructed with: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+FAIL request.formData() with input: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+FAIL response.formData() with input: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+PASS URLSearchParams constructed with: †&†=x
+PASS request.formData() with input: †&†=x
+PASS response.formData() with input: †&†=x
+FAIL URLSearchParams constructed with: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL request.formData() with input: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL response.formData() with input: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL URLSearchParams constructed with: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL request.formData() with input: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL response.formData() with input: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL URLSearchParams constructed with: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+FAIL request.formData() with input: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+FAIL response.formData() with input: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+PASS URLSearchParams constructed with: 
+PASS request.formData() with input: 
+PASS response.formData() with input: 
+PASS URLSearchParams constructed with: a
+PASS request.formData() with input: a
+PASS response.formData() with input: a
+PASS URLSearchParams constructed with: a=b
+PASS request.formData() with input: a=b
+PASS response.formData() with input: a=b
+PASS URLSearchParams constructed with: a=
+PASS request.formData() with input: a=
+PASS response.formData() with input: a=
+PASS URLSearchParams constructed with: =b
+PASS request.formData() with input: =b
+PASS response.formData() with input: =b
+PASS URLSearchParams constructed with: &
+PASS request.formData() with input: &
+PASS response.formData() with input: &
+PASS URLSearchParams constructed with: &a
+PASS request.formData() with input: &a
+PASS response.formData() with input: &a
+PASS URLSearchParams constructed with: a&
+PASS request.formData() with input: a&
+PASS response.formData() with input: a&
+PASS URLSearchParams constructed with: a&a
+PASS request.formData() with input: a&a
+PASS response.formData() with input: a&a
+PASS URLSearchParams constructed with: a&b&c
+PASS request.formData() with input: a&b&c
+PASS response.formData() with input: a&b&c
+PASS URLSearchParams constructed with: a=b&c=d
+PASS request.formData() with input: a=b&c=d
+PASS response.formData() with input: a=b&c=d
+PASS URLSearchParams constructed with: a=b&c=d&
+PASS request.formData() with input: a=b&c=d&
+PASS response.formData() with input: a=b&c=d&
+PASS URLSearchParams constructed with: &&&a=b&&&&c=d&
+PASS request.formData() with input: &&&a=b&&&&c=d&
+PASS response.formData() with input: &&&a=b&&&&c=d&
+PASS URLSearchParams constructed with: a=a&a=b&a=c
+PASS request.formData() with input: a=a&a=b&a=c
+PASS response.formData() with input: a=a&a=b&a=c
+PASS URLSearchParams constructed with: a==a
+PASS request.formData() with input: a==a
+PASS response.formData() with input: a==a
+PASS URLSearchParams constructed with: a=a+b+c+d
+PASS request.formData() with input: a=a+b+c+d
+PASS response.formData() with input: a=a+b+c+d
+PASS URLSearchParams constructed with: %=a
+PASS request.formData() with input: %=a
+PASS response.formData() with input: %=a
+PASS URLSearchParams constructed with: %a=a
+PASS request.formData() with input: %a=a
+PASS response.formData() with input: %a=a
+PASS URLSearchParams constructed with: %a_=a
+PASS request.formData() with input: %a_=a
+PASS response.formData() with input: %a_=a
+PASS URLSearchParams constructed with: %61=a
+PASS request.formData() with input: %61=a
+PASS response.formData() with input: %61=a
+PASS URLSearchParams constructed with: %61+%4d%4D=
+PASS request.formData() with input: %61+%4d%4D=
+PASS response.formData() with input: %61+%4d%4D=
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.js
similarity index 91%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.js
index 40a54f1..65e894b 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.js
@@ -1,10 +1,3 @@
-<!doctype html>
-<meta charset=utf-8>
-<title>application/x-www-form-urlencoded parser test</title>
-<script src=/resources/testharness.js></script>
-<script src=/resources/testharnessreport.js></script>
-<div id=log></div>
-<script>
 [
   { "input": "test", "output": [["test", ""]] },
   { "input": "\uFEFFtest=\uFEFF", "output": [["\uFEFFtest", "\uFEFF"]] },
@@ -67,5 +60,4 @@
       }
     })
   }, "response.formData() with input: " + val.input)
-})
-</script>
+});
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.worker-expected.txt
new file mode 100644
index 0000000..4e3b49f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlencoded-parser.any.worker-expected.txt
@@ -0,0 +1,94 @@
+This is a testharness.js-based test.
+Found 90 tests; 73 PASS, 17 FAIL, 0 TIMEOUT, 0 NOTRUN.
+PASS URLSearchParams constructed with: test
+PASS request.formData() with input: test
+PASS response.formData() with input: test
+PASS URLSearchParams constructed with: test=
+FAIL request.formData() with input: test= assert_array_equals: property 0, expected "test" but got "test"
+FAIL response.formData() with input: test= assert_array_equals: property 0, expected "test" but got "test"
+PASS URLSearchParams constructed with: %EF%BB%BFtest=%EF%BB%BF
+PASS request.formData() with input: %EF%BB%BFtest=%EF%BB%BF
+PASS response.formData() with input: %EF%BB%BFtest=%EF%BB%BF
+FAIL URLSearchParams constructed with: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL request.formData() with input: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL response.formData() with input: %FE%FF assert_array_equals: property 0, expected "\ufffd\ufffd" but got "þÿ"
+FAIL URLSearchParams constructed with: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+FAIL request.formData() with input: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+FAIL response.formData() with input: %FF%FE assert_array_equals: property 0, expected "\ufffd\ufffd" but got "ÿþ"
+PASS URLSearchParams constructed with: †&†=x
+PASS request.formData() with input: †&†=x
+PASS response.formData() with input: †&†=x
+FAIL URLSearchParams constructed with: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL request.formData() with input: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL response.formData() with input: %C2 assert_array_equals: property 0, expected "\ufffd" but got "Â"
+FAIL URLSearchParams constructed with: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL request.formData() with input: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL response.formData() with input: %C2x assert_array_equals: property 0, expected "\ufffdx" but got "Âx"
+FAIL URLSearchParams constructed with: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+FAIL request.formData() with input: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+FAIL response.formData() with input: _charset_=windows-1252&test=%C2x assert_array_equals: property 1, expected "\ufffdx" but got "Âx"
+PASS URLSearchParams constructed with: 
+PASS request.formData() with input: 
+PASS response.formData() with input: 
+PASS URLSearchParams constructed with: a
+PASS request.formData() with input: a
+PASS response.formData() with input: a
+PASS URLSearchParams constructed with: a=b
+PASS request.formData() with input: a=b
+PASS response.formData() with input: a=b
+PASS URLSearchParams constructed with: a=
+PASS request.formData() with input: a=
+PASS response.formData() with input: a=
+PASS URLSearchParams constructed with: =b
+PASS request.formData() with input: =b
+PASS response.formData() with input: =b
+PASS URLSearchParams constructed with: &
+PASS request.formData() with input: &
+PASS response.formData() with input: &
+PASS URLSearchParams constructed with: &a
+PASS request.formData() with input: &a
+PASS response.formData() with input: &a
+PASS URLSearchParams constructed with: a&
+PASS request.formData() with input: a&
+PASS response.formData() with input: a&
+PASS URLSearchParams constructed with: a&a
+PASS request.formData() with input: a&a
+PASS response.formData() with input: a&a
+PASS URLSearchParams constructed with: a&b&c
+PASS request.formData() with input: a&b&c
+PASS response.formData() with input: a&b&c
+PASS URLSearchParams constructed with: a=b&c=d
+PASS request.formData() with input: a=b&c=d
+PASS response.formData() with input: a=b&c=d
+PASS URLSearchParams constructed with: a=b&c=d&
+PASS request.formData() with input: a=b&c=d&
+PASS response.formData() with input: a=b&c=d&
+PASS URLSearchParams constructed with: &&&a=b&&&&c=d&
+PASS request.formData() with input: &&&a=b&&&&c=d&
+PASS response.formData() with input: &&&a=b&&&&c=d&
+PASS URLSearchParams constructed with: a=a&a=b&a=c
+PASS request.formData() with input: a=a&a=b&a=c
+PASS response.formData() with input: a=a&a=b&a=c
+PASS URLSearchParams constructed with: a==a
+PASS request.formData() with input: a==a
+PASS response.formData() with input: a==a
+PASS URLSearchParams constructed with: a=a+b+c+d
+PASS request.formData() with input: a=a+b+c+d
+PASS response.formData() with input: a=a+b+c+d
+PASS URLSearchParams constructed with: %=a
+PASS request.formData() with input: %=a
+PASS response.formData() with input: %=a
+PASS URLSearchParams constructed with: %a=a
+PASS request.formData() with input: %a=a
+PASS response.formData() with input: %a=a
+PASS URLSearchParams constructed with: %a_=a
+PASS request.formData() with input: %a_=a
+PASS response.formData() with input: %a_=a
+PASS URLSearchParams constructed with: %61=a
+PASS request.formData() with input: %61=a
+PASS response.formData() with input: %61=a
+PASS URLSearchParams constructed with: %61+%4d%4D=
+PASS request.formData() with input: %61+%4d%4D=
+PASS response.formData() with input: %61+%4d%4D=
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.any.js
similarity index 84%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.any.js
index ca367e6f..5a73761 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-append.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-append">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams();
     params.append('a', 'b');
@@ -45,6 +37,3 @@
     params.append('first', 10);
     assert_equals(params.get('first'), '1', 'Search params object has name "first" with value "1"');
 }, 'Append multiple');
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.any.js
similarity index 96%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.any.js
index 0637a386..5412fc8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-constructor.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#urlsearchparams">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams();
     assert_equals(params + '', '');
@@ -182,6 +174,3 @@
   let params2 = new URLSearchParams(params)
   assert_equals(params2.get("a"), "b")
 }, "Custom [Symbol.iterator]")
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.any.js
similarity index 87%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.any.js
index d4bc0a6d..1aa9b313 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-delete.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-delete">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=b&c=d');
     params.delete('a');
@@ -51,6 +43,3 @@
     assert_equals(url.href, 'http://example.com/', 'url.href does not have ?');
     assert_equals(url.search, '', 'url.search does not have ?');
 }, 'Removing non-existent param removes ? from URL');
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.any.js
similarity index 90%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.any.js
index 29c0ee8..7969a0c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-foreach.any.js
@@ -1,9 +1,3 @@
-<!doctype html>
-<meta charset="utf-8">
-<link rel="help" href="https://url.spec.whatwg.org/#dom-urlsearchparams-has">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=1&b=2&c=3');
     var keys = [];
@@ -80,4 +74,3 @@
     assert_array_equals(seen, ["param0", "param2"], "param1 should not have been seen by the loop");
     assert_equals(String(searchParams), "param1=1", "param1 should remain");
 }, "delete every param seen during iteration");
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.any.js
similarity index 79%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.any.js
index 1d27727c..a2610fc 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-get.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-get">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=b&c=d');
     assert_equals(params.get('a'), 'b');
@@ -27,6 +19,3 @@
     assert_equals(params.get('third'), '', 'Search params object has name "third" with the empty value.');
     assert_equals(params.get('fourth'), null, 'Search params object has no "fourth" name and value.');
 }, 'More get() basics');
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.any.js
similarity index 82%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.any.js
index db6c7e3..5d1a353 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-getall.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-getAll">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=b&c=d');
     assert_array_equals(params.getAll('a'), ['b']);
@@ -31,6 +23,3 @@
     assert_true(matches && matches.length == 1, 'Search params object has values for name "a"');
     assert_array_equals(matches, ['one'], 'Search params object has expected name "a" values');
 }, 'getAll() multiples');
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.any.js
similarity index 78%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.any.js
index ad045f73..673dce7 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-has.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-has">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=b&c=d');
     assert_true(params.has('a'));
@@ -30,6 +22,3 @@
     params.delete('first');
     assert_false(params.has('first'), 'Search params object has no name "first"');
 }, 'has() following delete()');
-</script>
-</head>
-</html>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.any.js
similarity index 78%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.any.js
index 61578b2..eb24cac 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-set.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-set">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams('a=b&c=d');
     params.set('a', 'B');
@@ -28,7 +20,3 @@
     assert_true(params.has('a'), 'Search params object has name "a"');
     assert_equals(params.get('a'), '4', 'Search params object has name "a" with value "4"');
 }, 'URLSearchParams.set');
-</script>
-</head>
-</html>
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.any.js
similarity index 82%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.any.js
index 34199894..4fd8cef 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-sort.any.js
@@ -1,9 +1,3 @@
-<!doctype html>
-<meta charset="utf8">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id=log></div>
-<script>
 [
   {
     "input": "z=b&a=b&z=a&a=a",
@@ -24,6 +18,18 @@
   {
     "input": "z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g",
     "output": [["a", "a"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["a", "f"], ["a", "g"], ["z", "z"], ["z", "y"], ["z", "x"], ["z", "w"], ["z", "v"], ["z", "u"], ["z", "t"]]
+  },
+  {
+    "input": "bbb&bb&aaa&aa=x&aa=y",
+    "output": [["aa", "x"], ["aa", "y"], ["aaa", ""], ["bb", ""], ["bbb", ""]]
+  },
+  {
+    "input": "z=z&=f&=t&=x",
+    "output": [["", "f"], ["", "t"], ["", "x"], ["z", "z"]]
+  },
+  {
+    "input": "a🌈&a💩",
+    "output": [["a🌈", ""], ["a💩", ""]]
   }
 ].forEach((val) => {
   test(() => {
@@ -54,4 +60,3 @@
   assert_equals(url.href, "http://example.com/")
   assert_equals(url.search, "")
 }, "Sorting non-existent params removes ? from URL")
-</script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.html b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.any.js
similarity index 92%
rename from third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.html
rename to third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.any.js
index 5e885b4..ef95c14 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/url/urlsearchparams-stringifier.any.js
@@ -1,11 +1,3 @@
-<!doctype html>
-<html>
-<head>
-<meta charset="utf8">
-<link rel="help" href="http://url.spec.whatwg.org/#dom-urlsearchparams-set">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
 test(function() {
     var params = new URLSearchParams();
     params.append('a', 'b c');
@@ -129,8 +121,3 @@
     assert_equals(url.toString(), 'http://www.example.com/?a=b%2Cc&x=y');
     assert_equals(params.toString(), 'a=b%2Cc&x=y');
 }, 'URLSearchParams connected to URL');
-
-</script>
-</head>
-</html>
-
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window-expected.txt
new file mode 100644
index 0000000..a9872fa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS webrtc-stats interfaces.
+PASS Partial dictionary RTCIceCandidateStats: original dictionary defined
+PASS Partial dictionary RTCIceCandidatePairStats: original dictionary defined
+FAIL Partial dictionary RTCRTPStreamStats: original dictionary defined assert_true: Original dictionary should be defined expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window.js b/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window.js
new file mode 100644
index 0000000..ead85cb
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc-stats/idlharness.window.js
@@ -0,0 +1,14 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://w3c.github.io/webrtc-stats/
+
+idl_test(
+  ['webrtc-stats'],
+  [], // No deps
+  idl_array => {
+    // No interfaces to test
+  },
+  'webrtc-stats interfaces.');
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt
index 7a8bc53..508220c 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy-expected.txt
@@ -10,5 +10,7 @@
 PASS setConfiguration({ rtcpMuxPolicy: 'negotiate' }) with initial rtcpMuxPolicy require should throw InvalidModificationError
 PASS setConfiguration({ rtcpMuxPolicy: 'require' }) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError
 PASS setConfiguration({}) with initial rtcpMuxPolicy negotiate should throw InvalidModificationError
+PASS setRemoteDescription throws InvalidAccessError when called with an offer without rtcp-mux and rtcpMuxPolicy is set to require
+PASS setRemoteDescription throws InvalidAccessError when called with an answer without rtcp-mux and rtcpMuxPolicy is set to require
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy.html b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy.html
index 6b65a12..1d99aa29 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy.html
+++ b/third_party/WebKit/LayoutTests/external/wpt/webrtc/RTCConfiguration-rtcpMuxPolicy.html
@@ -140,4 +140,56 @@
       Tested    2
       Total     2
    */
+  const FINGERPRINT_SHA256 = '00:00:00:00:00:00:00:00:00:00:00:00:00' +
+      ':00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00';
+  const ICEUFRAG = 'someufrag';
+  const ICEPWD = 'somelongpwdwithenoughrandomness';
+
+  promise_test(async t => {
+    // audio-only SDP offer without BUNDLE and rtcp-mux.
+    const sdp = 'v=0\r\n' +
+        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
+        's=-\r\n' +
+        't=0 0\r\n' +
+        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
+        'c=IN IP4 0.0.0.0\r\n' +
+        'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
+        'a=ice-ufrag:' + ICEUFRAG + '\r\n' +
+        'a=ice-pwd:' + ICEPWD + '\r\n' +
+        'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' +
+        'a=setup:actpass\r\n' +
+        'a=mid:audio1\r\n' +
+        'a=sendonly\r\n' +
+        'a=rtcp-rsize\r\n' +
+        'a=rtpmap:111 opus/48000/2\r\n';
+    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
+    t.add_cleanup(() => pc.close());
+
+    return promise_rejects(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'offer', sdp}));
+  }, 'setRemoteDescription throws InvalidAccessError when called with an offer without rtcp-mux and rtcpMuxPolicy is set to require');
+
+  promise_test(async t => {
+    // audio-only SDP answer without BUNDLE and rtcp-mux.
+    // Also omitting a=mid in order to avoid parsing it from the offer as this needs to match.
+    const sdp = 'v=0\r\n' +
+        'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' +
+        's=-\r\n' +
+        't=0 0\r\n' +
+        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
+        'c=IN IP4 0.0.0.0\r\n' +
+        'a=rtcp:9 IN IP4 0.0.0.0\r\n' +
+        'a=ice-ufrag:' + ICEUFRAG + '\r\n' +
+        'a=ice-pwd:' + ICEPWD + '\r\n' +
+        'a=fingerprint:sha-256 ' + FINGERPRINT_SHA256 + '\r\n' +
+        'a=setup:active\r\n' +
+        'a=sendonly\r\n' +
+        'a=rtcp-rsize\r\n' +
+        'a=rtpmap:111 opus/48000/2\r\n';
+    const pc = new RTCPeerConnection({rtcpMuxPolicy: 'require'});
+    t.add_cleanup(() => pc.close());
+
+    await pc.createOffer({offerToReceiveAudio: true})
+      .then(offer => pc.setLocalDescription(offer));
+    return promise_rejects(t, 'InvalidAccessError', pc.setRemoteDescription({type: 'answer', sdp}));
+  }, 'setRemoteDescription throws InvalidAccessError when called with an answer without rtcp-mux and rtcpMuxPolicy is set to require');
 </script>
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any-expected.txt
index 8a2ac19..3de160f8 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
-PASS idlharness
 PASS WebUSB IDL test
+FAIL USB device setup promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'configurations' of undefined"
 PASS Partial interface Navigator: original interface defined
 PASS Partial interface Navigator: valid exposure set
 PASS Partial interface WorkerNavigator: original interface defined
@@ -29,9 +29,9 @@
 PASS USBConnectionEvent interface: existence and properties of interface prototype object's "constructor" property
 PASS USBConnectionEvent interface: existence and properties of interface prototype object's @@unscopables property
 PASS USBConnectionEvent interface: attribute device
-PASS USBConnectionEvent must be primary interface of usbConnectionEvent
-PASS Stringification of usbConnectionEvent
-PASS USBConnectionEvent interface: usbConnectionEvent must inherit property "device" with the proper type
+FAIL USBConnectionEvent must be primary interface of usbConnectionEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
+FAIL Stringification of usbConnectionEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
+FAIL USBConnectionEvent interface: usbConnectionEvent must inherit property "device" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
 PASS USBDevice interface: existence and properties of interface object
 PASS USBDevice interface object length
 PASS USBDevice interface object name
@@ -69,50 +69,50 @@
 PASS USBDevice interface: operation isochronousTransferIn(octet, [object Object])
 PASS USBDevice interface: operation isochronousTransferOut(octet, BufferSource, [object Object])
 PASS USBDevice interface: operation reset()
-PASS USBDevice must be primary interface of usbDevice
-PASS Stringification of usbDevice
-PASS USBDevice interface: usbDevice must inherit property "usbVersionMajor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "usbVersionMinor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "usbVersionSubminor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceClass" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceSubclass" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceProtocol" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "vendorId" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "productId" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionMajor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionMinor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionSubminor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "manufacturerName" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "productName" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "serialNumber" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "configuration" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "configurations" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "opened" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "open()" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "close()" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "selectConfiguration(octet)" with the proper type
-PASS USBDevice interface: calling selectConfiguration(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "claimInterface(octet)" with the proper type
-PASS USBDevice interface: calling claimInterface(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "releaseInterface(octet)" with the proper type
-PASS USBDevice interface: calling releaseInterface(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "selectAlternateInterface(octet, octet)" with the proper type
-PASS USBDevice interface: calling selectAlternateInterface(octet, octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "controlTransferIn(USBControlTransferParameters, unsigned short)" with the proper type
-PASS USBDevice interface: calling controlTransferIn(USBControlTransferParameters, unsigned short) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "controlTransferOut(USBControlTransferParameters, BufferSource)" with the proper type
-PASS USBDevice interface: calling controlTransferOut(USBControlTransferParameters, BufferSource) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "clearHalt(USBDirection, octet)" with the proper type
-PASS USBDevice interface: calling clearHalt(USBDirection, octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "transferIn(octet, unsigned long)" with the proper type
-PASS USBDevice interface: calling transferIn(octet, unsigned long) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "transferOut(octet, BufferSource)" with the proper type
-PASS USBDevice interface: calling transferOut(octet, BufferSource) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "isochronousTransferIn(octet, [object Object])" with the proper type
-PASS USBDevice interface: calling isochronousTransferIn(octet, [object Object]) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "isochronousTransferOut(octet, BufferSource, [object Object])" with the proper type
-PASS USBDevice interface: calling isochronousTransferOut(octet, BufferSource, [object Object]) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "reset()" with the proper type
+FAIL USBDevice must be primary interface of usbDevice assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL Stringification of usbDevice assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionMajor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionMinor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionSubminor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceClass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceSubclass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceProtocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "vendorId" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "productId" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionMajor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionMinor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionSubminor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "manufacturerName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "productName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "serialNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "configuration" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "configurations" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "opened" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "open()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "close()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "selectConfiguration(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling selectConfiguration(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "claimInterface(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling claimInterface(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "releaseInterface(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling releaseInterface(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "selectAlternateInterface(octet, octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling selectAlternateInterface(octet, octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "controlTransferIn(USBControlTransferParameters, unsigned short)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling controlTransferIn(USBControlTransferParameters, unsigned short) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "controlTransferOut(USBControlTransferParameters, BufferSource)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling controlTransferOut(USBControlTransferParameters, BufferSource) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "clearHalt(USBDirection, octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling clearHalt(USBDirection, octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "transferIn(octet, unsigned long)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling transferIn(octet, unsigned long) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "transferOut(octet, BufferSource)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling transferOut(octet, BufferSource) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "isochronousTransferIn(octet, [object Object])" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling isochronousTransferIn(octet, [object Object]) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "isochronousTransferOut(octet, BufferSource, [object Object])" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling isochronousTransferOut(octet, BufferSource, [object Object]) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "reset()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
 PASS USBInTransferResult interface: existence and properties of interface object
 PASS USBInTransferResult interface object length
 PASS USBInTransferResult interface object name
@@ -192,11 +192,11 @@
 PASS USBConfiguration interface: attribute configurationValue
 PASS USBConfiguration interface: attribute configurationName
 PASS USBConfiguration interface: attribute interfaces
-PASS USBConfiguration must be primary interface of usbConfiguration
-PASS Stringification of usbConfiguration
-PASS USBConfiguration interface: usbConfiguration must inherit property "configurationValue" with the proper type
-PASS USBConfiguration interface: usbConfiguration must inherit property "configurationName" with the proper type
-PASS USBConfiguration interface: usbConfiguration must inherit property "interfaces" with the proper type
+FAIL USBConfiguration must be primary interface of usbConfiguration assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL Stringification of usbConfiguration assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "configurationValue" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "configurationName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "interfaces" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
 PASS USBInterface interface: existence and properties of interface object
 PASS USBInterface interface object length
 PASS USBInterface interface object name
@@ -207,12 +207,12 @@
 PASS USBInterface interface: attribute alternate
 PASS USBInterface interface: attribute alternates
 PASS USBInterface interface: attribute claimed
-PASS USBInterface must be primary interface of usbInterface
-PASS Stringification of usbInterface
-PASS USBInterface interface: usbInterface must inherit property "interfaceNumber" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "alternate" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "alternates" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "claimed" with the proper type
+FAIL USBInterface must be primary interface of usbInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL Stringification of usbInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "interfaceNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "alternate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "alternates" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "claimed" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
 PASS USBAlternateInterface interface: existence and properties of interface object
 PASS USBAlternateInterface interface object length
 PASS USBAlternateInterface interface object name
@@ -225,14 +225,14 @@
 PASS USBAlternateInterface interface: attribute interfaceProtocol
 PASS USBAlternateInterface interface: attribute interfaceName
 PASS USBAlternateInterface interface: attribute endpoints
-PASS USBAlternateInterface must be primary interface of usbAlternateInterface
-PASS Stringification of usbAlternateInterface
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "alternateSetting" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceClass" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceSubclass" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceProtocol" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceName" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "endpoints" with the proper type
+FAIL USBAlternateInterface must be primary interface of usbAlternateInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL Stringification of usbAlternateInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "alternateSetting" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceClass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceSubclass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceProtocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "endpoints" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
 PASS USBEndpoint interface: existence and properties of interface object
 PASS USBEndpoint interface object length
 PASS USBEndpoint interface object name
@@ -243,12 +243,12 @@
 PASS USBEndpoint interface: attribute direction
 PASS USBEndpoint interface: attribute type
 PASS USBEndpoint interface: attribute packetSize
-PASS USBEndpoint must be primary interface of usbEndpoint
-PASS Stringification of usbEndpoint
-PASS USBEndpoint interface: usbEndpoint must inherit property "endpointNumber" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "direction" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "type" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "packetSize" with the proper type
+FAIL USBEndpoint must be primary interface of usbEndpoint assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL Stringification of usbEndpoint assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "endpointNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "direction" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "packetSize" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
 FAIL USBPermissionResult interface: existence and properties of interface object assert_own_property: self does not have own property "USBPermissionResult" expected property "USBPermissionResult" missing
 FAIL USBPermissionResult interface object length assert_own_property: self does not have own property "USBPermissionResult" expected property "USBPermissionResult" missing
 FAIL USBPermissionResult interface object name assert_own_property: self does not have own property "USBPermissionResult" expected property "USBPermissionResult" missing
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.js b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.js
index e520200..9ba0198a 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.js
@@ -2,67 +2,45 @@
 // META: script=/resources/idlharness.js
 // META: script=/webusb/resources/fake-devices.js
 // META: script=/webusb/resources/usb-helpers.js
+
 'use strict';
 
-// Object instances used by the IDL test.
-var usbDevice;
-var usbConfiguration;
-var usbInterface;
-var usbAlternateInterface;
-var usbEndpoint;
-var usbConnectionEvent;
+idl_test(
+  ['webusb'],
+  ['permissions', 'html', 'dom'],
+  async idl_array => {
+    if (self.GLOBAL.isWindow()) {
+      idl_array.add_objects({ Navigator: ['navigator'] });
+    } else if (self.GLOBAL.isWorker()) {
+      idl_array.add_objects({ WorkerNavigator: ['navigator'] });
+    }
 
-usb_test(async () => {
-  // Ignored errors are surfaced in idlharness.js's test_object below.
-  let device;
-  try {
-    device = (await getFakeDevice()).device;
-  } catch (e) {}
+    idl_array.add_objects({
+      USB: ['navigator.usb'],
+      USBAlternateInterface: ['usbAlternateInterface'],
+      USBConfiguration: ['usbConfiguration'],
+      USBConnectionEvent: ['usbConnectionEvent'],
+      USBDevice: ['usbDevice'],
+      USBEndpoint: ['usbEndpoint'],
+      USBInterface: ['usbInterface'],
+      USBInTransferResult: ['new USBInTransferResult("ok")'],
+      USBOutTransferResult: ['new USBOutTransferResult("ok")'],
+      USBIsochronousInTransferResult: ['new USBIsochronousInTransferResult([])'],
+      USBIsochronousOutTransferResult: ['new USBIsochronousOutTransferResult([])'],
+      USBIsochronousInTransferPacket: ['new USBIsochronousInTransferPacket("ok")'],
+      USBIsochronousOutTransferPacket: ['new USBIsochronousOutTransferPacket("ok")'],
+    });
 
-  return idl_test(
-    ['webusb'],
-    ['html', 'dom'],
-    idl_array => {
-      // Untested IDL interfaces
-      idl_array.add_untested_idls('dictionary PermissionDescriptor {};');
-      idl_array.add_untested_idls('interface PermissionStatus {};');
-
-      try {
-        usbDevice = device;
-        usbConfiguration = usbDevice.configurations[0];
-        usbInterface = usbConfiguration.interfaces[0];
-        usbAlternateInterface = usbInterface.alternates[0];
-        usbEndpoint = usbAlternateInterface.endpoints[0];
-        usbConnectionEvent =
-            new USBConnectionEvent('connect', { device: usbDevice })
-      } catch (e) {}
-
-      if (self.GLOBAL.isWindow()) {
-        idl_array.add_objects({ Navigator: ['navigator'] });
-      } else if (self.GLOBAL.isWorker()) {
-        idl_array.add_objects({ WorkerNavigator: ['navigator'] });
-      }
-
-      idl_array.add_objects({
-        Navigator: ['navigator'],
-        USB: ['navigator.usb'],
-        USBAlternateInterface: ['usbAlternateInterface'],
-        USBConfiguration: ['usbConfiguration'],
-        USBConnectionEvent: ['usbConnectionEvent'],
-        USBDevice: ['usbDevice'],
-        USBEndpoint: ['usbEndpoint'],
-        USBInterface: ['usbInterface'],
-        USBInTransferResult: ['new USBInTransferResult("ok")'],
-        USBOutTransferResult: ['new USBOutTransferResult("ok")'],
-        USBIsochronousInTransferResult: ['new USBIsochronousInTransferResult([])'],
-        USBIsochronousOutTransferResult: ['new USBIsochronousOutTransferResult([])'],
-        USBIsochronousInTransferPacket: ['new USBIsochronousInTransferPacket("ok")'],
-        USBIsochronousOutTransferPacket: ['new USBIsochronousOutTransferPacket("ok")'],
-      });
-      idl_array.prevent_multiple_testing('Navigator');
-    },
-    'WebUSB IDL test'
-  )
-});
-
-done();
+    return usb_test(async () => {
+      // Ignored errors are surfaced in idlharness.js's test_object below.
+      self.usbDevice = await getFakeDevice().device;
+      self.usbConfiguration = usbDevice.configurations[0];
+      self.usbInterface = usbConfiguration.interfaces[0];
+      self.usbAlternateInterface = usbInterface.alternates[0];
+      self.usbEndpoint = usbAlternateInterface.endpoints[0];
+      self.usbConnectionEvent =
+          new USBConnectionEvent('connect', { device: usbDevice });
+    }, 'USB device setup');
+  },
+  'WebUSB IDL test'
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.worker-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.worker-expected.txt
index 81bf807..dd75495 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/external/wpt/webusb/idlharness.https.any.worker-expected.txt
@@ -1,6 +1,6 @@
 This is a testharness.js-based test.
-PASS idlharness
 PASS WebUSB IDL test
+FAIL USB device setup promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'configurations' of undefined"
 PASS Partial interface Navigator: original interface defined
 PASS Partial interface Navigator: valid exposure set
 PASS Partial interface WorkerNavigator: original interface defined
@@ -28,9 +28,9 @@
 PASS USBConnectionEvent interface: existence and properties of interface prototype object's "constructor" property
 PASS USBConnectionEvent interface: existence and properties of interface prototype object's @@unscopables property
 PASS USBConnectionEvent interface: attribute device
-PASS USBConnectionEvent must be primary interface of usbConnectionEvent
-PASS Stringification of usbConnectionEvent
-PASS USBConnectionEvent interface: usbConnectionEvent must inherit property "device" with the proper type
+FAIL USBConnectionEvent must be primary interface of usbConnectionEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
+FAIL Stringification of usbConnectionEvent assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
+FAIL USBConnectionEvent interface: usbConnectionEvent must inherit property "device" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConnectionEvent is not defined"
 PASS USBDevice interface: existence and properties of interface object
 PASS USBDevice interface object length
 PASS USBDevice interface object name
@@ -68,50 +68,50 @@
 PASS USBDevice interface: operation isochronousTransferIn(octet, [object Object])
 PASS USBDevice interface: operation isochronousTransferOut(octet, BufferSource, [object Object])
 PASS USBDevice interface: operation reset()
-PASS USBDevice must be primary interface of usbDevice
-PASS Stringification of usbDevice
-PASS USBDevice interface: usbDevice must inherit property "usbVersionMajor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "usbVersionMinor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "usbVersionSubminor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceClass" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceSubclass" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceProtocol" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "vendorId" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "productId" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionMajor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionMinor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "deviceVersionSubminor" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "manufacturerName" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "productName" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "serialNumber" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "configuration" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "configurations" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "opened" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "open()" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "close()" with the proper type
-PASS USBDevice interface: usbDevice must inherit property "selectConfiguration(octet)" with the proper type
-PASS USBDevice interface: calling selectConfiguration(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "claimInterface(octet)" with the proper type
-PASS USBDevice interface: calling claimInterface(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "releaseInterface(octet)" with the proper type
-PASS USBDevice interface: calling releaseInterface(octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "selectAlternateInterface(octet, octet)" with the proper type
-PASS USBDevice interface: calling selectAlternateInterface(octet, octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "controlTransferIn(USBControlTransferParameters, unsigned short)" with the proper type
-PASS USBDevice interface: calling controlTransferIn(USBControlTransferParameters, unsigned short) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "controlTransferOut(USBControlTransferParameters, BufferSource)" with the proper type
-PASS USBDevice interface: calling controlTransferOut(USBControlTransferParameters, BufferSource) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "clearHalt(USBDirection, octet)" with the proper type
-PASS USBDevice interface: calling clearHalt(USBDirection, octet) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "transferIn(octet, unsigned long)" with the proper type
-PASS USBDevice interface: calling transferIn(octet, unsigned long) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "transferOut(octet, BufferSource)" with the proper type
-PASS USBDevice interface: calling transferOut(octet, BufferSource) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "isochronousTransferIn(octet, [object Object])" with the proper type
-PASS USBDevice interface: calling isochronousTransferIn(octet, [object Object]) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "isochronousTransferOut(octet, BufferSource, [object Object])" with the proper type
-PASS USBDevice interface: calling isochronousTransferOut(octet, BufferSource, [object Object]) on usbDevice with too few arguments must throw TypeError
-PASS USBDevice interface: usbDevice must inherit property "reset()" with the proper type
+FAIL USBDevice must be primary interface of usbDevice assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL Stringification of usbDevice assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionMajor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionMinor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "usbVersionSubminor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceClass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceSubclass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceProtocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "vendorId" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "productId" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionMajor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionMinor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "deviceVersionSubminor" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "manufacturerName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "productName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "serialNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "configuration" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "configurations" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "opened" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "open()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "close()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "selectConfiguration(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling selectConfiguration(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "claimInterface(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling claimInterface(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "releaseInterface(octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling releaseInterface(octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "selectAlternateInterface(octet, octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling selectAlternateInterface(octet, octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "controlTransferIn(USBControlTransferParameters, unsigned short)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling controlTransferIn(USBControlTransferParameters, unsigned short) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "controlTransferOut(USBControlTransferParameters, BufferSource)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling controlTransferOut(USBControlTransferParameters, BufferSource) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "clearHalt(USBDirection, octet)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling clearHalt(USBDirection, octet) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "transferIn(octet, unsigned long)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling transferIn(octet, unsigned long) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "transferOut(octet, BufferSource)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling transferOut(octet, BufferSource) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "isochronousTransferIn(octet, [object Object])" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling isochronousTransferIn(octet, [object Object]) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "isochronousTransferOut(octet, BufferSource, [object Object])" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: calling isochronousTransferOut(octet, BufferSource, [object Object]) on usbDevice with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
+FAIL USBDevice interface: usbDevice must inherit property "reset()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbDevice is not defined"
 PASS USBInTransferResult interface: existence and properties of interface object
 PASS USBInTransferResult interface object length
 PASS USBInTransferResult interface object name
@@ -191,11 +191,11 @@
 PASS USBConfiguration interface: attribute configurationValue
 PASS USBConfiguration interface: attribute configurationName
 PASS USBConfiguration interface: attribute interfaces
-PASS USBConfiguration must be primary interface of usbConfiguration
-PASS Stringification of usbConfiguration
-PASS USBConfiguration interface: usbConfiguration must inherit property "configurationValue" with the proper type
-PASS USBConfiguration interface: usbConfiguration must inherit property "configurationName" with the proper type
-PASS USBConfiguration interface: usbConfiguration must inherit property "interfaces" with the proper type
+FAIL USBConfiguration must be primary interface of usbConfiguration assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL Stringification of usbConfiguration assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "configurationValue" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "configurationName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
+FAIL USBConfiguration interface: usbConfiguration must inherit property "interfaces" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbConfiguration is not defined"
 PASS USBInterface interface: existence and properties of interface object
 PASS USBInterface interface object length
 PASS USBInterface interface object name
@@ -206,12 +206,12 @@
 PASS USBInterface interface: attribute alternate
 PASS USBInterface interface: attribute alternates
 PASS USBInterface interface: attribute claimed
-PASS USBInterface must be primary interface of usbInterface
-PASS Stringification of usbInterface
-PASS USBInterface interface: usbInterface must inherit property "interfaceNumber" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "alternate" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "alternates" with the proper type
-PASS USBInterface interface: usbInterface must inherit property "claimed" with the proper type
+FAIL USBInterface must be primary interface of usbInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL Stringification of usbInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "interfaceNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "alternate" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "alternates" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
+FAIL USBInterface interface: usbInterface must inherit property "claimed" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbInterface is not defined"
 PASS USBAlternateInterface interface: existence and properties of interface object
 PASS USBAlternateInterface interface object length
 PASS USBAlternateInterface interface object name
@@ -224,14 +224,14 @@
 PASS USBAlternateInterface interface: attribute interfaceProtocol
 PASS USBAlternateInterface interface: attribute interfaceName
 PASS USBAlternateInterface interface: attribute endpoints
-PASS USBAlternateInterface must be primary interface of usbAlternateInterface
-PASS Stringification of usbAlternateInterface
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "alternateSetting" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceClass" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceSubclass" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceProtocol" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceName" with the proper type
-PASS USBAlternateInterface interface: usbAlternateInterface must inherit property "endpoints" with the proper type
+FAIL USBAlternateInterface must be primary interface of usbAlternateInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL Stringification of usbAlternateInterface assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "alternateSetting" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceClass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceSubclass" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceProtocol" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "interfaceName" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
+FAIL USBAlternateInterface interface: usbAlternateInterface must inherit property "endpoints" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbAlternateInterface is not defined"
 PASS USBEndpoint interface: existence and properties of interface object
 PASS USBEndpoint interface object length
 PASS USBEndpoint interface object name
@@ -242,17 +242,15 @@
 PASS USBEndpoint interface: attribute direction
 PASS USBEndpoint interface: attribute type
 PASS USBEndpoint interface: attribute packetSize
-PASS USBEndpoint must be primary interface of usbEndpoint
-PASS Stringification of usbEndpoint
-PASS USBEndpoint interface: usbEndpoint must inherit property "endpointNumber" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "direction" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "type" with the proper type
-PASS USBEndpoint interface: usbEndpoint must inherit property "packetSize" with the proper type
+FAIL USBEndpoint must be primary interface of usbEndpoint assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL Stringification of usbEndpoint assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "endpointNumber" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "direction" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "type" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
+FAIL USBEndpoint interface: usbEndpoint must inherit property "packetSize" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: usbEndpoint is not defined"
 PASS USBPermissionResult interface: existence and properties of interface object
 PASS Navigator interface: existence and properties of interface object
-FAIL Navigator interface: navigator must not have property "usb" assert_false: expected false got true
 PASS WorkerNavigator interface: attribute usb
 PASS WorkerNavigator interface: navigator must inherit property "usb" with the proper type
-FAIL PermissionStatus interface: existence and properties of interface object assert_false: expected false got true
 Harness: the test ran to completion.
 
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webusb/resources/usb-helpers.js b/third_party/WebKit/LayoutTests/external/wpt/webusb/resources/usb-helpers.js
index 81a712c..e2b7d46 100644
--- a/third_party/WebKit/LayoutTests/external/wpt/webusb/resources/usb-helpers.js
+++ b/third_party/WebKit/LayoutTests/external/wpt/webusb/resources/usb-helpers.js
@@ -9,7 +9,7 @@
 //
 //   --enable-blink-features=MojoJS,MojoJSTest
 let loadChromiumResources = Promise.resolve().then(() => {
-  if (!MojoInterfaceInterceptor) {
+  if (!('MojoInterfaceInterceptor' in self)) {
     // Do nothing on non-Chromium-based browsers or when the Mojo bindings are
     // not present in the global namespace.
     return;
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window-expected.txt
new file mode 100644
index 0000000..447a11b8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window-expected.txt
@@ -0,0 +1,233 @@
+This is a testharness.js-based test.
+FAIL Test IDL implementation of WebXR API promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'requestDevice' of undefined"
+PASS Partial interface Navigator: original interface defined
+PASS Partial dictionary WebGLContextAttributes: original dictionary defined
+PASS Partial interface mixin WebGLRenderingContextBase: original interface mixin defined
+PASS XR interface: existence and properties of interface object
+PASS XR interface object length
+PASS XR interface object name
+PASS XR interface: existence and properties of interface prototype object
+PASS XR interface: existence and properties of interface prototype object's "constructor" property
+PASS XR interface: existence and properties of interface prototype object's @@unscopables property
+PASS XR interface: operation requestDevice()
+PASS XR interface: attribute ondevicechange
+FAIL XR must be primary interface of navigator.XR assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL Stringification of navigator.XR assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL XR interface: navigator.XR must inherit property "requestDevice()" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+FAIL XR interface: navigator.XR must inherit property "ondevicechange" with the proper type assert_equals: wrong typeof object expected "object" but got "undefined"
+PASS XRDevice interface: existence and properties of interface object
+PASS XRDevice interface object length
+PASS XRDevice interface object name
+PASS XRDevice interface: existence and properties of interface prototype object
+PASS XRDevice interface: existence and properties of interface prototype object's "constructor" property
+PASS XRDevice interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRDevice interface: operation supportsSession(XRSessionCreationOptions)
+PASS XRDevice interface: operation requestSession(XRSessionCreationOptions)
+FAIL XRDevice must be primary interface of device assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+FAIL Stringification of device assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+FAIL XRDevice interface: device must inherit property "supportsSession(XRSessionCreationOptions)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+FAIL XRDevice interface: calling supportsSession(XRSessionCreationOptions) on device with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+FAIL XRDevice interface: device must inherit property "requestSession(XRSessionCreationOptions)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+FAIL XRDevice interface: calling requestSession(XRSessionCreationOptions) on device with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: device is not defined"
+PASS XRSession interface: existence and properties of interface object
+PASS XRSession interface object length
+PASS XRSession interface object name
+PASS XRSession interface: existence and properties of interface prototype object
+PASS XRSession interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSession interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRSession interface: attribute device
+PASS XRSession interface: attribute immersive
+PASS XRSession interface: attribute outputContext
+PASS XRSession interface: attribute environmentBlendMode
+PASS XRSession interface: attribute depthNear
+PASS XRSession interface: attribute depthFar
+PASS XRSession interface: attribute baseLayer
+PASS XRSession interface: operation requestFrameOfReference(XRFrameOfReferenceType, XRFrameOfReferenceOptions)
+PASS XRSession interface: operation getInputSources()
+PASS XRSession interface: operation requestAnimationFrame(XRFrameRequestCallback)
+PASS XRSession interface: operation cancelAnimationFrame(long)
+PASS XRSession interface: operation end()
+PASS XRSession interface: attribute onblur
+PASS XRSession interface: attribute onfocus
+PASS XRSession interface: attribute onresetpose
+PASS XRSession interface: attribute onend
+FAIL XRSession interface: attribute onselect assert_true: The prototype object must have a property "onselect" expected true got false
+FAIL XRSession interface: attribute onselectstart assert_true: The prototype object must have a property "onselectstart" expected true got false
+FAIL XRSession interface: attribute onselectend assert_true: The prototype object must have a property "onselectend" expected true got false
+FAIL XRSession must be primary interface of session assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL Stringification of session assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "device" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "immersive" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "outputContext" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "environmentBlendMode" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "depthNear" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "depthFar" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "baseLayer" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "requestFrameOfReference(XRFrameOfReferenceType, XRFrameOfReferenceOptions)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: calling requestFrameOfReference(XRFrameOfReferenceType, XRFrameOfReferenceOptions) on session with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "getInputSources()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "requestAnimationFrame(XRFrameRequestCallback)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: calling requestAnimationFrame(XRFrameRequestCallback) on session with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "cancelAnimationFrame(long)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: calling cancelAnimationFrame(long) on session with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "end()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onblur" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onfocus" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onresetpose" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onend" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onselect" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onselectstart" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+FAIL XRSession interface: session must inherit property "onselectend" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: session is not defined"
+PASS XRFrame interface: existence and properties of interface object
+PASS XRFrame interface object length
+PASS XRFrame interface object name
+PASS XRFrame interface: existence and properties of interface prototype object
+PASS XRFrame interface: existence and properties of interface prototype object's "constructor" property
+PASS XRFrame interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRFrame interface: attribute session
+PASS XRFrame interface: attribute views
+PASS XRFrame interface: operation getDevicePose(XRCoordinateSystem)
+PASS XRFrame interface: operation getInputPose(XRInputSource, XRCoordinateSystem)
+FAIL XRCoordinateSystem interface: existence and properties of interface object assert_equals: prototype of XRCoordinateSystem is not EventTarget expected function "function EventTarget() { [native code] }" but got function "function () { [native code] }"
+PASS XRCoordinateSystem interface object length
+PASS XRCoordinateSystem interface object name
+FAIL XRCoordinateSystem interface: existence and properties of interface prototype object assert_equals: prototype of XRCoordinateSystem.prototype is not EventTarget.prototype expected object "[object EventTarget]" but got object "[object Object]"
+PASS XRCoordinateSystem interface: existence and properties of interface prototype object's "constructor" property
+PASS XRCoordinateSystem interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRCoordinateSystem interface: operation getTransformTo(XRCoordinateSystem)
+PASS XRFrameOfReference interface: existence and properties of interface object
+PASS XRFrameOfReference interface object length
+PASS XRFrameOfReference interface object name
+PASS XRFrameOfReference interface: existence and properties of interface prototype object
+PASS XRFrameOfReference interface: existence and properties of interface prototype object's "constructor" property
+PASS XRFrameOfReference interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRFrameOfReference interface: attribute bounds
+PASS XRFrameOfReference interface: attribute emulatedHeight
+FAIL XRFrameOfReference interface: attribute onboundschange assert_true: The prototype object must have a property "onboundschange" expected true got false
+PASS XRStageBounds interface: existence and properties of interface object
+PASS XRStageBounds interface object length
+PASS XRStageBounds interface object name
+PASS XRStageBounds interface: existence and properties of interface prototype object
+PASS XRStageBounds interface: existence and properties of interface prototype object's "constructor" property
+PASS XRStageBounds interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRStageBounds interface: attribute geometry
+PASS XRStageBoundsPoint interface: existence and properties of interface object
+PASS XRStageBoundsPoint interface object length
+PASS XRStageBoundsPoint interface object name
+PASS XRStageBoundsPoint interface: existence and properties of interface prototype object
+PASS XRStageBoundsPoint interface: existence and properties of interface prototype object's "constructor" property
+PASS XRStageBoundsPoint interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRStageBoundsPoint interface: attribute x
+PASS XRStageBoundsPoint interface: attribute z
+PASS XRView interface: existence and properties of interface object
+PASS XRView interface object length
+PASS XRView interface object name
+PASS XRView interface: existence and properties of interface prototype object
+PASS XRView interface: existence and properties of interface prototype object's "constructor" property
+PASS XRView interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRView interface: attribute eye
+PASS XRView interface: attribute projectionMatrix
+PASS XRViewport interface: existence and properties of interface object
+PASS XRViewport interface object length
+PASS XRViewport interface object name
+PASS XRViewport interface: existence and properties of interface prototype object
+PASS XRViewport interface: existence and properties of interface prototype object's "constructor" property
+PASS XRViewport interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRViewport interface: attribute x
+PASS XRViewport interface: attribute y
+PASS XRViewport interface: attribute width
+PASS XRViewport interface: attribute height
+PASS XRDevicePose interface: existence and properties of interface object
+PASS XRDevicePose interface object length
+PASS XRDevicePose interface object name
+PASS XRDevicePose interface: existence and properties of interface prototype object
+PASS XRDevicePose interface: existence and properties of interface prototype object's "constructor" property
+PASS XRDevicePose interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRDevicePose interface: attribute poseModelMatrix
+PASS XRDevicePose interface: operation getViewMatrix(XRView)
+PASS XRInputSource interface: existence and properties of interface object
+PASS XRInputSource interface object length
+PASS XRInputSource interface object name
+PASS XRInputSource interface: existence and properties of interface prototype object
+PASS XRInputSource interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSource interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSource interface: attribute handedness
+PASS XRInputSource interface: attribute targetRayMode
+PASS XRRay interface: existence and properties of interface object
+PASS XRRay interface object length
+PASS XRRay interface object name
+PASS XRRay interface: existence and properties of interface prototype object
+PASS XRRay interface: existence and properties of interface prototype object's "constructor" property
+PASS XRRay interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRRay interface: attribute origin
+PASS XRRay interface: attribute direction
+PASS XRRay interface: attribute transformMatrix
+PASS XRInputPose interface: existence and properties of interface object
+PASS XRInputPose interface object length
+PASS XRInputPose interface object name
+PASS XRInputPose interface: existence and properties of interface prototype object
+PASS XRInputPose interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputPose interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputPose interface: attribute emulatedPosition
+PASS XRInputPose interface: attribute targetRay
+PASS XRInputPose interface: attribute gripMatrix
+PASS XRLayer interface: existence and properties of interface object
+PASS XRLayer interface object length
+PASS XRLayer interface object name
+PASS XRLayer interface: existence and properties of interface prototype object
+PASS XRLayer interface: existence and properties of interface prototype object's "constructor" property
+PASS XRLayer interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRWebGLLayer interface: existence and properties of interface object
+PASS XRWebGLLayer interface object length
+PASS XRWebGLLayer interface object name
+PASS XRWebGLLayer interface: existence and properties of interface prototype object
+PASS XRWebGLLayer interface: existence and properties of interface prototype object's "constructor" property
+PASS XRWebGLLayer interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRWebGLLayer interface: attribute context
+PASS XRWebGLLayer interface: attribute antialias
+PASS XRWebGLLayer interface: attribute depth
+PASS XRWebGLLayer interface: attribute stencil
+PASS XRWebGLLayer interface: attribute alpha
+PASS XRWebGLLayer interface: attribute multiview
+PASS XRWebGLLayer interface: attribute framebuffer
+PASS XRWebGLLayer interface: attribute framebufferWidth
+PASS XRWebGLLayer interface: attribute framebufferHeight
+PASS XRWebGLLayer interface: operation getViewport(XRView)
+PASS XRWebGLLayer interface: operation requestViewportScaling(double)
+PASS XRWebGLLayer interface: operation getNativeFramebufferScaleFactor(XRSession)
+PASS XRPresentationContext interface: existence and properties of interface object
+PASS XRPresentationContext interface object length
+PASS XRPresentationContext interface object name
+PASS XRPresentationContext interface: existence and properties of interface prototype object
+PASS XRPresentationContext interface: existence and properties of interface prototype object's "constructor" property
+PASS XRPresentationContext interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRPresentationContext interface: attribute canvas
+PASS XRSessionEvent interface: existence and properties of interface object
+PASS XRSessionEvent interface object length
+PASS XRSessionEvent interface object name
+PASS XRSessionEvent interface: existence and properties of interface prototype object
+PASS XRSessionEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRSessionEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRSessionEvent interface: attribute session
+PASS XRInputSourceEvent interface: existence and properties of interface object
+PASS XRInputSourceEvent interface object length
+PASS XRInputSourceEvent interface object name
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object's "constructor" property
+PASS XRInputSourceEvent interface: existence and properties of interface prototype object's @@unscopables property
+PASS XRInputSourceEvent interface: attribute frame
+PASS XRInputSourceEvent interface: attribute inputSource
+FAIL XRCoordinateSystemEvent interface: existence and properties of interface object assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface object length assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface object name assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface: existence and properties of interface prototype object assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+FAIL XRCoordinateSystemEvent interface: attribute coordinateSystem assert_own_property: self does not have own property "XRCoordinateSystemEvent" expected property "XRCoordinateSystemEvent" missing
+PASS WebGLRenderingContext interface: operation setCompatibleXRDevice(XRDevice)
+PASS Navigator interface: attribute xr
+PASS Navigator interface: navigator must inherit property "xr" with the proper type
+PASS WorkerGlobalScope interface: existence and properties of interface object
+PASS WorkerNavigator interface: existence and properties of interface object
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window.js b/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window.js
new file mode 100644
index 0000000..6ec963d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/external/wpt/webxr/idlharness.https.window.js
@@ -0,0 +1,22 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+// https://immersive-web.github.io/webxr/
+
+idl_test(
+  ['webxr'],
+  ['webgl1', 'html', 'dom'],
+  async idl_array => {
+    idl_array.add_objects({
+      Navigator: ['navigator'],
+      XR: ['navigator.XR'],
+      XRDevice: ['device'],
+      XRSession: ['session'],
+    });
+    self.device = await navigator.XR.requestDevice();
+    self.session = await device.requestSession();
+  },
+  'Test IDL implementation of WebXR API'
+);
diff --git a/third_party/WebKit/LayoutTests/external/wpt/webxr/interfaces.https.html b/third_party/WebKit/LayoutTests/external/wpt/webxr/interfaces.https.html
deleted file mode 100644
index 65b2a853..0000000
--- a/third_party/WebKit/LayoutTests/external/wpt/webxr/interfaces.https.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebXR Device API IDL Tests</title>
-<link rel="help" href="https://immersive-web.github.io/webxr/spec/latest/">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/WebIDLParser.js"></script>
-<script src="/resources/idlharness.js"></script>
-<script>
-"use strict";
-
-idl_test(
-  ['webxr'],
-  ['webgl1', 'html', 'dom'],
-  idl_array => {
-    idl_array.add_objects({
-      Navigator:['navigator'],
-    });
-  },
-  'Test IDL implementation of WebXR API');
-</script>
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/continuation-outline-crash.html b/third_party/WebKit/LayoutTests/fast/multicol/continuation-outline-crash.html
new file mode 100644
index 0000000..f353193d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/multicol/continuation-outline-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<span style="outline:auto;">
+  <div style="columns:1;"><br></div>
+</span>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+  test(() => { }, "No crash or DCHECK failure.");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/scroll-snap/animate-fling-to-snap-points.html b/third_party/WebKit/LayoutTests/fast/scroll-snap/animate-fling-to-snap-points.html
index c3ad5f5..711489a 100644
--- a/third_party/WebKit/LayoutTests/fast/scroll-snap/animate-fling-to-snap-points.html
+++ b/third_party/WebKit/LayoutTests/fast/scroll-snap/animate-fling-to-snap-points.html
@@ -33,7 +33,9 @@
 </div>
 
 <script>
+var body = document.body;
 var scroller = document.getElementById("scroller");
+var space = document.getElementById("space");
 
 function scrollLeft() {
   return scroller.scrollLeft;
@@ -51,7 +53,7 @@
 
 promise_test (async () => {
   scroller.scrollTo(0, 0);
-  await swipe(100, 200, 200, 'upleft', 1000);
+  await swipe(100, 200, 200, 'upleft', 900);
   await waitForAnimationEnd(scrollLeft, 1000, 30);
   await waitFor( () => {
     return approx_equals(scroller.scrollLeft, 200, 1) &&
@@ -59,4 +61,32 @@
   });
 }, "With fling enabled, the scroll ends at the closest snap point to the fling destination.")
 
-</script>
\ No newline at end of file
+function MakeUnscrollable() {
+  scroller.removeChild(space);
+}
+function MakeScrollable() {
+  scroller.appendChild(space);
+}
+promise_test (async () => {
+  scroller.scrollTo(0, 0);
+  await swipe(100, 200, 200, 'upleft', 1000);
+  await waitFor( () => {
+    return scroller.scrollLeft > 120;
+  });
+  MakeUnscrollable();
+  await waitForAnimationEnd(scrollLeft, 1000, 20);
+  MakeScrollable();
+}, "Should not crash if the scroller becomes unscrollable during fling.");
+
+promise_test (async () => {
+  scroller.scrollTo(0, 0);
+  await swipe(100, 200, 200, 'upleft', 1000);
+  await waitFor( () => {
+    return scroller.scrollLeft > 120;
+  });
+  body.removeChild(scroller);
+  await waitForAnimationEnd(scrollLeft, 1000, 20);
+  body.appendChild(scroller);
+}, "Should not crash if the scroller is removed during fling.");
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/dynamic/first-letter-after-list-marker-expected.txt b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/dynamic/first-letter-after-list-marker-expected.txt
new file mode 100644
index 0000000..245d8d5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/flag-specific/enable-blink-features=LayoutNG/fast/dynamic/first-letter-after-list-marker-expected.txt
@@ -0,0 +1,14 @@
+layer at (0,0) size 800x600
+  LayoutView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  LayoutNGBlockFlow {HTML} at (0,0) size 800x600
+    LayoutNGBlockFlow {BODY} at (8,8) size 784x492
+      LayoutNGBlockFlow {UL} at (0,0) size 784x100
+        LayoutNGListItem {LI} at (40,0) size 744x100
+          LayoutNGListMarker (anonymous) at (-61,0) size 29x100
+            LayoutText (anonymous) at (0,0) size 29x100
+              text run at (0,0) width 29: "\x{2022} "
+          LayoutInline {<pseudo:first-letter>} at (0,0) size 100x100 [color=#008000]
+            LayoutTextFragment (anonymous) at (0,0) size 100x100
+              text run at (0,0) width 100: "a"
+          LayoutTextFragment {#text} at (0,0) size 0x0
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/powerfulFeatureRestrictions/old-powerful-features-on-insecure-origin-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/powerfulFeatureRestrictions/old-powerful-features-on-insecure-origin-expected.txt
index e16a290..4a23810a 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/powerfulFeatureRestrictions/old-powerful-features-on-insecure-origin-expected.txt
+++ b/third_party/WebKit/LayoutTests/http/tests/security/powerfulFeatureRestrictions/old-powerful-features-on-insecure-origin-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE WARNING: Application Cache is deprecated in non-secure contexts, and will be restricted to secure contexts in M69, around September 2018. Please consider migrating your application to HTTPS, and eventually shifting over to Service Workers. See https://goo.gl/rStTGz for more details.
+CONSOLE WARNING: Application Cache is restricted to secure contexts. Please consider migrating your application to HTTPS, and eventually shifting over to Service Workers. See https://goo.gl/rStTGz for more details.
 CONSOLE WARNING: line 68: getCurrentPosition() and watchPosition() no longer work on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.
 CONSOLE WARNING: line 87: getUserMedia() no longer works on insecure origins. To use this feature, you should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.
 CONSOLE WARNING: line 430: The devicemotion event is deprecated on insecure origins, and support will be removed in the future. You should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details.
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element-expected.txt b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element-expected.txt
new file mode 100644
index 0000000..4051866c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element-expected.txt
@@ -0,0 +1,660 @@
+Tests DOMSnapshot.getSnapshot exports layout tree nodes associated with pseudo elements.
+{
+  "domNodes": [
+    {
+      "nodeType": 9,
+      "nodeName": "#document",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        1
+      ],
+      "layoutNodeIndex": 0,
+      "documentURL": "<string>",
+      "baseURL": "<string>",
+      "documentEncoding": "windows-1252",
+      "frameId": "<string>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "HTML",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        2,
+        8
+      ],
+      "layoutNodeIndex": 1,
+      "frameId": "<string>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "HEAD",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        3,
+        5,
+        6,
+        7
+      ]
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "STYLE",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        4
+      ]
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n    p {\n      position: absolute;\n      height: 200px;\n      width: 200px;\n      font: 10px Ahem;\n    }\n    .c1::first-letter { font-weight: lighter; color: green; }\n    .c2::first-letter { font-weight: bold; color: blue; }\n    .c2::before { counter-increment: square; content: 'square: ' counter(square) url(square.png) '! '; }\n  ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n  ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "SCRIPT",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "attributes": [
+        {
+          "name": "src",
+          "value": "../../resources/ahem.js"
+        }
+      ]
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n  ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "BODY",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        9,
+        10,
+        13,
+        14,
+        18,
+        19,
+        23
+      ],
+      "layoutNodeIndex": 2
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n    ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "P",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        12
+      ],
+      "attributes": [
+        {
+          "name": "class",
+          "value": "c1"
+        }
+      ],
+      "pseudoElementIndexes": [
+        11
+      ],
+      "layoutNodeIndex": 3
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "<pseudo:first-letter>",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 4,
+      "pseudoType": "first-letter"
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "I have a first letter.",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 6
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n    ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "P",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        16
+      ],
+      "attributes": [
+        {
+          "name": "class",
+          "value": "c1"
+        }
+      ],
+      "pseudoElementIndexes": [
+        15
+      ],
+      "layoutNodeIndex": 7
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "<pseudo:first-letter>",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 8,
+      "pseudoType": "first-letter"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "SPAN",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        17
+      ],
+      "layoutNodeIndex": 10
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "I have a first letter because of my parent.",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 11
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n    ",
+      "backendNodeId": "<number>"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "P",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "childNodeIndexes": [
+        22
+      ],
+      "attributes": [
+        {
+          "name": "class",
+          "value": "c2"
+        }
+      ],
+      "pseudoElementIndexes": [
+        20,
+        21
+      ],
+      "layoutNodeIndex": 12
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "<pseudo:first-letter>",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 13,
+      "pseudoType": "first-letter"
+    },
+    {
+      "nodeType": 1,
+      "nodeName": "<pseudo:before>",
+      "nodeValue": "",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 15,
+      "pseudoType": "before"
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "I have some content before me with a first letter.",
+      "backendNodeId": "<number>",
+      "layoutNodeIndex": 20
+    },
+    {
+      "nodeType": 3,
+      "nodeName": "#text",
+      "nodeValue": "\n  \n\n",
+      "backendNodeId": "<number>"
+    }
+  ],
+  "layoutTreeNodes": [
+    {
+      "domNodeIndex": 0,
+      "boundingBox": {
+        "x": 0,
+        "y": 0,
+        "width": 800,
+        "height": 600
+      }
+    },
+    {
+      "domNodeIndex": 1,
+      "boundingBox": {
+        "x": 0,
+        "y": 0,
+        "width": 800,
+        "height": 600
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 8,
+      "boundingBox": {
+        "x": 8,
+        "y": 8,
+        "width": 784,
+        "height": 584
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 10,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 200,
+        "height": 200
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 11,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 10,
+        "height": 10
+      },
+      "styleIndex": 1
+    },
+    {
+      "domNodeIndex": 11,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 10,
+        "height": 10
+      },
+      "layoutText": "I",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 18,
+            "width": 10,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 1
+        }
+      ],
+      "styleIndex": 1
+    },
+    {
+      "domNodeIndex": 12,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 140,
+        "height": 20
+      },
+      "layoutText": " have a first letter.",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 18,
+            "y": 18,
+            "width": 130,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 13
+        },
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 28,
+            "width": 70,
+            "height": 10
+          },
+          "startCharacterIndex": 14,
+          "numCharacters": 7
+        }
+      ],
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 14,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 200,
+        "height": 200
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 15,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 10,
+        "height": 10
+      },
+      "styleIndex": 1
+    },
+    {
+      "domNodeIndex": 15,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 10,
+        "height": 10
+      },
+      "layoutText": "I",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 18,
+            "width": 10,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 1
+        }
+      ],
+      "styleIndex": 1
+    },
+    {
+      "domNodeIndex": 16,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 200,
+        "height": 30
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 17,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 200,
+        "height": 30
+      },
+      "layoutText": " have a first letter because of my parent.",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 18,
+            "y": 18,
+            "width": 130,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 13
+        },
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 28,
+            "width": 200,
+            "height": 10
+          },
+          "startCharacterIndex": 14,
+          "numCharacters": 20
+        },
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 38,
+            "width": 70,
+            "height": 10
+          },
+          "startCharacterIndex": 35,
+          "numCharacters": 7
+        }
+      ],
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 19,
+      "boundingBox": {
+        "x": 8,
+        "y": 18,
+        "width": 200,
+        "height": 200
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 20,
+      "boundingBox": {
+        "x": 8,
+        "y": 110,
+        "width": 10,
+        "height": 10
+      },
+      "styleIndex": 2
+    },
+    {
+      "domNodeIndex": 20,
+      "boundingBox": {
+        "x": 8,
+        "y": 110,
+        "width": 10,
+        "height": 10
+      },
+      "layoutText": "s",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 110,
+            "width": 10,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 1
+        }
+      ],
+      "styleIndex": 2
+    },
+    {
+      "domNodeIndex": 21,
+      "boundingBox": {
+        "x": 8,
+        "y": 110,
+        "width": 200,
+        "height": 10
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 21,
+      "boundingBox": {
+        "x": 18,
+        "y": 110,
+        "width": 70,
+        "height": 10
+      },
+      "layoutText": "quare: ",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 18,
+            "y": 110,
+            "width": 70,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 7
+        }
+      ],
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 21,
+      "boundingBox": {
+        "x": 88,
+        "y": 110,
+        "width": 10,
+        "height": 10
+      },
+      "layoutText": "1",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 88,
+            "y": 110,
+            "width": 10,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 1
+        }
+      ],
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 21,
+      "boundingBox": {
+        "x": 98,
+        "y": 18,
+        "width": 100,
+        "height": 100
+      },
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 21,
+      "boundingBox": {
+        "x": 198,
+        "y": 110,
+        "width": 10,
+        "height": 10
+      },
+      "layoutText": "! ",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 198,
+            "y": 110,
+            "width": 10,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 1
+        }
+      ],
+      "styleIndex": 0
+    },
+    {
+      "domNodeIndex": 22,
+      "boundingBox": {
+        "x": 8,
+        "y": 120,
+        "width": 190,
+        "height": 30
+      },
+      "layoutText": "I have some content before me with a first letter.",
+      "inlineTextNodes": [
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 120,
+            "width": 190,
+            "height": 10
+          },
+          "startCharacterIndex": 0,
+          "numCharacters": 19
+        },
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 130,
+            "width": 160,
+            "height": 10
+          },
+          "startCharacterIndex": 20,
+          "numCharacters": 16
+        },
+        {
+          "boundingBox": {
+            "x": 8,
+            "y": 140,
+            "width": 130,
+            "height": 10
+          },
+          "startCharacterIndex": 37,
+          "numCharacters": 13
+        }
+      ],
+      "styleIndex": 0
+    }
+  ],
+  "computedStyles": [
+    {
+      "properties": [
+        {
+          "name": "font-weight",
+          "value": "400"
+        },
+        {
+          "name": "color",
+          "value": "rgb(0, 0, 0)"
+        }
+      ]
+    },
+    {
+      "properties": [
+        {
+          "name": "font-weight",
+          "value": "100"
+        },
+        {
+          "name": "color",
+          "value": "rgb(0, 128, 0)"
+        }
+      ]
+    },
+    {
+      "properties": [
+        {
+          "name": "font-weight",
+          "value": "700"
+        },
+        {
+          "name": "color",
+          "value": "rgb(0, 0, 255)"
+        }
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element.js b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element.js
new file mode 100644
index 0000000..7ad7296
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/dom-snapshot/dom-snapshot-getSnapshot-pseudo-element.js
@@ -0,0 +1,17 @@
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startURL('../resources/dom-snapshot-pseudo-element.html', 'Tests DOMSnapshot.getSnapshot exports layout tree nodes associated with pseudo elements.');
+
+  function stabilize(key, value) {
+    var unstableKeys = ['documentURL', 'baseURL', 'frameId', 'backendNodeId'];
+    if (unstableKeys.indexOf(key) !== -1)
+      return '<' + typeof(value) + '>';
+    return value;
+  }
+
+  var response = await dp.DOMSnapshot.getSnapshot({'computedStyleWhitelist': ['font-weight', 'color'], 'includeEventListeners': true});
+  if (response.error)
+    testRunner.log(response);
+  else
+    testRunner.log(JSON.stringify(response.result, stabilize, 2));
+  testRunner.completeTest();
+})
diff --git a/third_party/WebKit/LayoutTests/inspector-protocol/resources/dom-snapshot-pseudo-element.html b/third_party/WebKit/LayoutTests/inspector-protocol/resources/dom-snapshot-pseudo-element.html
new file mode 100644
index 0000000..f564227
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/inspector-protocol/resources/dom-snapshot-pseudo-element.html
@@ -0,0 +1,19 @@
+<html>
+  <style>
+    p {
+      position: absolute;
+      height: 200px;
+      width: 200px;
+      font: 10px Ahem;
+    }
+    .c1::first-letter { font-weight: lighter; color: green; }
+    .c2::first-letter { font-weight: bold; color: blue; }
+    .c2::before { counter-increment: square; content: 'square: ' counter(square) url(square.png) '! '; }
+  </style>
+  <script src="../../resources/ahem.js"></script>
+  <body>
+    <p class='c1'>I have a first letter.</p>
+    <p class='c1'><span>I have a first letter because of my parent.</span></p>
+    <p class='c2'>I have some content before me with a first letter.</p>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/external/wpt/url/url-constructor-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/external/wpt/url/url-constructor-expected.txt
index 1568c5b..580fd68 100644
--- a/third_party/WebKit/LayoutTests/platform/linux/external/wpt/url/url-constructor-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/linux/external/wpt/url/url-constructor-expected.txt
@@ -1,9 +1,5 @@
 This is a testharness.js-based test.
-Found 519 tests; 404 PASS, 115 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS URL.searchParams getter
-PASS URL.searchParams updating, clearing
-PASS URL.searchParams setter, invalid values
-PASS URL.searchParams and URL.search setters, update propagation
+Found 515 tests; 400 PASS, 115 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
diff --git a/third_party/WebKit/LayoutTests/platform/mac/external/wpt/url/url-constructor-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/external/wpt/url/url-constructor-expected.txt
index 1568c5b..580fd68 100644
--- a/third_party/WebKit/LayoutTests/platform/mac/external/wpt/url/url-constructor-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/mac/external/wpt/url/url-constructor-expected.txt
@@ -1,9 +1,5 @@
 This is a testharness.js-based test.
-Found 519 tests; 404 PASS, 115 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS URL.searchParams getter
-PASS URL.searchParams updating, clearing
-PASS URL.searchParams setter, invalid values
-PASS URL.searchParams and URL.search setters, update propagation
+Found 515 tests; 400 PASS, 115 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
diff --git a/third_party/WebKit/LayoutTests/platform/win/external/wpt/url/url-constructor-expected.txt b/third_party/WebKit/LayoutTests/platform/win/external/wpt/url/url-constructor-expected.txt
index dd1d7a9..52d15c9 100644
--- a/third_party/WebKit/LayoutTests/platform/win/external/wpt/url/url-constructor-expected.txt
+++ b/third_party/WebKit/LayoutTests/platform/win/external/wpt/url/url-constructor-expected.txt
@@ -1,9 +1,5 @@
 This is a testharness.js-based test.
-Found 519 tests; 398 PASS, 121 FAIL, 0 TIMEOUT, 0 NOTRUN.
-PASS URL.searchParams getter
-PASS URL.searchParams updating, clearing
-PASS URL.searchParams setter, invalid values
-PASS URL.searchParams and URL.search setters, update propagation
+Found 515 tests; 394 PASS, 121 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Loading data…
 PASS Parsing: <http://example	.
 org> against <http://example.org/foo/bar>
diff --git a/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash-expected.txt b/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash-expected.txt
new file mode 100644
index 0000000..7ef22e9
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash-expected.txt
@@ -0,0 +1 @@
+PASS
diff --git a/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash.html b/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash.html
new file mode 100644
index 0000000..72aa667
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/transforms/transformed-scroller-snapping-crash.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<style>
+  #heightChange {
+    vertical-align: 10%;
+    height: 200px;
+    display: inline-block;
+  }
+  #scroller {
+    overflow: scroll;
+    position: absolute;
+    padding-bottom: 101%;
+    transform: scale(0.9);
+    height: 10px;
+  }
+  #text {
+    vertical-align: 10%;
+  }
+</style>
+<div id="heightChange"></div>
+<div id="scroller"><span id="text">PASS</span></div>
+<!-- Tests the property tree update in the presence of transformed scroll height
+subpixel snapping. It passes if it does not crash. -->
+<script>
+  if (testRunner)
+    testRunner.dumpAsText();
+  requestAnimationFrame(function() {
+    heightChange.style.height = '0';
+  });
+</script>
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
index cfe2afb0..c44a629 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1173,110 +1173,6 @@
 [Worker]     method sort
 [Worker]     method toString
 [Worker]     method values
-[Worker] interface USB : EventTarget
-[Worker]     attribute @@toStringTag
-[Worker]     getter onconnect
-[Worker]     getter ondisconnect
-[Worker]     method constructor
-[Worker]     method getDevices
-[Worker]     setter onconnect
-[Worker]     setter ondisconnect
-[Worker] interface USBAlternateInterface
-[Worker]     attribute @@toStringTag
-[Worker]     getter alternateSetting
-[Worker]     getter endpoints
-[Worker]     getter interfaceClass
-[Worker]     getter interfaceName
-[Worker]     getter interfaceProtocol
-[Worker]     getter interfaceSubclass
-[Worker]     method constructor
-[Worker] interface USBConfiguration
-[Worker]     attribute @@toStringTag
-[Worker]     getter configurationName
-[Worker]     getter configurationValue
-[Worker]     getter interfaces
-[Worker]     method constructor
-[Worker] interface USBConnectionEvent : Event
-[Worker]     attribute @@toStringTag
-[Worker]     getter device
-[Worker]     method constructor
-[Worker] interface USBDevice
-[Worker]     attribute @@toStringTag
-[Worker]     getter configuration
-[Worker]     getter configurations
-[Worker]     getter deviceClass
-[Worker]     getter deviceProtocol
-[Worker]     getter deviceSubclass
-[Worker]     getter deviceVersionMajor
-[Worker]     getter deviceVersionMinor
-[Worker]     getter deviceVersionSubminor
-[Worker]     getter manufacturerName
-[Worker]     getter opened
-[Worker]     getter productId
-[Worker]     getter productName
-[Worker]     getter serialNumber
-[Worker]     getter usbVersionMajor
-[Worker]     getter usbVersionMinor
-[Worker]     getter usbVersionSubminor
-[Worker]     getter vendorId
-[Worker]     method claimInterface
-[Worker]     method clearHalt
-[Worker]     method close
-[Worker]     method constructor
-[Worker]     method controlTransferIn
-[Worker]     method controlTransferOut
-[Worker]     method isochronousTransferIn
-[Worker]     method isochronousTransferOut
-[Worker]     method open
-[Worker]     method releaseInterface
-[Worker]     method reset
-[Worker]     method selectAlternateInterface
-[Worker]     method selectConfiguration
-[Worker]     method transferIn
-[Worker]     method transferOut
-[Worker] interface USBEndpoint
-[Worker]     attribute @@toStringTag
-[Worker]     getter direction
-[Worker]     getter endpointNumber
-[Worker]     getter packetSize
-[Worker]     getter type
-[Worker]     method constructor
-[Worker] interface USBInTransferResult
-[Worker]     attribute @@toStringTag
-[Worker]     getter data
-[Worker]     getter status
-[Worker]     method constructor
-[Worker] interface USBInterface
-[Worker]     attribute @@toStringTag
-[Worker]     getter alternate
-[Worker]     getter alternates
-[Worker]     getter claimed
-[Worker]     getter interfaceNumber
-[Worker]     method constructor
-[Worker] interface USBIsochronousInTransferPacket
-[Worker]     attribute @@toStringTag
-[Worker]     getter data
-[Worker]     getter status
-[Worker]     method constructor
-[Worker] interface USBIsochronousInTransferResult
-[Worker]     attribute @@toStringTag
-[Worker]     getter data
-[Worker]     getter packets
-[Worker]     method constructor
-[Worker] interface USBIsochronousOutTransferPacket
-[Worker]     attribute @@toStringTag
-[Worker]     getter bytesWritten
-[Worker]     getter status
-[Worker]     method constructor
-[Worker] interface USBIsochronousOutTransferResult
-[Worker]     attribute @@toStringTag
-[Worker]     getter packets
-[Worker]     method constructor
-[Worker] interface USBOutTransferResult
-[Worker]     attribute @@toStringTag
-[Worker]     getter bytesWritten
-[Worker]     getter status
-[Worker]     method constructor
 [Worker] interface WebGL2ComputeRenderingContext
 [Worker]     attribute @@toStringTag
 [Worker]     attribute ACTIVE_ATOMIC_COUNTER_BUFFERS
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index bb6945f..d69d971c 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -25,6 +25,7 @@
     "feature_policy/feature_policy.mojom",
     "fetch/fetch_api_response.mojom",
     "file/file_utilities.mojom",
+    "filesystem/file_system.mojom",
     "frame/find_in_page.mojom",
     "leak_detector/leak_detector.mojom",
     "loader/navigation_predictor.mojom",
@@ -60,6 +61,7 @@
 
   public_deps = [
     ":speech_recognition_error_code",
+    "//components/services/filesystem/public/interfaces",
     "//mojo/public/mojom/base",
     "//services/device/public/mojom",
     "//services/network/public/mojom",
diff --git a/third_party/blink/public/mojom/filesystem/OWNERS b/third_party/blink/public/mojom/filesystem/OWNERS
new file mode 100644
index 0000000..4de3ede8
--- /dev/null
+++ b/third_party/blink/public/mojom/filesystem/OWNERS
@@ -0,0 +1,7 @@
+file://third_party/blink/renderer/modules/filesystem/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>FileSystem
diff --git a/third_party/blink/public/mojom/filesystem/file_system.mojom b/third_party/blink/public/mojom/filesystem/file_system.mojom
new file mode 100644
index 0000000..75796f8
--- /dev/null
+++ b/third_party/blink/public/mojom/filesystem/file_system.mojom
@@ -0,0 +1,180 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module blink.mojom;
+
+import "components/services/filesystem/public/interfaces/types.mojom";
+import "url/mojom/url.mojom";
+import "mojo/public/mojom/base/file_error.mojom";
+import "mojo/public/mojom/base/file_path.mojom";
+import "mojo/public/mojom/base/file_info.mojom";
+import "mojo/public/mojom/base/time.mojom";
+
+enum FileSystemType {
+  kTemporary,
+  kPersistent,
+  kIsolated,
+  kExternal
+};
+
+struct FileSystemInfo {
+  string name;
+  url.mojom.Url root_url;
+  FileSystemType mount_type = kTemporary;
+};
+
+// Interface for renderers to cancel running file system operations. A
+// FileSystemCancellableOperationRequest is passed as a parameter for any
+// operation that can be cancelled.
+interface FileSystemCancellableOperation {
+  // Cancels the associated operation. It may not always be possible to cancel
+  // an operation, and partial writes are possible. |error_code| indicates if
+  // the operation was successfully canceled.
+  Cancel() => (mojo_base.mojom.FileError error_code);
+};
+
+// Operations that need to repeatedly call a particular callback use this
+// interface. For example, the Write operation needs to call DidWrite
+// repeatedly, and uses a FileSystemOperationListenerPtr passed from the
+// renderer to call it.
+// Note: For operations that only need a callback called once, we can directly
+// use mojo return value callbacks.
+interface FileSystemOperationListener {
+  // Called by ReadDirectory when entries have been obtained. |has_more| is
+  // false when all the entries are obtained, and indicates that the callback
+  // will not be called again.
+  ResultsRetrieved(array<filesystem.mojom.DirectoryEntry> entries,
+                   bool has_more);
+
+  // Called repeatedly by Write to indicate progress. If |complete| is true,
+  // the operation is complete and the callback will not be called again.
+  DidWrite(int64 byte_count, bool complete);
+
+  // Called by all operations that use a listener to indicate an error. No
+  // other listener callbacks will be called after this.
+  ErrorOccurred(mojo_base.mojom.FileError error_code);
+};
+
+// Used by the renderer to notify the browser that it has received a snapshot
+// after calling CreateSnapshotFile. The browser sends an interface ptr along
+// with the result of the CreateSnapshotFile call.
+interface ReceivedSnapshotListener {
+  DidReceiveSnapshotFile();
+};
+
+// Interface provided by the browser to the renderer to carry out filesystem
+// operations.
+interface FileSystemManager {
+  // Opens a new filesystem and returns a name and root path for the requested
+  // filesystem and a success error code if the operation succeeds. If the
+  // operation fails, |error_code| indicates the reason for failure.
+  Open(url.mojom.Url origin_url, blink.mojom.FileSystemType file_system_type) =>
+      (string name,
+       url.mojom.Url root_url,
+       mojo_base.mojom.FileError error_code);
+
+  // Resolves a filesystem URL and returns the filesystem information and
+  // metadata (file path and type) if the operation is successful. |error_code|
+  // indicates the reason for failure if the operation fails.
+  ResolveURL(url.mojom.Url filesystem_url) =>
+      (FileSystemInfo info,
+       mojo_base.mojom.FilePath file_path,
+       bool is_directory,
+       mojo_base.mojom.FileError error_code);
+
+  // Moves a file or directory at |src_path| to |dest_path|. Returns
+  // |error_code| after completion to indicate if the operation succeeded or
+  // failed.
+  Move(url.mojom.Url src_path, url.mojom.Url dest_path) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Copies a file or directory at |src_path| to |dest_path|. Returns
+  // |error_code| after completion to indicate if the operation succeeded or
+  // failed.
+  Copy(url.mojom.Url src_path, url.mojom.Url dest_path) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Deletes a file or directory at the given |path|. To delete recursively, set
+  // |recursive| to true. Returns |error_code| after completion to indicate if
+  // the operation succeeded or failed.
+  Remove(url.mojom.Url path, bool recursive) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Retrieves the metadata information of the file or directory at the given
+  // |path|. This may not always return the local platform path in remote
+  // filesystem cases. Returns valid metadata if the retrieval is successful,
+  // and uses |error_code| to indicate if the operation was successful.
+  ReadMetadata(url.mojom.Url path) =>
+      (mojo_base.mojom.FileInfo file_info,
+       mojo_base.mojom.FileError error_code);
+
+  // Creates a file or directory at the given |path| (based on |is_directory|).
+  // If |exclusive| is true, the operation fails if the |path| already exists.
+  // Returns |error_code| after completion to indicate if the operation
+  // succeeded or failed.
+  Create(url.mojom.Url path,
+         bool exclusive,
+         bool is_directory,
+         bool recursive) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Checks if a file exists at the given |path| if |is_directory| is false or
+  // checks if a directory exists at the given |path| otherwise. Returns
+  // |error_code| to indicate if the file was successfully found or if an error
+  // occurred.
+  Exists(url.mojom.Url path, bool is_directory) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Reads directory entries of a given directory at |path|. Calls
+  // ResultsRetrieved on |listener| when results are ready, or ErrorOccurred
+  // if the operation fails.
+  ReadDirectory(url.mojom.Url path, FileSystemOperationListener listener);
+
+  // Write data (indicated by |blob_uuid|) to the given file at |file_path|,
+  // at |position|. Calls DidWrite on |listener| to provide progress updates on
+  // the write, and |ErrorOccurred| if the operation fails. The operation can
+  // also be cancelled using the interface ptr associated with |op_request|.
+  Write(url.mojom.Url file_path,
+        string blob_uuid,
+        int64 position,
+        FileSystemCancellableOperation& op_request,
+        FileSystemOperationListener listener);
+
+  // Changes the file length of the file at |file_path| to the |length|
+  // indicated. Returns |error_code| after completion to indicate if the
+  // operation succeeded or failed. The operation can also be cancelled using
+  // the interface ptr associated with |op_request|.
+  Truncate(url.mojom.Url file_path,
+           int64 length,
+           FileSystemCancellableOperation& op_request) =>
+      (mojo_base.mojom.FileError error_code);
+
+  TouchFile(url.mojom.Url path,
+            mojo_base.mojom.Time last_access_time,
+            mojo_base.mojom.Time last_modified_time) =>
+      (mojo_base.mojom.FileError error_code);
+
+  // Creates a snapshot file for a given file specified by |file_path|. Returns
+  // the metadata of the created snapshot file, which also includes a local
+  // platform path to the snapshot image (|platform_path|).
+  //
+  // In local filesystem cases the backend may simply return the metadata of the
+  // file itself (exactly like ReadMetadata would), while in remote filesystem
+  // cases, the backend may download the file into a temporary snapshot file and
+  // return the metadata of the temporary file.
+  //
+  // If |snapshot_listener| is provided, the renderer is expected to call
+  // DidReceiveSnapshotFile on the listener (which allows the backend to drop
+  // a ref to the temporary snapshot file).
+  CreateSnapshotFile(url.mojom.Url file_path) =>
+      (mojo_base.mojom.FileInfo file_info,
+       mojo_base.mojom.FilePath platform_path,
+       mojo_base.mojom.FileError error_code,
+       ReceivedSnapshotListener? snapshot_listener);
+
+  // Synchronously gets the platform path for the given |file_path|.
+  [Sync]
+  GetPlatformPath(url.mojom.Url file_path) =>
+      (mojo_base.mojom.FilePath platform_path);
+};
diff --git a/third_party/blink/public/platform/mac/web_scrollbar_theme.h b/third_party/blink/public/platform/mac/web_scrollbar_theme.h
index f9e45ba49..639e9608 100644
--- a/third_party/blink/public/platform/mac/web_scrollbar_theme.h
+++ b/third_party/blink/public/platform/mac/web_scrollbar_theme.h
@@ -61,14 +61,14 @@
       bool redraw,
       bool jump_on_track_click);
 
+// Registered clients will receive a callback whenever
+// UpdateScrollbarsWithNSDefaults is called.
+#if INSIDE_BLINK
   static float InitialButtonDelay();
   static float AutoscrollButtonDelay();
   static ScrollerStyle PreferredScrollerStyle();
   static bool JumpOnTrackClick();
 
-// Registered clients will receive a callback whenever
-// UpdateScrollbarsWithNSDefaults is called.
-#if INSIDE_BLINK
   static void RegisterClient(WebScrollbarThemeClient& client);
   static void UnregisterClient(WebScrollbarThemeClient& client);
 #endif
diff --git a/third_party/blink/public/platform/modules/app_banner/app_banner.mojom b/third_party/blink/public/platform/modules/app_banner/app_banner.mojom
index c2cd2c3b..df58e11 100644
--- a/third_party/blink/public/platform/modules/app_banner/app_banner.mojom
+++ b/third_party/blink/public/platform/modules/app_banner/app_banner.mojom
@@ -12,7 +12,7 @@
 interface AppBannerController {
   // The browser asks the renderer if the app banner should be shown.
   BannerPromptRequest(AppBannerService service, AppBannerEvent& event,
-                      array<string> platform) =>
+                      array<string> platform, bool require_gesture) =>
       (AppBannerPromptReply reply, string referrer);
 };
 
@@ -24,5 +24,5 @@
 
 interface AppBannerService {
   // The renderer asks the browser to display a previously offered app banner.
-  DisplayAppBanner(bool user_gesture);
+  DisplayAppBanner();
 };
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index ff1fd3e..8629aea2c 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -349,7 +349,7 @@
   // A request to fetch contents associated with this URL from metadata cache.
   virtual void FetchCachedCode(
       const WebURL&,
-      base::OnceCallback<void(const std::vector<uint8_t>&)>) {}
+      base::OnceCallback<void(base::Time, const std::vector<uint8_t>&)>) {}
 
   // A suggestion to cache this metadata in association with this URL which
   // resource is in CacheStorage.
diff --git a/third_party/blink/public/platform/web_scroll_into_view_params.h b/third_party/blink/public/platform/web_scroll_into_view_params.h
index d6b7adb..9c61ba4 100644
--- a/third_party/blink/public/platform/web_scroll_into_view_params.h
+++ b/third_party/blink/public/platform/web_scroll_into_view_params.h
@@ -37,7 +37,7 @@
   struct Alignment {
     Alignment() = default;
 #if INSIDE_BLINK
-    BLINK_PLATFORM_EXPORT Alignment(const ScrollAlignment&);
+    BLINK_EXPORT Alignment(const ScrollAlignment&);
 #endif
     AlignmentBehavior rect_visible = kNoScroll;
     AlignmentBehavior rect_hidden = kCenter;
@@ -89,7 +89,7 @@
 
   WebScrollIntoViewParams() = default;
 #if INSIDE_BLINK
-  BLINK_PLATFORM_EXPORT WebScrollIntoViewParams(
+  BLINK_EXPORT WebScrollIntoViewParams(
       ScrollAlignment,
       ScrollAlignment,
       ScrollType scroll_type = kProgrammaticScroll,
@@ -98,13 +98,13 @@
       bool is_for_scroll_sequence = false,
       bool zoom_into_rect = false);
 
-  BLINK_PLATFORM_EXPORT ScrollAlignment GetScrollAlignmentX() const;
+  BLINK_EXPORT ScrollAlignment GetScrollAlignmentX() const;
 
-  BLINK_PLATFORM_EXPORT ScrollAlignment GetScrollAlignmentY() const;
+  BLINK_EXPORT ScrollAlignment GetScrollAlignmentY() const;
 
-  BLINK_PLATFORM_EXPORT ScrollType GetScrollType() const;
+  BLINK_EXPORT ScrollType GetScrollType() const;
 
-  BLINK_PLATFORM_EXPORT ScrollBehavior GetScrollBehavior() const;
+  BLINK_EXPORT ScrollBehavior GetScrollBehavior() const;
 #endif
 };
 
diff --git a/third_party/blink/renderer/bindings/core/v8/script_streamer.cc b/third_party/blink/renderer/bindings/core/v8/script_streamer.cc
index 5365b6d..af9364b 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_streamer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_streamer.cc
@@ -321,6 +321,16 @@
   streamer->StreamingCompleteOnBackgroundThread();
 }
 
+bool ScriptStreamer::HasEnoughDataForStreaming(size_t resource_buffer_size) {
+  if (RuntimeEnabledFeatures::ScheduledScriptStreamingEnabled()) {
+    // Enable streaming for small scripts, but we must still check the BOM
+    // before starting streaming.
+    return resource_buffer_size >= kMaximumLengthOfBOM;
+  }
+  // Only stream larger scripts.
+  return resource_buffer_size >= small_script_threshold_;
+}
+
 void ScriptStreamer::NotifyAppendData(ScriptResource* resource) {
   DCHECK(IsMainThread());
   if (streaming_suppressed_)
@@ -330,14 +340,13 @@
     // enough - wait until the next data chunk comes before deciding whether
     // to start the streaming.
     DCHECK(resource->ResourceBuffer());
-    if (resource->ResourceBuffer()->size() < small_script_threshold_)
+    if (!HasEnoughDataForStreaming(resource->ResourceBuffer()->size()))
       return;
     have_enough_data_for_streaming_ = true;
 
     {
       // Check for BOM (byte order marks), because that might change our
       // understanding of the data encoding.
-      constexpr size_t kMaximumLengthOfBOM = 4;
       char maybe_bom[kMaximumLengthOfBOM] = {};
       if (!resource->ResourceBuffer()->GetBytes(maybe_bom,
                                                 kMaximumLengthOfBOM)) {
diff --git a/third_party/blink/renderer/bindings/core/v8/script_streamer.h b/third_party/blink/renderer/bindings/core/v8/script_streamer.h
index 3411460..80a39ce 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_streamer.h
+++ b/third_party/blink/renderer/bindings/core/v8/script_streamer.h
@@ -120,6 +120,8 @@
   // Scripts whose first data chunk is smaller than this constant won't be
   // streamed. Non-const for testing.
   static size_t small_script_threshold_;
+  // Maximum size of the BOM marker.
+  static constexpr size_t kMaximumLengthOfBOM = 4;
 
   ScriptStreamer(ClassicPendingScript*,
                  ScriptState*,
@@ -128,6 +130,7 @@
 
   void StreamingComplete();
   void NotifyFinishedToClient();
+  bool HasEnoughDataForStreaming(size_t resource_buffer_size);
 
   Member<ClassicPendingScript> pending_script_;
   // Whether ScriptStreamer is detached from the Resource. In those cases, the
diff --git a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h
index e8218fb..3c13bf9 100644
--- a/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h
+++ b/third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h
@@ -113,13 +113,15 @@
   };
 
   struct SerializeOptions {
+    STACK_ALLOCATED();
+
+   public:
     enum WasmSerializationPolicy {
       kUnspecified,  // Invalid value, used as default initializer.
       kTransfer,     // In-memory transfer without (necessarily) serializing.
       kSerialize,    // Serialize to a byte stream.
       kBlockedInNonSecureContext  // Block transfer or serialization.
     };
-    STACK_ALLOCATED();
 
     SerializeOptions() = default;
     explicit SerializeOptions(StoragePolicy for_storage)
@@ -159,6 +161,8 @@
   // case of failure.
   struct DeserializeOptions {
     STACK_ALLOCATED();
+
+   public:
     MessagePortArray* message_ports = nullptr;
     const WebBlobInfoArray* blob_info = nullptr;
     bool read_wasm_from_stream = false;
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.cc b/third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.cc
index 41ef61f5..80664f7 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_context_snapshot.cc
@@ -114,6 +114,8 @@
 
 struct DataForDeserializer {
   STACK_ALLOCATED();
+
+ public:
   Member<Document> document;
 };
 
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
index 5866a90..cee9f1e 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.cc
@@ -60,7 +60,7 @@
 }  // namespace
 
 // static
-V8EventListener* V8EventListenerHelper::GetEventListener(
+V8AbstractEventListener* V8EventListenerHelper::GetEventListener(
     ScriptState* script_state,
     v8::Local<v8::Value> value,
     bool is_attribute,
@@ -77,7 +77,7 @@
           ? V8PrivateProperty::GetV8EventListenerAttributeListener(isolate)
           : V8PrivateProperty::GetV8EventListenerListener(isolate);
 
-  return GetEventListenerInternal<V8EventListener>(
+  return GetEventListenerInternal<V8AbstractEventListener>(
       script_state, object, listener_property, lookup,
       [object, is_attribute, script_state, listener_property]() {
         return script_state->World().IsWorkerWorld()
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h
index b1e1dc0..e7551ea0 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h
+++ b/third_party/blink/renderer/bindings/core/v8/v8_event_listener_helper.h
@@ -53,10 +53,11 @@
   STATIC_ONLY(V8EventListenerHelper);
 
  public:
-  CORE_EXPORT static V8EventListener* GetEventListener(ScriptState*,
-                                                       v8::Local<v8::Value>,
-                                                       bool is_attribute,
-                                                       ListenerLookupType);
+  CORE_EXPORT static V8AbstractEventListener* GetEventListener(
+      ScriptState*,
+      v8::Local<v8::Value>,
+      bool is_attribute,
+      ListenerLookupType);
 
   CORE_EXPORT static V8ErrorHandler* EnsureErrorHandler(ScriptState*,
                                                         v8::Local<v8::Value>);
diff --git a/third_party/blink/renderer/bindings/modules/BUILD.gn b/third_party/blink/renderer/bindings/modules/BUILD.gn
index 01b6956d..93df2008 100644
--- a/third_party/blink/renderer/bindings/modules/BUILD.gn
+++ b/third_party/blink/renderer/bindings/modules/BUILD.gn
@@ -76,7 +76,10 @@
     "$blink_modules_output_dir/event_modules_factory.cc",
   ]
   deps = make_core_generated_deps + [ "//third_party/blink/renderer/bindings/modules:modules_bindings_generated_event_interfaces" ]
-  deps += [ "//media/midi:mojo_blink" ]
+  deps += [
+    "//device/gamepad/public/mojom:mojom_blink",
+    "//media/midi:mojo_blink",
+  ]
 }
 
 make_names("event_modules_names") {
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index e58caefb..35b1b1e 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -206,6 +206,7 @@
     "//third_party/blink/renderer/core/probe",
     "//third_party/blink/renderer/core/resize_observer",
     "//third_party/blink/renderer/core/script",
+    "//third_party/blink/renderer/core/scroll",
     "//third_party/blink/renderer/core/streams",
     "//third_party/blink/renderer/core/style:rendering",
     "//third_party/blink/renderer/core/style:svg_style",
@@ -1728,7 +1729,6 @@
     "dom/events/event_target_test.cc",
     "dom/events/listener_leak_test.cc",
     "dom/first_letter_pseudo_element_test.cc",
-    "dom/flat_tree_traversal_ng_test.cc",
     "dom/flat_tree_traversal_test.cc",
     "dom/idle_deadline_test.cc",
     "dom/layout_tree_builder_traversal_test.cc",
@@ -2083,6 +2083,8 @@
     "script/module_map_test.cc",
     "script/script_module_resolver_impl_test.cc",
     "script/script_runner_test.cc",
+    "scroll/scrollable_area_test.cc",
+    "scroll/scrollbar_theme_overlay_test.cc",
     "streams/readable_stream_operations_test.cc",
     "style/border_value_test.cc",
     "style/computed_style_test.cc",
@@ -2148,6 +2150,14 @@
   if (!is_android) {
     deps += [ "//third_party/blink/renderer/core/mojo:unit_tests" ]
   }
+
+  if (!is_mac) {
+    sources += [ "scroll/scroll_animator_test.cc" ]
+  }
+
+  if (use_default_render_theme) {
+    sources += [ "scroll/scrollbar_theme_aura_test.cc" ]
+  }
 }
 
 jumbo_source_set("perf_tests") {
diff --git a/third_party/blink/renderer/core/DEPS b/third_party/blink/renderer/core/DEPS
index 7b89d3a5..2527fda 100644
--- a/third_party/blink/renderer/core/DEPS
+++ b/third_party/blink/renderer/core/DEPS
@@ -6,11 +6,17 @@
     "+base/unguessable_token.h",
     "+build/mac",
     "+build/win",
+    "+cc/animation/animation_curve.h",
+    "+cc/animation/scroll_offset_animations.h",
+    "+cc/animation/scroll_offset_animation_curve.h",
+    "+cc/animation/scroll_state.h",
     "+cc/base/region.h",
     "+cc/input/browser_controls_state.h",
     "+cc/input/event_listener_properties.h",
     "+cc/input/layer_selection_bound.h",
     "+cc/input/overscroll_behavior.h",
+    "+cc/input/scrollbar.h",
+    "+cc/input/scroll_state.h",
     "+cc/layers/content_layer_client.h",
     "+cc/layers/layer.h",
     "+cc/layers/layer_position_constraint.h",
@@ -35,6 +41,7 @@
     "+services/service_manager/public",
     "+services/ui/public/interfaces/ime/ime.mojom-shared.h",
     "+skia/public/interfaces/bitmap_skbitmap_struct_traits.h",
+    "+skia/ext/skia_utils_mac.h",
     "+third_party/blink/public/common",
     "+third_party/blink/public/mojom",
     "+third_party/blink/public/public_buildflags.h",
@@ -45,6 +52,7 @@
     "-third_party/blink/renderer/modules",
     "+third_party/skia/include",
     "+ui/gfx/geometry",
+    "+ui/gfx/skia_util.h",
     "-web",
     # We do not want any new dependencies into core/exported until we resolve
     # controller layer.
@@ -58,6 +66,7 @@
 specific_include_rules = {
     # Additional allowed includes for tests.
     ".*_test(_.*)?\.(cc|h)" : [
+        "+base/message_loop/message_loop.h",
         # Test harness may use cc directly instead of going through WebViewImpl etc.
         "+cc",
 	# TODO(crbug.com/838693): Test harnesses use LayerTreeView
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index 56acf864..92760ef 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -79,20 +79,11 @@
   return result;
 }
 
-double AnimationEffect::ActiveDurationInternal() const {
-  const double result =
-      timing_.playback_rate
-          ? RepeatedDuration() / std::abs(timing_.playback_rate)
-          : std::numeric_limits<double>::infinity();
-  DCHECK_GE(result, 0);
-  return result;
-}
-
 double AnimationEffect::EndTimeInternal() const {
   // Per the spec, the end time has a lower bound of 0.0:
   // https://drafts.csswg.org/web-animations-1/#end-time
-  return std::max(
-      timing_.start_delay + ActiveDurationInternal() + timing_.end_delay, 0.0);
+  return std::max(timing_.start_delay + RepeatedDuration() + timing_.end_delay,
+                  0.0);
 }
 
 void AnimationEffect::UpdateSpecifiedTiming(const Timing& timing) {
@@ -129,7 +120,7 @@
     ComputedEffectTiming& computed_timing) const {
   // ComputedEffectTiming members.
   computed_timing.setEndTime(EndTimeInternal() * 1000);
-  computed_timing.setActiveDuration(ActiveDurationInternal() * 1000);
+  computed_timing.setActiveDuration(RepeatedDuration() * 1000);
 
   if (IsNull(EnsureCalculated().local_time)) {
     computed_timing.setLocalTimeToNull();
@@ -193,7 +184,7 @@
   const double local_time = inherited_time;
   double time_to_next_iteration = std::numeric_limits<double>::infinity();
   if (needs_update) {
-    const double active_duration = this->ActiveDurationInternal();
+    const double active_duration = RepeatedDuration();
 
     const Phase current_phase =
         CalculatePhase(active_duration, local_time, timing_);
@@ -210,14 +201,14 @@
       const double start_offset = MultiplyZeroAlwaysGivesZero(
           timing_.iteration_start, iteration_duration);
       DCHECK_GE(start_offset, 0);
-      const double scaled_active_time = CalculateScaledActiveTime(
-          active_duration, active_time, start_offset, timing_);
+      const double offset_active_time =
+          CalculateOffsetActiveTime(active_duration, active_time, start_offset);
       const double iteration_time = CalculateIterationTime(
-          iteration_duration, RepeatedDuration(), scaled_active_time,
-          start_offset, current_phase, timing_);
+          iteration_duration, active_duration, offset_active_time, start_offset,
+          current_phase, timing_);
 
       current_iteration = CalculateCurrentIteration(
-          iteration_duration, iteration_time, scaled_active_time, timing_);
+          iteration_duration, iteration_time, offset_active_time, timing_);
       const base::Optional<double> transformed_time = CalculateTransformedTime(
           current_iteration, iteration_duration, iteration_time, timing_);
 
@@ -231,20 +222,14 @@
         progress = transformed_time.value() / iteration_duration;
 
       if (!IsNull(iteration_time)) {
-        time_to_next_iteration = (iteration_duration - iteration_time) /
-                                 std::abs(timing_.playback_rate);
+        time_to_next_iteration = iteration_duration - iteration_time;
         if (active_duration - active_time < time_to_next_iteration)
           time_to_next_iteration = std::numeric_limits<double>::infinity();
       }
     } else {
       const double kLocalIterationDuration = 1;
-      const double local_repeated_duration =
-          kLocalIterationDuration * timing_.iteration_count;
-      DCHECK_GE(local_repeated_duration, 0);
       const double local_active_duration =
-          timing_.playback_rate
-              ? local_repeated_duration / std::abs(timing_.playback_rate)
-              : std::numeric_limits<double>::infinity();
+          kLocalIterationDuration * timing_.iteration_count;
       DCHECK_GE(local_active_duration, 0);
       const double local_local_time =
           local_time < timing_.start_delay
@@ -259,14 +244,14 @@
       const double start_offset =
           timing_.iteration_start * kLocalIterationDuration;
       DCHECK_GE(start_offset, 0);
-      const double scaled_active_time = CalculateScaledActiveTime(
-          local_active_duration, local_active_time, start_offset, timing_);
+      const double offset_active_time = CalculateOffsetActiveTime(
+          local_active_duration, local_active_time, start_offset);
       const double iteration_time = CalculateIterationTime(
-          kLocalIterationDuration, local_repeated_duration, scaled_active_time,
+          kLocalIterationDuration, local_active_duration, offset_active_time,
           start_offset, current_phase, timing_);
 
       current_iteration = CalculateCurrentIteration(
-          kLocalIterationDuration, iteration_time, scaled_active_time, timing_);
+          kLocalIterationDuration, iteration_time, offset_active_time, timing_);
       progress = CalculateTransformedTime(
           current_iteration, kLocalIterationDuration, iteration_time, timing_);
     }
diff --git a/third_party/blink/renderer/core/animation/animation_effect.h b/third_party/blink/renderer/core/animation/animation_effect.h
index c2c2842f..d90030e 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.h
+++ b/third_party/blink/renderer/core/animation/animation_effect.h
@@ -113,7 +113,7 @@
   }
 
   double IterationDuration() const;
-  double ActiveDurationInternal() const;
+  double RepeatedDuration() const;
   double EndTimeInternal() const;
 
   const Timing& SpecifiedTiming() const { return timing_; }
@@ -152,8 +152,6 @@
   }
   void ClearEventDelegate() { event_delegate_ = nullptr; }
 
-  double RepeatedDuration() const;
-
   virtual void UpdateChildrenAndEffects() const = 0;
   virtual double IntrinsicIterationDuration() const { return 0; }
   virtual double CalculateTimeToEffectChange(
diff --git a/third_party/blink/renderer/core/animation/animation_effect_test.cc b/third_party/blink/renderer/core/animation/animation_effect_test.cc
index 1391301..ba175ec 100644
--- a/third_party/blink/renderer/core/animation/animation_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect_test.cc
@@ -137,7 +137,7 @@
   EXPECT_TRUE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(2, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(2, animation_node->RepeatedDuration());
   EXPECT_EQ(0, animation_node->Progress());
 
   animation_node->UpdateInheritedTime(1);
@@ -147,7 +147,7 @@
   EXPECT_TRUE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(2, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(2, animation_node->RepeatedDuration());
   EXPECT_EQ(0.5, animation_node->Progress());
 
   animation_node->UpdateInheritedTime(2);
@@ -157,7 +157,7 @@
   EXPECT_FALSE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(2, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(2, animation_node->RepeatedDuration());
   EXPECT_EQ(1, animation_node->Progress());
 
   animation_node->UpdateInheritedTime(3);
@@ -167,7 +167,7 @@
   EXPECT_FALSE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(2, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(2, animation_node->RepeatedDuration());
   EXPECT_EQ(1, animation_node->Progress());
 }
 
@@ -247,12 +247,12 @@
   TestAnimationEffect* animation_node = TestAnimationEffect::Create(timing);
 
   animation_node->UpdateInheritedTime(-1);
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_TRUE(IsNull(animation_node->CurrentIteration()));
   EXPECT_FALSE(animation_node->Progress());
 
   animation_node->UpdateInheritedTime(0);
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_EQ(0, animation_node->CurrentIteration());
   EXPECT_EQ(0, animation_node->Progress());
 }
@@ -269,7 +269,7 @@
   EXPECT_FALSE(animation_node->Progress());
 
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
+            animation_node->RepeatedDuration());
 
   animation_node->UpdateInheritedTime(0);
   EXPECT_EQ(0, animation_node->CurrentIteration());
@@ -375,7 +375,7 @@
   EXPECT_FALSE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_EQ(1, animation_node->Progress());
 
   animation_node->UpdateInheritedTime(1);
@@ -385,7 +385,7 @@
   EXPECT_FALSE(animation_node->IsCurrent());
   EXPECT_TRUE(animation_node->IsInEffect());
   EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_EQ(1, animation_node->Progress());
 }
 
@@ -476,12 +476,12 @@
   TestAnimationEffect* animation_node = TestAnimationEffect::Create(timing);
 
   animation_node->UpdateInheritedTime(-1);
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_TRUE(IsNull(animation_node->CurrentIteration()));
   EXPECT_FALSE(animation_node->Progress());
 
   animation_node->UpdateInheritedTime(0);
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
             animation_node->CurrentIteration());
   EXPECT_EQ(1, animation_node->Progress());
@@ -575,7 +575,7 @@
   animation_node->UpdateInheritedTime(0);
 
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
+            animation_node->RepeatedDuration());
   EXPECT_EQ(AnimationEffect::kPhaseActive, animation_node->GetPhase());
   EXPECT_TRUE(animation_node->IsInPlay());
   EXPECT_TRUE(animation_node->IsCurrent());
@@ -586,7 +586,7 @@
   animation_node->UpdateInheritedTime(1);
 
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
+            animation_node->RepeatedDuration());
   EXPECT_EQ(AnimationEffect::kPhaseActive, animation_node->GetPhase());
   EXPECT_TRUE(animation_node->IsInPlay());
   EXPECT_TRUE(animation_node->IsCurrent());
@@ -604,7 +604,7 @@
 
   animation_node->UpdateInheritedTime(0);
 
-  EXPECT_EQ(0, animation_node->ActiveDurationInternal());
+  EXPECT_EQ(0, animation_node->RepeatedDuration());
   EXPECT_EQ(AnimationEffect::kPhaseAfter, animation_node->GetPhase());
   EXPECT_FALSE(animation_node->IsInPlay());
   EXPECT_FALSE(animation_node->IsCurrent());
@@ -632,7 +632,7 @@
   animation_node->UpdateInheritedTime(0);
 
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
+            animation_node->RepeatedDuration());
   EXPECT_EQ(AnimationEffect::kPhaseActive, animation_node->GetPhase());
   EXPECT_TRUE(animation_node->IsInPlay());
   EXPECT_TRUE(animation_node->IsCurrent());
@@ -643,7 +643,7 @@
   animation_node->UpdateInheritedTime(1);
 
   EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
+            animation_node->RepeatedDuration());
   EXPECT_EQ(AnimationEffect::kPhaseActive, animation_node->GetPhase());
   EXPECT_TRUE(animation_node->IsInPlay());
   EXPECT_TRUE(animation_node->IsCurrent());
@@ -652,35 +652,6 @@
   EXPECT_EQ(0, animation_node->Progress());
 }
 
-TEST(AnimationAnimationEffectTest, InfiniteDurationZeroPlaybackRate) {
-  Timing timing;
-  timing.iteration_duration = std::numeric_limits<double>::infinity();
-  timing.playback_rate = 0;
-  TestAnimationEffect* animation_node = TestAnimationEffect::Create(timing);
-
-  animation_node->UpdateInheritedTime(0);
-
-  EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
-  EXPECT_EQ(AnimationEffect::kPhaseActive, animation_node->GetPhase());
-  EXPECT_TRUE(animation_node->IsInPlay());
-  EXPECT_TRUE(animation_node->IsCurrent());
-  EXPECT_TRUE(animation_node->IsInEffect());
-  EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(0, animation_node->Progress());
-
-  animation_node->UpdateInheritedTime(std::numeric_limits<double>::infinity());
-
-  EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            animation_node->ActiveDurationInternal());
-  EXPECT_EQ(AnimationEffect::kPhaseAfter, animation_node->GetPhase());
-  EXPECT_FALSE(animation_node->IsInPlay());
-  EXPECT_FALSE(animation_node->IsCurrent());
-  EXPECT_TRUE(animation_node->IsInEffect());
-  EXPECT_EQ(0, animation_node->CurrentIteration());
-  EXPECT_EQ(0, animation_node->Progress());
-}
-
 TEST(AnimationAnimationEffectTest, EndTime) {
   Timing timing;
   timing.start_delay = 1;
diff --git a/third_party/blink/renderer/core/animation/animation_test.cc b/third_party/blink/renderer/core/animation/animation_test.cc
index b36783cd1..2806c8e 100644
--- a/third_party/blink/renderer/core/animation/animation_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_test.cc
@@ -115,11 +115,9 @@
     return StringKeyframeEffectModel::Create(StringKeyframeVector());
   }
 
-  KeyframeEffect* MakeAnimation(double duration = 30,
-                                double playback_rate = 1) {
+  KeyframeEffect* MakeAnimation(double duration = 30) {
     Timing timing;
     timing.iteration_duration = duration;
-    timing.playback_rate = playback_rate;
     return KeyframeEffect::Create(nullptr, MakeEmptyEffectModel(), timing);
   }
 
@@ -860,7 +858,6 @@
 
   Timing timing;
   timing.iteration_duration = 30;
-  timing.playback_rate = 1;
   KeyframeEffect* keyframe_effect_composited = KeyframeEffect::Create(
       ToElement(object_composited->GetNode()), MakeEmptyEffectModel(), timing);
   Animation* animation_composited = timeline->Play(keyframe_effect_composited);
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index c9013c9c8..c7b658b 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -475,7 +475,7 @@
   // Compositor's time offset is positive for seeking into the animation.
   out.scaled_time_offset =
       -timing.start_delay / animation_playback_rate + time_offset;
-  out.playback_rate = timing.playback_rate * animation_playback_rate;
+  out.playback_rate = animation_playback_rate;
   out.fill_mode = timing.fill_mode == Timing::FillMode::AUTO
                       ? Timing::FillMode::NONE
                       : timing.fill_mode;
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index d1fcc00..3ec8581d 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -180,7 +180,6 @@
     timing.iteration_start = 0;
     timing.iteration_count = 1;
     timing.iteration_duration = 1.0;
-    timing.playback_rate = 1.0;
     timing.direction = Timing::PlaybackDirection::NORMAL;
     timing.timing_function = linear_timing_function_;
     return timing;
@@ -500,21 +499,6 @@
   EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
 }
 
-TEST_F(AnimationCompositorAnimationsTest,
-       ConvertTimingForCompositorPlaybackRate) {
-  timing_.playback_rate = 1.0;
-  EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
-  EXPECT_DOUBLE_EQ(1.0, compositor_timing_.playback_rate);
-
-  timing_.playback_rate = -2.3;
-  EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
-  EXPECT_DOUBLE_EQ(-2.3, compositor_timing_.playback_rate);
-
-  timing_.playback_rate = 1.6;
-  EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
-  EXPECT_DOUBLE_EQ(1.6, compositor_timing_.playback_rate);
-}
-
 TEST_F(AnimationCompositorAnimationsTest, ConvertTimingForCompositorDirection) {
   timing_.direction = Timing::PlaybackDirection::NORMAL;
   EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
@@ -842,10 +826,9 @@
 
   timing_.iteration_count = 5;
   timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
-  timing_.playback_rate = 2.0;
 
   std::unique_ptr<CompositorKeyframeModel> keyframe_model =
-      ConvertToCompositorAnimation(*effect);
+      ConvertToCompositorAnimation(*effect, 2.0);
   EXPECT_EQ(CompositorTargetProperty::OPACITY,
             keyframe_model->TargetProperty());
   EXPECT_EQ(5.0, keyframe_model->Iterations());
@@ -1064,37 +1047,6 @@
 }
 
 TEST_F(AnimationCompositorAnimationsTest,
-       createSimpleOpacityAnimationPlaybackRates) {
-  // KeyframeEffect to convert
-  StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
-      CreateReplaceOpKeyframe(CSSPropertyOpacity, "0.2", 0),
-      CreateReplaceOpKeyframe(CSSPropertyOpacity, "0.5", 1.0));
-
-  const double kPlaybackRate = 2;
-  const double kAnimationPlaybackRate = -1.5;
-
-  timing_.playback_rate = kPlaybackRate;
-
-  std::unique_ptr<CompositorKeyframeModel> keyframe_model =
-      ConvertToCompositorAnimation(*effect, kAnimationPlaybackRate);
-  EXPECT_EQ(CompositorTargetProperty::OPACITY,
-            keyframe_model->TargetProperty());
-  EXPECT_EQ(1.0, keyframe_model->Iterations());
-  EXPECT_EQ(0, keyframe_model->TimeOffset());
-  EXPECT_EQ(CompositorKeyframeModel::Direction::NORMAL,
-            keyframe_model->GetDirection());
-  EXPECT_EQ(kPlaybackRate * kAnimationPlaybackRate,
-            keyframe_model->PlaybackRate());
-
-  std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
-      keyframe_model->FloatCurveForTesting();
-
-  CompositorFloatAnimationCurve::Keyframes keyframes =
-      keyframed_float_curve->KeyframesForTesting();
-  ASSERT_EQ(2UL, keyframes.size());
-}
-
-TEST_F(AnimationCompositorAnimationsTest,
        createSimpleOpacityAnimationFillModeNone) {
   // KeyframeEffect to convert
   StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index 47c3e13..e66e539 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -1173,9 +1173,10 @@
   }
 
   if (current_phase == AnimationEffect::kPhaseAfter &&
-      previous_phase_ != AnimationEffect::kPhaseAfter)
+      previous_phase_ != AnimationEffect::kPhaseAfter) {
     MaybeDispatch(Document::kAnimationEndListener, EventTypeNames::animationend,
-                  animation_node.ActiveDurationInternal());
+                  animation_node.RepeatedDuration());
+  }
 
   previous_phase_ = current_phase;
   previous_iteration_ = current_iteration;
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.h b/third_party/blink/renderer/core/animation/css/css_animations.h
index 4aa84f96..1be0c02 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.h
+++ b/third_party/blink/renderer/core/animation/css/css_animations.h
@@ -165,6 +165,8 @@
 
   struct TransitionUpdateState {
     STACK_ALLOCATED();
+
+   public:
     CSSAnimationUpdate& update;
     Member<const Element> animating_element;
     const ComputedStyle& old_style;
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index 13869f38..3516911bc 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -437,7 +437,7 @@
     double local_time,
     double time_to_next_iteration) const {
   const double start_time = SpecifiedTiming().start_delay;
-  const double end_time_minus_end_delay = start_time + ActiveDurationInternal();
+  const double end_time_minus_end_delay = start_time + RepeatedDuration();
   const double end_time =
       end_time_minus_end_delay + SpecifiedTiming().end_delay;
   const double after_time = std::min(end_time_minus_end_delay, end_time);
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect_test.cc b/third_party/blink/renderer/core/animation/keyframe_effect_test.cc
index 8f70fa7..26d3be12 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect_test.cc
@@ -358,69 +358,4 @@
   EXPECT_EQ(100, keyframe_effect->TimeToReverseEffectChange());
 }
 
-TEST_F(KeyframeEffectTest, TimeToEffectChangeWithPlaybackRate) {
-  Timing timing;
-  timing.iteration_duration = 100;
-  timing.start_delay = 100;
-  timing.end_delay = 100;
-  timing.playback_rate = 2;
-  timing.fill_mode = Timing::FillMode::NONE;
-  KeyframeEffect* keyframe_effect =
-      KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
-  Animation* animation = GetDocument().Timeline().Play(keyframe_effect);
-  double inf = std::numeric_limits<double>::infinity();
-
-  EXPECT_EQ(100, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(inf, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(100);
-  EXPECT_EQ(50, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(149);
-  EXPECT_EQ(1, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(150);
-  // End-exclusive.
-  EXPECT_EQ(inf, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(200);
-  EXPECT_EQ(inf, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(50, keyframe_effect->TimeToReverseEffectChange());
-}
-
-TEST_F(KeyframeEffectTest, TimeToEffectChangeWithNegativePlaybackRate) {
-  Timing timing;
-  timing.iteration_duration = 100;
-  timing.start_delay = 100;
-  timing.end_delay = 100;
-  timing.playback_rate = -2;
-  timing.fill_mode = Timing::FillMode::NONE;
-  KeyframeEffect* keyframe_effect =
-      KeyframeEffect::Create(nullptr, CreateEmptyEffectModel(), timing);
-  Animation* animation = GetDocument().Timeline().Play(keyframe_effect);
-  double inf = std::numeric_limits<double>::infinity();
-
-  EXPECT_EQ(100, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(inf, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(100);
-  EXPECT_EQ(50, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(149);
-  EXPECT_EQ(1, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(150);
-  EXPECT_EQ(inf, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(0, keyframe_effect->TimeToReverseEffectChange());
-
-  animation->SetCurrentTimeInternal(200);
-  EXPECT_EQ(inf, keyframe_effect->TimeToForwardsEffectChange());
-  EXPECT_EQ(50, keyframe_effect->TimeToReverseEffectChange());
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/timing.h b/third_party/blink/renderer/core/animation/timing.h
index 44b1fd03..1418fbf 100644
--- a/third_party/blink/renderer/core/animation/timing.h
+++ b/third_party/blink/renderer/core/animation/timing.h
@@ -62,7 +62,6 @@
         iteration_start(0),
         iteration_count(1),
         iteration_duration(std::numeric_limits<double>::quiet_NaN()),
-        playback_rate(1),
         direction(PlaybackDirection::NORMAL),
         timing_function(LinearTimingFunction::Shared()) {}
 
@@ -73,7 +72,6 @@
     DCHECK_GE(iteration_start, 0);
     DCHECK_GE(iteration_count, 0);
     DCHECK(std::isnan(iteration_duration) || iteration_duration >= 0);
-    DCHECK(std::isfinite(playback_rate));
     DCHECK(timing_function);
   }
 
@@ -85,7 +83,6 @@
            ((std::isnan(iteration_duration) &&
              std::isnan(other.iteration_duration)) ||
             iteration_duration == other.iteration_duration) &&
-           playback_rate == other.playback_rate &&
            direction == other.direction &&
            DataEquivalent(timing_function.get(), other.timing_function.get());
   }
@@ -99,8 +96,6 @@
   double iteration_count;
   double iteration_duration;
 
-  // TODO(crbug.com/630915) Remove playbackRate
-  double playback_rate;
   PlaybackDirection direction;
   scoped_refptr<TimingFunction> timing_function;
 };
diff --git a/third_party/blink/renderer/core/animation/timing_calculations.h b/third_party/blink/renderer/core/animation/timing_calculations.h
index c777bed..781f6598 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations.h
+++ b/third_party/blink/renderer/core/animation/timing_calculations.h
@@ -110,10 +110,9 @@
   }
 }
 
-static inline double CalculateScaledActiveTime(double active_duration,
+static inline double CalculateOffsetActiveTime(double active_duration,
                                                double active_time,
-                                               double start_offset,
-                                               const Timing& specified) {
+                                               double start_offset) {
   DCHECK_GE(active_duration, 0);
   DCHECK_GE(start_offset, 0);
 
@@ -122,17 +121,10 @@
 
   DCHECK(active_time >= 0 && active_time <= active_duration);
 
-  if (specified.playback_rate == 0)
-    return start_offset;
-
   if (!std::isfinite(active_time))
     return std::numeric_limits<double>::infinity();
 
-  return MultiplyZeroAlwaysGivesZero(specified.playback_rate < 0
-                                         ? active_time - active_duration
-                                         : active_time,
-                                     specified.playback_rate) +
-         start_offset;
+  return active_time + start_offset;
 }
 
 static inline bool EndsOnIterationBoundary(double iteration_count,
@@ -145,7 +137,7 @@
 // text.
 static inline double CalculateIterationTime(double iteration_duration,
                                             double repeated_duration,
-                                            double scaled_active_time,
+                                            double offset_active_time,
                                             double start_offset,
                                             AnimationEffect::Phase phase,
                                             const Timing& specified) {
@@ -154,26 +146,26 @@
             MultiplyZeroAlwaysGivesZero(iteration_duration,
                                         specified.iteration_count));
 
-  if (IsNull(scaled_active_time))
+  if (IsNull(offset_active_time))
     return NullValue();
 
-  DCHECK_GE(scaled_active_time, 0);
-  DCHECK_LE(scaled_active_time, repeated_duration + start_offset);
+  DCHECK_GE(offset_active_time, 0);
+  DCHECK_LE(offset_active_time, repeated_duration + start_offset);
 
-  if (!std::isfinite(scaled_active_time) ||
-      (scaled_active_time - start_offset == repeated_duration &&
+  if (!std::isfinite(offset_active_time) ||
+      (offset_active_time - start_offset == repeated_duration &&
        specified.iteration_count &&
        EndsOnIterationBoundary(specified.iteration_count,
                                specified.iteration_start)))
     return iteration_duration;
 
-  DCHECK(std::isfinite(scaled_active_time));
-  double iteration_time = fmod(scaled_active_time, iteration_duration);
+  DCHECK(std::isfinite(offset_active_time));
+  double iteration_time = fmod(offset_active_time, iteration_duration);
 
   // This implements step 3 of
   // https://drafts.csswg.org/web-animations/#calculating-the-simple-iteration-progress
   if (iteration_time == 0 && phase == AnimationEffect::kPhaseAfter &&
-      repeated_duration != 0 && scaled_active_time != 0)
+      repeated_duration != 0 && offset_active_time != 0)
     return iteration_duration;
 
   return iteration_time;
@@ -181,25 +173,25 @@
 
 static inline double CalculateCurrentIteration(double iteration_duration,
                                                double iteration_time,
-                                               double scaled_active_time,
+                                               double offset_active_time,
                                                const Timing& specified) {
   DCHECK_GT(iteration_duration, 0);
   DCHECK(IsNull(iteration_time) || iteration_time >= 0);
 
-  if (IsNull(scaled_active_time))
+  if (IsNull(offset_active_time))
     return NullValue();
 
   DCHECK_GE(iteration_time, 0);
   DCHECK_LE(iteration_time, iteration_duration);
-  DCHECK_GE(scaled_active_time, 0);
+  DCHECK_GE(offset_active_time, 0);
 
-  if (!scaled_active_time)
+  if (!offset_active_time)
     return 0;
 
   if (iteration_time == iteration_duration)
     return specified.iteration_start + specified.iteration_count - 1;
 
-  return floor(scaled_active_time / iteration_duration);
+  return floor(offset_active_time / iteration_duration);
 }
 
 static inline double CalculateDirectedTime(double current_iteration,
diff --git a/third_party/blink/renderer/core/animation/timing_calculations_test.cc b/third_party/blink/renderer/core/animation/timing_calculations_test.cc
index 2ee1ba6..8e6ea850 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations_test.cc
+++ b/third_party/blink/renderer/core/animation/timing_calculations_test.cc
@@ -98,39 +98,18 @@
       AnimationEffect::kPhaseNone, timing)));
 }
 
-TEST(AnimationTimingCalculationsTest, ScaledActiveTime) {
-  Timing timing;
-
-  // calculateScaledActiveTime(activeDuration, activeTime, startOffset, timing)
-
+TEST(AnimationTimingCalculationsTest, OffsetActiveTime) {
   // if the active time is null
-  EXPECT_TRUE(IsNull(CalculateScaledActiveTime(4, NullValue(), 5, timing)));
+  EXPECT_TRUE(IsNull(CalculateOffsetActiveTime(4, NullValue(), 5)));
 
-  // if the playback rate is negative
-  timing.playback_rate = -1;
-  EXPECT_EQ(35, CalculateScaledActiveTime(40, 10, 5, timing));
-
-  // otherwise
-  timing.playback_rate = 0;
-  EXPECT_EQ(5, CalculateScaledActiveTime(40, 10, 5, timing));
-  timing.playback_rate = 1;
-  EXPECT_EQ(15, CalculateScaledActiveTime(40, 10, 5, timing));
+  // normal case
+  EXPECT_EQ(15, CalculateOffsetActiveTime(40, 10, 5));
 
   // infinte activeTime
-  timing.playback_rate = 0;
-  EXPECT_EQ(0, CalculateScaledActiveTime(
-                   std::numeric_limits<double>::infinity(),
-                   std::numeric_limits<double>::infinity(), 0, timing));
-  timing.playback_rate = 1;
-  EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            CalculateScaledActiveTime(std::numeric_limits<double>::infinity(),
-                                      std::numeric_limits<double>::infinity(),
-                                      0, timing));
-  timing.playback_rate = -1;
-  EXPECT_EQ(std::numeric_limits<double>::infinity(),
-            CalculateScaledActiveTime(std::numeric_limits<double>::infinity(),
-                                      std::numeric_limits<double>::infinity(),
-                                      0, timing));
+  EXPECT_EQ(
+      std::numeric_limits<double>::infinity(),
+      CalculateOffsetActiveTime(std::numeric_limits<double>::infinity(),
+                                std::numeric_limits<double>::infinity(), 0));
 }
 
 TEST(AnimationTimingCalculationsTest, IterationTime) {
diff --git a/third_party/blink/renderer/core/animation/timing_input_test.cc b/third_party/blink/renderer/core/animation/timing_input_test.cc
index d70c3df..1163021 100644
--- a/third_party/blink/renderer/core/animation/timing_input_test.cc
+++ b/third_party/blink/renderer/core/animation/timing_input_test.cc
@@ -451,7 +451,6 @@
   EXPECT_EQ(control_timing.iteration_start, updated_timing.iteration_start);
   EXPECT_EQ(control_timing.iteration_count, updated_timing.iteration_count);
   EXPECT_TRUE(std::isnan(updated_timing.iteration_duration));
-  EXPECT_EQ(control_timing.playback_rate, updated_timing.playback_rate);
   EXPECT_EQ(control_timing.direction, updated_timing.direction);
   EXPECT_EQ(*control_timing.timing_function, *updated_timing.timing_function);
 }
@@ -471,7 +470,6 @@
   EXPECT_EQ(control_timing.iteration_start, updated_timing.iteration_start);
   EXPECT_EQ(control_timing.iteration_count, updated_timing.iteration_count);
   EXPECT_TRUE(std::isnan(updated_timing.iteration_duration));
-  EXPECT_EQ(control_timing.playback_rate, updated_timing.playback_rate);
   EXPECT_EQ(control_timing.direction, updated_timing.direction);
   EXPECT_EQ(*control_timing.timing_function, *updated_timing.timing_function);
 }
diff --git a/third_party/blink/renderer/core/css/CSSProperties.json5 b/third_party/blink/renderer/core/css/CSSProperties.json5
index c4478b93..c09f646 100644
--- a/third_party/blink/renderer/core/css/CSSProperties.json5
+++ b/third_party/blink/renderer/core/css/CSSProperties.json5
@@ -2633,7 +2633,7 @@
       field_size: 4,
       field_template: "primitive",
       default_value: "ScrollCustomization::kScrollDirectionNone",
-      include_paths: ["third_party/blink/renderer/platform/scroll/scroll_customization.h"],
+      include_paths: ["third_party/blink/renderer/core/scroll/scroll_customization.h"],
       runtime_flag: "ScrollCustomization",
     },
     {
diff --git a/third_party/blink/renderer/core/css/css_gradient_value.cc b/third_party/blink/renderer/core/css/css_gradient_value.cc
index c3c219f7..44f7f400 100644
--- a/third_party/blink/renderer/core/css/css_gradient_value.cc
+++ b/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -170,6 +170,7 @@
 struct CSSGradientValue::GradientDesc {
   STACK_ALLOCATED();
 
+ public:
   GradientDesc(const FloatPoint& p0,
                const FloatPoint& p1,
                GradientSpreadMethod spread_method)
diff --git a/third_party/blink/renderer/core/css/css_primitive_value_mappings.h b/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
index cc46ccb..0b9818b4e 100644
--- a/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
+++ b/third_party/blink/renderer/core/css/css_primitive_value_mappings.h
@@ -37,6 +37,8 @@
 #include "third_party/blink/renderer/core/css/css_reflection_direction.h"
 #include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
+#include "third_party/blink/renderer/core/scroll/scroll_customization.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/core/style/svg_computed_style_defs.h"
 #include "third_party/blink/renderer/platform/fonts/font_description.h"
@@ -45,8 +47,6 @@
 #include "third_party/blink/renderer/platform/graphics/graphics_types.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
 #include "third_party/blink/renderer/platform/length.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_customization.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/text/text_run.h"
 #include "third_party/blink/renderer/platform/text/writing_mode.h"
 #include "third_party/blink/renderer/platform/theme_types.h"
diff --git a/third_party/blink/renderer/core/css/resolver/css_variable_resolver.h b/third_party/blink/renderer/core/css/resolver/css_variable_resolver.h
index e5a574c..5fff98a 100644
--- a/third_party/blink/renderer/core/css/resolver/css_variable_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/css_variable_resolver.h
@@ -52,6 +52,7 @@
   struct Options {
     STACK_ALLOCATED();
 
+   public:
     // Treat any references to animation-tainted custom properties as invalid.
     //
     // Custom properties used in @keyframe rules become 'animation-tainted'
@@ -83,6 +84,7 @@
   struct Result {
     STACK_ALLOCATED();
 
+   public:
     Vector<CSSParserToken> tokens;
     Vector<String> backing_strings;
     bool is_animation_tainted = false;
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h
index 2d63cf3..a0a9564 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -179,6 +179,8 @@
 
   struct CacheSuccess {
     STACK_ALLOCATED();
+
+   public:
     bool is_inherited_cache_hit;
     bool is_non_inherited_cache_hit;
     unsigned cache_hash;
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index ea861fe..2b913137 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -59,10 +59,10 @@
 #include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h
index 2a9b012..014dd14 100644
--- a/third_party/blink/renderer/core/css/selector_checker.h
+++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -132,6 +132,8 @@
 
   struct MatchResult {
     STACK_ALLOCATED();
+
+   public:
     MatchResult() : dynamic_pseudo(kPseudoIdNone), specificity(0) {}
 
     PseudoId dynamic_pseudo;
diff --git a/third_party/blink/renderer/core/dom/BUILD.gn b/third_party/blink/renderer/core/dom/BUILD.gn
index 740a7852..97a5327 100644
--- a/third_party/blink/renderer/core/dom/BUILD.gn
+++ b/third_party/blink/renderer/core/dom/BUILD.gn
@@ -140,8 +140,6 @@
     "first_letter_pseudo_element.h",
     "flat_tree_traversal.cc",
     "flat_tree_traversal.h",
-    "flat_tree_traversal_ng.cc",
-    "flat_tree_traversal_ng.h",
     "frame_request_callback_collection.cc",
     "frame_request_callback_collection.h",
     "global_event_handlers.h",
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 2a52b12..1fec6fd 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -235,6 +235,7 @@
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h"
 #include "third_party/blink/renderer/core/script/script_runner.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
 #include "third_party/blink/renderer/core/svg/svg_script_element.h"
 #include "third_party/blink/renderer/core/svg/svg_title_element.h"
@@ -272,7 +273,6 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/text/platform_locale.h"
 #include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index e3fbb6a5..086af79 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -139,6 +139,8 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observation.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/core/svg/svg_a_element.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
 #include "third_party/blink/renderer/core/svg_names.h"
@@ -153,8 +155,6 @@
 #include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/wtf/bit_vector.h"
 #include "third_party/blink/renderer/platform/wtf/hash_functions.h"
 #include "third_party/blink/renderer/platform/wtf/text/cstring.h"
@@ -2934,7 +2934,6 @@
         ToElement(change.sibling_changed), change.sibling_before_change,
         change.sibling_after_change);
 
-  // TODO(hayato): Confirm that we can skip this if a shadow tree is v1.
   if (ShadowRoot* shadow_root = GetShadowRoot())
     shadow_root->SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
 }
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index f7d4037..671b88c 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -36,9 +36,9 @@
 #include "third_party/blink/renderer/core/html/focus_options.h"
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer.h"
+#include "third_party/blink/renderer/core/scroll/scroll_customization.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_customization.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 
@@ -121,6 +121,7 @@
 struct FocusParams {
   STACK_ALLOCATED();
 
+ public:
   FocusParams() = default;
   FocusParams(SelectionBehaviorOnFocus selection,
               WebFocusType focus_type,
@@ -421,6 +422,8 @@
   enum class AttributeModificationReason { kDirectly, kByParser, kByCloning };
   struct AttributeModificationParams {
     STACK_ALLOCATED();
+
+   public:
     AttributeModificationParams(const QualifiedName& qname,
                                 const AtomicString& old_value,
                                 const AtomicString& new_value,
diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
index 64f00f0..00d6c2f7 100644
--- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
+++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/layout/layout_object_inlines.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
+#include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
 #include "third_party/blink/renderer/platform/text/text_break_iterator.h"
 #include "third_party/blink/renderer/platform/wtf/text/unicode.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -113,6 +114,10 @@
       !parent_layout_object->BehavesLikeBlockContainer())
     return nullptr;
 
+  LayoutObject* marker =
+      parent_layout_object->IsLayoutNGListItem()
+          ? ToLayoutNGListItem(parent_layout_object)->Marker()
+          : nullptr;
   // Drill down into our children and look for our first text child.
   LayoutObject* first_letter_text_layout_object =
       parent_layout_object->SlowFirstChild();
@@ -139,7 +144,8 @@
         break;
       first_letter_text_layout_object =
           first_letter_text_layout_object->NextSibling();
-    } else if (first_letter_text_layout_object->IsListMarker()) {
+    } else if (first_letter_text_layout_object->IsListMarker() ||
+               first_letter_text_layout_object == marker) {
       first_letter_text_layout_object =
           first_letter_text_layout_object->NextSibling();
     } else if (first_letter_text_layout_object
@@ -170,6 +176,9 @@
       // setting up the first letter then.
       return nullptr;
     } else {
+      if (first_letter_text_layout_object->IsLayoutNGListItem())
+        marker = ToLayoutNGListItem(first_letter_text_layout_object)->Marker();
+
       first_letter_text_layout_object =
           first_letter_text_layout_object->SlowFirstChild();
     }
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
index 35b1842..8f8dff21 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
@@ -32,44 +32,55 @@
 
 namespace blink {
 
-Node* FlatTreeTraversal::TraverseChild(const Node& node,
-                                       TraversalDirection direction) {
-  if (ShadowRoot* shadow_root = node.GetShadowRoot()) {
-    return ResolveDistributionStartingAt(direction == kTraversalDirectionForward
-                                             ? shadow_root->firstChild()
-                                             : shadow_root->lastChild(),
-                                         direction);
-  }
-  return ResolveDistributionStartingAt(direction == kTraversalDirectionForward
-                                           ? node.firstChild()
-                                           : node.lastChild(),
-                                       direction);
+bool CanBeDistributedToV0InsertionPoint(const Node& node) {
+  return node.IsInV0ShadowTree() || node.IsChildOfV0ShadowHost();
 }
 
-Node* FlatTreeTraversal::ResolveDistributionStartingAt(
-    const Node* node,
-    TraversalDirection direction) {
-  if (!node)
-    return nullptr;
-  for (const Node* sibling = node; sibling;
-       sibling = (direction == kTraversalDirectionForward
-                      ? sibling->nextSibling()
-                      : sibling->previousSibling())) {
-    if (const HTMLSlotElement* slot =
-            ToHTMLSlotElementIfSupportsAssignmentOrNull(*sibling)) {
-      if (Node* found = (direction == kTraversalDirectionForward
-                             ? slot->FirstDistributedNode()
-                             : slot->LastDistributedNode()))
-        return found;
-      continue;
+Node* FlatTreeTraversal::TraverseChild(const Node& node,
+                                       TraversalDirection direction) {
+  if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(node)) {
+    if (slot->AssignedNodes().IsEmpty()) {
+      return direction == kTraversalDirectionForward ? slot->firstChild()
+                                                     : slot->lastChild();
     }
-    if (node->IsInV0ShadowTree())
-      return V0ResolveDistributionStartingAt(*sibling, direction);
-    return const_cast<Node*>(sibling);
+    return direction == kTraversalDirectionForward ? slot->FirstAssignedNode()
+                                                   : slot->LastAssignedNode();
   }
-  return nullptr;
+  Node* child;
+  if (ShadowRoot* shadow_root = node.GetShadowRoot()) {
+    child = direction == kTraversalDirectionForward ? shadow_root->firstChild()
+                                                    : shadow_root->lastChild();
+  } else {
+    child = direction == kTraversalDirectionForward ? node.firstChild()
+                                                    : node.lastChild();
+  }
+
+  if (!child)
+    return nullptr;
+
+  if (child->IsInV0ShadowTree()) {
+    return V0ResolveDistributionStartingAt(*child, direction);
+  }
+  return child;
 }
 
+// This needs only for v0
+// Node* FlatTreeTraversalNg::ResolveDistributionStartingAt(
+//     const Node* node,
+//     TraversalDirection direction) {
+//   if (!node)
+//     return nullptr;
+//   for (const Node* sibling = node; sibling;
+//        sibling = (direction == kTraversalDirectionForward
+//                       ? sibling->nextSibling()
+//                       : sibling->previousSibling())) {
+//     if (node->IsInV0ShadowTree())
+//       return V0ResolveDistributionStartingAt(*sibling, direction);
+//     return const_cast<Node*>(sibling);
+//   }
+//   return nullptr;
+// }
+
 Node* FlatTreeTraversal::V0ResolveDistributionStartingAt(
     const Node& node,
     TraversalDirection direction) {
@@ -103,35 +114,36 @@
   if (ShadowRootWhereNodeCanBeDistributedForV0(node))
     return TraverseSiblingsForV0Distribution(node, direction);
 
-  if (Node* found = ResolveDistributionStartingAt(
-          direction == kTraversalDirectionForward ? node.nextSibling()
-                                                  : node.previousSibling(),
-          direction))
-    return found;
+  Node* sibling = direction == kTraversalDirectionForward
+                      ? node.nextSibling()
+                      : node.previousSibling();
 
-  // Slotted nodes are already handled in traverseSiblingsForV1HostChild()
-  // above, here is for fallback contents.
-  if (auto* slot =
-          ToHTMLSlotElementIfSupportsAssignmentOrNull(node.parentElement())) {
-    if (slot->AssignedNodes().IsEmpty())
-      return TraverseSiblings(*slot, direction);
+  if (!node.IsInV0ShadowTree())
+    return sibling;
+
+  if (sibling) {
+    if (Node* found = V0ResolveDistributionStartingAt(*sibling, direction))
+      return found;
   }
 
+  // // Slotted nodes are already handled in traverseSiblingsForV1HostChild()
+  // // above, here is for fallback contents.
+  // if (auto* slot = ToHTMLSlotElementOrNull(node.parentElement())) {
+  //   if (slot->SupportsAssignment() && slot->AssignedNodes().IsEmpty())
+  //     return TraverseSiblings(*slot, direction);
+  // }
   return nullptr;
 }
 
 Node* FlatTreeTraversal::TraverseSiblingsForV1HostChild(
     const Node& node,
     TraversalDirection direction) {
-  HTMLSlotElement* slot = node.FinalDestinationSlot();
+  HTMLSlotElement* slot = node.AssignedSlot();
   if (!slot)
     return nullptr;
-  if (Node* sibling_in_distributed_nodes =
-          (direction == kTraversalDirectionForward
-               ? slot->DistributedNodeNextTo(node)
-               : slot->DistributedNodePreviousTo(node)))
-    return sibling_in_distributed_nodes;
-  return TraverseSiblings(*slot, direction);
+  return direction == kTraversalDirectionForward
+             ? slot->AssignedNodeNextTo(node)
+             : slot->AssignedNodePreviousTo(node);
 }
 
 Node* FlatTreeTraversal::TraverseSiblingsForV0Distribution(
@@ -155,18 +167,14 @@
   if (node.IsPseudoElement())
     return node.ParentOrShadowHostNode();
 
-  if (node.IsChildOfV1ShadowHost()) {
-    HTMLSlotElement* slot = node.FinalDestinationSlot();
-    if (!slot)
-      return nullptr;
-    return TraverseParent(*slot);
-  }
+  if (node.IsChildOfV1ShadowHost())
+    return node.AssignedSlot();
 
-  if (auto* slot =
+  if (auto* parent_slot =
           ToHTMLSlotElementIfSupportsAssignmentOrNull(node.parentElement())) {
-    if (!slot->AssignedNodes().IsEmpty())
+    if (!parent_slot->AssignedNodes().IsEmpty())
       return nullptr;
-    return TraverseParent(*slot, details);
+    return parent_slot;
   }
 
   if (CanBeDistributedToV0InsertionPoint(node))
@@ -208,8 +216,6 @@
 }
 
 Node* FlatTreeTraversal::ChildAt(const Node& node, unsigned index) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::ChildAt(node, index);
   AssertPrecondition(node);
   Node* child = TraverseFirstChild(node);
   while (child && index--)
@@ -219,8 +225,6 @@
 }
 
 Node* FlatTreeTraversal::NextSkippingChildren(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::NextSkippingChildren(node);
   if (Node* next_sibling = TraverseNextSibling(node))
     return next_sibling;
   return TraverseNextAncestorSibling(node);
@@ -229,8 +233,6 @@
 bool FlatTreeTraversal::ContainsIncludingPseudoElement(
     const ContainerNode& container,
     const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::ContainsIncludingPseudoElement(container, node);
   AssertPrecondition(container);
   AssertPrecondition(node);
   // This can be slower than FlatTreeTraversal::contains() because we
@@ -244,8 +246,6 @@
 }
 
 Node* FlatTreeTraversal::PreviousSkippingChildren(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::PreviousSkippingChildren(node);
   if (Node* previous_sibling = TraversePreviousSibling(node))
     return previous_sibling;
   return TraversePreviousAncestorSibling(node);
@@ -269,8 +269,6 @@
 // between DOM tree traversal and flat tree tarversal.
 Node* FlatTreeTraversal::PreviousPostOrder(const Node& current,
                                            const Node* stay_within) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::PreviousPostOrder(current, stay_within);
   AssertPrecondition(current);
   if (stay_within)
     AssertPrecondition(*stay_within);
@@ -288,8 +286,6 @@
 }
 
 bool FlatTreeTraversal::IsDescendantOf(const Node& node, const Node& other) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::IsDescendantOf(node, other);
   AssertPrecondition(node);
   AssertPrecondition(other);
   if (!HasChildren(other) || node.isConnected() != other.isConnected())
@@ -304,8 +300,6 @@
 
 Node* FlatTreeTraversal::CommonAncestor(const Node& node_a,
                                         const Node& node_b) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::CommonAncestor(node_a, node_b);
   AssertPrecondition(node_a);
   AssertPrecondition(node_b);
   Node* result = node_a.CommonAncestor(
@@ -335,8 +329,6 @@
 }
 
 unsigned FlatTreeTraversal::Index(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::Index(node);
   AssertPrecondition(node);
   unsigned count = 0;
   for (Node* runner = TraversePreviousSibling(node); runner;
@@ -346,8 +338,6 @@
 }
 
 unsigned FlatTreeTraversal::CountChildren(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::CountChildren(node);
   AssertPrecondition(node);
   unsigned count = 0;
   for (Node* runner = TraverseFirstChild(node); runner;
@@ -357,8 +347,6 @@
 }
 
 Node* FlatTreeTraversal::LastWithin(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::LastWithin(node);
   AssertPrecondition(node);
   Node* descendant = TraverseLastChild(node);
   for (Node* child = descendant; child; child = LastChild(*child))
@@ -368,8 +356,6 @@
 }
 
 Node& FlatTreeTraversal::LastWithinOrSelf(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::LastWithinOrSelf(node);
   AssertPrecondition(node);
   Node* last_descendant = LastWithin(node);
   Node& result = last_descendant ? *last_descendant : const_cast<Node&>(node);
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.h b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
index 829bde1..d741cc3 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.h
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
@@ -29,7 +29,6 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h"
 #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
 #include "third_party/blink/renderer/core/dom/node_traversal.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
@@ -41,6 +40,8 @@
 class ContainerNode;
 class Node;
 
+bool CanBeDistributedToV0InsertionPoint(const Node& node);
+
 // Flat tree version of |NodeTraversal|.
 //
 // None of member functions takes a |ShadowRoot| or an active insertion point,
@@ -98,8 +99,6 @@
   static bool IsDescendantOf(const Node& /*node*/, const Node& other);
 
   static bool Contains(const ContainerNode& container, const Node& node) {
-    if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-      return FlatTreeTraversalNg::Contains(container, node);
     AssertPrecondition(container);
     AssertPrecondition(node);
     return container == node || IsDescendantOf(node, container);
@@ -194,8 +193,6 @@
 inline ContainerNode* FlatTreeTraversal::Parent(
     const Node& node,
     ParentTraversalDetails* details) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::Parent(node, details);
   AssertPrecondition(node);
   ContainerNode* result = TraverseParent(node, details);
   AssertPostcondition(result);
@@ -203,15 +200,11 @@
 }
 
 inline Element* FlatTreeTraversal::ParentElement(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::ParentElement(node);
   ContainerNode* parent = FlatTreeTraversal::Parent(node);
   return parent && parent->IsElementNode() ? ToElement(parent) : nullptr;
 }
 
 inline Node* FlatTreeTraversal::NextSibling(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::NextSibling(node);
   AssertPrecondition(node);
   Node* result = TraverseSiblings(node, kTraversalDirectionForward);
   AssertPostcondition(result);
@@ -219,8 +212,6 @@
 }
 
 inline Node* FlatTreeTraversal::PreviousSibling(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::PreviousSibling(node);
   AssertPrecondition(node);
   Node* result = TraverseSiblings(node, kTraversalDirectionBackward);
   AssertPostcondition(result);
@@ -228,8 +219,6 @@
 }
 
 inline Node* FlatTreeTraversal::Next(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::Next(node);
   AssertPrecondition(node);
   Node* result = TraverseNext(node);
   AssertPostcondition(result);
@@ -238,8 +227,6 @@
 
 inline Node* FlatTreeTraversal::Next(const Node& node,
                                      const Node* stay_within) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::Next(node, stay_within);
   AssertPrecondition(node);
   Node* result = TraverseNext(node, stay_within);
   AssertPostcondition(result);
@@ -248,8 +235,6 @@
 
 inline Node* FlatTreeTraversal::NextSkippingChildren(const Node& node,
                                                      const Node* stay_within) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::NextSkippingChildren(node, stay_within);
   AssertPrecondition(node);
   Node* result = TraverseNextSkippingChildren(node, stay_within);
   AssertPostcondition(result);
@@ -286,8 +271,6 @@
 }
 
 inline Node* FlatTreeTraversal::Previous(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::Previous(node);
   AssertPrecondition(node);
   Node* result = TraversePrevious(node);
   AssertPostcondition(result);
@@ -304,8 +287,6 @@
 }
 
 inline Node* FlatTreeTraversal::FirstChild(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::FirstChild(node);
   AssertPrecondition(node);
   Node* result = TraverseChild(node, kTraversalDirectionForward);
   AssertPostcondition(result);
@@ -313,8 +294,6 @@
 }
 
 inline Node* FlatTreeTraversal::LastChild(const Node& node) {
-  if (RuntimeEnabledFeatures::SlotInFlatTreeEnabled())
-    return FlatTreeTraversalNg::LastChild(node);
   AssertPrecondition(node);
   Node* result = TraverseLastChild(node);
   AssertPostcondition(result);
@@ -360,4 +339,4 @@
 
 }  // namespace blink
 
-#endif
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_FLAT_TREE_TRAVERSAL_H_
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.cc
deleted file mode 100644
index c6500a0..0000000
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.cc
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2012 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h"
-
-#include "third_party/blink/renderer/core/dom/element.h"
-#include "third_party/blink/renderer/core/html/html_shadow_element.h"
-#include "third_party/blink/renderer/core/html/html_slot_element.h"
-
-namespace blink {
-
-bool CanBeDistributedToV0InsertionPoint(const Node& node) {
-  return node.IsInV0ShadowTree() || node.IsChildOfV0ShadowHost();
-}
-
-Node* FlatTreeTraversalNg::TraverseChild(const Node& node,
-                                         TraversalDirection direction) {
-  if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(node)) {
-    if (slot->AssignedNodes().IsEmpty()) {
-      return direction == kTraversalDirectionForward ? slot->firstChild()
-                                                     : slot->lastChild();
-    }
-    return direction == kTraversalDirectionForward ? slot->FirstAssignedNode()
-                                                   : slot->LastAssignedNode();
-  }
-
-  Node* child;
-  if (ShadowRoot* shadow_root = node.GetShadowRoot()) {
-    child = direction == kTraversalDirectionForward ? shadow_root->firstChild()
-                                                    : shadow_root->lastChild();
-  } else {
-    child = direction == kTraversalDirectionForward ? node.firstChild()
-                                                    : node.lastChild();
-  }
-
-  if (!child)
-    return nullptr;
-
-  if (child->IsInV0ShadowTree()) {
-    return V0ResolveDistributionStartingAt(*child, direction);
-  }
-  return child;
-}
-
-// This needs only for v0
-// Node* FlatTreeTraversalNg::ResolveDistributionStartingAt(
-//     const Node* node,
-//     TraversalDirection direction) {
-//   if (!node)
-//     return nullptr;
-//   for (const Node* sibling = node; sibling;
-//        sibling = (direction == kTraversalDirectionForward
-//                       ? sibling->nextSibling()
-//                       : sibling->previousSibling())) {
-//     if (node->IsInV0ShadowTree())
-//       return V0ResolveDistributionStartingAt(*sibling, direction);
-//     return const_cast<Node*>(sibling);
-//   }
-//   return nullptr;
-// }
-
-Node* FlatTreeTraversalNg::V0ResolveDistributionStartingAt(
-    const Node& node,
-    TraversalDirection direction) {
-  DCHECK(!ToHTMLSlotElementIfSupportsAssignmentOrNull(node));
-  for (const Node* sibling = &node; sibling;
-       sibling = (direction == kTraversalDirectionForward
-                      ? sibling->nextSibling()
-                      : sibling->previousSibling())) {
-    if (!IsActiveV0InsertionPoint(*sibling))
-      return const_cast<Node*>(sibling);
-    const V0InsertionPoint& insertion_point = ToV0InsertionPoint(*sibling);
-    if (Node* found = (direction == kTraversalDirectionForward
-                           ? insertion_point.FirstDistributedNode()
-                           : insertion_point.LastDistributedNode()))
-      return found;
-    DCHECK(IsHTMLShadowElement(insertion_point) ||
-           (IsHTMLContentElement(insertion_point) &&
-            !insertion_point.HasChildren()));
-  }
-  return nullptr;
-}
-
-// TODO(hayato): This may return a wrong result for a node which is not in a
-// document flat tree.  See FlatTreeTraversalNgTest's redistribution test for
-// details.
-Node* FlatTreeTraversalNg::TraverseSiblings(const Node& node,
-                                            TraversalDirection direction) {
-  if (node.IsChildOfV1ShadowHost())
-    return TraverseSiblingsForV1HostChild(node, direction);
-
-  if (ShadowRootWhereNodeCanBeDistributedForV0(node))
-    return TraverseSiblingsForV0Distribution(node, direction);
-
-  Node* sibling = direction == kTraversalDirectionForward
-                      ? node.nextSibling()
-                      : node.previousSibling();
-
-  if (!node.IsInV0ShadowTree())
-    return sibling;
-
-  if (sibling) {
-    if (Node* found = V0ResolveDistributionStartingAt(*sibling, direction))
-      return found;
-  }
-
-  // // Slotted nodes are already handled in traverseSiblingsForV1HostChild()
-  // // above, here is for fallback contents.
-  // if (auto* slot = ToHTMLSlotElementOrNull(node.parentElement())) {
-  //   if (slot->SupportsAssignment() && slot->AssignedNodes().IsEmpty())
-  //     return TraverseSiblings(*slot, direction);
-  // }
-  return nullptr;
-}
-
-Node* FlatTreeTraversalNg::TraverseSiblingsForV1HostChild(
-    const Node& node,
-    TraversalDirection direction) {
-  HTMLSlotElement* slot = node.AssignedSlot();
-  if (!slot)
-    return nullptr;
-  return direction == kTraversalDirectionForward
-             ? slot->AssignedNodeNextTo(node)
-             : slot->AssignedNodePreviousTo(node);
-}
-
-Node* FlatTreeTraversalNg::TraverseSiblingsForV0Distribution(
-    const Node& node,
-    TraversalDirection direction) {
-  const V0InsertionPoint* final_destination = ResolveReprojection(&node);
-  if (!final_destination)
-    return nullptr;
-  if (Node* found = (direction == kTraversalDirectionForward
-                         ? final_destination->DistributedNodeNextTo(&node)
-                         : final_destination->DistributedNodePreviousTo(&node)))
-    return found;
-  return TraverseSiblings(*final_destination, direction);
-}
-
-ContainerNode* FlatTreeTraversalNg::TraverseParent(
-    const Node& node,
-    ParentTraversalDetails* details) {
-  // TODO(hayato): Stop this hack for a pseudo element because a pseudo element
-  // is not a child of its parentOrShadowHostNode() in a flat tree.
-  if (node.IsPseudoElement())
-    return node.ParentOrShadowHostNode();
-
-  if (node.IsChildOfV1ShadowHost())
-    return node.AssignedSlot();
-
-  if (auto* parent_slot =
-          ToHTMLSlotElementIfSupportsAssignmentOrNull(node.parentElement())) {
-    if (!parent_slot->AssignedNodes().IsEmpty())
-      return nullptr;
-    return parent_slot;
-  }
-
-  if (CanBeDistributedToV0InsertionPoint(node))
-    return TraverseParentForV0(node, details);
-
-  DCHECK(!ShadowRootWhereNodeCanBeDistributedForV0(node));
-  return TraverseParentOrHost(node);
-}
-
-ContainerNode* FlatTreeTraversalNg::TraverseParentForV0(
-    const Node& node,
-    ParentTraversalDetails* details) {
-  if (ShadowRootWhereNodeCanBeDistributedForV0(node)) {
-    if (const V0InsertionPoint* insertion_point = ResolveReprojection(&node)) {
-      if (details)
-        details->DidTraverseInsertionPoint(insertion_point);
-      // The node is distributed. But the distribution was stopped at this
-      // insertion point.
-      if (ShadowRootWhereNodeCanBeDistributedForV0(*insertion_point))
-        return nullptr;
-      return TraverseParent(*insertion_point);
-    }
-    return nullptr;
-  }
-  ContainerNode* parent = TraverseParentOrHost(node);
-  if (IsActiveV0InsertionPoint(*parent))
-    return nullptr;
-  return parent;
-}
-
-ContainerNode* FlatTreeTraversalNg::TraverseParentOrHost(const Node& node) {
-  ContainerNode* parent = node.parentNode();
-  if (!parent)
-    return nullptr;
-  if (!parent->IsShadowRoot())
-    return parent;
-  ShadowRoot* shadow_root = ToShadowRoot(parent);
-  return &shadow_root->host();
-}
-
-Node* FlatTreeTraversalNg::ChildAt(const Node& node, unsigned index) {
-  AssertPrecondition(node);
-  Node* child = TraverseFirstChild(node);
-  while (child && index--)
-    child = NextSibling(*child);
-  AssertPostcondition(child);
-  return child;
-}
-
-Node* FlatTreeTraversalNg::NextSkippingChildren(const Node& node) {
-  if (Node* next_sibling = TraverseNextSibling(node))
-    return next_sibling;
-  return TraverseNextAncestorSibling(node);
-}
-
-bool FlatTreeTraversalNg::ContainsIncludingPseudoElement(
-    const ContainerNode& container,
-    const Node& node) {
-  AssertPrecondition(container);
-  AssertPrecondition(node);
-  // This can be slower than FlatTreeTraversalNg::contains() because we
-  // can't early exit even when container doesn't have children.
-  for (const Node* current = &node; current;
-       current = TraverseParent(*current)) {
-    if (current == &container)
-      return true;
-  }
-  return false;
-}
-
-Node* FlatTreeTraversalNg::PreviousSkippingChildren(const Node& node) {
-  if (Node* previous_sibling = TraversePreviousSibling(node))
-    return previous_sibling;
-  return TraversePreviousAncestorSibling(node);
-}
-
-Node* FlatTreeTraversalNg::PreviousAncestorSiblingPostOrder(
-    const Node& current,
-    const Node* stay_within) {
-  DCHECK(!FlatTreeTraversalNg::PreviousSibling(current));
-  for (Node* parent = FlatTreeTraversalNg::Parent(current); parent;
-       parent = FlatTreeTraversalNg::Parent(*parent)) {
-    if (parent == stay_within)
-      return nullptr;
-    if (Node* previous_sibling = FlatTreeTraversalNg::PreviousSibling(*parent))
-      return previous_sibling;
-  }
-  return nullptr;
-}
-
-// TODO(yosin) We should consider introducing template class to share code
-// between DOM tree traversal and flat tree tarversal.
-Node* FlatTreeTraversalNg::PreviousPostOrder(const Node& current,
-                                             const Node* stay_within) {
-  AssertPrecondition(current);
-  if (stay_within)
-    AssertPrecondition(*stay_within);
-  if (Node* last_child = TraverseLastChild(current)) {
-    AssertPostcondition(last_child);
-    return last_child;
-  }
-  if (current == stay_within)
-    return nullptr;
-  if (Node* previous_sibling = TraversePreviousSibling(current)) {
-    AssertPostcondition(previous_sibling);
-    return previous_sibling;
-  }
-  return PreviousAncestorSiblingPostOrder(current, stay_within);
-}
-
-bool FlatTreeTraversalNg::IsDescendantOf(const Node& node, const Node& other) {
-  AssertPrecondition(node);
-  AssertPrecondition(other);
-  if (!HasChildren(other) || node.isConnected() != other.isConnected())
-    return false;
-  for (const ContainerNode* n = TraverseParent(node); n;
-       n = TraverseParent(*n)) {
-    if (n == other)
-      return true;
-  }
-  return false;
-}
-
-Node* FlatTreeTraversalNg::CommonAncestor(const Node& node_a,
-                                          const Node& node_b) {
-  AssertPrecondition(node_a);
-  AssertPrecondition(node_b);
-  Node* result = node_a.CommonAncestor(node_b, [](const Node& node) {
-    return FlatTreeTraversalNg::Parent(node);
-  });
-  AssertPostcondition(result);
-  return result;
-}
-
-Node* FlatTreeTraversalNg::TraverseNextAncestorSibling(const Node& node) {
-  DCHECK(!TraverseNextSibling(node));
-  for (Node* parent = TraverseParent(node); parent;
-       parent = TraverseParent(*parent)) {
-    if (Node* next_sibling = TraverseNextSibling(*parent))
-      return next_sibling;
-  }
-  return nullptr;
-}
-
-Node* FlatTreeTraversalNg::TraversePreviousAncestorSibling(const Node& node) {
-  DCHECK(!TraversePreviousSibling(node));
-  for (Node* parent = TraverseParent(node); parent;
-       parent = TraverseParent(*parent)) {
-    if (Node* previous_sibling = TraversePreviousSibling(*parent))
-      return previous_sibling;
-  }
-  return nullptr;
-}
-
-unsigned FlatTreeTraversalNg::Index(const Node& node) {
-  AssertPrecondition(node);
-  unsigned count = 0;
-  for (Node* runner = TraversePreviousSibling(node); runner;
-       runner = PreviousSibling(*runner))
-    ++count;
-  return count;
-}
-
-unsigned FlatTreeTraversalNg::CountChildren(const Node& node) {
-  AssertPrecondition(node);
-  unsigned count = 0;
-  for (Node* runner = TraverseFirstChild(node); runner;
-       runner = TraverseNextSibling(*runner))
-    ++count;
-  return count;
-}
-
-Node* FlatTreeTraversalNg::LastWithin(const Node& node) {
-  AssertPrecondition(node);
-  Node* descendant = TraverseLastChild(node);
-  for (Node* child = descendant; child; child = LastChild(*child))
-    descendant = child;
-  AssertPostcondition(descendant);
-  return descendant;
-}
-
-Node& FlatTreeTraversalNg::LastWithinOrSelf(const Node& node) {
-  AssertPrecondition(node);
-  Node* last_descendant = LastWithin(node);
-  Node& result = last_descendant ? *last_descendant : const_cast<Node&>(node);
-  AssertPostcondition(&result);
-  return result;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h b/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h
deleted file mode 100644
index bb93d2a7..0000000
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2012 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- *     * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *     * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_FLAT_TREE_TRAVERSAL_NG_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_FLAT_TREE_TRAVERSAL_NG_H_
-
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
-#include "third_party/blink/renderer/core/dom/node_traversal.h"
-#include "third_party/blink/renderer/core/dom/shadow_root.h"
-#include "third_party/blink/renderer/core/dom/v0_insertion_point.h"
-#include "third_party/blink/renderer/platform/wtf/allocator.h"
-
-namespace blink {
-
-class ContainerNode;
-class Node;
-
-bool CanBeDistributedToV0InsertionPoint(const Node& node);
-
-// Flat tree version of |NodeTraversal|.
-//
-// None of member functions takes a |ShadowRoot| or an active insertion point,
-// e.g. roughly speaking <content> and <shadow> in the shadow tree, see
-// |InsertionPoint::isActive()| for details of active insertion points, since
-// they aren't appeared in the flat tree. |assertPrecondition()| and
-// |assertPostCondition()| check this condition.
-//
-// FIXME: Make some functions inline to optimise the performance.
-// https://bugs.webkit.org/show_bug.cgi?id=82702
-class CORE_EXPORT FlatTreeTraversalNg {
-  STATIC_ONLY(FlatTreeTraversalNg);
-
- public:
-  typedef LayoutTreeBuilderTraversal::ParentDetails ParentTraversalDetails;
-  using TraversalNodeType = Node;
-
-  static Node* Next(const Node&);
-  static Node* Next(const Node&, const Node* stay_within);
-  static Node* Previous(const Node&);
-
-  static Node* FirstChild(const Node&);
-  static Node* LastChild(const Node&);
-  static bool HasChildren(const Node&);
-
-  static ContainerNode* Parent(const Node&, ParentTraversalDetails* = nullptr);
-  static Element* ParentElement(const Node&);
-
-  static Node* NextSibling(const Node&);
-  static Node* PreviousSibling(const Node&);
-
-  // Returns a child node at |index|. If |index| is greater than or equal to
-  // the children, this function returns |nullptr|.
-  static Node* ChildAt(const Node&, unsigned index);
-
-  // Flat tree version of |NodeTraversal::nextSkippingChildren()|. This
-  // function is similar to |next()| but skips child nodes of a specified
-  // node.
-  static Node* NextSkippingChildren(const Node&);
-  static Node* NextSkippingChildren(const Node&, const Node* stay_within);
-
-  static Node* FirstWithin(const Node& current) { return FirstChild(current); }
-
-  // Flat tree version of |NodeTraversal::previousSkippingChildren()|
-  // similar to |previous()| but skipping child nodes of the specified node.
-  static Node* PreviousSkippingChildren(const Node&);
-
-  // Like previous, but visits parents before their children.
-  static Node* PreviousPostOrder(const Node&,
-                                 const Node* stay_within = nullptr);
-
-  // Flat tree version of |Node::isDescendantOf(other)|. This function
-  // returns true if |other| contains |node|, otherwise returns
-  // false. If |other| is |node|, this function returns false.
-  static bool IsDescendantOf(const Node& /*node*/, const Node& other);
-
-  static bool Contains(const ContainerNode& container, const Node& node) {
-    AssertPrecondition(container);
-    AssertPrecondition(node);
-    return container == node || IsDescendantOf(node, container);
-  }
-
-  static bool ContainsIncludingPseudoElement(const ContainerNode&, const Node&);
-
-  // Returns a common ancestor of |nodeA| and |nodeB| if exists, otherwise
-  // returns |nullptr|.
-  static Node* CommonAncestor(const Node& node_a, const Node& node_b);
-
-  // Flat tree version of |Node::nodeIndex()|. This function returns a
-  // zero base position number of the specified node in child nodes list, or
-  // zero if the specified node has no parent.
-  static unsigned Index(const Node&);
-
-  // Flat tree version of |ContainerNode::countChildren()|. This function
-  // returns the number of the child nodes of the specified node in the
-  // flat tree.
-  static unsigned CountChildren(const Node&);
-
-  static Node* LastWithin(const Node&);
-  static Node& LastWithinOrSelf(const Node&);
-
-  // Flat tree range helper functions for range based for statement.
-  // TODO(dom-team): We should have following functions to match with
-  // |NodeTraversal|:
-  //   - AncestorsOf()
-  //   - DescendantsOf()
-  //   - InclusiveAncestorsOf()
-  //   - InclusiveDescendantsOf()
-  //   - StartsAt()
-  //   - StartsAfter()
-  static TraversalRange<TraversalChildrenIterator<FlatTreeTraversalNg>>
-  ChildrenOf(const Node&);
-
- private:
-  enum TraversalDirection {
-    kTraversalDirectionForward,
-    kTraversalDirectionBackward
-  };
-
-  static void AssertPrecondition(const Node& node) {
-    DCHECK(!node.NeedsDistributionRecalc());
-    DCHECK(node.CanParticipateInFlatTree());
-  }
-
-  static void AssertPostcondition(const Node* node) {
-#if DCHECK_IS_ON()
-    if (node)
-      AssertPrecondition(*node);
-#endif
-  }
-
-  // static Node* ResolveDistributionStartingAt(const Node*,
-  // TraversalDirection);
-  static Node* V0ResolveDistributionStartingAt(const Node&, TraversalDirection);
-
-  static Node* TraverseNext(const Node&);
-  static Node* TraverseNext(const Node&, const Node* stay_within);
-  static Node* TraverseNextSkippingChildren(const Node&,
-                                            const Node* stay_within);
-  static Node* TraversePrevious(const Node&);
-
-  static Node* TraverseFirstChild(const Node&);
-  static Node* TraverseLastChild(const Node&);
-  static Node* TraverseChild(const Node&, TraversalDirection);
-
-  static ContainerNode* TraverseParent(const Node&,
-                                       ParentTraversalDetails* = nullptr);
-  // TODO(hayato): Make ParentTraversalDetails be aware of slot elements too.
-  static ContainerNode* TraverseParentForV0(const Node&,
-                                            ParentTraversalDetails* = nullptr);
-  static ContainerNode* TraverseParentOrHost(const Node&);
-
-  static Node* TraverseNextSibling(const Node&);
-  static Node* TraversePreviousSibling(const Node&);
-
-  static Node* TraverseSiblings(const Node&, TraversalDirection);
-  static Node* TraverseSiblingsForV1HostChild(const Node&, TraversalDirection);
-  static Node* TraverseSiblingsForV0Distribution(const Node&,
-                                                 TraversalDirection);
-
-  static Node* TraverseNextAncestorSibling(const Node&);
-  static Node* TraversePreviousAncestorSibling(const Node&);
-  static Node* PreviousAncestorSiblingPostOrder(const Node& current,
-                                                const Node* stay_within);
-};
-
-inline ContainerNode* FlatTreeTraversalNg::Parent(
-    const Node& node,
-    ParentTraversalDetails* details) {
-  AssertPrecondition(node);
-  ContainerNode* result = TraverseParent(node, details);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Element* FlatTreeTraversalNg::ParentElement(const Node& node) {
-  ContainerNode* parent = FlatTreeTraversalNg::Parent(node);
-  return parent && parent->IsElementNode() ? ToElement(parent) : nullptr;
-}
-
-inline Node* FlatTreeTraversalNg::NextSibling(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraverseSiblings(node, kTraversalDirectionForward);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::PreviousSibling(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraverseSiblings(node, kTraversalDirectionBackward);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::Next(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraverseNext(node);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::Next(const Node& node,
-                                       const Node* stay_within) {
-  AssertPrecondition(node);
-  Node* result = TraverseNext(node, stay_within);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::NextSkippingChildren(
-    const Node& node,
-    const Node* stay_within) {
-  AssertPrecondition(node);
-  Node* result = TraverseNextSkippingChildren(node, stay_within);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::TraverseNext(const Node& node) {
-  if (Node* next = TraverseFirstChild(node))
-    return next;
-  for (const Node* next = &node; next; next = TraverseParent(*next)) {
-    if (Node* sibling = TraverseNextSibling(*next))
-      return sibling;
-  }
-  return nullptr;
-}
-
-inline Node* FlatTreeTraversalNg::TraverseNext(const Node& node,
-                                               const Node* stay_within) {
-  if (Node* next = TraverseFirstChild(node))
-    return next;
-  return TraverseNextSkippingChildren(node, stay_within);
-}
-
-inline Node* FlatTreeTraversalNg::TraverseNextSkippingChildren(
-    const Node& node,
-    const Node* stay_within) {
-  for (const Node* next = &node; next; next = TraverseParent(*next)) {
-    if (next == stay_within)
-      return nullptr;
-    if (Node* sibling = TraverseNextSibling(*next))
-      return sibling;
-  }
-  return nullptr;
-}
-
-inline Node* FlatTreeTraversalNg::Previous(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraversePrevious(node);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::TraversePrevious(const Node& node) {
-  if (Node* previous = TraversePreviousSibling(node)) {
-    while (Node* child = TraverseLastChild(*previous))
-      previous = child;
-    return previous;
-  }
-  return TraverseParent(node);
-}
-
-inline Node* FlatTreeTraversalNg::FirstChild(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraverseChild(node, kTraversalDirectionForward);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline Node* FlatTreeTraversalNg::LastChild(const Node& node) {
-  AssertPrecondition(node);
-  Node* result = TraverseLastChild(node);
-  AssertPostcondition(result);
-  return result;
-}
-
-inline bool FlatTreeTraversalNg::HasChildren(const Node& node) {
-  return FirstChild(node);
-}
-
-inline Node* FlatTreeTraversalNg::TraverseNextSibling(const Node& node) {
-  return TraverseSiblings(node, kTraversalDirectionForward);
-}
-
-inline Node* FlatTreeTraversalNg::TraversePreviousSibling(const Node& node) {
-  return TraverseSiblings(node, kTraversalDirectionBackward);
-}
-
-inline Node* FlatTreeTraversalNg::TraverseFirstChild(const Node& node) {
-  return TraverseChild(node, kTraversalDirectionForward);
-}
-
-inline Node* FlatTreeTraversalNg::TraverseLastChild(const Node& node) {
-  return TraverseChild(node, kTraversalDirectionBackward);
-}
-
-// TraverseRange<T> implementations
-inline TraversalRange<TraversalChildrenIterator<FlatTreeTraversalNg>>
-FlatTreeTraversalNg::ChildrenOf(const Node& parent) {
-  return TraversalRange<TraversalChildrenIterator<FlatTreeTraversalNg>>(
-      &parent);
-}
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_FLAT_TREE_TRAVERSAL_NG_H_
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng_test.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal_ng_test.cc
deleted file mode 100644
index 7f909cc..0000000
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal_ng_test.cc
+++ /dev/null
@@ -1,772 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/core/dom/flat_tree_traversal_ng.h"
-
-#include <memory>
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/dom/element.h"
-#include "third_party/blink/renderer/core/dom/node.h"
-#include "third_party/blink/renderer/core/dom/node_traversal.h"
-#include "third_party/blink/renderer/core/dom/shadow_root.h"
-#include "third_party/blink/renderer/core/frame/local_frame_view.h"
-#include "third_party/blink/renderer/core/html/html_element.h"
-#include "third_party/blink/renderer/core/testing/page_test_base.h"
-#include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/geometry/int_size.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
-#include "third_party/blink/renderer/platform/wtf/compiler.h"
-#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
-#include "third_party/blink/renderer/platform/wtf/vector.h"
-
-namespace blink {
-// To avoid symbol collisions in jumbo builds.
-namespace flat_tree_traversal_ng_test {
-
-class FlatTreeTraversalNgTest : public PageTestBase,
-                                private ScopedSlotInFlatTreeForTest,
-                                ScopedIncrementalShadowDOMForTest {
- public:
-  FlatTreeTraversalNgTest()
-      : ScopedSlotInFlatTreeForTest(true),
-        ScopedIncrementalShadowDOMForTest(true) {}
-
- protected:
-  // Sets |mainHTML| to BODY element with |innerHTML| property and attaches
-  // shadow root to child with |shadowHTML|, then update distribution for
-  // calling member functions in |FlatTreeTraversalNg|.
-  void SetupSampleHTML(const char* main_html,
-                       const char* shadow_html,
-                       unsigned);
-
-  void SetupDocumentTree(const char* main_html);
-
-  void AttachV0ShadowRoot(Element& shadow_host, const char* shadow_inner_html);
-  void AttachOpenShadowRoot(Element& shadow_host,
-                            const char* shadow_inner_html);
-};
-
-void FlatTreeTraversalNgTest::SetupSampleHTML(const char* main_html,
-                                              const char* shadow_html,
-                                              unsigned index) {
-  Element* body = GetDocument().body();
-  body->SetInnerHTMLFromString(String::FromUTF8(main_html));
-  Element* shadow_host = ToElement(NodeTraversal::ChildAt(*body, index));
-  ShadowRoot& shadow_root = shadow_host->CreateV0ShadowRootForTesting();
-  shadow_root.SetInnerHTMLFromString(String::FromUTF8(shadow_html));
-  body->UpdateDistributionForFlatTreeTraversal();
-}
-
-void FlatTreeTraversalNgTest::SetupDocumentTree(const char* main_html) {
-  Element* body = GetDocument().body();
-  body->SetInnerHTMLFromString(String::FromUTF8(main_html));
-}
-
-void FlatTreeTraversalNgTest::AttachV0ShadowRoot(
-    Element& shadow_host,
-    const char* shadow_inner_html) {
-  ShadowRoot& shadow_root = shadow_host.CreateV0ShadowRootForTesting();
-  shadow_root.SetInnerHTMLFromString(String::FromUTF8(shadow_inner_html));
-  GetDocument().body()->UpdateDistributionForFlatTreeTraversal();
-}
-
-void FlatTreeTraversalNgTest::AttachOpenShadowRoot(
-    Element& shadow_host,
-    const char* shadow_inner_html) {
-  ShadowRoot& shadow_root =
-      shadow_host.AttachShadowRootInternal(ShadowRootType::kOpen);
-  shadow_root.SetInnerHTMLFromString(String::FromUTF8(shadow_inner_html));
-  GetDocument().body()->UpdateDistributionForFlatTreeTraversal();
-}
-
-namespace {
-
-void TestCommonAncestor(Node* expected_result,
-                        const Node& node_a,
-                        const Node& node_b) {
-  Node* result1 = FlatTreeTraversalNg::CommonAncestor(node_a, node_b);
-  EXPECT_EQ(expected_result, result1)
-      << "commonAncestor(" << node_a.textContent() << ","
-      << node_b.textContent() << ")";
-  Node* result2 = FlatTreeTraversalNg::CommonAncestor(node_b, node_a);
-  EXPECT_EQ(expected_result, result2)
-      << "commonAncestor(" << node_b.textContent() << ","
-      << node_a.textContent() << ")";
-}
-
-}  // namespace
-
-// Test case for
-//  - childAt
-//  - countChildren
-//  - hasChildren
-//  - index
-//  - isDescendantOf
-TEST_F(FlatTreeTraversalNgTest, childAt) {
-  const char* main_html =
-      "<div id='m0'>"
-      "<span id='m00'>m00</span>"
-      "<span id='m01'>m01</span>"
-      "</div>";
-  const char* shadow_html =
-      "<a id='s00'>s00</a>"
-      "<content select='#m01'></content>"
-      "<a id='s02'>s02</a>"
-      "<a id='s03'><content select='#m00'></content></a>"
-      "<a id='s04'>s04</a>";
-  SetupSampleHTML(main_html, shadow_html, 0);
-
-  Element* body = GetDocument().body();
-  Element* m0 = body->QuerySelector("#m0");
-  Element* m00 = m0->QuerySelector("#m00");
-  Element* m01 = m0->QuerySelector("#m01");
-
-  Element* shadow_host = m0;
-  ShadowRoot* shadow_root = shadow_host->OpenShadowRoot();
-  Element* s00 = shadow_root->QuerySelector("#s00");
-  Element* s02 = shadow_root->QuerySelector("#s02");
-  Element* s03 = shadow_root->QuerySelector("#s03");
-  Element* s04 = shadow_root->QuerySelector("#s04");
-
-  const unsigned kNumberOfChildNodes = 5;
-  Node* expected_child_nodes[5] = {s00, m01, s02, s03, s04};
-
-  ASSERT_EQ(kNumberOfChildNodes,
-            FlatTreeTraversalNg::CountChildren(*shadow_host));
-  EXPECT_TRUE(FlatTreeTraversalNg::HasChildren(*shadow_host));
-
-  for (unsigned index = 0; index < kNumberOfChildNodes; ++index) {
-    Node* child = FlatTreeTraversalNg::ChildAt(*shadow_host, index);
-    EXPECT_EQ(expected_child_nodes[index], child)
-        << "FlatTreeTraversalNg::childAt(*shadowHost, " << index << ")";
-    EXPECT_EQ(index, FlatTreeTraversalNg::Index(*child))
-        << "FlatTreeTraversalNg::index(FlatTreeTraversalNg(*shadowHost, "
-        << index << "))";
-    EXPECT_TRUE(FlatTreeTraversalNg::IsDescendantOf(*child, *shadow_host))
-        << "FlatTreeTraversalNg::isDescendantOf(*FlatTreeTraversalNg(*"
-           "shadowHost, "
-        << index << "), *shadowHost)";
-  }
-  EXPECT_EQ(nullptr,
-            FlatTreeTraversalNg::ChildAt(*shadow_host, kNumberOfChildNodes + 1))
-      << "Out of bounds childAt() returns nullptr.";
-
-  // Distribute node |m00| is child of node in shadow tree |s03|.
-  EXPECT_EQ(m00, FlatTreeTraversalNg::ChildAt(*s03, 0));
-}
-
-TEST_F(FlatTreeTraversalNgTest, ChildrenOf) {
-  SetupSampleHTML(
-      "<p id=sample>ZERO<span slot=three>three</b><span "
-      "slot=one>one</b>FOUR</p>",
-      "zero<slot name=one></slot>two<slot name=three></slot>four", 0);
-  Element* const sample = GetDocument().getElementById("sample");
-
-  HeapVector<Member<Node>> expected_nodes;
-  for (Node* runner = FlatTreeTraversalNg::FirstChild(*sample); runner;
-       runner = FlatTreeTraversalNg::NextSibling(*runner)) {
-    expected_nodes.push_back(runner);
-  }
-
-  HeapVector<Member<Node>> actual_nodes;
-  for (Node& child : FlatTreeTraversalNg::ChildrenOf(*sample))
-    actual_nodes.push_back(&child);
-
-  EXPECT_EQ(expected_nodes, actual_nodes);
-}
-
-// Test case for
-//  - commonAncestor
-//  - isDescendantOf
-TEST_F(FlatTreeTraversalNgTest, commonAncestor) {
-  // We build following flat tree:
-  //             ____BODY___
-  //             |    |     |
-  //            m0    m1    m2       m1 is shadow host having m10, m11, m12.
-  //            _|_   |   __|__
-  //           |   |  |   |    |
-  //          m00 m01 |   m20 m21
-  //             _____|_____________
-  //             |  |   |    |     |
-  //            s10 s11 s12 s13  s14
-  //                         |
-  //                       __|__
-  //                |      |    |
-  //                m12    m10 m11 <-- distributed
-  // where: each symbol consists with prefix, child index, child-child index.
-  //  prefix "m" means node in main tree,
-  //  prefix "d" means node in main tree and distributed
-  //  prefix "s" means node in shadow tree
-  const char* main_html =
-      "<a id='m0'><b id='m00'>m00</b><b id='m01'>m01</b></a>"
-      "<a id='m1'>"
-      "<b id='m10'>m10</b>"
-      "<b id='m11'>m11</b>"
-      "<b id='m12'>m12</b>"
-      "</a>"
-      "<a id='m2'><b id='m20'>m20</b><b id='m21'>m21</b></a>";
-  const char* shadow_html =
-      "<a id='s10'>s10</a>"
-      "<a id='s11'><content select='#m12'></content></a>"
-      "<a id='s12'>s12</a>"
-      "<a id='s13'>"
-      "<content select='#m10'></content>"
-      "<content select='#m11'></content>"
-      "</a>"
-      "<a id='s14'>s14</a>";
-  SetupSampleHTML(main_html, shadow_html, 1);
-  Element* body = GetDocument().body();
-  Element* m0 = body->QuerySelector("#m0");
-  Element* m1 = body->QuerySelector("#m1");
-  Element* m2 = body->QuerySelector("#m2");
-
-  Element* m00 = body->QuerySelector("#m00");
-  Element* m01 = body->QuerySelector("#m01");
-  Element* m10 = body->QuerySelector("#m10");
-  Element* m11 = body->QuerySelector("#m11");
-  Element* m12 = body->QuerySelector("#m12");
-  Element* m20 = body->QuerySelector("#m20");
-  Element* m21 = body->QuerySelector("#m21");
-
-  ShadowRoot* shadow_root = m1->OpenShadowRoot();
-  Element* s10 = shadow_root->QuerySelector("#s10");
-  Element* s11 = shadow_root->QuerySelector("#s11");
-  Element* s12 = shadow_root->QuerySelector("#s12");
-  Element* s13 = shadow_root->QuerySelector("#s13");
-  Element* s14 = shadow_root->QuerySelector("#s14");
-
-  TestCommonAncestor(body, *m0, *m1);
-  TestCommonAncestor(body, *m1, *m2);
-  TestCommonAncestor(body, *m1, *m20);
-  TestCommonAncestor(body, *s14, *m21);
-
-  TestCommonAncestor(m0, *m0, *m0);
-  TestCommonAncestor(m0, *m00, *m01);
-
-  TestCommonAncestor(m1, *m1, *m1);
-  TestCommonAncestor(m1, *s10, *s14);
-  TestCommonAncestor(m1, *s10, *m12);
-  TestCommonAncestor(m1, *s12, *m12);
-  TestCommonAncestor(m1, *m10, *m12);
-
-  TestCommonAncestor(m01, *m01, *m01);
-  TestCommonAncestor(s11, *s11, *m12);
-  TestCommonAncestor(s13, *m10, *m11);
-
-  s12->remove(ASSERT_NO_EXCEPTION);
-  TestCommonAncestor(s12, *s12, *s12);
-  TestCommonAncestor(nullptr, *s12, *s11);
-  TestCommonAncestor(nullptr, *s12, *m01);
-  TestCommonAncestor(nullptr, *s12, *m20);
-
-  m20->remove(ASSERT_NO_EXCEPTION);
-  TestCommonAncestor(m20, *m20, *m20);
-  TestCommonAncestor(nullptr, *m20, *s12);
-  TestCommonAncestor(nullptr, *m20, *m1);
-}
-
-// Test case for
-//  - nextSkippingChildren
-//  - previousSkippingChildren
-TEST_F(FlatTreeTraversalNgTest, nextSkippingChildren) {
-  const char* main_html =
-      "<div id='m0'>m0</div>"
-      "<div id='m1'>"
-      "<span id='m10'>m10</span>"
-      "<span id='m11'>m11</span>"
-      "</div>"
-      "<div id='m2'>m2</div>";
-  const char* shadow_html =
-      "<content select='#m11'></content>"
-      "<a id='s11'>s11</a>"
-      "<a id='s12'>"
-      "<b id='s120'>s120</b>"
-      "<content select='#m10'></content>"
-      "</a>";
-  SetupSampleHTML(main_html, shadow_html, 1);
-
-  Element* body = GetDocument().body();
-  Element* m0 = body->QuerySelector("#m0");
-  Element* m1 = body->QuerySelector("#m1");
-  Element* m2 = body->QuerySelector("#m2");
-
-  Element* m10 = body->QuerySelector("#m10");
-  Element* m11 = body->QuerySelector("#m11");
-
-  ShadowRoot* shadow_root = m1->OpenShadowRoot();
-  Element* s11 = shadow_root->QuerySelector("#s11");
-  Element* s12 = shadow_root->QuerySelector("#s12");
-  Element* s120 = shadow_root->QuerySelector("#s120");
-
-  // Main tree node to main tree node
-  EXPECT_EQ(*m1, FlatTreeTraversalNg::NextSkippingChildren(*m0));
-  EXPECT_EQ(*m0, FlatTreeTraversalNg::PreviousSkippingChildren(*m1));
-
-  // Distribute node to main tree node
-  EXPECT_EQ(*m2, FlatTreeTraversalNg::NextSkippingChildren(*m10));
-  EXPECT_EQ(*m1, FlatTreeTraversalNg::PreviousSkippingChildren(*m2));
-
-  // Distribute node to node in shadow tree
-  EXPECT_EQ(*s11, FlatTreeTraversalNg::NextSkippingChildren(*m11));
-  EXPECT_EQ(*m11, FlatTreeTraversalNg::PreviousSkippingChildren(*s11));
-
-  // Node in shadow tree to distributed node
-  EXPECT_EQ(*s11, FlatTreeTraversalNg::NextSkippingChildren(*m11));
-  EXPECT_EQ(*m11, FlatTreeTraversalNg::PreviousSkippingChildren(*s11));
-
-  EXPECT_EQ(*m10, FlatTreeTraversalNg::NextSkippingChildren(*s120));
-  EXPECT_EQ(*s120, FlatTreeTraversalNg::PreviousSkippingChildren(*m10));
-
-  // Node in shadow tree to main tree
-  EXPECT_EQ(*m2, FlatTreeTraversalNg::NextSkippingChildren(*s12));
-  EXPECT_EQ(*m1, FlatTreeTraversalNg::PreviousSkippingChildren(*m2));
-}
-
-// Test case for
-//  - lastWithin
-//  - lastWithinOrSelf
-TEST_F(FlatTreeTraversalNgTest, lastWithin) {
-  const char* main_html =
-      "<div id='m0'>m0</div>"
-      "<div id='m1'>"
-      "<span id='m10'>m10</span>"
-      "<span id='m11'>m11</span>"
-      "<span id='m12'>m12</span>"  // #m12 is not distributed.
-      "</div>"
-      "<div id='m2'></div>";
-  const char* shadow_html =
-      "<content select='#m11'></content>"
-      "<a id='s11'>s11</a>"
-      "<a id='s12'>"
-      "<content select='#m10'></content>"
-      "</a>";
-  SetupSampleHTML(main_html, shadow_html, 1);
-
-  Element* body = GetDocument().body();
-  Element* m0 = body->QuerySelector("#m0");
-  Element* m1 = body->QuerySelector("#m1");
-  Element* m2 = body->QuerySelector("#m2");
-
-  Element* m10 = body->QuerySelector("#m10");
-
-  ShadowRoot* shadow_root = m1->OpenShadowRoot();
-  Element* s11 = shadow_root->QuerySelector("#s11");
-  Element* s12 = shadow_root->QuerySelector("#s12");
-
-  EXPECT_EQ(m0->firstChild(), FlatTreeTraversalNg::LastWithin(*m0));
-  EXPECT_EQ(*m0->firstChild(), FlatTreeTraversalNg::LastWithinOrSelf(*m0));
-
-  EXPECT_EQ(m10->firstChild(), FlatTreeTraversalNg::LastWithin(*m1));
-  EXPECT_EQ(*m10->firstChild(), FlatTreeTraversalNg::LastWithinOrSelf(*m1));
-
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::LastWithin(*m2));
-  EXPECT_EQ(*m2, FlatTreeTraversalNg::LastWithinOrSelf(*m2));
-
-  EXPECT_EQ(s11->firstChild(), FlatTreeTraversalNg::LastWithin(*s11));
-  EXPECT_EQ(*s11->firstChild(), FlatTreeTraversalNg::LastWithinOrSelf(*s11));
-
-  EXPECT_EQ(m10->firstChild(), FlatTreeTraversalNg::LastWithin(*s12));
-  EXPECT_EQ(*m10->firstChild(), FlatTreeTraversalNg::LastWithinOrSelf(*s12));
-}
-
-TEST_F(FlatTreeTraversalNgTest, previousPostOrder) {
-  const char* main_html =
-      "<div id='m0'>m0</div>"
-      "<div id='m1'>"
-      "<span id='m10'>m10</span>"
-      "<span id='m11'>m11</span>"
-      "</div>"
-      "<div id='m2'>m2</div>";
-  const char* shadow_html =
-      "<content select='#m11'></content>"
-      "<a id='s11'>s11</a>"
-      "<a id='s12'>"
-      "<b id='s120'>s120</b>"
-      "<content select='#m10'></content>"
-      "</a>";
-  SetupSampleHTML(main_html, shadow_html, 1);
-
-  Element* body = GetDocument().body();
-  Element* m0 = body->QuerySelector("#m0");
-  Element* m1 = body->QuerySelector("#m1");
-  Element* m2 = body->QuerySelector("#m2");
-
-  Element* m10 = body->QuerySelector("#m10");
-  Element* m11 = body->QuerySelector("#m11");
-
-  ShadowRoot* shadow_root = m1->OpenShadowRoot();
-  Element* s11 = shadow_root->QuerySelector("#s11");
-  Element* s12 = shadow_root->QuerySelector("#s12");
-  Element* s120 = shadow_root->QuerySelector("#s120");
-
-  EXPECT_EQ(*m0->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*m0));
-  EXPECT_EQ(*s12, FlatTreeTraversalNg::PreviousPostOrder(*m1));
-  EXPECT_EQ(*m10->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*m10));
-  EXPECT_EQ(*s120, FlatTreeTraversalNg::PreviousPostOrder(*m10->firstChild()));
-  EXPECT_EQ(*s120,
-            FlatTreeTraversalNg::PreviousPostOrder(*m10->firstChild(), s12));
-  EXPECT_EQ(*m11->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*m11));
-  EXPECT_EQ(*m0, FlatTreeTraversalNg::PreviousPostOrder(*m11->firstChild()));
-  EXPECT_EQ(nullptr,
-            FlatTreeTraversalNg::PreviousPostOrder(*m11->firstChild(), m11));
-  EXPECT_EQ(*m2->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*m2));
-
-  EXPECT_EQ(*s11->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*s11));
-  EXPECT_EQ(*m10, FlatTreeTraversalNg::PreviousPostOrder(*s12));
-  EXPECT_EQ(*s120->firstChild(), FlatTreeTraversalNg::PreviousPostOrder(*s120));
-  EXPECT_EQ(*s11, FlatTreeTraversalNg::PreviousPostOrder(*s120->firstChild()));
-  EXPECT_EQ(nullptr,
-            FlatTreeTraversalNg::PreviousPostOrder(*s120->firstChild(), s12));
-}
-
-TEST_F(FlatTreeTraversalNgTest, nextSiblingNotInDocumentFlatTree) {
-  const char* main_html =
-      "<div id='m0'>m0</div>"
-      "<div id='m1'>"
-      "<span id='m10'>m10</span>"
-      "<span id='m11'>m11</span>"
-      "</div>"
-      "<div id='m2'>m2</div>";
-  const char* shadow_html = "<content select='#m11'></content>";
-  SetupSampleHTML(main_html, shadow_html, 1);
-
-  Element* body = GetDocument().body();
-  Element* m10 = body->QuerySelector("#m10");
-
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*m10));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*m10));
-}
-
-TEST_F(FlatTreeTraversalNgTest, redistribution) {
-  const char* main_html =
-      "<div id='m0'>m0</div>"
-      "<div id='m1'>"
-      "<span id='m10'>m10</span>"
-      "<span id='m11'>m11</span>"
-      "</div>"
-      "<div id='m2'>m2</div>";
-  const char* shadow_html1 =
-      "<div id='s1'>"
-      "<content></content>"
-      "</div>";
-
-  SetupSampleHTML(main_html, shadow_html1, 1);
-
-  const char* shadow_html2 =
-      "<div id='s2'>"
-      "<content select='#m10'></content>"
-      "<span id='s21'>s21</span>"
-      "</div>";
-
-  Element* body = GetDocument().body();
-  Element* m1 = body->QuerySelector("#m1");
-  Element* m10 = body->QuerySelector("#m10");
-
-  ShadowRoot* shadow_root1 = m1->OpenShadowRoot();
-  Element* s1 = shadow_root1->QuerySelector("#s1");
-
-  AttachV0ShadowRoot(*s1, shadow_html2);
-
-  ShadowRoot* shadow_root2 = s1->OpenShadowRoot();
-  Element* s21 = shadow_root2->QuerySelector("#s21");
-
-  EXPECT_EQ(s21, FlatTreeTraversalNg::NextSibling(*m10));
-  EXPECT_EQ(m10, FlatTreeTraversalNg::PreviousSibling(*s21));
-
-  // FlatTreeTraversalNg::traverseSiblings does not work for a node which is not
-  // in a document flat tree.
-  // e.g. The following test fails. The result of
-  // FlatTreeTraversalNg::previousSibling(*m11)) will be #m10, instead of
-  // nullptr. Element* m11 = body->querySelector("#m11"); EXPECT_EQ(nullptr,
-  // FlatTreeTraversalNg::previousSibling(*m11));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1Simple) {
-  const char* main_html =
-      "<div id='host'>"
-      "<div id='child1' slot='slot1'></div>"
-      "<div id='child2' slot='slot2'></div>"
-      "</div>";
-  const char* shadow_html =
-      "<div id='shadow-child1'></div>"
-      "<slot name='slot1'></slot>"
-      "<slot name='slot2'></slot>"
-      "<div id='shadow-child2'></div>";
-
-  SetupDocumentTree(main_html);
-  Element* body = GetDocument().body();
-  Element* host = body->QuerySelector("#host");
-  Element* child1 = body->QuerySelector("#child1");
-  Element* child2 = body->QuerySelector("#child2");
-
-  AttachOpenShadowRoot(*host, shadow_html);
-  ShadowRoot* shadow_root = host->OpenShadowRoot();
-  Element* slot1 = shadow_root->QuerySelector("[name=slot1]");
-  Element* slot2 = shadow_root->QuerySelector("[name=slot2]");
-  Element* shadow_child1 = shadow_root->QuerySelector("#shadow-child1");
-  Element* shadow_child2 = shadow_root->QuerySelector("#shadow-child2");
-
-  EXPECT_TRUE(slot1);
-  EXPECT_TRUE(slot2);
-  EXPECT_EQ(shadow_child1, FlatTreeTraversalNg::FirstChild(*host));
-  EXPECT_EQ(slot1, FlatTreeTraversalNg::NextSibling(*shadow_child1));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*child1));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*child2));
-  EXPECT_EQ(slot2, FlatTreeTraversalNg::NextSibling(*slot1));
-  EXPECT_EQ(shadow_child2, FlatTreeTraversalNg::NextSibling(*slot2));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1Redistribution) {
-  // composed tree:
-  // d1
-  // ├──/shadow-root
-  // │   └── d1-1
-  // │       ├──/shadow-root
-  // │       │   ├── d1-1-1
-  // │       │   ├── slot name=d1-1-s1
-  // │       │   ├── slot name=d1-1-s2
-  // │       │   └── d1-1-2
-  // │       ├── d1-2
-  // │       ├── slot id=d1-s0
-  // │       ├── slot name=d1-s1 slot=d1-1-s1
-  // │       ├── slot name=d1-s2
-  // │       ├── d1-3
-  // │       └── d1-4 slot=d1-1-s1
-  // ├── d2 slot=d1-s1
-  // ├── d3 slot=d1-s2
-  // ├── d4 slot=nonexistent
-  // └── d5
-
-  // flat tree:
-  // d1
-  // └── d1-1
-  //     ├── d1-1-1
-  //     ├── slot name=d1-1-s1
-  //     │   ├── slot name=d1-s1 slot=d1-1-s1
-  //     │   │   └── d2 slot=d1-s1
-  //     │   └── d1-4 slot=d1-1-s1
-  //     ├── slot name=d1-1-s2
-  //     └── d1-1-2
-  const char* main_html =
-      "<div id='d1'>"
-      "<div id='d2' slot='d1-s1'></div>"
-      "<div id='d3' slot='d1-s2'></div>"
-      "<div id='d4' slot='nonexistent'></div>"
-      "<div id='d5'></div>"
-      "</div>"
-      "<div id='d6'></div>";
-  const char* shadow_html1 =
-      "<div id='d1-1'>"
-      "<div id='d1-2'></div>"
-      "<slot id='d1-s0'></slot>"
-      "<slot name='d1-s1' slot='d1-1-s1'></slot>"
-      "<slot name='d1-s2'></slot>"
-      "<div id='d1-3'></div>"
-      "<div id='d1-4' slot='d1-1-s1'></div>"
-      "</div>";
-  const char* shadow_html2 =
-      "<div id='d1-1-1'></div>"
-      "<slot name='d1-1-s1'></slot>"
-      "<slot name='d1-1-s2'></slot>"
-      "<div id='d1-1-2'></div>";
-
-  SetupDocumentTree(main_html);
-
-  Element* body = GetDocument().body();
-  Element* d1 = body->QuerySelector("#d1");
-  Element* d2 = body->QuerySelector("#d2");
-  Element* d3 = body->QuerySelector("#d3");
-  Element* d4 = body->QuerySelector("#d4");
-  Element* d5 = body->QuerySelector("#d5");
-  Element* d6 = body->QuerySelector("#d6");
-
-  AttachOpenShadowRoot(*d1, shadow_html1);
-  ShadowRoot* shadow_root1 = d1->OpenShadowRoot();
-  Element* d11 = shadow_root1->QuerySelector("#d1-1");
-  Element* d12 = shadow_root1->QuerySelector("#d1-2");
-  Element* d13 = shadow_root1->QuerySelector("#d1-3");
-  Element* d14 = shadow_root1->QuerySelector("#d1-4");
-  Element* d1s0 = shadow_root1->QuerySelector("#d1-s0");
-  Element* d1s1 = shadow_root1->QuerySelector("[name=d1-s1]");
-  Element* d1s2 = shadow_root1->QuerySelector("[name=d1-s2]");
-
-  AttachOpenShadowRoot(*d11, shadow_html2);
-  ShadowRoot* shadow_root2 = d11->OpenShadowRoot();
-  Element* d111 = shadow_root2->QuerySelector("#d1-1-1");
-  Element* d112 = shadow_root2->QuerySelector("#d1-1-2");
-  Element* d11s1 = shadow_root2->QuerySelector("[name=d1-1-s1]");
-  Element* d11s2 = shadow_root2->QuerySelector("[name=d1-1-s2]");
-
-  EXPECT_TRUE(d5);
-  EXPECT_TRUE(d12);
-  EXPECT_TRUE(d13);
-  EXPECT_TRUE(d1s0);
-  EXPECT_TRUE(d1s1);
-  EXPECT_TRUE(d1s2);
-  EXPECT_TRUE(d11s1);
-  EXPECT_TRUE(d11s2);
-
-  EXPECT_EQ(d11, FlatTreeTraversalNg::Next(*d1));
-  EXPECT_EQ(d111, FlatTreeTraversalNg::Next(*d11));
-  EXPECT_EQ(d11s1, FlatTreeTraversalNg::Next(*d111));
-  EXPECT_EQ(d1s1, FlatTreeTraversalNg::Next(*d11s1));
-  EXPECT_EQ(d2, FlatTreeTraversalNg::Next(*d1s1));
-  EXPECT_EQ(d14, FlatTreeTraversalNg::Next(*d2));
-  EXPECT_EQ(d11s2, FlatTreeTraversalNg::Next(*d14));
-  EXPECT_EQ(d112, FlatTreeTraversalNg::Next(*d11s2));
-  EXPECT_EQ(d6, FlatTreeTraversalNg::Next(*d112));
-
-  EXPECT_EQ(d112, FlatTreeTraversalNg::Previous(*d6));
-
-  EXPECT_EQ(d11, FlatTreeTraversalNg::Parent(*d111));
-  EXPECT_EQ(d11, FlatTreeTraversalNg::Parent(*d112));
-  EXPECT_EQ(d1s1, FlatTreeTraversalNg::Parent(*d2));
-  EXPECT_EQ(d11s1, FlatTreeTraversalNg::Parent(*d14));
-  EXPECT_EQ(d1s2, FlatTreeTraversalNg::Parent(*d3));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::Parent(*d4));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1SlotInDocumentTree) {
-  const char* main_html =
-      "<div id='parent'>"
-      "<slot>"
-      "<div id='child1'></div>"
-      "<div id='child2'></div>"
-      "</slot>"
-      "</div>";
-
-  SetupDocumentTree(main_html);
-  Element* body = GetDocument().body();
-  Element* parent = body->QuerySelector("#parent");
-  Element* slot = body->QuerySelector("slot");
-  Element* child1 = body->QuerySelector("#child1");
-  Element* child2 = body->QuerySelector("#child2");
-
-  EXPECT_EQ(slot, FlatTreeTraversalNg::FirstChild(*parent));
-  EXPECT_EQ(child1, FlatTreeTraversalNg::FirstChild(*slot));
-  EXPECT_EQ(child2, FlatTreeTraversalNg::NextSibling(*child1));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*child2));
-  EXPECT_EQ(slot, FlatTreeTraversalNg::Parent(*child1));
-  EXPECT_EQ(slot, FlatTreeTraversalNg::Parent(*child2));
-  EXPECT_EQ(parent, FlatTreeTraversalNg::Parent(*slot));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1FallbackContent) {
-  const char* main_html = "<div id='d1'></div>";
-  const char* shadow_html =
-      "<div id='before'></div>"
-      "<slot><p>fallback content</p></slot>"
-      "<div id='after'></div>";
-
-  SetupDocumentTree(main_html);
-
-  Element* body = GetDocument().body();
-  Element* d1 = body->QuerySelector("#d1");
-
-  AttachOpenShadowRoot(*d1, shadow_html);
-  ShadowRoot* shadow_root = d1->OpenShadowRoot();
-  Element* before = shadow_root->QuerySelector("#before");
-  Element* after = shadow_root->QuerySelector("#after");
-  Element* fallback_content = shadow_root->QuerySelector("p");
-  Element* slot = shadow_root->QuerySelector("slot");
-
-  EXPECT_EQ(before, FlatTreeTraversalNg::FirstChild(*d1));
-  EXPECT_EQ(after, FlatTreeTraversalNg::LastChild(*d1));
-  EXPECT_EQ(slot, FlatTreeTraversalNg::Parent(*fallback_content));
-
-  EXPECT_EQ(slot, FlatTreeTraversalNg::NextSibling(*before));
-  EXPECT_EQ(after, FlatTreeTraversalNg::NextSibling(*slot));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*fallback_content));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*after));
-
-  EXPECT_EQ(slot, FlatTreeTraversalNg::PreviousSibling(*after));
-  EXPECT_EQ(before, FlatTreeTraversalNg::PreviousSibling(*slot));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*fallback_content));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*before));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1FallbackContentSkippedInTraversal) {
-  const char* main_html = "<div id='d1'><span></span></div>";
-  const char* shadow_html =
-      "<div id='before'></div>"
-      "<slot><p>fallback content</p></slot>"
-      "<div id='after'></div>";
-
-  SetupDocumentTree(main_html);
-
-  Element* body = GetDocument().body();
-  Element* d1 = body->QuerySelector("#d1");
-  Element* span = body->QuerySelector("span");
-
-  AttachOpenShadowRoot(*d1, shadow_html);
-  ShadowRoot* shadow_root = d1->OpenShadowRoot();
-  Element* before = shadow_root->QuerySelector("#before");
-  Element* after = shadow_root->QuerySelector("#after");
-  Element* fallback_content = shadow_root->QuerySelector("p");
-  Element* slot = shadow_root->QuerySelector("slot");
-
-  EXPECT_EQ(before, FlatTreeTraversalNg::FirstChild(*d1));
-  EXPECT_EQ(after, FlatTreeTraversalNg::LastChild(*d1));
-  EXPECT_EQ(slot, FlatTreeTraversalNg::Parent(*span));
-  EXPECT_EQ(d1, FlatTreeTraversalNg::Parent(*slot));
-
-  EXPECT_EQ(slot, FlatTreeTraversalNg::NextSibling(*before));
-  EXPECT_EQ(after, FlatTreeTraversalNg::NextSibling(*slot));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*after));
-
-  EXPECT_EQ(slot, FlatTreeTraversalNg::PreviousSibling(*after));
-  EXPECT_EQ(before, FlatTreeTraversalNg::PreviousSibling(*slot));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*before));
-
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::Parent(*fallback_content));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*fallback_content));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*fallback_content));
-}
-
-TEST_F(FlatTreeTraversalNgTest, v1AllFallbackContent) {
-  const char* main_html = "<div id='d1'></div>";
-  const char* shadow_html =
-      "<slot name='a'><p id='x'>fallback content X</p></slot>"
-      "<slot name='b'><p id='y'>fallback content Y</p></slot>"
-      "<slot name='c'><p id='z'>fallback content Z</p></slot>";
-
-  SetupDocumentTree(main_html);
-
-  Element* body = GetDocument().body();
-  Element* d1 = body->QuerySelector("#d1");
-
-  AttachOpenShadowRoot(*d1, shadow_html);
-  ShadowRoot* shadow_root = d1->OpenShadowRoot();
-  Element* slot_a = shadow_root->QuerySelector("slot[name=a]");
-  Element* slot_b = shadow_root->QuerySelector("slot[name=b]");
-  Element* slot_c = shadow_root->QuerySelector("slot[name=c]");
-  Element* fallback_x = shadow_root->QuerySelector("#x");
-  Element* fallback_y = shadow_root->QuerySelector("#y");
-  Element* fallback_z = shadow_root->QuerySelector("#z");
-
-  EXPECT_EQ(slot_a, FlatTreeTraversalNg::FirstChild(*d1));
-  EXPECT_EQ(slot_c, FlatTreeTraversalNg::LastChild(*d1));
-
-  EXPECT_EQ(fallback_x, FlatTreeTraversalNg::FirstChild(*slot_a));
-  EXPECT_EQ(fallback_y, FlatTreeTraversalNg::FirstChild(*slot_b));
-  EXPECT_EQ(fallback_z, FlatTreeTraversalNg::FirstChild(*slot_c));
-
-  EXPECT_EQ(slot_a, FlatTreeTraversalNg::Parent(*fallback_x));
-  EXPECT_EQ(slot_b, FlatTreeTraversalNg::Parent(*fallback_y));
-  EXPECT_EQ(slot_c, FlatTreeTraversalNg::Parent(*fallback_z));
-  EXPECT_EQ(d1, FlatTreeTraversalNg::Parent(*slot_a));
-
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*fallback_x));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*fallback_y));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::NextSibling(*fallback_z));
-
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*fallback_z));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*fallback_y));
-  EXPECT_EQ(nullptr, FlatTreeTraversalNg::PreviousSibling(*fallback_x));
-}
-
-}  // namespace flat_tree_traversal_ng_test
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal_test.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal_test.cc
index 517360e4..dbb09dae 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal_test.cc
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal_test.cc
@@ -16,20 +16,23 @@
 #include "third_party/blink/renderer/core/testing/page_test_base.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/compiler.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
+// To avoid symbol collisions in jumbo builds.
+namespace flat_tree_traversal_test {
 
 class FlatTreeTraversalTest : public PageTestBase,
                               private ScopedSlotInFlatTreeForTest,
                               ScopedIncrementalShadowDOMForTest {
  public:
   FlatTreeTraversalTest()
-      : ScopedSlotInFlatTreeForTest(false),
-        ScopedIncrementalShadowDOMForTest(false) {}
+      : ScopedSlotInFlatTreeForTest(true),
+        ScopedIncrementalShadowDOMForTest(true) {}
 
  protected:
   // Sets |mainHTML| to BODY element with |innerHTML| property and attaches
@@ -142,7 +145,8 @@
         << "FlatTreeTraversal::index(FlatTreeTraversal(*shadowHost, " << index
         << "))";
     EXPECT_TRUE(FlatTreeTraversal::IsDescendantOf(*child, *shadow_host))
-        << "FlatTreeTraversal::isDescendantOf(*FlatTreeTraversal(*shadowHost, "
+        << "FlatTreeTraversal::isDescendantOf(*FlatTreeTraversal(*"
+           "shadowHost, "
         << index << "), *shadowHost)";
   }
   EXPECT_EQ(nullptr,
@@ -508,9 +512,9 @@
   // FlatTreeTraversal::traverseSiblings does not work for a node which is not
   // in a document flat tree.
   // e.g. The following test fails. The result of
-  // FlatTreeTraversal::previousSibling(*m11)) will be #m10, instead of nullptr.
-  // Element* m11 = body->querySelector("#m11");
-  // EXPECT_EQ(nullptr, FlatTreeTraversal::previousSibling(*m11));
+  // FlatTreeTraversal::previousSibling(*m11)) will be #m10, instead of
+  // nullptr. Element* m11 = body->querySelector("#m11"); EXPECT_EQ(nullptr,
+  // FlatTreeTraversal::previousSibling(*m11));
 }
 
 TEST_F(FlatTreeTraversalTest, v1Simple) {
@@ -541,12 +545,44 @@
   EXPECT_TRUE(slot1);
   EXPECT_TRUE(slot2);
   EXPECT_EQ(shadow_child1, FlatTreeTraversal::FirstChild(*host));
-  EXPECT_EQ(child1, FlatTreeTraversal::NextSibling(*shadow_child1));
-  EXPECT_EQ(child2, FlatTreeTraversal::NextSibling(*child1));
-  EXPECT_EQ(shadow_child2, FlatTreeTraversal::NextSibling(*child2));
+  EXPECT_EQ(slot1, FlatTreeTraversal::NextSibling(*shadow_child1));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*child1));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*child2));
+  EXPECT_EQ(slot2, FlatTreeTraversal::NextSibling(*slot1));
+  EXPECT_EQ(shadow_child2, FlatTreeTraversal::NextSibling(*slot2));
 }
 
 TEST_F(FlatTreeTraversalTest, v1Redistribution) {
+  // composed tree:
+  // d1
+  // ├──/shadow-root
+  // │   └── d1-1
+  // │       ├──/shadow-root
+  // │       │   ├── d1-1-1
+  // │       │   ├── slot name=d1-1-s1
+  // │       │   ├── slot name=d1-1-s2
+  // │       │   └── d1-1-2
+  // │       ├── d1-2
+  // │       ├── slot id=d1-s0
+  // │       ├── slot name=d1-s1 slot=d1-1-s1
+  // │       ├── slot name=d1-s2
+  // │       ├── d1-3
+  // │       └── d1-4 slot=d1-1-s1
+  // ├── d2 slot=d1-s1
+  // ├── d3 slot=d1-s2
+  // ├── d4 slot=nonexistent
+  // └── d5
+
+  // flat tree:
+  // d1
+  // └── d1-1
+  //     ├── d1-1-1
+  //     ├── slot name=d1-1-s1
+  //     │   ├── slot name=d1-s1 slot=d1-1-s1
+  //     │   │   └── d2 slot=d1-s1
+  //     │   └── d1-4 slot=d1-1-s1
+  //     ├── slot name=d1-1-s2
+  //     └── d1-1-2
   const char* main_html =
       "<div id='d1'>"
       "<div id='d2' slot='d1-s1'></div>"
@@ -605,20 +641,24 @@
   EXPECT_TRUE(d1s2);
   EXPECT_TRUE(d11s1);
   EXPECT_TRUE(d11s2);
+
   EXPECT_EQ(d11, FlatTreeTraversal::Next(*d1));
   EXPECT_EQ(d111, FlatTreeTraversal::Next(*d11));
-  EXPECT_EQ(d2, FlatTreeTraversal::Next(*d111));
+  EXPECT_EQ(d11s1, FlatTreeTraversal::Next(*d111));
+  EXPECT_EQ(d1s1, FlatTreeTraversal::Next(*d11s1));
+  EXPECT_EQ(d2, FlatTreeTraversal::Next(*d1s1));
   EXPECT_EQ(d14, FlatTreeTraversal::Next(*d2));
-  EXPECT_EQ(d112, FlatTreeTraversal::Next(*d14));
+  EXPECT_EQ(d11s2, FlatTreeTraversal::Next(*d14));
+  EXPECT_EQ(d112, FlatTreeTraversal::Next(*d11s2));
   EXPECT_EQ(d6, FlatTreeTraversal::Next(*d112));
 
   EXPECT_EQ(d112, FlatTreeTraversal::Previous(*d6));
 
   EXPECT_EQ(d11, FlatTreeTraversal::Parent(*d111));
   EXPECT_EQ(d11, FlatTreeTraversal::Parent(*d112));
-  EXPECT_EQ(d11, FlatTreeTraversal::Parent(*d2));
-  EXPECT_EQ(d11, FlatTreeTraversal::Parent(*d14));
-  EXPECT_EQ(nullptr, FlatTreeTraversal::Parent(*d3));
+  EXPECT_EQ(d1s1, FlatTreeTraversal::Parent(*d2));
+  EXPECT_EQ(d11s1, FlatTreeTraversal::Parent(*d14));
+  EXPECT_EQ(d1s2, FlatTreeTraversal::Parent(*d3));
   EXPECT_EQ(nullptr, FlatTreeTraversal::Parent(*d4));
 }
 
@@ -664,17 +704,20 @@
   Element* before = shadow_root->QuerySelector("#before");
   Element* after = shadow_root->QuerySelector("#after");
   Element* fallback_content = shadow_root->QuerySelector("p");
+  Element* slot = shadow_root->QuerySelector("slot");
 
   EXPECT_EQ(before, FlatTreeTraversal::FirstChild(*d1));
   EXPECT_EQ(after, FlatTreeTraversal::LastChild(*d1));
-  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*fallback_content));
+  EXPECT_EQ(slot, FlatTreeTraversal::Parent(*fallback_content));
 
-  EXPECT_EQ(fallback_content, FlatTreeTraversal::NextSibling(*before));
-  EXPECT_EQ(after, FlatTreeTraversal::NextSibling(*fallback_content));
+  EXPECT_EQ(slot, FlatTreeTraversal::NextSibling(*before));
+  EXPECT_EQ(after, FlatTreeTraversal::NextSibling(*slot));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*fallback_content));
   EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*after));
 
-  EXPECT_EQ(fallback_content, FlatTreeTraversal::PreviousSibling(*after));
-  EXPECT_EQ(before, FlatTreeTraversal::PreviousSibling(*fallback_content));
+  EXPECT_EQ(slot, FlatTreeTraversal::PreviousSibling(*after));
+  EXPECT_EQ(before, FlatTreeTraversal::PreviousSibling(*slot));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*fallback_content));
   EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*before));
 }
 
@@ -696,17 +739,19 @@
   Element* before = shadow_root->QuerySelector("#before");
   Element* after = shadow_root->QuerySelector("#after");
   Element* fallback_content = shadow_root->QuerySelector("p");
+  Element* slot = shadow_root->QuerySelector("slot");
 
   EXPECT_EQ(before, FlatTreeTraversal::FirstChild(*d1));
   EXPECT_EQ(after, FlatTreeTraversal::LastChild(*d1));
-  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*span));
+  EXPECT_EQ(slot, FlatTreeTraversal::Parent(*span));
+  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*slot));
 
-  EXPECT_EQ(span, FlatTreeTraversal::NextSibling(*before));
-  EXPECT_EQ(after, FlatTreeTraversal::NextSibling(*span));
+  EXPECT_EQ(slot, FlatTreeTraversal::NextSibling(*before));
+  EXPECT_EQ(after, FlatTreeTraversal::NextSibling(*slot));
   EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*after));
 
-  EXPECT_EQ(span, FlatTreeTraversal::PreviousSibling(*after));
-  EXPECT_EQ(before, FlatTreeTraversal::PreviousSibling(*span));
+  EXPECT_EQ(slot, FlatTreeTraversal::PreviousSibling(*after));
+  EXPECT_EQ(before, FlatTreeTraversal::PreviousSibling(*slot));
   EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*before));
 
   EXPECT_EQ(nullptr, FlatTreeTraversal::Parent(*fallback_content));
@@ -728,40 +773,33 @@
 
   AttachOpenShadowRoot(*d1, shadow_html);
   ShadowRoot* shadow_root = d1->OpenShadowRoot();
+  Element* slot_a = shadow_root->QuerySelector("slot[name=a]");
+  Element* slot_b = shadow_root->QuerySelector("slot[name=b]");
+  Element* slot_c = shadow_root->QuerySelector("slot[name=c]");
   Element* fallback_x = shadow_root->QuerySelector("#x");
   Element* fallback_y = shadow_root->QuerySelector("#y");
   Element* fallback_z = shadow_root->QuerySelector("#z");
 
-  EXPECT_EQ(fallback_x, FlatTreeTraversal::FirstChild(*d1));
-  EXPECT_EQ(fallback_z, FlatTreeTraversal::LastChild(*d1));
-  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*fallback_x));
-  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*fallback_y));
-  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*fallback_z));
+  EXPECT_EQ(slot_a, FlatTreeTraversal::FirstChild(*d1));
+  EXPECT_EQ(slot_c, FlatTreeTraversal::LastChild(*d1));
 
-  EXPECT_EQ(fallback_y, FlatTreeTraversal::NextSibling(*fallback_x));
-  EXPECT_EQ(fallback_z, FlatTreeTraversal::NextSibling(*fallback_y));
+  EXPECT_EQ(fallback_x, FlatTreeTraversal::FirstChild(*slot_a));
+  EXPECT_EQ(fallback_y, FlatTreeTraversal::FirstChild(*slot_b));
+  EXPECT_EQ(fallback_z, FlatTreeTraversal::FirstChild(*slot_c));
+
+  EXPECT_EQ(slot_a, FlatTreeTraversal::Parent(*fallback_x));
+  EXPECT_EQ(slot_b, FlatTreeTraversal::Parent(*fallback_y));
+  EXPECT_EQ(slot_c, FlatTreeTraversal::Parent(*fallback_z));
+  EXPECT_EQ(d1, FlatTreeTraversal::Parent(*slot_a));
+
+  EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*fallback_x));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*fallback_y));
   EXPECT_EQ(nullptr, FlatTreeTraversal::NextSibling(*fallback_z));
 
-  EXPECT_EQ(fallback_y, FlatTreeTraversal::PreviousSibling(*fallback_z));
-  EXPECT_EQ(fallback_x, FlatTreeTraversal::PreviousSibling(*fallback_y));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*fallback_z));
+  EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*fallback_y));
   EXPECT_EQ(nullptr, FlatTreeTraversal::PreviousSibling(*fallback_x));
 }
 
-TEST_F(FlatTreeTraversalTest, v0ParentDetailsInsertionPoint) {
-  const char* main_html = "<div><span></span></div>";
-  const char* shadow_html = "<content></content>";
-
-  SetupSampleHTML(main_html, shadow_html, 0);
-
-  Element* span = GetDocument().body()->QuerySelector("span");
-  ASSERT_TRUE(span);
-
-  FlatTreeTraversal::ParentTraversalDetails details;
-  EXPECT_FALSE(details.GetInsertionPoint());
-
-  ContainerNode* parent = FlatTreeTraversal::Parent(*span, &details);
-  ASSERT_TRUE(parent);
-  EXPECT_TRUE(details.GetInsertionPoint());
-}
-
+}  // namespace flat_tree_traversal_test
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index 302eb608..3b04cb2 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -634,6 +634,8 @@
 
   struct AttachContext {
     STACK_ALLOCATED();
+
+   public:
     // Keep track of previously attached in-flow box during attachment so that
     // we don't need to backtrack past display:none/contents and out of flow
     // objects when we need to do whitespace re-attachment.
diff --git a/third_party/blink/renderer/core/editing/commands/editor_command.cc b/third_party/blink/renderer/core/editing/commands/editor_command.cc
index 51df2dab..9411126 100644
--- a/third_party/blink/renderer/core/editing/commands/editor_command.cc
+++ b/third_party/blink/renderer/core/editing/commands/editor_command.cc
@@ -64,9 +64,9 @@
 #include "third_party/blink/renderer/core/input/event_handler.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/histogram.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
 #include <iterator>
diff --git a/third_party/blink/renderer/core/editing/frame_selection.h b/third_party/blink/renderer/core/editing/frame_selection.h
index 64b02e31..c3913b1 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.h
+++ b/third_party/blink/renderer/core/editing/frame_selection.h
@@ -77,6 +77,7 @@
 struct LayoutSelectionStatus {
   STACK_ALLOCATED();
 
+ public:
   LayoutSelectionStatus(unsigned passed_start,
                         unsigned passed_end,
                         SelectSoftLineBreak passed_line_break)
diff --git a/third_party/blink/renderer/core/editing/inline_box_position.h b/third_party/blink/renderer/core/editing/inline_box_position.h
index bc473b24..a48b5af 100644
--- a/third_party/blink/renderer/core/editing/inline_box_position.h
+++ b/third_party/blink/renderer/core/editing/inline_box_position.h
@@ -43,6 +43,7 @@
 struct InlineBoxPosition {
   STACK_ALLOCATED();
 
+ public:
   const InlineBox* const inline_box;
   const int offset_in_box;
 
diff --git a/third_party/blink/renderer/core/editing/layout_selection.cc b/third_party/blink/renderer/core/editing/layout_selection.cc
index b22fae2..3c4637f 100644
--- a/third_party/blink/renderer/core/editing/layout_selection.cc
+++ b/third_party/blink/renderer/core/editing/layout_selection.cc
@@ -111,6 +111,8 @@
 // (LayoutObject, offset).
 struct SelectionPaintRange {
   STACK_ALLOCATED();
+
+ public:
   bool IsNull() const { return !start_layout_object; }
   LayoutObject* start_layout_object = nullptr;
   base::Optional<unsigned> start_offset = base::nullopt;
@@ -122,6 +124,8 @@
 // current SelectionState which is kStart, kEnd, kStartAndEnd or kInside.
 struct OldSelectedLayoutObjects {
   STACK_ALLOCATED();
+
+ public:
   OldSelectedLayoutObjects() = default;
   OldSelectedLayoutObjects(OldSelectedLayoutObjects&& other) {
     paint_range = other.paint_range;
@@ -142,6 +146,7 @@
 struct NewPaintRangeAndSelectedLayoutObjects {
   STACK_ALLOCATED();
 
+ public:
   NewPaintRangeAndSelectedLayoutObjects() = default;
   NewPaintRangeAndSelectedLayoutObjects(
       SelectionPaintRange passed_paint_range,
@@ -478,6 +483,8 @@
 // LayoutObjectAndOffset represents start or end of SelectionPaintRange.
 struct LayoutObjectAndOffset {
   STACK_ALLOCATED();
+
+ public:
   LayoutObject* layout_object;
   base::Optional<unsigned> offset;
 
diff --git a/third_party/blink/renderer/core/editing/local_caret_rect.h b/third_party/blink/renderer/core/editing/local_caret_rect.h
index 9e6de0ba..3a9c15a 100644
--- a/third_party/blink/renderer/core/editing/local_caret_rect.h
+++ b/third_party/blink/renderer/core/editing/local_caret_rect.h
@@ -17,6 +17,7 @@
 struct LocalCaretRect {
   STACK_ALLOCATED();
 
+ public:
   const LayoutObject* layout_object = nullptr;
   LayoutRect rect;
 
diff --git a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc
index 66300a7..049ab4e 100644
--- a/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc
+++ b/third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc
@@ -102,6 +102,7 @@
 struct SuggestionInfosWithNodeAndHighlightColor {
   STACK_ALLOCATED();
 
+ public:
   Persistent<Node> text_node;
   Color highlight_color;
   Vector<TextSuggestionInfo> suggestion_infos;
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 4f5d20f..f27eff93 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -135,6 +135,8 @@
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
 #include "third_party/blink/renderer/core/testing/null_execution_context.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
@@ -151,8 +153,6 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h"
 #include "third_party/blink/renderer/platform/testing/histogram_tester.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
 #include "third_party/blink/renderer/platform/testing/scoped_fake_plugin_registry.h"
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
index 57e1b53..cf141b7 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
@@ -92,6 +92,8 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
@@ -101,8 +103,6 @@
 #include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h"
 #include "third_party/blink/renderer/platform/keyboard_codes.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 35a2a48..ff939dc 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -141,6 +141,7 @@
 #include "third_party/blink/renderer/core/paint/first_meaningful_paint_detector.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_host.h"
@@ -162,7 +163,6 @@
 #include "third_party/blink/renderer/platform/scheduler/public/page_lifecycle_state.h"
 #include "third_party/blink/renderer/platform/scheduler/public/page_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index 68ca203..710613e 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -34,6 +34,11 @@
     AdTracker::Trace(visitor);
   }
 
+  bool RequestWithUrlTaggedAsAd(const String& url) const {
+    DCHECK(is_ad_.Contains(url));
+    return is_ad_.at(url);
+  }
+
  protected:
   String ScriptAtTopOfStack(ExecutionContext* execution_context) override {
     if (script_at_top_.IsEmpty())
@@ -63,9 +68,12 @@
     AdTracker::WillSendRequest(execution_context, identifier, document_loader,
                                resource_request, redirect_response,
                                fetch_initiator_info, resource_type);
+    is_ad_.insert(resource_request.Url().GetString(),
+                  resource_request.IsAdResource());
   }
 
  private:
+  HashMap<String, bool> is_ad_;
   String script_at_top_;
   Member<ExecutionContext> execution_context_;
   String ad_suffix_;
@@ -179,11 +187,12 @@
   Persistent<TestAdTracker> ad_tracker_;
 };
 
-// Resources loaded by ad script are tagged as ads.
-TEST_F(AdTrackerSimTest, ResourceLoadedWhileExecutingAdScript) {
-  SimRequest ad_resource("https://example.com/ad_script.js", "text/javascript");
-  SimRequest vanilla_script("https://example.com/vanilla_script.js",
-                            "text/javascript");
+// Script loaded by ad script is tagged as ad.
+TEST_F(AdTrackerSimTest, ScriptLoadedWhileExecutingAdScript) {
+  const char kAdUrl[] = "https://example.com/ad_script.js";
+  const char kVanillaUrl[] = "https://example.com/vanilla_script.js";
+  SimRequest ad_resource(kAdUrl, "text/javascript");
+  SimRequest vanilla_script(kVanillaUrl, "text/javascript");
 
   ad_tracker_->SetAdSuffix("ad_script.js");
 
@@ -196,10 +205,57 @@
     )SCRIPT");
   vanilla_script.Complete("");
 
-  EXPECT_TRUE(IsKnownAdScript(&GetDocument(),
-                              String("https://example.com/ad_script.js")));
-  EXPECT_TRUE(IsKnownAdScript(&GetDocument(),
-                              String("https://example.com/vanilla_script.js")));
+  EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kAdUrl));
+  EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kVanillaUrl));
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl));
+}
+
+// Image loaded by ad script is tagged as ad.
+TEST_F(AdTrackerSimTest, ImageLoadedWhileExecutingAdScript) {
+  const char kAdUrl[] = "https://example.com/ad_script.js";
+  const char kVanillaUrl[] = "https://example.com/vanilla_image.jpg";
+  SimRequest ad_resource(kAdUrl, "text/javascript");
+  SimRequest vanilla_image(kVanillaUrl, "image/jpeg");
+
+  ad_tracker_->SetAdSuffix("ad_script.js");
+
+  main_resource_->Complete("<body></body><script src=ad_script.js></script>");
+
+  ad_resource.Complete(R"SCRIPT(
+    image = document.createElement("img");
+    image.src = "vanilla_image.jpg";
+    document.body.appendChild(image);
+    )SCRIPT");
+  vanilla_image.Complete("");
+
+  EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kAdUrl));
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
+  // TODO(crbug.com/848916): Should be true.
+  EXPECT_FALSE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl));
+}
+
+// Frame loaded by ad script is tagged as ad.
+TEST_F(AdTrackerSimTest, FrameLoadedWhileExecutingAdScript) {
+  const char kAdUrl[] = "https://example.com/ad_script.js";
+  const char kVanillaUrl[] = "https://example.com/vanilla_page.html";
+  SimRequest ad_resource(kAdUrl, "text/javascript");
+  SimRequest vanilla_page(kVanillaUrl, "text/html");
+
+  ad_tracker_->SetAdSuffix("ad_script.js");
+
+  main_resource_->Complete("<body></body><script src=ad_script.js></script>");
+
+  ad_resource.Complete(R"SCRIPT(
+    iframe = document.createElement("iframe");
+    iframe.src = "vanilla_page.html";
+    document.body.appendChild(iframe);
+    )SCRIPT");
+  vanilla_page.Complete("");
+
+  EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kAdUrl));
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl));
 }
 
 // A script tagged as an ad in one frame shouldn't cause it to be considered
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index c162e6d4..d2d76c9 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -341,15 +341,11 @@
 
     case WebFeature::kApplicationCacheManifestSelectInsecureOrigin:
     case WebFeature::kApplicationCacheAPIInsecureOrigin:
-      return {
-          "ApplicationCacheAPIInsecureOrigin", kM69,
-          String::Format(
-              "Application Cache is deprecated in non-secure contexts, and "
-              "will be restricted to secure contexts in %s. Please consider "
-              "migrating your application to HTTPS, and eventually shifting "
-              "over to Service Workers. See https://goo.gl/rStTGz for more "
-              "details.",
-              MilestoneString(kM69))};
+      return {"ApplicationCacheAPIInsecureOrigin", kM70,
+              "Application Cache is restricted to secure contexts. Please "
+              "consider migrating your application to HTTPS, and eventually "
+              "shifting over to Service Workers. See https://goo.gl/rStTGz for "
+              "more details."};
 
     case WebFeature::kNotificationInsecureOrigin:
     case WebFeature::kNotificationAPIInsecureOriginIframe:
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.h b/third_party/blink/renderer/core/frame/frame_test_helpers.h
index 644cec99..d257734 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.h
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.h
@@ -55,9 +55,9 @@
 #include "third_party/blink/public/web/web_view_client.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 #define EXPECT_FLOAT_POINT_EQ(expected, actual)    \
   do {                                             \
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc
index 11ae352..7ef06a4 100644
--- a/third_party/blink/renderer/core/frame/history.cc
+++ b/third_party/blink/renderer/core/frame/history.cc
@@ -148,22 +148,28 @@
   if (!GetFrame()->GetSettings()->GetShouldThrottlePushState())
     return false;
 
-  const int kStateUpdateLimit = 50;
-
-  if (state_flood_guard.count > kStateUpdateLimit) {
-    static constexpr auto kStateUpdateLimitResetInterval =
-        TimeDelta::FromSeconds(10);
-    const auto now = CurrentTimeTicks();
-    if (now - state_flood_guard.last_updated > kStateUpdateLimitResetInterval) {
-      state_flood_guard.count = 0;
-      state_flood_guard.last_updated = now;
-      return false;
-    }
-    return true;
+  if (state_flood_guard.tokens > 0) {
+    state_flood_guard.tokens--;
+    return false;
   }
 
-  state_flood_guard.count++;
-  return false;
+  const auto now = TimeTicks::Now();
+  const TimeDelta elapsed = now - state_flood_guard.last_token_grant;
+  static constexpr base::TimeDelta kTimePerToken =
+      base::TimeDelta::FromMilliseconds(16);
+  static constexpr int kMaxTokens = 50;
+  // It is OK to truncate from int64_t to int here.
+  const int tokens_earned = std::min<int>(elapsed / kTimePerToken, kMaxTokens);
+
+  if (tokens_earned > 0) {
+    state_flood_guard.tokens = tokens_earned - 1;  // One consumed immediately.
+    state_flood_guard.last_token_grant = now;
+    return false;
+  }
+
+  // Drop this event (though ideally it would be delivered once there are tokens
+  // again, unless obsoleted by a subsequent state object change).
+  return true;
 }
 
 bool History::stateChanged() const {
diff --git a/third_party/blink/renderer/core/frame/history.h b/third_party/blink/renderer/core/frame/history.h
index 95e5a638..87b47d71 100644
--- a/third_party/blink/renderer/core/frame/history.h
+++ b/third_party/blink/renderer/core/frame/history.h
@@ -107,8 +107,8 @@
 
   scoped_refptr<SerializedScriptValue> last_state_object_requested_;
   struct {
-    int count;
-    TimeTicks last_updated;
+    int tokens = 0;
+    TimeTicks last_token_grant;
   } state_flood_guard;
 };
 
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 414ffe5..d99f96d 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -96,6 +96,7 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/script/modulator.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h"
@@ -103,7 +104,6 @@
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index 3b1f204a..bdc4d4c 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -31,9 +31,9 @@
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/frame/dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 3096bad..f6b5d59f 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -108,6 +108,8 @@
 #include "third_party/blink/renderer/core/paint/pre_paint_tree_walk.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer_controller.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
@@ -133,8 +135,6 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_alignment.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/transforms/transform_state.h"
 #include "third_party/blink/renderer/platform/ukm_time_aggregator.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -1642,7 +1642,7 @@
   }
 }
 
-PlatformChromeClient* LocalFrameView::GetChromeClient() const {
+ChromeClient* LocalFrameView::GetChromeClient() const {
   Page* page = GetFrame().GetPage();
   if (!page)
     return nullptr;
@@ -3450,7 +3450,7 @@
 }
 
 void LocalFrameView::ScheduleAnimation() {
-  if (auto* client = ToChromeClient(GetChromeClient()))
+  if (auto* client = GetChromeClient())
     client->ScheduleAnimation(this);
 }
 
@@ -3734,7 +3734,7 @@
 }
 
 IntRect LocalFrameView::FrameToScreen(const IntRect& rect) const {
-  if (auto* client = ToChromeClient(GetChromeClient()))
+  if (auto* client = GetChromeClient())
     return client->ViewportToScreen(FrameToViewport(rect), this);
   return IntRect();
 }
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index c0b189a..59920cc 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -41,8 +41,8 @@
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/layout_object_counter.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer_client.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
@@ -420,7 +420,7 @@
   // The window that hosts the LocalFrameView. The LocalFrameView will
   // communicate scrolls and repaints to the host window in the window's
   // coordinate space.
-  PlatformChromeClient* GetChromeClient() const;
+  ChromeClient* GetChromeClient() const;
 
   // Functions for child manipulation and inspection.
   bool IsSelfVisible() const {
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.cc b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
index 3dc134c..171d036 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
@@ -9,12 +9,12 @@
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/layout/scroll_anchor.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/geometry/double_rect.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_alignment.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 
 namespace blink {
 namespace {
@@ -532,7 +532,7 @@
              : LayoutViewport().GetScrollbarElementId(orientation);
 }
 
-PlatformChromeClient* RootFrameViewport::GetChromeClient() const {
+ChromeClient* RootFrameViewport::GetChromeClient() const {
   return LayoutViewport().GetChromeClient();
 }
 
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.h b/third_party/blink/renderer/core/frame/root_frame_viewport.h
index 6dd9770..d9c8fe8 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.h
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.h
@@ -7,7 +7,7 @@
 
 #include "base/single_thread_task_runner.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
 namespace blink {
 
@@ -106,7 +106,7 @@
   CompositorElementId GetScrollbarElementId(
       ScrollbarOrientation orientation) override;
   bool ScrollAnimatorEnabled() const override;
-  PlatformChromeClient* GetChromeClient() const override;
+  ChromeClient* GetChromeClient() const override;
   SmoothScrollSequencer* GetSmoothScrollSequencer() const override;
   void ServiceScrollAnimations(double) override;
   void UpdateCompositorScrollAnimations() override;
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc b/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
index 03c37576..f37a736e 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport_test.cc
@@ -10,13 +10,13 @@
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_scroll_into_view_params.h"
 #include "third_party/blink/public/platform/web_thread.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h"
 #include "third_party/blink/renderer/platform/geometry/double_rect.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_alignment.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
 
 namespace {
 blink::ScrollbarThemeMock scrollbar_theme_;
diff --git a/third_party/blink/renderer/core/frame/settings.cc b/third_party/blink/renderer/core/frame/settings.cc
index 46800b19..c56c10e 100644
--- a/third_party/blink/renderer/core/frame/settings.cc
+++ b/third_party/blink/renderer/core/frame/settings.cc
@@ -30,7 +30,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "build/build_config.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 512a3209..675c0d0 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -50,14 +50,14 @@
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 #include "third_party/blink/renderer/platform/geometry/double_rect.h"
 #include "third_party/blink/renderer/platform/geometry/float_size.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
 
 namespace blink {
 
@@ -628,7 +628,7 @@
   return GetPage().GetSettings().GetScrollAnimatorEnabled();
 }
 
-PlatformChromeClient* VisualViewport::GetChromeClient() const {
+ChromeClient* VisualViewport::GetChromeClient() const {
   return &GetPage().GetChromeClient();
 }
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.h b/third_party/blink/renderer/core/frame/visual_viewport.h
index 6f10824..7fe7a393 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.h
+++ b/third_party/blink/renderer/core/frame/visual_viewport.h
@@ -38,13 +38,13 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/float_size.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer_client.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
@@ -182,7 +182,7 @@
   IntPoint RootFrameToViewport(const IntPoint&) const;
 
   // ScrollableArea implementation
-  PlatformChromeClient* GetChromeClient() const override;
+  ChromeClient* GetChromeClient() const override;
   bool ShouldUseIntegerScrollOffset() const override;
   void SetScrollOffset(const ScrollOffset&,
                        ScrollType,
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index f37a0043..3ccc357b 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -220,6 +220,7 @@
 #include "third_party/blink/renderer/core/page/print_context.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
@@ -242,7 +243,6 @@
 #include "third_party/blink/renderer/platform/loader/fetch/substitute_data.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
index 4e74a020..6c893781 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
@@ -91,6 +91,12 @@
   virtual ContextType GetContextType() const = 0;
   virtual bool IsComposited() const = 0;
   virtual bool IsAccelerated() const = 0;
+  virtual bool IsOriginTopLeft() const {
+    // Canvas contexts have the origin of coordinates on the top left corner.
+    // Accelerated resources (e.g. GPU textures) have their origin of
+    // coordinates in the uppper left corner.
+    return !IsAccelerated();
+  }
   virtual bool ShouldAntialias() const { return false; }
   virtual void SetIsHidden(bool) = 0;
   virtual bool isContextLost() const { return true; }
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 6780239..c3a3d75 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -420,13 +420,15 @@
         context_->PaintRenderingResultsToCanvas(kBackBuffer);
       scoped_refptr<CanvasResource> canvas_resource =
           ResourceProvider()->ProduceFrame();
-      FloatRect src_rect(0, 0, Size().Width(), Size().Height());
+      const FloatRect src_rect(0, 0, Size().Width(), Size().Height());
       dirty_rect_.Intersect(src_rect);
-      IntRect int_dirty = EnclosingIntRect(dirty_rect_);
-      SkIRect damage_rect = SkIRect::MakeXYWH(
+      const IntRect int_dirty = EnclosingIntRect(dirty_rect_);
+      const SkIRect damage_rect = SkIRect::MakeXYWH(
           int_dirty.X(), int_dirty.Y(), int_dirty.Width(), int_dirty.Height());
+      const bool needs_vertical_flip = !RenderingContext()->IsOriginTopLeft();
       frame_dispatcher_->DispatchFrameSync(std::move(canvas_resource),
-                                           start_time, damage_rect);
+                                           start_time, damage_rect,
+                                           needs_vertical_flip);
       (void)start_time;
       (void)damage_rect;
       dirty_rect_ = FloatRect();
diff --git a/third_party/blink/renderer/core/html/forms/date_time_edit_element.h b/third_party/blink/renderer/core/html/forms/date_time_edit_element.h
index 68bea3cf..25f9175 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_edit_element.h
+++ b/third_party/blink/renderer/core/html/forms/date_time_edit_element.h
@@ -66,6 +66,8 @@
 
   struct LayoutParameters {
     STACK_ALLOCATED();
+
+   public:
     String date_time_format;
     String fallback_date_time_format;
     Locale& locale;
diff --git a/third_party/blink/renderer/core/html/forms/date_time_field_elements.h b/third_party/blink/renderer/core/html/forms/date_time_field_elements.h
index 758f9a71..f2ff719 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_field_elements.h
+++ b/third_party/blink/renderer/core/html/forms/date_time_field_elements.h
@@ -290,6 +290,8 @@
  public:
   struct Parameters {
     STACK_ALLOCATED();
+
+   public:
     int minimum_year;
     int maximum_year;
     bool min_is_specified;
diff --git a/third_party/blink/renderer/core/html/forms/spin_button_element.cc b/third_party/blink/renderer/core/html/forms/spin_button_element.cc
index 1800cd0..5700a6b 100644
--- a/third_party/blink/renderer/core/html/forms/spin_button_element.cc
+++ b/third_party/blink/renderer/core/html/forms/spin_button_element.cc
@@ -37,7 +37,7 @@
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/html/image_document.cc b/third_party/blink/renderer/core/html/image_document.cc
index 746d7345..2bde271 100644
--- a/third_party/blink/renderer/core/html/image_document.cc
+++ b/third_party/blink/renderer/core/html/image_document.cc
@@ -49,10 +49,10 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
 #include "third_party/blink/renderer/core/loader/resource/image_resource.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/html/list_item_ordinal.h b/third_party/blink/renderer/core/html/list_item_ordinal.h
index 299c837..2de3354 100644
--- a/third_party/blink/renderer/core/html/list_item_ordinal.h
+++ b/third_party/blink/renderer/core/html/list_item_ordinal.h
@@ -89,6 +89,8 @@
   static Node* EnclosingList(const Node*);
   struct NodeAndOrdinal {
     STACK_ALLOCATED();
+
+   public:
     Persistent<const Node> node;
     ListItemOrdinal* ordinal = nullptr;
     operator bool() const { return node; }
diff --git a/third_party/blink/renderer/core/html/parser/xss_auditor.h b/third_party/blink/renderer/core/html/parser/xss_auditor.h
index c15a1dc..3cdaa40 100644
--- a/third_party/blink/renderer/core/html/parser/xss_auditor.h
+++ b/third_party/blink/renderer/core/html/parser/xss_auditor.h
@@ -45,6 +45,8 @@
 
 struct FilterTokenRequest {
   STACK_ALLOCATED();
+
+ public:
   FilterTokenRequest(HTMLToken& token,
                      HTMLSourceTracker& source_tracker,
                      bool should_allow_cdata)
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
index 098bff1..5927125 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
@@ -47,6 +47,8 @@
 
 struct VTTDisplayParameters {
   STACK_ALLOCATED();
+
+ public:
   VTTDisplayParameters();
 
   FloatPoint position;
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index ae3c3f03..e0037e8 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -87,6 +87,8 @@
 #include "third_party/blink/renderer/core/page/touch_adjustment.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/core/style/cursor_data.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -96,8 +98,6 @@
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/windows_keyboard_codes.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -1924,9 +1924,8 @@
   IntPoint location_in_viewport =
       visual_viewport.RootFrameToViewport(location_in_root_frame);
   IntPoint global_position =
-      ToChromeClient(view->GetChromeClient())
-          ->ViewportToScreen(IntRect(location_in_viewport, IntSize()),
-                             frame_->View())
+      view->GetChromeClient()->ViewportToScreen(
+          IntRect(location_in_viewport, IntSize()), frame_->View())
           .Location();
 
   Node* target_node =
diff --git a/third_party/blink/renderer/core/input/event_handling_util.cc b/third_party/blink/renderer/core/input/event_handling_util.cc
index 0ee5fc4..9c9612c 100644
--- a/third_party/blink/renderer/core/input/event_handling_util.cc
+++ b/third_party/blink/renderer/core/input/event_handling_util.cc
@@ -11,7 +11,7 @@
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
 namespace blink {
 namespace EventHandlingUtil {
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc
index dbe687c2fe..ec56b34 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.cc
+++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -20,7 +20,7 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 
 #if BUILDFLAG(ENABLE_UNHANDLED_TAP)
 #include "services/service_manager/public/cpp/interface_provider.h"
diff --git a/third_party/blink/renderer/core/input/scroll_manager.cc b/third_party/blink/renderer/core/input/scroll_manager.cc
index f2ffe18d..9bce900f 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.cc
+++ b/third_party/blink/renderer/core/input/scroll_manager.cc
@@ -26,9 +26,9 @@
 #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scroll_customization.h"
 #include "third_party/blink/renderer/platform/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_customization.h"
 
 namespace blink {
 namespace {
@@ -57,6 +57,15 @@
           .event_time = event.TimeStamp()};
 }
 
+ScrollableArea* ScrollableAreaForSnapping(LayoutBox* layout_box) {
+  if (!layout_box)
+    return nullptr;
+
+  return layout_box->IsLayoutView()
+             ? layout_box->GetFrameView()->GetScrollableArea()
+             : layout_box->GetScrollableArea();
+}
+
 }  // namespace
 
 ScrollManager::ScrollManager(LocalFrame& frame) : frame_(frame) {
@@ -608,20 +617,14 @@
 }
 
 LayoutBox* ScrollManager::LayoutBoxForSnapping() const {
+  if (!previous_gesture_scrolled_element_)
+    return nullptr;
   Element* document_element = frame_->GetDocument()->documentElement();
   return previous_gesture_scrolled_element_ == document_element
              ? frame_->GetDocument()->GetLayoutView()
              : previous_gesture_scrolled_element_->GetLayoutBox();
 }
 
-ScrollableArea* ScrollManager::ScrollableAreaForSnapping() const {
-  Element* document_element = frame_->GetDocument()->documentElement();
-  return previous_gesture_scrolled_element_ == document_element
-             ? frame_->View()->GetScrollableArea()
-             : previous_gesture_scrolled_element_->GetLayoutBox()
-                   ->GetScrollableArea();
-}
-
 void ScrollManager::SnapAtGestureScrollEnd() {
   if (!previous_gesture_scrolled_element_ ||
       (!did_scroll_x_for_scroll_gesture_ && !did_scroll_y_for_scroll_gesture_))
@@ -654,7 +657,11 @@
 
 gfx::Vector2dF ScrollManager::ScrollByForSnapFling(
     const gfx::Vector2dF& delta) {
-  DCHECK(previous_gesture_scrolled_element_);
+  ScrollableArea* scrollable_area =
+      ScrollableAreaForSnapping(LayoutBoxForSnapping());
+  if (!scrollable_area)
+    return gfx::Vector2dF();
+
   std::unique_ptr<ScrollStateData> scroll_state_data =
       std::make_unique<ScrollStateData>();
 
@@ -672,13 +679,16 @@
       previous_gesture_scrolled_element_);
 
   CustomizedScroll(*scroll_state);
-
-  ScrollableArea* scrollable_area = ScrollableAreaForSnapping();
   FloatPoint end_position = scrollable_area->ScrollPosition();
   return gfx::Vector2dF(end_position.X(), end_position.Y());
 }
 
 void ScrollManager::ScrollEndForSnapFling() {
+  if (current_scroll_chain_.empty()) {
+    NotifyScrollPhaseEndForCustomizedScroll();
+    ClearGestureScrollState();
+    return;
+  }
   std::unique_ptr<ScrollStateData> scroll_state_data =
       std::make_unique<ScrollStateData>();
   scroll_state_data->is_ending = true;
diff --git a/third_party/blink/renderer/core/input/scroll_manager.h b/third_party/blink/renderer/core/input/scroll_manager.h
index 72f0136..6053e47 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.h
+++ b/third_party/blink/renderer/core/input/scroll_manager.h
@@ -27,7 +27,6 @@
 class PaintLayer;
 class PaintLayerScrollableArea;
 class Page;
-class ScrollableArea;
 class Scrollbar;
 class ScrollState;
 class WebGestureEvent;
@@ -140,7 +139,6 @@
   void NotifyScrollPhaseEndForCustomizedScroll();
 
   LayoutBox* LayoutBoxForSnapping() const;
-  ScrollableArea* ScrollableAreaForSnapping() const;
 
   // NOTE: If adding a new field to this class please ensure that it is
   // cleared in |ScrollManager::clear()|.
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_host.cc b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
index e31a9f24..ccd906e 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_host.cc
+++ b/third_party/blink/renderer/core/inspector/dev_tools_host.cc
@@ -158,7 +158,7 @@
   float zoom_factor = frontend_frame_->PageZoomFactor();
   // Cancel the device scale factor applied to the zoom factor in
   // use-zoom-for-dsf mode.
-  const PlatformChromeClient* client =
+  const ChromeClient* client =
       frontend_frame_->View()->GetChromeClient();
   float window_to_viewport_ratio = client->WindowToViewportScalar(1.0f);
   return zoom_factor / window_to_viewport_ratio;
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
index 15f1d79..71dcf382 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
@@ -361,7 +361,8 @@
   int index = dom_nodes_->length();
   dom_nodes_->addItem(std::move(owned_value));
 
-  int layoutNodeIndex = VisitLayoutTreeNode(node, index);
+  int layoutNodeIndex =
+      VisitLayoutTreeNode(node->GetLayoutObject(), node, index);
   if (layoutNodeIndex != -1)
     value->setLayoutNodeIndex(layoutNodeIndex);
 
@@ -430,10 +431,13 @@
       if (InspectorDOMAgent::GetPseudoElementType(element->GetPseudoId(),
                                                   &pseudo_type)) {
         value->setPseudoType(pseudo_type);
+        if (node->GetLayoutObject())
+          VisitPseudoLayoutChildren(node, index);
       }
     } else {
-      value->setPseudoElementIndexes(VisitPseudoElements(
-          element, include_event_listeners, include_user_agent_shadow_tree));
+      value->setPseudoElementIndexes(
+          VisitPseudoElements(element, index, include_event_listeners,
+                              include_user_agent_shadow_tree));
     }
 
     HTMLImageElement* image_element = ToHTMLImageElementOrNull(node);
@@ -586,7 +590,7 @@
   nodes->getNodeValue(nullptr)->addItem(AddString(node_value));
   nodes->getBackendNodeId(nullptr)->addItem(backend_node_id);
   nodes->getAttributes(nullptr)->addItem(BuildArrayForElementAttributes2(node));
-  BuildLayoutTreeNode(node, index);
+  BuildLayoutTreeNode(node->GetLayoutObject(), node, index);
 
   if (origin_url_map_ && origin_url_map_->Contains(backend_node_id)) {
     String origin_url = origin_url_map_->at(backend_node_id);
@@ -636,6 +640,8 @@
       if (InspectorDOMAgent::GetPseudoElementType(element->GetPseudoId(),
                                                   &pseudo_type)) {
         SetRare(nodes->getPseudoType(nullptr), index, pseudo_type);
+        if (node->GetLayoutObject())
+          VisitPseudoLayoutChildren2(node, index);
       }
     } else {
       VisitPseudoElements2(element, index);
@@ -722,45 +728,54 @@
   }
 }
 
+void InspectorDOMSnapshotAgent::VisitPseudoLayoutChildren(Node* pseudo_node,
+                                                          int index) {
+  for (LayoutObject* child = pseudo_node->GetLayoutObject()->SlowFirstChild();
+       child; child = child->NextSibling()) {
+    if (child->IsAnonymous())
+      VisitLayoutTreeNode(child, pseudo_node, index);
+  }
+}
+
+void InspectorDOMSnapshotAgent::VisitPseudoLayoutChildren2(Node* pseudo_node,
+                                                           int index) {
+  for (LayoutObject* child = pseudo_node->GetLayoutObject()->SlowFirstChild();
+       child; child = child->NextSibling()) {
+    if (child->IsAnonymous())
+      BuildLayoutTreeNode(child, pseudo_node, index);
+  }
+}
+
 std::unique_ptr<protocol::Array<int>>
 InspectorDOMSnapshotAgent::VisitPseudoElements(
     Element* parent,
+    int index,
     bool include_event_listeners,
     bool include_user_agent_shadow_tree) {
-  if (!parent->GetPseudoElement(kPseudoIdBefore) &&
+  if (!parent->GetPseudoElement(kPseudoIdFirstLetter) &&
+      !parent->GetPseudoElement(kPseudoIdBefore) &&
       !parent->GetPseudoElement(kPseudoIdAfter)) {
     return nullptr;
   }
 
   auto pseudo_elements = protocol::Array<int>::create();
-
-  if (parent->GetPseudoElement(kPseudoIdBefore)) {
-    pseudo_elements->addItem(
-        VisitNode(parent->GetPseudoElement(kPseudoIdBefore),
-                  include_event_listeners, include_user_agent_shadow_tree));
+  for (PseudoId pseudo_id :
+       {kPseudoIdFirstLetter, kPseudoIdBefore, kPseudoIdAfter}) {
+    if (Node* pseudo_node = parent->GetPseudoElement(pseudo_id)) {
+      pseudo_elements->addItem(VisitNode(pseudo_node, include_event_listeners,
+                                         include_user_agent_shadow_tree));
+    }
   }
-  if (parent->GetPseudoElement(kPseudoIdAfter)) {
-    pseudo_elements->addItem(VisitNode(parent->GetPseudoElement(kPseudoIdAfter),
-                                       include_event_listeners,
-                                       include_user_agent_shadow_tree));
-  }
-
   return pseudo_elements;
 }
 
 void InspectorDOMSnapshotAgent::VisitPseudoElements2(Element* parent,
                                                      int parent_index) {
-  if (!parent->GetPseudoElement(kPseudoIdBefore) &&
-      !parent->GetPseudoElement(kPseudoIdAfter)) {
-    return;
+  for (PseudoId pseudo_id :
+       {kPseudoIdFirstLetter, kPseudoIdBefore, kPseudoIdAfter}) {
+    if (Node* pseudo_node = parent->GetPseudoElement(pseudo_id))
+      VisitNode2(pseudo_node, parent_index);
   }
-
-  auto pseudo_elements = protocol::Array<int>::create();
-
-  if (parent->GetPseudoElement(kPseudoIdBefore))
-    VisitNode2(parent->GetPseudoElement(kPseudoIdBefore), parent_index);
-  if (parent->GetPseudoElement(kPseudoIdAfter))
-    VisitNode2(parent->GetPseudoElement(kPseudoIdAfter), parent_index);
 }
 
 std::unique_ptr<protocol::Array<protocol::DOMSnapshot::NameValue>>
@@ -792,8 +807,9 @@
   return result;
 }
 
-int InspectorDOMSnapshotAgent::VisitLayoutTreeNode(Node* node, int node_index) {
-  LayoutObject* layout_object = node->GetLayoutObject();
+int InspectorDOMSnapshotAgent::VisitLayoutTreeNode(LayoutObject* layout_object,
+                                                   Node* node,
+                                                   int node_index) {
   if (!layout_object)
     return -1;
 
@@ -846,8 +862,9 @@
   return index;
 }
 
-int InspectorDOMSnapshotAgent::BuildLayoutTreeNode(Node* node, int node_index) {
-  LayoutObject* layout_object = node->GetLayoutObject();
+int InspectorDOMSnapshotAgent::BuildLayoutTreeNode(LayoutObject* layout_object,
+                                                   Node* node,
+                                                   int node_index) {
   if (!layout_object)
     return -1;
   auto* layout_tree_snapshot = document_->getLayout();
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
index 322420f1..455ab8b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.h
@@ -93,8 +93,14 @@
       bool include_event_listeners,
       bool include_user_agent_shadow_tree);
   void VisitContainerChildren2(Node* container, int parent_index);
+
+  // Collect LayoutTreeNodes owned by a pseudo element.
+  void VisitPseudoLayoutChildren(Node* pseudo_node, int index);
+  void VisitPseudoLayoutChildren2(Node* pseudo_node, int index);
+
   std::unique_ptr<protocol::Array<int>> VisitPseudoElements(
       Element* parent,
+      int index,
       bool include_event_listeners,
       bool include_user_agent_shadow_tree);
   void VisitPseudoElements2(Element* parent, int parent_index);
@@ -102,11 +108,11 @@
   BuildArrayForElementAttributes(Element*);
   std::unique_ptr<protocol::Array<int>> BuildArrayForElementAttributes2(Node*);
 
-  // Adds a LayoutTreeNode for the LayoutObject of the given Node to
-  // |layout_tree_nodes_| and returns its index. Returns -1 if the Node has no
-  // associated LayoutObject.
-  int VisitLayoutTreeNode(Node*, int node_index);
-  int BuildLayoutTreeNode(Node*, int node_index);
+  // Adds a LayoutTreeNode for the LayoutObject to |layout_tree_nodes_| and
+  // returns its index. Returns -1 if the Node has no associated LayoutObject.
+  // Associates LayoutObjects under a pseudo element with the element.
+  int VisitLayoutTreeNode(LayoutObject*, Node*, int node_index);
+  int BuildLayoutTreeNode(LayoutObject*, Node*, int node_index);
 
   // Returns the index of the ComputedStyle in |computed_styles_| for the given
   // Node. Adds a new ComputedStyle if necessary, but ensures no duplicates are
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
index d28d1a1..2e07a2a0 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
@@ -28,13 +28,6 @@
 using protocol::Response;
 
 namespace EmulationAgentState {
-static const char kScriptExecutionDisabled[] = "scriptExecutionDisabled";
-static const char kScrollbarsHidden[] = "scrollbarsHidden";
-static const char kDocumentCookieDisabled[] = "documentCookieDisabled";
-static const char kTouchEventEmulationEnabled[] = "touchEventEmulationEnabled";
-static const char kMaxTouchPoints[] = "maxTouchPoints";
-static const char kEmulatedMedia[] = "emulatedMedia";
-static const char kNavigatorPlatform[] = "navigatorPlatform";
 static const char kVirtualTimeBudget[] = "virtualTimeBudget";
 static const char kVirtualTimeBudgetInitalOffset[] =
     "virtualTimeBudgetInitalOffset";
@@ -44,15 +37,24 @@
 static const char kVirtualTimeTaskStarvationCount[] =
     "virtualTimeTaskStarvationCount";
 static const char kWaitForNavigation[] = "waitForNavigation";
-static const char kUserAgentOverride[] = "userAgentOverride";
-static const char kAcceptLanguageOverride[] = "acceptLanguage";
 }  // namespace EmulationAgentState
 
 InspectorEmulationAgent::InspectorEmulationAgent(
     WebLocalFrameImpl* web_local_frame_impl)
     : web_local_frame_(web_local_frame_impl),
-      default_background_color_override_rgba_(
-          &agent_state_, /*default_value=*/ WTF::String()) {}
+      default_background_color_override_rgba_(&agent_state_,
+                                              /*default_value=*/WTF::String()),
+      script_execution_disabled_(&agent_state_, /*default_value=*/false),
+      scrollbars_hidden_(&agent_state_, /*default_value=*/false),
+      document_cookie_disabled_(&agent_state_, /*default_value=*/false),
+      touch_event_emulation_enabled_(&agent_state_, /*default_value=*/false),
+      max_touch_points_(&agent_state_, /*default_value=*/1),
+      emulated_media_(&agent_state_, /*default_value=*/WTF::String()),
+      navigator_platform_override_(&agent_state_,
+                                   /*default_value=*/WTF::String()),
+      user_agent_override_(&agent_state_, /*default_value=*/WTF::String()),
+      accept_language_override_(&agent_state_,
+                                /*default_value=*/WTF::String()) {}
 
 InspectorEmulationAgent::~InspectorEmulationAgent() = default;
 
@@ -61,36 +63,22 @@
 }
 
 void InspectorEmulationAgent::Restore() {
-  String user_agent;
-  state_->getString(EmulationAgentState::kUserAgentOverride, &user_agent);
-  String accept_language;
-  state_->getString(EmulationAgentState::kAcceptLanguageOverride,
-                    &accept_language);
-  String navigator_platform;
-  state_->getString(EmulationAgentState::kNavigatorPlatform,
-                    &navigator_platform);
-  setUserAgentOverride(user_agent, accept_language, navigator_platform);
+  setUserAgentOverride(user_agent_override_.Get(),
+                       accept_language_override_.Get(),
+                       navigator_platform_override_.Get());
   if (!web_local_frame_)
     return;
 
   // Following code only runs for pages.
-  if (state_->booleanProperty(EmulationAgentState::kScriptExecutionDisabled,
-                              false)) {
+  if (script_execution_disabled_.Get())
     GetWebViewImpl()->GetDevToolsEmulator()->SetScriptExecutionDisabled(true);
-  }
-  if (state_->booleanProperty(EmulationAgentState::kScrollbarsHidden, false))
+  if (scrollbars_hidden_.Get())
     GetWebViewImpl()->GetDevToolsEmulator()->SetScrollbarsHidden(true);
-  if (state_->booleanProperty(EmulationAgentState::kDocumentCookieDisabled,
-                              false)) {
+  if (document_cookie_disabled_.Get())
     GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(true);
-  }
-  setTouchEmulationEnabled(
-      state_->booleanProperty(EmulationAgentState::kTouchEventEmulationEnabled,
-                              false),
-      state_->integerProperty(EmulationAgentState::kMaxTouchPoints, 1));
-  String emulated_media;
-  state_->getString(EmulationAgentState::kEmulatedMedia, &emulated_media);
-  setEmulatedMedia(emulated_media);
+  setTouchEmulationEnabled(touch_event_emulation_enabled_.Get(),
+                           max_touch_points_.Get());
+  setEmulatedMedia(emulated_media_.Get());
   if (!default_background_color_override_rgba_.Get().IsNull()) {
     std::unique_ptr<protocol::Value> parsed = protocol::StringUtil::parseJSON(
         default_background_color_override_rgba_.Get());
@@ -200,11 +188,9 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  if (state_->booleanProperty(EmulationAgentState::kScriptExecutionDisabled,
-                              false) == value) {
+  if (script_execution_disabled_.Get() == value)
     return response;
-  }
-  state_->setBoolean(EmulationAgentState::kScriptExecutionDisabled, value);
+  script_execution_disabled_.Set(value);
   GetWebViewImpl()->GetDevToolsEmulator()->SetScriptExecutionDisabled(value);
   return response;
 }
@@ -213,11 +199,9 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  if (state_->booleanProperty(EmulationAgentState::kScrollbarsHidden, false) ==
-      hidden) {
+  if (scrollbars_hidden_.Get() == hidden)
     return response;
-  }
-  state_->setBoolean(EmulationAgentState::kScrollbarsHidden, hidden);
+  scrollbars_hidden_.Set(hidden);
   GetWebViewImpl()->GetDevToolsEmulator()->SetScrollbarsHidden(hidden);
   return response;
 }
@@ -226,11 +210,9 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  if (state_->booleanProperty(EmulationAgentState::kDocumentCookieDisabled,
-                              false) == disabled) {
+  if (document_cookie_disabled_.Get() == disabled)
     return response;
-  }
-  state_->setBoolean(EmulationAgentState::kDocumentCookieDisabled, disabled);
+  document_cookie_disabled_.Set(disabled);
   GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(disabled);
   return response;
 }
@@ -247,8 +229,8 @@
         "Touch points must be between 1 and " +
         String::Number(WebTouchEvent::kTouchesLengthCap));
   }
-  state_->setBoolean(EmulationAgentState::kTouchEventEmulationEnabled, enabled);
-  state_->setInteger(EmulationAgentState::kMaxTouchPoints, max_points);
+  touch_event_emulation_enabled_.Set(enabled);
+  max_touch_points_.Set(max_points);
   GetWebViewImpl()->GetDevToolsEmulator()->SetTouchEventEmulationEnabled(
       enabled, max_points);
   return response;
@@ -258,7 +240,7 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  state_->setString(EmulationAgentState::kEmulatedMedia, media);
+  emulated_media_.Set(media);
   GetWebViewImpl()->GetPage()->GetSettings().SetMediaTypeOverride(media);
   return response;
 }
@@ -398,15 +380,12 @@
     const ResourceResponse& redirect_response,
     const FetchInitiatorInfo& initiator_info,
     Resource::Type resource_type) {
-  String accept_lang_override;
-  state_->getString(EmulationAgentState::kAcceptLanguageOverride,
-                    &accept_lang_override);
-  if (!accept_lang_override.IsEmpty() &&
+  if (!accept_language_override_.Get().IsEmpty() &&
       request.HttpHeaderField("Accept-Language").IsEmpty()) {
     request.SetHTTPHeaderField(
         "Accept-Language",
-        AtomicString(
-            NetworkUtils::GenerateAcceptLanguageHeader(accept_lang_override)));
+        AtomicString(NetworkUtils::GenerateAcceptLanguageHeader(
+            accept_language_override_.Get())));
   }
 }
 
@@ -415,7 +394,7 @@
   Response response = AssertPage();
   if (!response.isSuccess())
     return response;
-  state_->setString(EmulationAgentState::kNavigatorPlatform, platform);
+  navigator_platform_override_.Set(platform);
   GetWebViewImpl()->GetPage()->GetSettings().SetNavigatorPlatformOverride(
       platform);
   return response;
@@ -499,32 +478,24 @@
     protocol::Maybe<String> platform) {
   if (!user_agent.IsEmpty() || accept_language.isJust() || platform.isJust())
     InnerEnable();
-  state_->setString(EmulationAgentState::kUserAgentOverride, user_agent);
-  state_->setString(EmulationAgentState::kAcceptLanguageOverride,
-                    accept_language.fromMaybe(String()));
-  state_->setString(EmulationAgentState::kNavigatorPlatform,
-                    platform.fromMaybe(String()));
+  user_agent_override_.Set(user_agent);
+  accept_language_override_.Set(accept_language.fromMaybe(String()));
+  navigator_platform_override_.Set(platform.fromMaybe(String()));
   if (web_local_frame_) {
     GetWebViewImpl()->GetPage()->GetSettings().SetNavigatorPlatformOverride(
-        platform.fromMaybe(String()));
+        navigator_platform_override_.Get());
   }
   return Response::OK();
 }
 
 void InspectorEmulationAgent::ApplyAcceptLanguageOverride(String* accept_lang) {
-  String accept_lang_override;
-  state_->getString(EmulationAgentState::kAcceptLanguageOverride,
-                    &accept_lang_override);
-  if (!accept_lang_override.IsEmpty())
-    *accept_lang = accept_lang_override;
+  if (!accept_language_override_.Get().IsEmpty())
+    *accept_lang = accept_language_override_.Get();
 }
 
 void InspectorEmulationAgent::ApplyUserAgentOverride(String* user_agent) {
-  String user_agent_override;
-  state_->getString(EmulationAgentState::kUserAgentOverride,
-                    &user_agent_override);
-  if (!user_agent_override.IsEmpty())
-    *user_agent = user_agent_override;
+  if (!user_agent_override_.Get().IsEmpty())
+    *user_agent = user_agent_override_.Get();
 }
 
 void InspectorEmulationAgent::InnerEnable() {
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
index 5d47a67e..0148ba8 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.h
@@ -123,6 +123,15 @@
   bool enabled_ = false;
 
   InspectorAgentState::String default_background_color_override_rgba_;
+  InspectorAgentState::Boolean script_execution_disabled_;
+  InspectorAgentState::Boolean scrollbars_hidden_;
+  InspectorAgentState::Boolean document_cookie_disabled_;
+  InspectorAgentState::Boolean touch_event_emulation_enabled_;
+  InspectorAgentState::Integer max_touch_points_;
+  InspectorAgentState::String emulated_media_;
+  InspectorAgentState::String navigator_platform_override_;
+  InspectorAgentState::String user_agent_override_;
+  InspectorAgentState::String accept_language_override_;
   DISALLOW_COPY_AND_ASSIGN(InspectorEmulationAgent);
 };
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_highlight.cc b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
index 71a3169..d1a996e 100644
--- a/third_party/blink/renderer/core/inspector/inspector_highlight.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_highlight.cc
@@ -15,10 +15,11 @@
 #include "third_party/blink/renderer/core/layout/layout_inline.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/graphics/path.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
+#include "third_party/blink/renderer/platform/layout_test_support.h"
 
 namespace blink {
 
@@ -418,11 +419,17 @@
   if (!layout_object)
     return;
 
+  // Just for testing, invert the content color for nodes rendered by LayoutNG.
+  // TODO(layout-dev): Stop munging the color before NG ships. crbug.com/869866
+  Color content_color = layout_object->IsLayoutNGObject() &&
+                                !LayoutTestSupport::IsRunningLayoutTest()
+                            ? Color(highlight_config.content.Rgb() ^ 0x00ffffff)
+                            : highlight_config.content;
+
   Vector<FloatQuad> svg_quads;
   if (BuildSVGQuads(node, svg_quads)) {
     for (size_t i = 0; i < svg_quads.size(); ++i) {
-      AppendQuad(svg_quads[i], highlight_config.content,
-                 highlight_config.content_outline);
+      AppendQuad(svg_quads[i], content_color, highlight_config.content_outline);
     }
     return;
   }
@@ -430,8 +437,8 @@
   FloatQuad content, padding, border, margin;
   if (!BuildNodeQuads(node, &content, &padding, &border, &margin))
     return;
-  AppendQuad(content, highlight_config.content,
-             highlight_config.content_outline, "content");
+  AppendQuad(content, content_color, highlight_config.content_outline,
+             "content");
   AppendQuad(padding, highlight_config.padding, Color::kTransparent, "padding");
   AppendQuad(border, highlight_config.border, Color::kTransparent, "border");
   AppendQuad(margin, highlight_config.margin, Color::kTransparent, "margin");
diff --git a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
index 85c4644..e97a0b7 100644
--- a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
@@ -22,9 +22,6 @@
 using protocol::Response;
 
 namespace {
-
-static const char kPerformanceAgentEnabled[] = "PerformanceAgentEnabled";
-
 constexpr bool isPlural(const char* str, int len) {
   return len > 1 && str[len - 2] == 's';
 }
@@ -40,34 +37,37 @@
 
 InspectorPerformanceAgent::InspectorPerformanceAgent(
     InspectedFrames* inspected_frames)
-    : inspected_frames_(inspected_frames) {}
+    : inspected_frames_(inspected_frames),
+      enabled_(&agent_state_, /*default_value=*/false) {}
 
 InspectorPerformanceAgent::~InspectorPerformanceAgent() = default;
 
 void InspectorPerformanceAgent::Restore() {
-  if (state_->booleanProperty(kPerformanceAgentEnabled, false))
-    enable();
+  if (enabled_.Get())
+    InnerEnable();
 }
 
-protocol::Response InspectorPerformanceAgent::enable() {
-  if (enabled_)
-    return Response::OK();
-  enabled_ = true;
-  state_->setBoolean(kPerformanceAgentEnabled, true);
+void InspectorPerformanceAgent::InnerEnable() {
   instrumenting_agents_->addInspectorPerformanceAgent(this);
   Platform::Current()->CurrentThread()->AddTaskTimeObserver(this);
   layout_start_ticks_ = TimeTicks();
   recalc_style_start_ticks_ = TimeTicks();
   task_start_ticks_ = TimeTicks();
   script_start_ticks_ = TimeTicks();
+}
+
+protocol::Response InspectorPerformanceAgent::enable() {
+  if (enabled_.Get())
+    return Response::OK();
+  enabled_.Set(true);
+  InnerEnable();
   return Response::OK();
 }
 
 protocol::Response InspectorPerformanceAgent::disable() {
-  if (!enabled_)
+  if (!enabled_.Get())
     return Response::OK();
-  enabled_ = false;
-  state_->setBoolean(kPerformanceAgentEnabled, false);
+  enabled_.Clear();
   instrumenting_agents_->removeInspectorPerformanceAgent(this);
   Platform::Current()->CurrentThread()->RemoveTaskTimeObserver(this);
   return Response::OK();
@@ -87,7 +87,7 @@
 Response InspectorPerformanceAgent::getMetrics(
     std::unique_ptr<protocol::Array<protocol::Performance::Metric>>*
         out_result) {
-  if (!enabled_) {
+  if (!enabled_.Get()) {
     *out_result = protocol::Array<protocol::Performance::Metric>::create();
     return Response::OK();
   }
@@ -148,7 +148,7 @@
 }
 
 void InspectorPerformanceAgent::ConsoleTimeStamp(const String& title) {
-  if (!enabled_)
+  if (!enabled_.Get())
     return;
   std::unique_ptr<protocol::Array<protocol::Performance::Metric>> metrics;
   getMetrics(&metrics);
diff --git a/third_party/blink/renderer/core/inspector/inspector_performance_agent.h b/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
index 3e0d3f87..c62b7bba 100644
--- a/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_performance_agent.h
@@ -65,9 +65,9 @@
   explicit InspectorPerformanceAgent(InspectedFrames*);
   void ScriptStarts();
   void ScriptEnds();
+  void InnerEnable();
 
   Member<InspectedFrames> inspected_frames_;
-  bool enabled_ = false;
   TimeDelta layout_duration_;
   TimeTicks layout_start_ticks_;
   TimeDelta recalc_style_duration_;
@@ -80,7 +80,7 @@
   unsigned long long recalc_style_count_ = 0;
   int script_call_depth_ = 0;
   int layout_depth_ = 0;
-
+  InspectorAgentState::Boolean enabled_;
   DISALLOW_COPY_AND_ASSIGN(InspectorPerformanceAgent);
 };
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_session_state.cc b/third_party/blink/renderer/core/inspector/inspector_session_state.cc
index c347068..6f78889 100644
--- a/third_party/blink/renderer/core/inspector/inspector_session_state.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_session_state.cc
@@ -106,7 +106,7 @@
     f->InitFrom(session_state);
 }
 
-void InspectorAgentState::Clear() {
+void InspectorAgentState::ClearAllFields() {
   for (Field* f : fields_)
     f->Clear();
 }
diff --git a/third_party/blink/renderer/core/inspector/inspector_session_state.h b/third_party/blink/renderer/core/inspector/inspector_session_state.h
index 77821dc..ec2fccc0 100644
--- a/third_party/blink/renderer/core/inspector/inspector_session_state.h
+++ b/third_party/blink/renderer/core/inspector/inspector_session_state.h
@@ -270,7 +270,7 @@
   void InitFrom(InspectorSessionState* session_state);
 
   // Clears all fields registered with this InspectorAgentState instance.
-  void Clear();
+  void ClearAllFields();
 
  private:
   const WTF::String domain_name_;
diff --git a/third_party/blink/renderer/core/inspector/inspector_session_state_test.cc b/third_party/blink/renderer/core/inspector/inspector_session_state_test.cc
index 44be80d..cae7ce5 100644
--- a/third_party/blink/renderer/core/inspector/inspector_session_state_test.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_session_state_test.cc
@@ -236,7 +236,7 @@
     AgentWithMapFields maps_agent;
     maps_agent.agent_state_.InitFrom(&session_state);
     maps_agent.strings_.Set("foo", "bar");
-    maps_agent.agent_state_.Clear();
+    maps_agent.agent_state_.ClearAllFields();
 
     EXPECT_TRUE(maps_agent.doubles_.IsEmpty());
     EXPECT_TRUE(maps_agent.strings_.IsEmpty());
@@ -250,7 +250,7 @@
     InspectorSessionState session_state(dev_tools_session.CloneCookie());
     AgentWithSimpleFields simple_agent;
     simple_agent.agent_state_.InitFrom(&session_state);
-    simple_agent.agent_state_.Clear();
+    simple_agent.agent_state_.ClearAllFields();
 
     dev_tools_session.ApplyUpdates(session_state.TakeUpdates());
   }
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc
index c45642f..e930b724 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.cc
+++ b/third_party/blink/renderer/core/layout/hit_test_result.cc
@@ -39,9 +39,9 @@
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/input_type_names.h"
 #include "third_party/blink/renderer/core/layout/layout_image.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/core/svg/svg_element.h"
 #include "third_party/blink/renderer/platform/geometry/region.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index 896588a..e54ad16 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -734,6 +734,8 @@
   // block-flow and inline-direction axis.
   struct LogicalExtentComputedValues {
     STACK_ALLOCATED();
+
+   public:
     LogicalExtentComputedValues() = default;
 
     // This is the dimension in the measured direction
diff --git a/third_party/blink/renderer/core/layout/layout_frame_set.h b/third_party/blink/renderer/core/layout/layout_frame_set.h
index 8de95dd..828edbf 100644
--- a/third_party/blink/renderer/core/layout/layout_frame_set.h
+++ b/third_party/blink/renderer/core/layout/layout_frame_set.h
@@ -41,6 +41,8 @@
 
 struct FrameEdgeInfo {
   STACK_ALLOCATED();
+
+ public:
   FrameEdgeInfo(bool prevent_resize = false, bool allow_border = true)
       : prevent_resize_(4), allow_border_(4) {
     prevent_resize_.Fill(prevent_resize);
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index 22a4073..85cf7b4 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -1585,18 +1585,24 @@
     const LayoutPoint& additional_offset,
     IncludeBlockVisualOverflowOrNot include_block_overflows) const {
   if (LayoutBoxModelObject* continuation = Continuation()) {
+    if (continuation->NeedsLayout()) {
+      // TODO(mstensho): Prevent this from happening. Before we can get the
+      // outlines of an object, we obviously need to have laid it out. We may
+      // currently end up in a situation where the continuation's layout isn't
+      // up-to-date here. This happens when calculating the overflow rectangle
+      // while laying out one of the earlier anonymous blocks that form the
+      // continuation chain. The outlines from the continuations should probably
+      // not be part of the overflow rectangle of that anonymous block at
+      // all. Yet, here we are. Bail.
+      return;
+    }
+    LayoutPoint offset = additional_offset;
     if (continuation->IsInline())
-      continuation->AddOutlineRects(
-          rects,
-          additional_offset + (continuation->ContainingBlock()->Location() -
-                               ContainingBlock()->Location()),
-          include_block_overflows);
+      offset += continuation->ContainingBlock()->Location();
     else
-      continuation->AddOutlineRects(
-          rects,
-          additional_offset + (ToLayoutBox(continuation)->Location() -
-                               ContainingBlock()->Location()),
-          include_block_overflows);
+      offset += ToLayoutBox(continuation)->Location();
+    offset -= ContainingBlock()->Location();
+    continuation->AddOutlineRects(rects, offset, include_block_overflows);
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 54d0f36b..6be5a8e 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -92,6 +92,7 @@
 #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/core/style/content_data.h"
 #include "third_party/blink/renderer/core/style/cursor_data.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
@@ -101,7 +102,6 @@
 #include "third_party/blink/renderer/platform/instance_counters.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/transforms/transform_state.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 851b7788..fb7fe55 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -619,6 +619,7 @@
   virtual bool IsLayoutFlowThread() const { return false; }
   virtual bool IsLayoutInline() const { return false; }
   virtual bool IsLayoutEmbeddedContent() const { return false; }
+  virtual bool IsLayoutNGObject() const { return false; }
 
   bool IsDocumentElement() const {
     return GetDocument().documentElement() == node_;
diff --git a/third_party/blink/renderer/core/layout/layout_scrollbar.h b/third_party/blink/renderer/core/layout/layout_scrollbar.h
index 9e0463e..1350555 100644
--- a/third_party/blink/renderer/core/layout/layout_scrollbar.h
+++ b/third_party/blink/renderer/core/layout/layout_scrollbar.h
@@ -26,9 +26,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SCROLLBAR_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SCROLLBAR_H_
 
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/layout/layout_scrollbar_theme.cc b/third_party/blink/renderer/core/layout/layout_scrollbar_theme.cc
index c358de3..477c563 100644
--- a/third_party/blink/renderer/core/layout/layout_scrollbar_theme.cc
+++ b/third_party/blink/renderer/core/layout/layout_scrollbar_theme.cc
@@ -27,9 +27,9 @@
 
 #include "third_party/blink/renderer/core/layout/layout_scrollbar.h"
 #include "third_party/blink/renderer/core/paint/scrollbar_painter.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/layout/layout_scrollbar_theme.h b/third_party/blink/renderer/core/layout/layout_scrollbar_theme.h
index fe60d6d1..f158bb4 100644
--- a/third_party/blink/renderer/core/layout/layout_scrollbar_theme.h
+++ b/third_party/blink/renderer/core/layout/layout_scrollbar_theme.h
@@ -26,7 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SCROLLBAR_THEME_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_SCROLLBAR_THEME_H_
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/layout_text_control.cc b/third_party/blink/renderer/core/layout/layout_text_control.cc
index 5799f09f..921bc92 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_control.cc
@@ -26,7 +26,7 @@
 #include "third_party/blink/renderer/core/html/forms/text_control_element.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/page/page.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/layout_theme.h b/third_party/blink/renderer/core/layout/layout_theme.h
index 363ef2d9a..61e2790d 100644
--- a/third_party/blink/renderer/core/layout/layout_theme.h
+++ b/third_party/blink/renderer/core/layout/layout_theme.h
@@ -45,7 +45,7 @@
 class LengthSize;
 class Locale;
 class Node;
-class PlatformChromeClient;
+class ChromeClient;
 class Theme;
 class ThemePainter;
 
@@ -190,7 +190,7 @@
   virtual int PopupInternalPaddingStart(const ComputedStyle&) const {
     return 0;
   }
-  virtual int PopupInternalPaddingEnd(const PlatformChromeClient*,
+  virtual int PopupInternalPaddingEnd(const ChromeClient*,
                                       const ComputedStyle&) const {
     return 0;
   }
diff --git a/third_party/blink/renderer/core/layout/layout_theme_default.cc b/third_party/blink/renderer/core/layout/layout_theme_default.cc
index 1c2a0a8..18bd95d 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_default.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_default.cc
@@ -28,11 +28,11 @@
 #include "third_party/blink/public/platform/web_theme_engine.h"
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/core/layout/layout_theme_font_provider.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/data_resource_helper.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/layout_test_support.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
@@ -317,7 +317,7 @@
 }
 
 int LayoutThemeDefault::PopupInternalPaddingEnd(
-    const PlatformChromeClient* client,
+    const ChromeClient* client,
     const ComputedStyle& style) const {
   if (style.Appearance() == kNoControlPart)
     return 0;
@@ -344,7 +344,7 @@
 }
 
 float LayoutThemeDefault::ClampedMenuListArrowPaddingSize(
-    const PlatformChromeClient* client,
+    const ChromeClient* client,
     const ComputedStyle& style) const {
   if (cached_menu_list_arrow_padding_size_ > 0 &&
       style.EffectiveZoom() == cached_menu_list_arrow_zoom_level_)
diff --git a/third_party/blink/renderer/core/layout/layout_theme_default.h b/third_party/blink/renderer/core/layout/layout_theme_default.h
index 994d74e..fcbd053 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_default.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_default.h
@@ -112,7 +112,7 @@
 
   // These methods define the padding for the MenuList's inner block.
   int PopupInternalPaddingStart(const ComputedStyle&) const override;
-  int PopupInternalPaddingEnd(const PlatformChromeClient*,
+  int PopupInternalPaddingEnd(const ChromeClient*,
                               const ComputedStyle&) const override;
   int PopupInternalPaddingTop(const ComputedStyle&) const override;
   int PopupInternalPaddingBottom(const ComputedStyle&) const override;
@@ -121,7 +121,7 @@
   // thickness, which is 3px or 4px, and we use the value from the default Aura
   // theme.
   int MenuListArrowWidthInDIP() const;
-  float ClampedMenuListArrowPaddingSize(const PlatformChromeClient*,
+  float ClampedMenuListArrowPaddingSize(const ChromeClient*,
                                         const ComputedStyle&) const;
 
   static void SetSelectionColors(unsigned active_background_color,
diff --git a/third_party/blink/renderer/core/layout/layout_theme_mac.h b/third_party/blink/renderer/core/layout/layout_theme_mac.h
index 6ec0a66..15b4973 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_mac.h
+++ b/third_party/blink/renderer/core/layout/layout_theme_mac.h
@@ -77,7 +77,7 @@
   int SliderTickOffsetFromTrackCenter() const override;
 
   int PopupInternalPaddingStart(const ComputedStyle&) const override;
-  int PopupInternalPaddingEnd(const PlatformChromeClient*,
+  int PopupInternalPaddingEnd(const ChromeClient*,
                               const ComputedStyle&) const override;
   int PopupInternalPaddingTop(const ComputedStyle&) const override;
   int PopupInternalPaddingBottom(const ComputedStyle&) const override;
diff --git a/third_party/blink/renderer/core/layout/layout_theme_mac.mm b/third_party/blink/renderer/core/layout/layout_theme_mac.mm
index 00c31f0..3c60c8a 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_mac.mm
+++ b/third_party/blink/renderer/core/layout/layout_theme_mac.mm
@@ -761,7 +761,7 @@
   return 0;
 }
 
-int LayoutThemeMac::PopupInternalPaddingEnd(const PlatformChromeClient*,
+int LayoutThemeMac::PopupInternalPaddingEnd(const ChromeClient*,
                                             const ComputedStyle& style) const {
   if (style.Appearance() == kMenulistPart)
     return PopupButtonPadding(
diff --git a/third_party/blink/renderer/core/layout/layout_view.h b/third_party/blink/renderer/core/layout/layout_view.h
index c2a41d8b..f29f4b3 100644
--- a/third_party/blink/renderer/core/layout/layout_view.h
+++ b/third_party/blink/renderer/core/layout/layout_view.h
@@ -28,9 +28,9 @@
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_state.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/pod_free_list_arena.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/line/layout_text_info.h b/third_party/blink/renderer/core/layout/line/layout_text_info.h
index cd3d0b5..80979ce2 100644
--- a/third_party/blink/renderer/core/layout/line/layout_text_info.h
+++ b/third_party/blink/renderer/core/layout/line/layout_text_info.h
@@ -33,6 +33,8 @@
 
 struct LayoutTextInfo {
   STACK_ALLOCATED();
+
+ public:
   LayoutTextInfo() : text_(nullptr), font_(nullptr) {}
 
   LineLayoutText text_;
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
index 1907888..1536cd4 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h
@@ -59,6 +59,8 @@
     return GetDerivedGeometry().LastFloatBlockStart();
   }
 
+  bool IsEmpty() const { return !num_exclusions_; }
+
   bool operator==(const NGExclusionSpace& other) const;
   bool operator!=(const NGExclusionSpace& other) const {
     return !(*this == other);
diff --git a/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h b/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h
index ee9f8b1..cf853395 100644
--- a/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h
+++ b/third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h
@@ -16,6 +16,7 @@
 struct CORE_EXPORT NGLineLayoutOpportunity {
   STACK_ALLOCATED();
 
+ public:
   NGLineLayoutOpportunity() {}
   NGLineLayoutOpportunity(LayoutUnit inline_size)
       : line_right_offset(inline_size), float_line_right_offset(inline_size) {}
diff --git a/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h b/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
index 8ea808210..5a456f2 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h
@@ -26,6 +26,7 @@
   bool IsOfType(LayoutObjectType type) const override {
     return type == kLayoutObjectNGText || LayoutText::IsOfType(type);
   }
+  bool IsLayoutNGObject() const override { return true; }
 
   bool HasValidLayout() const { return valid_ng_items_; }
   const Vector<NGInlineItem*>& InlineItems() const {
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
index bd4125a..24658d7 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -36,6 +36,8 @@
 // Represents a data struct that are needed for 'text-align' and justifications.
 struct NGLineAlign {
   STACK_ALLOCATED();
+
+ public:
   NGLineAlign(const NGLineInfo&);
   NGLineAlign() = delete;
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h b/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h
index 6ff73e9..45f1acd8 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h
@@ -18,6 +18,7 @@
   void UpdateBlockLayout(bool relayout_children) override;
 
   bool IsFlexibleBox() const final { return true; }
+  bool IsLayoutNGObject() const override { return true; }
   const char* GetName() const override { return "LayoutNGFlexibleBox"; }
 
  protected:
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
index 9a4fcba..807454bf 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
@@ -30,6 +30,8 @@
   explicit LayoutNGMixin(Element* element);
   ~LayoutNGMixin() override;
 
+  bool IsLayoutNGObject() const override { return true; }
+
   NGInlineNodeData* TakeNGInlineNodeData() override;
   NGInlineNodeData* GetNGInlineNodeData() const override;
   void ResetNGInlineNodeData() override;
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
index a060bb1..cb5c14d 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h
@@ -26,6 +26,7 @@
     return StyleRef().ListStyleImage() &&
            !StyleRef().ListStyleImage()->ErrorOccurred();
   }
+  bool IsLayoutNGObject() const override { return true; }
 
   void UpdateMarkerTextIfNeeded() {
     if (marker_ && !is_marker_text_updated_ && !IsMarkerImage())
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
index 469de1b..c85d0a8 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker.h
@@ -33,6 +33,8 @@
 
   bool IsContentImage() const;
 
+  bool IsLayoutNGObject() const override { return true; }
+
   LayoutObject* SymbolMarkerLayoutText() const;
 
   // Marker text with suffix, e.g. "1. ", for use in accessibility.
diff --git a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h
index 8d38ae2..44af6cb 100644
--- a/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h
+++ b/third_party/blink/renderer/core/layout/ng/list/layout_ng_list_marker_image.h
@@ -17,6 +17,8 @@
   explicit LayoutNGListMarkerImage(Element*);
   static LayoutNGListMarkerImage* CreateAnonymous(Document*);
 
+  bool IsLayoutNGObject() const override { return true; }
+
  private:
   bool IsOfType(LayoutObjectType) const override;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h b/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h
index 16d9404..0b2bfd2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_child_iterator.h
@@ -47,6 +47,7 @@
 struct NGBlockChildIterator::Entry {
   STACK_ALLOCATED();
 
+ public:
   Entry(NGLayoutInputNode node, NGBreakToken* token)
       : node(node), token(token) {}
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 9d70913..828cf16 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -682,8 +682,11 @@
 
   PropagateBaselinesFromChildren();
 
-  DCHECK(exclusion_space_);
-  container_builder_.SetExclusionSpace(std::move(exclusion_space_));
+  // An exclusion space is confined to nodes within the same formatting context.
+  if (!ConstraintSpace().IsNewFormattingContext()) {
+    DCHECK(exclusion_space_);
+    container_builder_.SetExclusionSpace(std::move(exclusion_space_));
+  }
 
   if (ConstraintSpace().UseFirstLineStyle())
     container_builder_.SetStyleVariant(NGStyleVariant::kFirstLine);
@@ -1045,9 +1048,18 @@
         child_available_size_.block_size};
     auto child_space =
         CreateConstraintSpaceForChild(child, child_data, child_available_size);
+
+    // All formatting context roots (like this child) should start with an empty
+    // exclusion space.
+    DCHECK(child_space->ExclusionSpace().IsEmpty());
+
     scoped_refptr<NGLayoutResult> layout_result =
         child.Layout(*child_space, child_break_token);
 
+    // Since this child establishes a new formatting context, no exclusion space
+    // should be returned.
+    DCHECK(!layout_result->ExclusionSpace());
+
     DCHECK(layout_result->PhysicalFragment());
     NGFragment fragment(ConstraintSpace().GetWritingMode(),
                         *layout_result->PhysicalFragment());
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
index cc9c2da..13c0b407 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_node.cc
@@ -716,13 +716,6 @@
   builder.SetBlockSize(box_size.block_size);
   builder.SetPadding(ComputePadding(constraint_space, box_->StyleRef()));
 
-  // For now we copy the exclusion space straight through, this is incorrect
-  // but needed as not all elements which participate in a BFC are switched
-  // over to LayoutNG yet.
-  // TODO(ikilpatrick): Remove this once the above isn't true.
-  builder.SetExclusionSpace(
-      std::make_unique<NGExclusionSpace>(constraint_space.ExclusionSpace()));
-
   CopyBaselinesFromOldLayout(constraint_space, &builder);
   UpdateShapeOutsideInfoIfNeeded(
       constraint_space.PercentageResolutionSize().inline_size);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
index f89781d..20002f8 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.h
@@ -59,6 +59,8 @@
   //    Inline containing block is positioned wrt default containing block.
   struct ContainingBlockInfo {
     STACK_ALLOCATED();
+
+   public:
     // Containing block style.
     const ComputedStyle* style;
     // Logical in containing block coordinates.
diff --git a/third_party/blink/renderer/core/layout/scrollbars_test.cc b/third_party/blink/renderer/core/layout/scrollbars_test.cc
index e35ef1a..5aef48d 100644
--- a/third_party/blink/renderer/core/layout/scrollbars_test.cc
+++ b/third_party/blink/renderer/core/layout/scrollbars_test.cc
@@ -20,10 +20,10 @@
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
diff --git a/third_party/blink/renderer/core/layout/shapes/shape.h b/third_party/blink/renderer/core/layout/shapes/shape.h
index b940726..45be4d0 100644
--- a/third_party/blink/renderer/core/layout/shapes/shape.h
+++ b/third_party/blink/renderer/core/layout/shapes/shape.h
@@ -44,6 +44,8 @@
 
 struct LineSegment {
   STACK_ALLOCATED();
+
+ public:
   LineSegment() : logical_left(0), logical_right(0), is_valid(false) {}
 
   LineSegment(float logical_left, float logical_right)
@@ -68,6 +70,8 @@
  public:
   struct DisplayPaths {
     STACK_ALLOCATED();
+
+   public:
     Path shape;
     Path margin_shape;
   };
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h
index 9f096f3..e0a7f6d 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h
@@ -74,6 +74,8 @@
 // is set to a fallback color.
 struct SVGPaintDescription {
   STACK_ALLOCATED();
+
+ public:
   SVGPaintDescription()
       : resource(nullptr), is_valid(false), has_fallback(false) {}
   SVGPaintDescription(Color color)
diff --git a/third_party/blink/renderer/core/layout/text_autosizer.h b/third_party/blink/renderer/core/layout/text_autosizer.h
index ad1deec..33a925a 100644
--- a/third_party/blink/renderer/core/layout/text_autosizer.h
+++ b/third_party/blink/renderer/core/layout/text_autosizer.h
@@ -212,6 +212,8 @@
 
   struct FingerprintSourceData {
     STACK_ALLOCATED();
+
+   public:
     FingerprintSourceData()
         : parent_hash_(0),
           qualified_name_hash_(0),
diff --git a/third_party/blink/renderer/core/loader/appcache/application_cache_host.h b/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
index 53568a1..7138430 100644
--- a/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
+++ b/third_party/blink/renderer/core/loader/appcache/application_cache_host.h
@@ -82,6 +82,8 @@
 
   struct CacheInfo {
     STACK_ALLOCATED();
+
+   public:
     CacheInfo(const KURL& manifest,
               double creation_time,
               double update_time,
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index 9cef095..85298ed 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -81,6 +81,7 @@
 #include "third_party/blink/renderer/core/page/viewport_description.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
 #include "third_party/blink/renderer/core/xml/parser/xml_document_parser.h"
 #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
@@ -94,7 +95,6 @@
 #include "third_party/blink/renderer/platform/network/network_utils.h"
 #include "third_party/blink/renderer/platform/plugins/plugin_script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
diff --git a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
index be9b52cb..a3c7196 100644
--- a/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
+++ b/third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
@@ -51,8 +51,8 @@
   base::TimeTicks commit_start_time = WTF::CurrentTimeTicks();
   current_frame_damage_rect_.join(damage_rect);
   GetOrCreateResourceDispatcher()->DispatchFrameSync(
-      std::move(canvas_resource), commit_start_time,
-      current_frame_damage_rect_);
+      std::move(canvas_resource), commit_start_time, current_frame_damage_rect_,
+      !RenderingContext()->IsOriginTopLeft() /* needs_vertical_flip */);
   current_frame_damage_rect_ = SkIRect::MakeEmpty();
 }
 
@@ -391,9 +391,9 @@
   if (current_frame_damage_rect_.isEmpty() || !canvas_resource)
     return;
   base::TimeTicks commit_start_time = WTF::CurrentTimeTicks();
-  GetOrCreateResourceDispatcher()->DispatchFrame(std::move(canvas_resource),
-                                                 commit_start_time,
-                                                 current_frame_damage_rect_);
+  GetOrCreateResourceDispatcher()->DispatchFrame(
+      std::move(canvas_resource), commit_start_time, current_frame_damage_rect_,
+      !RenderingContext()->IsOriginTopLeft() /* needs_vertical_flip */);
   current_frame_damage_rect_ = SkIRect::MakeEmpty();
 }
 
diff --git a/third_party/blink/renderer/core/page/chrome_client.cc b/third_party/blink/renderer/core/page/chrome_client.cc
index 9849c205..58b649a 100644
--- a/third_party/blink/renderer/core/page/chrome_client.cc
+++ b/third_party/blink/renderer/core/page/chrome_client.cc
@@ -41,7 +41,6 @@
 
 void ChromeClient::Trace(blink::Visitor* visitor) {
   visitor->Trace(last_mouse_over_node_);
-  PlatformChromeClient::Trace(visitor);
 }
 
 void ChromeClient::InstallSupplements(LocalFrame& frame) {
diff --git a/third_party/blink/renderer/core/page/chrome_client.h b/third_party/blink/renderer/core/page/chrome_client.h
index 9bc3438..e87423f4 100644
--- a/third_party/blink/renderer/core/page/chrome_client.h
+++ b/third_party/blink/renderer/core/page/chrome_client.h
@@ -43,7 +43,6 @@
 #include "third_party/blink/renderer/platform/cursor.h"
 #include "third_party/blink/renderer/platform/graphics/touch_action.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/text/text_direction.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
@@ -95,8 +94,19 @@
 struct WebScreenInfo;
 struct WebWindowFeatures;
 
-class CORE_EXPORT ChromeClient : public PlatformChromeClient {
+class CORE_EXPORT ChromeClient
+    : public GarbageCollectedFinalized<ChromeClient> {
+  DISALLOW_COPY_AND_ASSIGN(ChromeClient);
+
  public:
+  virtual ~ChromeClient() = default;
+
+  // Converts the scalar value from the window coordinates to the viewport
+  // scale.
+  virtual float WindowToViewportScalar(const float) const = 0;
+
+  virtual bool IsPopup() { return false; }
+
   virtual void ChromeDestroyed() = 0;
 
   // Requests the host invalidate the contents.
@@ -184,10 +194,8 @@
 
   virtual WebViewImpl* GetWebView() const = 0;
 
-  // Methods used by PlatformChromeClient.
   virtual WebScreenInfo GetScreenInfo() const = 0;
   virtual void SetCursor(const Cursor&, LocalFrame* local_root) = 0;
-  // End methods used by PlatformChromeClient.
 
   virtual void SetCursorOverridden(bool) = 0;
 
@@ -361,10 +369,10 @@
     std::move(callback).Run(false);
   }
 
-  void Trace(blink::Visitor*) override;
+  virtual void Trace(blink::Visitor*);
 
  protected:
-  ~ChromeClient() override = default;
+  ChromeClient() = default;
 
   virtual void ShowMouseOverURL(const HitTestResult&) = 0;
   virtual void SetWindowRect(const IntRect&, LocalFrame&) = 0;
@@ -391,12 +399,6 @@
   FRIEND_TEST_ALL_PREFIXES(ChromeClientTest, SetToolTipFlood);
 };
 
-inline ChromeClient* ToChromeClient(PlatformChromeClient* client) {
-  // In production code, a PlatformChromeClient instance is always a
-  // ChromeClient instance.
-  return static_cast<ChromeClient*>(client);
-}
-
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_CHROME_CLIENT_H_
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index c1140f5..e870e98 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -68,14 +68,14 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/core/svg/graphics/svg_image_chrome_client.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/plugins/plugin_data.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/page/print_context_test.cc b/third_party/blink/renderer/core/page/print_context_test.cc
index 0477c0d..7aa89bc 100644
--- a/third_party/blink/renderer/core/page/print_context_test.cc
+++ b/third_party/blink/renderer/core/page/print_context_test.cc
@@ -14,12 +14,12 @@
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 #include "third_party/skia/include/core/SkCanvas.h"
diff --git a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
index e6511d0..616035d 100644
--- a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
+++ b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
@@ -21,9 +21,9 @@
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/page/scrolling/scroll_state.h b/third_party/blink/renderer/core/page/scrolling/scroll_state.h
index 3fdcde6..e9f67a28 100644
--- a/third_party/blink/renderer/core/page/scrolling/scroll_state.h
+++ b/third_party/blink/renderer/core/page/scrolling/scroll_state.h
@@ -10,9 +10,9 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/page/scrolling/scroll_state_init.h"
+#include "third_party/blink/renderer/core/scroll/scroll_state_data.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_state_data.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
index 4b5cc04..3ff148e 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.cc
@@ -64,15 +64,15 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/transforms/transform_state.h"
 #if defined(OS_MACOSX)
-#include "third_party/blink/renderer/platform/mac/scroll_animator_mac.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_mac.h"
 #endif
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_layer_tree_view.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/scroll/main_thread_scrolling_reason.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 using blink::WebRect;
diff --git a/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.cc b/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.cc
index 380e5cc..b439f2c 100644
--- a/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.cc
+++ b/third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.cc
@@ -20,7 +20,7 @@
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/page/scrolling/viewport_scroll_callback.cc b/third_party/blink/renderer/core/page/scrolling/viewport_scroll_callback.cc
index b7fabf2..01150786 100644
--- a/third_party/blink/renderer/core/page/scrolling/viewport_scroll_callback.cc
+++ b/third_party/blink/renderer/core/page/scrolling/viewport_scroll_callback.cc
@@ -10,8 +10,8 @@
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/page/scrolling/overscroll_controller.h"
 #include "third_party/blink/renderer/core/page/scrolling/scroll_state.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/geometry/float_size.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/page/validation_message_client_impl.cc b/third_party/blink/renderer/core/page/validation_message_client_impl.cc
index ad3d52a6..e8fe7cb 100644
--- a/third_party/blink/renderer/core/page/validation_message_client_impl.cc
+++ b/third_party/blink/renderer/core/page/validation_message_client_impl.cc
@@ -38,7 +38,6 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/validation_message_overlay_delegate.h"
 #include "third_party/blink/renderer/platform/layout_test_support.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/page/viewport_test.cc b/third_party/blink/renderer/core/page/viewport_test.cc
index fff7fea..8451db59 100644
--- a/third_party/blink/renderer/core/page/viewport_test.cc
+++ b/third_party/blink/renderer/core/page/viewport_test.cc
@@ -45,13 +45,13 @@
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/viewport_description.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
 #include "third_party/blink/renderer/platform/geometry/int_point.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/length.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/testing/histogram_tester.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 1e9edab9..367ced6 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -1658,12 +1658,10 @@
         ToIntSize(overflow_clip_rect.Location()));
   }
 
-  IntSize scroll_size =
-      PixelSnappedIntRect(
-          LayoutRect(
-              LayoutPoint(owning_layer_.SubpixelAccumulation()),
-              LayoutSize(layout_box.ScrollWidth(), layout_box.ScrollHeight())))
-          .Size();
+  PaintLayerScrollableArea* scrollable_area = owning_layer_.GetScrollableArea();
+  IntSize scroll_size = scrollable_area->PixelSnappedContentsSize(
+      LayoutPoint(owning_layer_.SubpixelAccumulation()));
+
   // Ensure scrolling contents are at least as large as the scroll clip
   scroll_size = scroll_size.ExpandedTo(overflow_clip_rect.Size());
 
@@ -1693,16 +1691,8 @@
 
   scrolling_contents_layer_->SetSize(scroll_size);
 
-  IntPoint scrolling_contents_layer_offset_from_layout_object;
-  if (PaintLayerScrollableArea* scrollable_area =
-          owning_layer_.GetScrollableArea()) {
-    scrolling_contents_layer_offset_from_layout_object =
-        -scrollable_area->ScrollOrigin();
-  }
-  scrolling_contents_layer_offset_from_layout_object.MoveBy(
-      overflow_clip_rect.Location());
   scrolling_contents_layer_->SetOffsetFromLayoutObject(
-      ToIntSize(scrolling_contents_layer_offset_from_layout_object));
+      overflow_clip_rect.Location() - scrollable_area->ScrollOrigin());
 }
 
 void CompositedLayerMapping::UpdateChildClippingMaskLayerGeometry() {
@@ -2380,6 +2370,8 @@
 
 struct AnimatingData {
   STACK_ALLOCATED();
+
+ public:
   Persistent<Node> owning_node = nullptr;
   Persistent<Element> animating_element = nullptr;
   const ComputedStyle* animating_style = nullptr;
diff --git a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
index ee6e788..a3de791f 100644
--- a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
+++ b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.cc
@@ -4,11 +4,11 @@
 
 #include "third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_as_text.h"
 
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/geometry/geometry_as_json.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/logging_canvas.h"
 #include "third_party/blink/renderer/platform/json/json_values.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_stream.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/paint/decoration_info.h b/third_party/blink/renderer/core/paint/decoration_info.h
index bd56be6..a3147ce 100644
--- a/third_party/blink/renderer/core/paint/decoration_info.h
+++ b/third_party/blink/renderer/core/paint/decoration_info.h
@@ -23,6 +23,7 @@
 struct DecorationInfo final {
   STACK_ALLOCATED();
 
+ public:
   LayoutUnit width;
   FloatPoint local_origin;
   bool antialias;
diff --git a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
index 34c5b8d0e..d0c7c1d 100644
--- a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
@@ -22,7 +22,7 @@
 #include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 // Integration tests of canvas painting code (in SPv2 mode).
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h
index e1de32a..c212076 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment_traversal.h
@@ -27,6 +27,7 @@
 struct CORE_EXPORT NGPaintFragmentTraversalContext {
   STACK_ALLOCATED();
 
+ public:
   static NGPaintFragmentTraversalContext Create(const NGPaintFragment*);
 
   bool IsNull() const { return !parent; }
diff --git a/third_party/blink/renderer/core/paint/nine_piece_image_grid.h b/third_party/blink/renderer/core/paint/nine_piece_image_grid.h
index 8797bce..a014db7 100644
--- a/third_party/blink/renderer/core/paint/nine_piece_image_grid.h
+++ b/third_party/blink/renderer/core/paint/nine_piece_image_grid.h
@@ -71,6 +71,8 @@
 
   struct CORE_EXPORT NinePieceDrawInfo {
     STACK_ALLOCATED();
+
+   public:
     bool is_drawable;
     bool is_corner_piece;
     FloatRect destination;
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 5ac372b..26487286 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -409,7 +409,7 @@
 
   auto* frame_view = paint_invalidation_container.GetFrameView();
   DCHECK(!frame_view->GetFrame().OwnerLayoutObject());
-  if (auto* client = ToChromeClient(frame_view->GetChromeClient())) {
+  if (auto* client = frame_view->GetChromeClient()) {
     client->InvalidateRect(IntRect(IntPoint(), frame_view->Size()));
   }
 }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
index 1adbacd..b57ccd362 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_painting_info.h
@@ -81,6 +81,8 @@
 
 struct PaintLayerPaintingInfo {
   STACK_ALLOCATED();
+
+ public:
   PaintLayerPaintingInfo(PaintLayer* in_root_layer,
                          const LayoutRect& in_dirty_rect,
                          GlobalPaintFlags global_paint_flags,
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index ee72a82..117b3e7 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -89,12 +89,12 @@
 #include "third_party/blink/renderer/core/paint/find_paint_offset_and_visual_rect_needing_update.h"
 #include "third_party/blink/renderer/core/paint/paint_invalidator.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_fragment.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_alignment.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 
 namespace blink {
 
@@ -238,7 +238,7 @@
   return GetLayoutBox()->GetFrame()->ShouldThrottleRendering();
 }
 
-PlatformChromeClient* PaintLayerScrollableArea::GetChromeClient() const {
+ChromeClient* PaintLayerScrollableArea::GetChromeClient() const {
   if (HasBeenDisposed())
     return nullptr;
   if (Page* page = GetLayoutBox()->GetFrame()->GetPage())
@@ -681,7 +681,15 @@
 }
 
 IntSize PaintLayerScrollableArea::ContentsSize() const {
-  return IntSize(PixelSnappedScrollWidth(), PixelSnappedScrollHeight());
+  LayoutPoint offset(
+      GetLayoutBox()->ClientLeft() + GetLayoutBox()->Location().X(),
+      GetLayoutBox()->ClientTop() + GetLayoutBox()->Location().Y());
+  return PixelSnappedContentsSize(offset);
+}
+
+IntSize PaintLayerScrollableArea::PixelSnappedContentsSize(
+    const LayoutPoint& paint_offset) const {
+  return PixelSnappedIntSize(overflow_rect_.Size(), paint_offset);
 }
 
 void PaintLayerScrollableArea::ContentsResized() {
@@ -838,16 +846,6 @@
   return overflow_rect_.Height();
 }
 
-int PaintLayerScrollableArea::PixelSnappedScrollWidth() const {
-  return SnapSizeToPixel(ScrollWidth(), GetLayoutBox()->ClientLeft() +
-                                            GetLayoutBox()->Location().X());
-}
-
-int PaintLayerScrollableArea::PixelSnappedScrollHeight() const {
-  return SnapSizeToPixel(ScrollHeight(), GetLayoutBox()->ClientTop() +
-                                             GetLayoutBox()->Location().Y());
-}
-
 void PaintLayerScrollableArea::UpdateScrollOrigin() {
   // This should do nothing prior to first layout; the if-clause will catch
   // that.
@@ -2314,7 +2312,7 @@
 }
 
 bool PaintLayerScrollableArea::ScheduleAnimation() {
-  if (ChromeClient* client = ToChromeClient(GetChromeClient())) {
+  if (ChromeClient* client = GetChromeClient()) {
     client->ScheduleAnimation(GetLayoutBox()->GetFrame()->View());
     return true;
   }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
index e0190ee9..30d0760 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
@@ -50,9 +50,9 @@
 #include "third_party/blink/renderer/core/layout/scroll_anchor.h"
 #include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_fragment.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
@@ -263,7 +263,7 @@
   }
 
   bool IsThrottled() const override;
-  PlatformChromeClient* GetChromeClient() const override;
+  ChromeClient* GetChromeClient() const override;
 
   SmoothScrollSequencer* GetSmoothScrollSequencer() const override;
 
@@ -318,6 +318,11 @@
   LayoutRect VisibleScrollSnapportRect(
       IncludeScrollbarsInRect = kExcludeScrollbars) const override;
   IntSize ContentsSize() const override;
+
+  // Similar to |ContentsSize| but snapped considering |paint_offset| which can
+  // have subpixel accumulation.
+  IntSize PixelSnappedContentsSize(const LayoutPoint& paint_offset) const;
+
   void ContentsResized() override;
   IntPoint LastKnownMousePosition() const override;
   bool ScrollAnimatorEnabled() const override;
@@ -397,8 +402,6 @@
 
   LayoutUnit ScrollWidth() const;
   LayoutUnit ScrollHeight() const;
-  int PixelSnappedScrollWidth() const;
-  int PixelSnappedScrollHeight() const;
 
   int VerticalScrollbarWidth(
       OverlayScrollbarClipBehavior =
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
index 4d989ee5..5090c310 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area_test.cc
@@ -8,10 +8,10 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 using testing::_;
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 8112754..1707b6c 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1278,7 +1278,9 @@
           box.OverflowClipRect(context_.current.paint_offset));
       state.contents_rect = IntRect(
           -scrollable_area->ScrollOrigin() + state.container_rect.Location(),
-          scrollable_area->ContentsSize());
+          scrollable_area->PixelSnappedContentsSize(
+              context_.current.paint_offset));
+
       // In flipped blocks writing mode, if there is scrollbar on the right,
       // we move the contents to the left with extra amount of ScrollTranslation
       // (-VerticalScrollbarWidth, 0). As contents_rect is in the space of
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index 049e27b5..a9d3c8e 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -5632,4 +5632,31 @@
   }
 }
 
+TEST_P(PaintPropertyTreeBuilderTest, SubpixelPositionedScrollNode) {
+  SetBodyInnerHTML(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      #scroller {
+        position: relative;
+        top: 0.5625px;
+        width: 200px;
+        height: 200.8125px;
+        overflow: auto;
+      }
+      #space {
+        width: 1000px;
+        height: 200.8125px;
+      }
+    </style>
+    <div id="scroller">
+      <div id="space"></div>
+    </div>
+  )HTML");
+
+  const auto* properties = PaintPropertiesForElement("scroller");
+  const auto* scroll_node = properties->ScrollTranslation()->ScrollNode();
+  EXPECT_EQ(IntRect(0, 0, 200, 200), scroll_node->ContainerRect());
+  EXPECT_EQ(IntRect(0, 0, 1000, 200), scroll_node->ContentsRect());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/scrollable_area_painter.cc b/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
index e466c1d..2413291 100644
--- a/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
+++ b/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
@@ -5,19 +5,19 @@
 #include "third_party/blink/renderer/core/paint/scrollable_area_painter.h"
 
 #include "third_party/blink/renderer/core/layout/layout_view.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
 #include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/paint/scrollbar_painter.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/paint/scrollbar_painter.h b/third_party/blink/renderer/core/paint/scrollbar_painter.h
index e73f5072..e5962c33 100644
--- a/third_party/blink/renderer/core/paint/scrollbar_painter.h
+++ b/third_party/blink/renderer/core/paint/scrollbar_painter.h
@@ -6,8 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SCROLLBAR_PAINTER_H_
 
 #include "base/macros.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/paint/text_paint_style.h b/third_party/blink/renderer/core/paint/text_paint_style.h
index 4bf2199..b333afeb 100644
--- a/third_party/blink/renderer/core/paint/text_paint_style.h
+++ b/third_party/blink/renderer/core/paint/text_paint_style.h
@@ -15,6 +15,8 @@
 
 struct CORE_EXPORT TextPaintStyle {
   STACK_ALLOCATED();
+
+ public:
   Color current_color;
   Color fill_color;
   Color stroke_color;
diff --git a/third_party/blink/renderer/core/scroll/BUILD.gn b/third_party/blink/renderer/core/scroll/BUILD.gn
new file mode 100644
index 0000000..b0ae575d
--- /dev/null
+++ b/third_party/blink/renderer/core/scroll/BUILD.gn
@@ -0,0 +1,61 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/blink/renderer/core/core.gni")
+
+blink_core_sources("scroll") {
+  sources = [
+    "ns_scroller_imp_details.h",
+    "programmatic_scroll_animator.cc",
+    "programmatic_scroll_animator.h",
+    "scroll_animator.cc",
+    "scroll_animator.h",
+    "scroll_animator_base.cc",
+    "scroll_animator_base.h",
+    "scroll_animator_compositor_coordinator.cc",
+    "scroll_animator_compositor_coordinator.h",
+    "scroll_animator_mac.h",
+    "scroll_animator_mac.mm",
+    "scroll_customization.cc",
+    "scroll_customization.h",
+    "scroll_state_data.h",
+    "scrollable_area.cc",
+    "scrollable_area.h",
+    "scrollbar.cc",
+    "scrollbar.h",
+    "scrollbar_layer_delegate.cc",
+    "scrollbar_layer_delegate.h",
+    "scrollbar_test_suite.h",
+    "scrollbar_theme.cc",
+    "scrollbar_theme.h",
+    "scrollbar_theme_android.cc",
+    "scrollbar_theme_mac.h",
+    "scrollbar_theme_mac.mm",
+    "scrollbar_theme_mock.cc",
+    "scrollbar_theme_mock.h",
+    "scrollbar_theme_overlay.cc",
+    "scrollbar_theme_overlay.h",
+    "scrollbar_theme_overlay_mock.h",
+    "smooth_scroll_sequencer.cc",
+    "smooth_scroll_sequencer.h",
+    "web_scroll_into_view_params.cc",
+  ]
+
+  if (is_mac) {
+    sources -= [
+      "scroll_animator.cc",
+      "scroll_animator.h",
+    ]
+    sources += [
+      "web_scrollbar_theme.mm",
+    ]
+  }
+
+  if (use_default_render_theme) {
+    sources += [
+      "scrollbar_theme_aura.cc",
+      "scrollbar_theme_aura.h",
+    ]
+  }
+}
diff --git a/third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h b/third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h
similarity index 93%
rename from third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h
rename to third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h
index c5b7581..4ffa7b2 100644
--- a/third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h
+++ b/third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h
@@ -23,8 +23,8 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef WebCore_NSScrollerImpDetails_h
-#define WebCore_NSScrollerImpDetails_h
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_NS_SCROLLER_IMP_DETAILS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_NS_SCROLLER_IMP_DETAILS_H_
 
 #import <AvailabilityMacros.h>
 
@@ -91,4 +91,4 @@
 - (void)endScrollGesture;
 @end
 
-#endif  // WebCore_NSScrollerImpDetails_h
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_NS_SCROLLER_IMP_DETAILS_H_
diff --git a/third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.cc b/third_party/blink/renderer/core/scroll/programmatic_scroll_animator.cc
similarity index 96%
rename from third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.cc
rename to third_party/blink/renderer/core/scroll/programmatic_scroll_animator.cc
index ee1eb08..76be7aa 100644
--- a/third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.cc
+++ b/third_party/blink/renderer/core/scroll/programmatic_scroll_animator.cc
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
 
 #include <memory>
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
 #include "third_party/blink/renderer/platform/animation/compositor_scroll_offset_animation_curve.h"
 #include "third_party/blink/renderer/platform/geometry/int_size.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h b/third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h
similarity index 84%
rename from third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h
rename to third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h
index 150fac9..f85c4438 100644
--- a/third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h
+++ b/third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
 
 #include <memory>
+#include "third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
 
 namespace blink {
 
@@ -25,7 +24,7 @@
 // ScrollAnimatorMac.
 
 class ProgrammaticScrollAnimator : public ScrollAnimatorCompositorCoordinator {
-  WTF_MAKE_NONCOPYABLE(ProgrammaticScrollAnimator);
+  DISALLOW_COPY_AND_ASSIGN(ProgrammaticScrollAnimator);
 
  public:
   static ProgrammaticScrollAnimator* Create(ScrollableArea* scrollable_area) {
@@ -73,4 +72,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_PROGRAMMATIC_SCROLL_ANIMATOR_H_
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator.cc b/third_party/blink/renderer/core/scroll/scroll_animator.cc
similarity index 98%
rename from third_party/blink/renderer/platform/scroll/scroll_animator.cc
rename to third_party/blink/renderer/core/scroll/scroll_animator.cc
index d53a19e..f4ed1cb 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.cc
@@ -28,7 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator.h"
 
 #include <memory>
 
@@ -36,11 +36,11 @@
 #include "cc/animation/scroll_offset_animation_curve.h"
 #include "cc/layers/picture_layer.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/scroll/main_thread_scrolling_reason.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator.h b/third_party/blink/renderer/core/scroll/scroll_animator.h
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/scroll_animator.h
rename to third_party/blink/renderer/core/scroll/scroll_animator.h
index ef0292b..9d57d96 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator.h
@@ -28,14 +28,14 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_H_
 
 #include <memory>
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_client.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_delegate.h"
 #include "third_party/blink/renderer/platform/animation/compositor_scroll_offset_animation_curve.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/timer.h"
 
 namespace blink {
@@ -97,7 +97,7 @@
 // animations is shared with ProgrammaticScrollAnimator, and lives mostly in the
 // common base class ScrollAnimatorCompositorCoordinator.
 
-class PLATFORM_EXPORT ScrollAnimator : public ScrollAnimatorBase {
+class CORE_EXPORT ScrollAnimator : public ScrollAnimatorBase {
  public:
   explicit ScrollAnimator(ScrollableArea*,
                           WTF::TimeFunction = WTF::CurrentTimeTicksInSeconds);
@@ -161,4 +161,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_H_
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator_base.cc b/third_party/blink/renderer/core/scroll/scroll_animator_base.cc
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/scroll_animator_base.cc
rename to third_party/blink/renderer/core/scroll/scroll_animator_base.cc
index db194b61..f14d688 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator_base.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_base.cc
@@ -28,9 +28,9 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator_base.h b/third_party/blink/renderer/core/scroll/scroll_animator_base.h
similarity index 91%
rename from third_party/blink/renderer/platform/scroll/scroll_animator_base.h
rename to third_party/blink/renderer/core/scroll/scroll_animator_base.h
index 6f7c3321..48470ab6 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator_base.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_base.h
@@ -28,12 +28,12 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_BASE_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_BASE_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_BASE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_BASE_H_
 
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 
@@ -50,7 +50,7 @@
 // ScrollAnimatorBase is directly instantiated when scroll animations are
 // disabled.  In this case, all scrolls are instantaneous.
 
-class PLATFORM_EXPORT ScrollAnimatorBase
+class CORE_EXPORT ScrollAnimatorBase
     : public ScrollAnimatorCompositorCoordinator {
  public:
   static ScrollAnimatorBase* Create(ScrollableArea*);
@@ -125,4 +125,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_BASE_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_BASE_H_
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.cc b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
similarity index 98%
rename from third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.cc
rename to third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
index 85be672..c462ba8d 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.cc
@@ -2,20 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h"
 
 #include <memory>
 
 #include "cc/animation/scroll_offset_animation_curve.h"
 #include "cc/layers/picture_layer.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_host.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_timeline.h"
 #include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
similarity index 93%
rename from third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h
rename to third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
index 949bada8..c2dbda1 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator_compositor_coordinator.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_compositor_coordinator.h
@@ -2,21 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
 
 #include <memory>
 #include "base/gtest_prod_util.h"
 #include "cc/animation/animation_curve.h"
 #include "cc/animation/scroll_offset_animations.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_client.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_delegate.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
 
 namespace blink {
 
@@ -31,11 +30,11 @@
 //
 // See ScrollAnimator.h for more information about scroll animations.
 
-class PLATFORM_EXPORT ScrollAnimatorCompositorCoordinator
+class CORE_EXPORT ScrollAnimatorCompositorCoordinator
     : public GarbageCollectedFinalized<ScrollAnimatorCompositorCoordinator>,
       private CompositorAnimationClient,
       CompositorAnimationDelegate {
-  WTF_MAKE_NONCOPYABLE(ScrollAnimatorCompositorCoordinator);
+  DISALLOW_COPY_AND_ASSIGN(ScrollAnimatorCompositorCoordinator);
   USING_PRE_FINALIZER(ScrollAnimatorCompositorCoordinator, Dispose);
 
  public:
@@ -196,4 +195,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_COMPOSITOR_COORDINATOR_H_
diff --git a/third_party/blink/renderer/platform/mac/scroll_animator_mac.h b/third_party/blink/renderer/core/scroll/scroll_animator_mac.h
similarity index 94%
rename from third_party/blink/renderer/platform/mac/scroll_animator_mac.h
rename to third_party/blink/renderer/core/scroll/scroll_animator_mac.h
index 289c2c0..de89825 100644
--- a/third_party/blink/renderer/platform/mac/scroll_animator_mac.h
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_mac.h
@@ -23,16 +23,16 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MAC_SCROLL_ANIMATOR_MAC_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MAC_SCROLL_ANIMATOR_MAC_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
 
 #include <memory>
 #include "base/single_thread_task_runner.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/geometry/float_point.h"
 #include "third_party/blink/renderer/platform/geometry/float_size.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
 #include "third_party/blink/renderer/platform/timer.h"
 #include "third_party/blink/renderer/platform/web_task_runner.h"
 #include "third_party/blink/renderer/platform/wtf/retain_ptr.h"
@@ -90,7 +90,7 @@
 // ExpansionTransition), scrollbar paint timer, plumbing of scrollbar paint
 // invalidations.
 
-class PLATFORM_EXPORT ScrollAnimatorMac : public ScrollAnimatorBase {
+class CORE_EXPORT ScrollAnimatorMac : public ScrollAnimatorBase {
   USING_PRE_FINALIZER(ScrollAnimatorMac, Dispose);
 
  public:
@@ -175,4 +175,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MAC_SCROLL_ANIMATOR_MAC_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_ANIMATOR_MAC_H_
diff --git a/third_party/blink/renderer/platform/mac/scroll_animator_mac.mm b/third_party/blink/renderer/core/scroll/scroll_animator_mac.mm
similarity index 98%
rename from third_party/blink/renderer/platform/mac/scroll_animator_mac.mm
rename to third_party/blink/renderer/core/scroll/scroll_animator_mac.mm
index 84f2c3ef..29f3f3e 100644
--- a/third_party/blink/renderer/platform/mac/scroll_animator_mac.mm
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_mac.mm
@@ -23,21 +23,21 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/mac/scroll_animator_mac.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_mac.h"
 
 #import <AppKit/AppKit.h>
 
 #include <memory>
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h"
 #include "third_party/blink/renderer/platform/animation/timing_function.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/mac/block_exceptions.h"
-#include "third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h"
 #include "third_party/blink/renderer/platform/timer.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
diff --git a/third_party/blink/renderer/platform/scroll/scroll_animator_test.cc b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
similarity index 98%
rename from third_party/blink/renderer/platform/scroll/scroll_animator_test.cc
rename to third_party/blink/renderer/core/scroll/scroll_animator_test.cc
index 8a8441eb..14951e6 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_animator_test.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_animator_test.cc
@@ -25,7 +25,7 @@
 
 // Tests for the ScrollAnimator class.
 
-#include "third_party/blink/renderer/platform/scroll/scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator.h"
 
 #include "base/single_thread_task_runner.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -33,12 +33,12 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_thread.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/geometry/float_point.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/scroll_customization.cc b/third_party/blink/renderer/core/scroll/scroll_customization.cc
similarity index 92%
rename from third_party/blink/renderer/platform/scroll/scroll_customization.cc
rename to third_party/blink/renderer/core/scroll/scroll_customization.cc
index 203b5b63..6199fc2 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_customization.cc
+++ b/third_party/blink/renderer/core/scroll/scroll_customization.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 "third_party/blink/renderer/platform/scroll/scroll_customization.h"
+#include "third_party/blink/renderer/core/scroll/scroll_customization.h"
 
 namespace blink {
 namespace ScrollCustomization {
diff --git a/third_party/blink/renderer/platform/scroll/scroll_customization.h b/third_party/blink/renderer/core/scroll/scroll_customization.h
similarity index 67%
rename from third_party/blink/renderer/platform/scroll/scroll_customization.h
rename to third_party/blink/renderer/core/scroll/scroll_customization.h
index dfa89ed..224ce426 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_customization.h
+++ b/third_party/blink/renderer/core/scroll/scroll_customization.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_CUSTOMIZATION_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_CUSTOMIZATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_CUSTOMIZATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_CUSTOMIZATION_H_
 
 #include <stdint.h>
 
-#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/core/core_export.h"
 
 namespace blink {
 namespace ScrollCustomization {
@@ -25,9 +25,9 @@
 constexpr ScrollDirection kScrollDirectionAuto =
     kScrollDirectionPanX | kScrollDirectionPanY;
 
-PLATFORM_EXPORT ScrollDirection GetScrollDirectionFromDeltas(double delta_x,
-                                                             double delta_y);
+CORE_EXPORT ScrollDirection GetScrollDirectionFromDeltas(double delta_x,
+                                                         double delta_y);
 }  // namespace ScrollCustomization
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_CUSTOMIZATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_CUSTOMIZATION_H_
diff --git a/third_party/blink/renderer/platform/scroll/scroll_state_data.h b/third_party/blink/renderer/core/scroll/scroll_state_data.h
similarity index 74%
rename from third_party/blink/renderer/platform/scroll/scroll_state_data.h
rename to third_party/blink/renderer/core/scroll/scroll_state_data.h
index db10c3d..a97ba8ab 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_state_data.h
+++ b/third_party/blink/renderer/core/scroll/scroll_state_data.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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_STATE_DATA_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLL_STATE_DATA_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_STATE_DATA_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLL_STATE_DATA_H_
 
 #include "cc/input/scroll_state.h"
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
similarity index 97%
rename from third_party/blink/renderer/platform/scroll/scrollable_area.cc
rename to third_party/blink/renderer/core/scroll/scrollable_area.cc
index 95c660bb..64c2203 100644
--- a/third_party/blink/renderer/platform/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -29,27 +29,27 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
 #include "build/build_config.h"
 #include "cc/layers/picture_layer.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scroll/main_thread_scrolling_reason.h"
-#include "third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
 
 static const int kPixelsPerLineStep = 40;
 static const float kMinFractionToStepWhenPaging = 0.875f;
 
 namespace blink {
 
-int ScrollableArea::PixelsPerLineStep(PlatformChromeClient* host) {
+int ScrollableArea::PixelsPerLineStep(ChromeClient* host) {
   if (!host)
     return kPixelsPerLineStep;
   return host->WindowToViewportScalar(kPixelsPerLineStep);
diff --git a/third_party/blink/renderer/platform/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
similarity index 96%
rename from third_party/blink/renderer/platform/scroll/scrollable_area.h
rename to third_party/blink/renderer/core/scroll/scrollable_area.h
index a517e8a..2734d4b 100644
--- a/third_party/blink/renderer/platform/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -23,20 +23,19 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLABLE_AREA_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLABLE_AREA_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLABLE_AREA_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLABLE_AREA_H_
 
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
-#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace base {
@@ -51,7 +50,7 @@
 class LayoutBox;
 class LayoutObject;
 class PaintLayer;
-class PlatformChromeClient;
+class ChromeClient;
 class ProgrammaticScrollAnimator;
 class ScrollAnchor;
 class ScrollAnimatorBase;
@@ -65,11 +64,11 @@
   kIncludeScrollbars,
 };
 
-class PLATFORM_EXPORT ScrollableArea : public GarbageCollectedMixin {
-  WTF_MAKE_NONCOPYABLE(ScrollableArea);
+class CORE_EXPORT ScrollableArea : public GarbageCollectedMixin {
+  DISALLOW_COPY_AND_ASSIGN(ScrollableArea);
 
  public:
-  static int PixelsPerLineStep(PlatformChromeClient*);
+  static int PixelsPerLineStep(ChromeClient*);
   static float MinFractionToStepWhenPaging();
   int MaxOverlapBetweenPages() const;
 
@@ -79,7 +78,7 @@
     return std::isfinite(value) ? value : 0.0;
   }
 
-  virtual PlatformChromeClient* GetChromeClient() const { return nullptr; }
+  virtual ChromeClient* GetChromeClient() const { return nullptr; }
 
   virtual SmoothScrollSequencer* GetSmoothScrollSequencer() const {
     return nullptr;
@@ -481,4 +480,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLABLE_AREA_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLABLE_AREA_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollable_area_test.cc b/third_party/blink/renderer/core/scroll/scrollable_area_test.cc
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/scrollable_area_test.cc
rename to third_party/blink/renderer/core/scroll/scrollable_area_test.cc
index 0f47eea5..4e97cdc1 100644
--- a/third_party/blink/renderer/platform/scroll/scrollable_area_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area_test.cc
@@ -2,19 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
-#include "base/message_loop/message_loop.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h"
 #include "third_party/blink/renderer/platform/testing/fake_graphics_layer.h"
 #include "third_party/blink/renderer/platform/testing/fake_graphics_layer_client.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -36,10 +35,7 @@
 
 }  // namespace
 
-class ScrollableAreaTest : public testing::Test {
- private:
-  base::MessageLoop message_loop_;
-};
+class ScrollableAreaTest : public testing::Test {};
 
 TEST_F(ScrollableAreaTest, ScrollAnimatorCurrentPositionShouldBeSync) {
   ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar.cc b/third_party/blink/renderer/core/scroll/scrollbar.cc
similarity index 97%
rename from third_party/blink/renderer/platform/scroll/scrollbar.cc
rename to third_party/blink/renderer/core/scroll/scrollbar.cc
index 89b50db2..b9ef07d 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar.cc
@@ -23,25 +23,25 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 
 #include <algorithm>
 #include "third_party/blink/public/platform/web_gesture_event.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_scrollbar_overlay_color_theme.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
 Scrollbar::Scrollbar(ScrollableArea* scrollable_area,
                      ScrollbarOrientation orientation,
                      ScrollbarControlSize control_size,
-                     PlatformChromeClient* chrome_client,
+                     ChromeClient* chrome_client,
                      ScrollbarTheme* theme)
     : scrollable_area_(scrollable_area),
       orientation_(orientation),
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar.h b/third_party/blink/renderer/core/scroll/scrollbar.h
similarity index 94%
rename from third_party/blink/renderer/platform/scroll/scrollbar.h
rename to third_party/blink/renderer/core/scroll/scrollbar.h
index 780489e..e1ccef3 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar.h
@@ -23,9 +23,10 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_H_
 
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
@@ -38,19 +39,19 @@
 class CullRect;
 class GraphicsContext;
 class IntRect;
-class PlatformChromeClient;
+class ChromeClient;
 class ScrollableArea;
 class ScrollbarTheme;
 class WebGestureEvent;
 class WebMouseEvent;
 
-class PLATFORM_EXPORT Scrollbar : public GarbageCollectedFinalized<Scrollbar>,
-                                  public DisplayItemClient {
+class CORE_EXPORT Scrollbar : public GarbageCollectedFinalized<Scrollbar>,
+                              public DisplayItemClient {
  public:
   static Scrollbar* Create(ScrollableArea* scrollable_area,
                            ScrollbarOrientation orientation,
                            ScrollbarControlSize size,
-                           PlatformChromeClient* chrome_client) {
+                           ChromeClient* chrome_client) {
     return new Scrollbar(scrollable_area, orientation, size, chrome_client);
   }
 
@@ -199,7 +200,7 @@
   Scrollbar(ScrollableArea*,
             ScrollbarOrientation,
             ScrollbarControlSize,
-            PlatformChromeClient* = nullptr,
+            ChromeClient* = nullptr,
             ScrollbarTheme* = nullptr);
 
   void AutoscrollTimerFired(TimerBase*);
@@ -213,7 +214,7 @@
   ScrollbarOrientation orientation_;
   ScrollbarControlSize control_size_;
   ScrollbarTheme& theme_;
-  Member<PlatformChromeClient> chrome_client_;
+  Member<ChromeClient> chrome_client_;
 
   int visible_size_;
   int total_size_;
@@ -247,4 +248,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.cc b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
index ed9bf1541..2641a25 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h"
 
 #include "third_party/blink/public/platform/web_point.h"
 #include "third_party/blink/public/platform/web_rect.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "ui/gfx/skia_util.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.h b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h
similarity index 82%
rename from third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.h
rename to third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h
index f8d5247..77b2b6e 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_layer_delegate.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.h
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
 
 #include <memory>
 
 #include "base/macros.h"
 #include "cc/input/scrollbar.h"
 #include "cc/paint/paint_canvas.h"
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
 
 namespace cc {
 class PaintCanvas;
@@ -24,7 +24,7 @@
 
 // Implementation of cc::Scrollbar, providing a delegate to query about
 // scrollbar state and to paint the image in the scrollbar.
-class PLATFORM_EXPORT ScrollbarLayerDelegate : public cc::Scrollbar {
+class CORE_EXPORT ScrollbarLayerDelegate : public cc::Scrollbar {
  public:
   ScrollbarLayerDelegate(blink::Scrollbar& scrollbar,
                          float device_scale_factor);
@@ -63,4 +63,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_LAYER_DELEGATE_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h b/third_party/blink/renderer/core/scroll/scrollbar_test_suite.h
similarity index 89%
rename from third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h
rename to third_party/blink/renderer/core/scroll/scrollbar_test_suite.h
index 60224eb..500c7ec 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_test_suite.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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_TEST_SUITE_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_TEST_SUITE_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_TEST_SUITE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_TEST_SUITE_H_
 
 #include <memory>
 #include "base/single_thread_task_runner.h"
@@ -11,16 +11,16 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_thread.h"
+#include "third_party/blink/renderer/core/loader/empty_clients.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/platform_chrome_client.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
 
 namespace blink {
 
-class MockPlatformChromeClient : public PlatformChromeClient {
+class MockPlatformChromeClient : public EmptyChromeClient {
  public:
   MockPlatformChromeClient() : is_popup_(false) {}
 
@@ -89,7 +89,7 @@
     return blink::scheduler::GetSingleThreadTaskRunnerForTesting();
   }
 
-  PlatformChromeClient* GetChromeClient() const override {
+  ChromeClient* GetChromeClient() const override {
     return chrome_client_.Get();
   }
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
similarity index 97%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme.cc
index a3352cc..80bc7e3 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
@@ -23,7 +23,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 #include "base/optional.h"
 #include "build/build_config.h"
@@ -31,6 +31,9 @@
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_point.h"
 #include "third_party/blink/public/platform/web_rect.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
@@ -39,9 +42,6 @@
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h"
 
 #if !defined(OS_MACOSX)
 #include "third_party/blink/public/platform/web_theme_engine.h"
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme.h b/third_party/blink/renderer/core/scroll/scrollbar_theme.h
similarity index 96%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme.h
index d1f4289..847d8ef 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme.h
@@ -23,15 +23,15 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_H_
 
 #include "third_party/blink/public/platform/web_scrollbar_buttons_placement.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/display_item.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 
 namespace blink {
 
@@ -39,8 +39,8 @@
 class GraphicsContext;
 class WebMouseEvent;
 
-class PLATFORM_EXPORT ScrollbarTheme {
-  WTF_MAKE_NONCOPYABLE(ScrollbarTheme);
+class CORE_EXPORT ScrollbarTheme {
+  DISALLOW_COPY_AND_ASSIGN(ScrollbarTheme);
   USING_FAST_MALLOC(ScrollbarTheme);
 
  public:
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_android.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_android.cc
similarity index 90%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_android.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_android.cc
index 325abb2..d1a6e11 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_android.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_android.cc
@@ -23,9 +23,9 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
similarity index 97%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
index 5b4a5f8..0d3b2ee 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.cc
@@ -28,21 +28,21 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h"
 
 #include "build/build_config.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_rect.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 #include "third_party/blink/renderer/platform/geometry/int_rect_outsets.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/layout_test_support.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h
similarity index 92%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h
index 2742065c..61188a1 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h
@@ -28,15 +28,15 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_AURA_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_AURA_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_AURA_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_AURA_H_
 
 #include "base/gtest_prod_util.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
-class PLATFORM_EXPORT ScrollbarThemeAura : public ScrollbarTheme {
+class CORE_EXPORT ScrollbarThemeAura : public ScrollbarTheme {
  public:
   int ScrollbarThickness(ScrollbarControlSize) override;
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura_test.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
similarity index 91%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_aura_test.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
index 058bf7a..8540f7d 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_aura_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_aura.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_aura.h"
 
-#include "base/message_loop/message_loop.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
 
 namespace blink {
@@ -30,10 +29,7 @@
 
 }  // namespace
 
-class ScrollbarThemeAuraTest : public testing::Test {
- private:
-  base::MessageLoop message_loop_;
-};
+class ScrollbarThemeAuraTest : public testing::Test {};
 
 TEST_F(ScrollbarThemeAuraTest, ButtonSizeHorizontal) {
   ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
similarity index 92%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
index 688a9ab..a67a4e59 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h
@@ -23,13 +23,13 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MAC_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MAC_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MAC_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MAC_H_
 
 #include <AppKit/AppKit.h>
 
-#include "third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/scroll/web_scrollbar_theme_client.h"
 
 typedef id ScrollbarPainter;
@@ -130,6 +130,6 @@
 
   scoped_refptr<Pattern> overhang_pattern_;
 };
-}
+}  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MAC_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MAC_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.mm b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
similarity index 98%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.mm
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
index 5a1f094..f67fa198 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.mm
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mac.mm
@@ -23,7 +23,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h"
 
 #include <Carbon/Carbon.h>
 #include "skia/ext/skia_utils_mac.h"
@@ -32,13 +32,13 @@
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_rect.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
+#include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_mac.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/mac/color_mac.h"
 #include "third_party/blink/renderer/platform/mac/local_current_graphics_context.h"
-#include "third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h"
-#include "third_party/blink/renderer/platform/mac/scroll_animator_mac.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/retain_ptr.h"
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_mock.cc
similarity index 96%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_mock.cc
index 73b2b35..8922c77 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mock.cc
@@ -23,12 +23,12 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h"
 
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h
similarity index 87%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h
index e463fdf..9f6e237a 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_mock.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_mock.h
@@ -23,16 +23,16 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MOCK_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MOCK_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MOCK_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MOCK_H_
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 
 namespace blink {
 
 // Scrollbar theme used in image snapshots, to eliminate appearance differences
 // between platforms.
-class PLATFORM_EXPORT ScrollbarThemeMock : public ScrollbarTheme {
+class CORE_EXPORT ScrollbarThemeMock : public ScrollbarTheme {
  public:
   int ScrollbarThickness(ScrollbarControlSize = kRegularScrollbar) override;
   bool UsesOverlayScrollbars() const override;
@@ -69,4 +69,4 @@
 };
 
 }  // namespace blink
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_MOCK_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_MOCK_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.cc
similarity index 98%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.cc
index 33121af3..030453c1 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.cc
@@ -23,14 +23,14 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_rect.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar.h"
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h
similarity index 92%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h
index b36d9cc..a3595fe 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h
@@ -23,17 +23,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_OVERLAY_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_OVERLAY_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_OVERLAY_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_OVERLAY_H_
 
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
 // This scrollbar theme is used to get overlay scrollbar for platforms other
 // than Mac. Mac's overlay scrollbars are in ScrollbarThemeMac*.
-class PLATFORM_EXPORT ScrollbarThemeOverlay : public ScrollbarTheme {
+class CORE_EXPORT ScrollbarThemeOverlay : public ScrollbarTheme {
  public:
   enum HitTestBehavior { kAllowHitTest, kDisallowHitTest };
 
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h
similarity index 85%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h
index 0a38a31..342bae02 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_mock.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_mock.h
@@ -28,14 +28,14 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 
 namespace blink {
 
-class PLATFORM_EXPORT ScrollbarThemeOverlayMock : public ScrollbarThemeOverlay {
+class CORE_EXPORT ScrollbarThemeOverlayMock : public ScrollbarThemeOverlay {
  public:
   ScrollbarThemeOverlayMock()
       : ScrollbarThemeOverlay(3, 4, kDisallowHitTest, Color(128, 128, 128)) {}
@@ -68,4 +68,4 @@
 };
 
 }  // namespace blink
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SCROLLBAR_THEME_OVERLAY_MOCK_H_
diff --git a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_test.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_test.cc
rename to third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
index f711b20..8a38772 100644
--- a/third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_overlay_test.cc
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_overlay.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
 
-#include "base/message_loop/message_loop.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_test_suite.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
 
 namespace blink {
@@ -13,10 +12,7 @@
 using testing::NiceMock;
 using testing::Return;
 
-class ScrollbarThemeOverlayTest : public testing::Test {
- private:
-  base::MessageLoop message_loop_;
-};
+class ScrollbarThemeOverlayTest : public testing::Test {};
 
 TEST_F(ScrollbarThemeOverlayTest, PaintInvalidation) {
   ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
diff --git a/third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.cc b/third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.cc
similarity index 87%
rename from third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.cc
rename to third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.cc
index 31fce84..0ed920fd 100644
--- a/third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.cc
+++ b/third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h"
+#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
 
-#include "third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h"
-#include "third_party/blink/renderer/platform/scroll/scrollable_area.h"
+#include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h b/third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h
similarity index 84%
rename from third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h
rename to third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h
index 0af018e..a5d028e 100644
--- a/third_party/blink/renderer/platform/scroll/smooth_scroll_sequencer.h
+++ b/third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h
@@ -1,10 +1,12 @@
 // Copyright (c) 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
 
 #include <utility>
+
+#include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/scroll/scroll_types.h"
 
@@ -37,7 +39,7 @@
 // A sequencer that queues the nested scrollers from inside to outside,
 // so that they can be animated from outside to inside when smooth scroll
 // is called.
-class PLATFORM_EXPORT SmoothScrollSequencer final
+class CORE_EXPORT SmoothScrollSequencer final
     : public GarbageCollected<SmoothScrollSequencer> {
  public:
   SmoothScrollSequencer() = default;
@@ -61,4 +63,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_SCROLL_SMOOTH_SCROLL_SEQUENCER_H_
diff --git a/third_party/blink/renderer/platform/scroll/web_scroll_into_view_params.cc b/third_party/blink/renderer/core/scroll/web_scroll_into_view_params.cc
similarity index 100%
rename from third_party/blink/renderer/platform/scroll/web_scroll_into_view_params.cc
rename to third_party/blink/renderer/core/scroll/web_scroll_into_view_params.cc
diff --git a/third_party/blink/renderer/platform/scroll/web_scrollbar_theme.mm b/third_party/blink/renderer/core/scroll/web_scrollbar_theme.mm
similarity index 95%
rename from third_party/blink/renderer/platform/scroll/web_scrollbar_theme.mm
rename to third_party/blink/renderer/core/scroll/web_scrollbar_theme.mm
index 19297c8..86a570a4 100644
--- a/third_party/blink/renderer/platform/scroll/web_scrollbar_theme.mm
+++ b/third_party/blink/renderer/core/scroll/web_scrollbar_theme.mm
@@ -32,8 +32,8 @@
 
 #import <AppKit/AppKit.h>
 
-#include "third_party/blink/renderer/platform/mac/ns_scroller_imp_details.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme_mac.h"
+#include "third_party/blink/renderer/core/scroll/ns_scroller_imp_details.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme_mac.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/svg/svg_path_data.h b/third_party/blink/renderer/core/svg/svg_path_data.h
index ca81e776..9e4f84ab 100644
--- a/third_party/blink/renderer/core/svg/svg_path_data.h
+++ b/third_party/blink/renderer/core/svg/svg_path_data.h
@@ -63,6 +63,8 @@
 
 struct PathSegmentData {
   STACK_ALLOCATED();
+
+ public:
   PathSegmentData()
       : command(kPathSegUnknown), arc_sweep(false), arc_large(false) {}
 
diff --git a/third_party/blink/renderer/core/testing/core_unit_test_helper.cc b/third_party/blink/renderer/core/testing/core_unit_test_helper.cc
index b86cc17..366e634 100644
--- a/third_party/blink/renderer/core/testing/core_unit_test_helper.cc
+++ b/third_party/blink/renderer/core/testing/core_unit_test_helper.cc
@@ -7,9 +7,9 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
 #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index c5b8d5e..abe9fa1 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -125,6 +125,9 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
+#include "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator_base.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/core/style_property_shorthand.h"
 #include "third_party/blink/renderer/core/svg/svg_image_element.h"
 #include "third_party/blink/renderer/core/svg_names.h"
@@ -161,9 +164,6 @@
 #include "third_party/blink/renderer/platform/loader/fetch/resource_load_priority.h"
 #include "third_party/blink/renderer/platform/network/network_state_notifier.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scroll/programmatic_scroll_animator.h"
-#include "third_party/blink/renderer/platform/scroll/scroll_animator_base.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/blink/renderer/platform/text/layout_locale.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
@@ -242,12 +242,6 @@
   if (!node)
     return nullptr;
 
-  if (node->IsDocumentNode()) {
-    // This can be removed after root layer scrolling is enabled.
-    if (LocalFrameView* frame_view = ToDocument(node)->View())
-      return frame_view->LayoutViewport();
-  }
-
   LayoutObject* layout_object = node->GetLayoutObject();
   if (!layout_object || !layout_object->IsBox())
     return nullptr;
diff --git a/third_party/blink/renderer/core/testing/sim/sim_test.cc b/third_party/blink/renderer/core/testing/sim/sim_test.cc
index b816ca7f..001d6b6 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_test.cc
+++ b/third_party/blink/renderer/core/testing/sim/sim_test.cc
@@ -11,8 +11,8 @@
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/layout_test_support.h"
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h b/third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h
index 45e00a5..5e867ff5 100644
--- a/third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h
+++ b/third_party/blink/renderer/core/testing/use_mock_scrollbar_settings.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TESTING_USE_MOCK_SCROLLBAR_SETTINGS_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TESTING_USE_MOCK_SCROLLBAR_SETTINGS_H_
 
-#include "third_party/blink/renderer/platform/scroll/scrollbar_theme.h"
+#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/core/workers/worklet.cc b/third_party/blink/renderer/core/workers/worklet.cc
index 4eabae256..6a78f29 100644
--- a/third_party/blink/renderer/core/workers/worklet.cc
+++ b/third_party/blink/renderer/core/workers/worklet.cc
@@ -76,9 +76,10 @@
   // loading.
   GetExecutionContext()
       ->GetTaskRunner(TaskType::kInternalLoading)
-      ->PostTask(FROM_HERE, WTF::Bind(&Worklet::FetchAndInvokeScript,
-                                      WrapPersistent(this), module_url_record,
-                                      options, WrapPersistent(pending_tasks)));
+      ->PostTask(FROM_HERE,
+                 WTF::Bind(&Worklet::FetchAndInvokeScript, WrapPersistent(this),
+                           module_url_record, options.credentials(),
+                           WrapPersistent(pending_tasks)));
   return promise;
 }
 
@@ -108,7 +109,7 @@
 // algorithm:
 // https://drafts.css-houdini.org/worklets/#dom-worklet-addmodule
 void Worklet::FetchAndInvokeScript(const KURL& module_url_record,
-                                   const WorkletOptions& options,
+                                   const String& credentials,
                                    WorkletPendingTasks* pending_tasks) {
   DCHECK(IsMainThread());
   if (!GetExecutionContext())
@@ -116,8 +117,7 @@
 
   // Step 6: "Let credentialOptions be the credentials member of options."
   network::mojom::FetchCredentialsMode credentials_mode;
-  bool result =
-      Request::ParseCredentialsMode(options.credentials(), &credentials_mode);
+  bool result = Request::ParseCredentialsMode(credentials, &credentials_mode);
   DCHECK(result);
 
   // Step 7: "Let outsideSettings be the relevant settings object of this."
diff --git a/third_party/blink/renderer/core/workers/worklet.h b/third_party/blink/renderer/core/workers/worklet.h
index 06e36dc6..696843c 100644
--- a/third_party/blink/renderer/core/workers/worklet.h
+++ b/third_party/blink/renderer/core/workers/worklet.h
@@ -66,7 +66,7 @@
 
  private:
   virtual void FetchAndInvokeScript(const KURL& module_url_record,
-                                    const WorkletOptions&,
+                                    const String& credentials,
                                     WorkletPendingTasks*);
 
   // Returns true if there are no global scopes or additional global scopes are
diff --git a/third_party/blink/renderer/core/xml/xpath_parser.h b/third_party/blink/renderer/core/xml/xpath_parser.h
index e245e40..5880f2a2 100644
--- a/third_party/blink/renderer/core/xml/xpath_parser.h
+++ b/third_party/blink/renderer/core/xml/xpath_parser.h
@@ -46,6 +46,8 @@
 
 struct Token {
   STACK_ALLOCATED();
+
+ public:
   int type;
   String str;
   Step::Axis axis;
diff --git a/third_party/blink/renderer/devtools/front_end/Runtime.js b/third_party/blink/renderer/devtools/front_end/Runtime.js
index f48f9e4..5e6e5f1 100644
--- a/third_party/blink/renderer/devtools/front_end/Runtime.js
+++ b/third_party/blink/renderer/devtools/front_end/Runtime.js
@@ -971,7 +971,10 @@
    */
   isEnabled(experimentName) {
     this._checkExperiment(experimentName);
-
+    // Check for explicitly disabled experiments first - the code could call setEnable(false) on the experiment enabled
+    // by default and we should respect that.
+    if (Runtime._experimentsSetting()[experimentName] === false)
+      return false;
     if (this._enabledTransiently[experimentName])
       return true;
     if (!this.supportEnabled())
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
index 1cf667e..dc8e8d35 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
@@ -13,6 +13,7 @@
    * @param {!Timeline.TimelineController.Client} client
    */
   constructor(target, client) {
+    this._target = target;
     this._tracingManager = target.model(SDK.TracingManager);
     this._performanceModel = new Timeline.PerformanceModel();
     this._performanceModel.setMainTarget(target);
@@ -34,7 +35,7 @@
    * @return {!SDK.Target}
    */
   mainTarget() {
-    return this._tracingManager.target();
+    return this._target;
   }
 
   /**
@@ -93,9 +94,11 @@
    */
   async stopRecording() {
     const tracingStoppedPromises = [];
-    tracingStoppedPromises.push(new Promise(resolve => this._tracingCompleteCallback = resolve));
+    if (this._tracingManager)
+      tracingStoppedPromises.push(new Promise(resolve => this._tracingCompleteCallback = resolve));
     tracingStoppedPromises.push(this._stopProfilingOnAllModels());
-    this._tracingManager.stop();
+    if (this._tracingManager)
+      this._tracingManager.stop();
     tracingStoppedPromises.push(SDK.targetManager.resumeAllTargets());
 
     this._client.loadingStarted();
@@ -171,14 +174,16 @@
    * @param {boolean=} enableJSSampling
    * @return {!Promise}
    */
-  _startRecordingWithCategories(categories, enableJSSampling) {
+  async _startRecordingWithCategories(categories, enableJSSampling) {
     SDK.targetManager.suspendAllTargets();
-    const profilingStartedPromise = enableJSSampling && !Runtime.experiments.isEnabled('timelineTracingJSProfile') ?
-        this._startProfilingOnAllModels() :
-        Promise.resolve();
+    if (enableJSSampling && !Runtime.experiments.isEnabled('timelineTracingJSProfile'))
+      await this._startProfilingOnAllModels();
+    if (!this._tracingManager)
+      return;
+
     const samplingFrequencyHz = Common.moduleSetting('highResolutionCpuProfiling').get() ? 10000 : 1000;
     const options = 'sampling-frequency=' + samplingFrequencyHz;
-    return profilingStartedPromise.then(() => this._tracingManager.start(this, categories, options));
+    return this._tracingManager.start(this, categories, options);
   }
 
   /**
@@ -294,6 +299,20 @@
         const pid = mainMetaEvent.thread.process().id();
         const mainCpuProfile = this._cpuProfiles.get(this._tracingManager.target().id());
         this._injectCpuProfileEvent(pid, mainMetaEvent.thread.id(), mainCpuProfile);
+      } else {
+        // Or there was no tracing manager in the main target at all, in this case build the model full
+        // of cpu profiles.
+        // Assign tids equal to the target ordinals for the thread name lookup.
+        // This is a little shaky because targets can disappear, but we will read these tids sooner than
+        // that.
+        // TODO(pfeldman): resolve this.
+        let counter = 1000;
+        for (const pair of this._cpuProfiles) {
+          const target = SDK.targetManager.targetById(pair[0]);
+          const tid = target ? SDK.targetManager.targets().indexOf(target) : ++counter;
+          this._tracingModel.addEvents(
+              TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(pair[1], tid));
+        }
       }
     }
 
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelineLoader.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelineLoader.js
index fb7920ab..ada59507 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelineLoader.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelineLoader.js
@@ -221,7 +221,7 @@
     let traceEvents;
     try {
       const profile = JSON.parse(text);
-      traceEvents = TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(profile);
+      traceEvents = TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(profile, 1);
     } catch (e) {
       this._reportErrorAndCancelLoading(Common.UIString('Malformed CPU profile format'));
       return;
diff --git a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineJSProfile.js b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineJSProfile.js
index d544dd7..f5a6bdd 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineJSProfile.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineJSProfile.js
@@ -227,9 +227,10 @@
 
   /**
    * @param {*} profile
+   * @param {number} tid
    * @return {!Array<!SDK.TracingManager.EventPayload>}
    */
-  static buildTraceProfileFromCpuProfile(profile) {
+  static buildTraceProfileFromCpuProfile(profile, tid) {
     if (!profile)
       return [];
     const events = [];
@@ -294,7 +295,7 @@
         name: name,
         ph: ph || 'X',
         pid: 1,
-        tid: 1,
+        tid,
         ts: ts,
         args: {data: data}
       });
diff --git a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
index d5a6910f..fb70f04 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
@@ -180,8 +180,9 @@
    * @param {!SDK.TracingModel} tracingModel
    */
   _processGenericTrace(tracingModel) {
-    const browserMainThread =
-        SDK.TracingModel.browserMainThread(tracingModel) || tracingModel.sortedProcesses()[0].sortedThreads()[0];
+    let browserMainThread = SDK.TracingModel.browserMainThread(tracingModel);
+    if (!browserMainThread && tracingModel.sortedProcesses().length)
+      browserMainThread = tracingModel.sortedProcesses()[0].sortedThreads()[0];
     for (const process of tracingModel.sortedProcesses()) {
       for (const thread of process.sortedThreads()) {
         this._processThreadEvents(
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc b/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
index a52b949..8042c76 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
@@ -36,7 +36,6 @@
 KeyframeEffect* CreateKeyframeEffect(Element* element) {
   Timing timing;
   timing.iteration_duration = 30;
-  timing.playback_rate = 1;
   return KeyframeEffect::Create(element, CreateEffectModel(), timing);
 }
 
diff --git a/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc b/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
index 0ae59ff..b747c91 100644
--- a/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
+++ b/third_party/blink/renderer/modules/app_banner/app_banner_controller.cc
@@ -34,6 +34,7 @@
     mojom::blink::AppBannerServicePtr service_ptr,
     mojom::blink::AppBannerEventRequest event_request,
     const Vector<String>& platforms,
+    bool require_gesture,
     BannerPromptRequestCallback callback) {
   if (!frame_ || !frame_->GetDocument()) {
     std::move(callback).Run(mojom::blink::AppBannerPromptReply::NONE, "");
@@ -43,7 +44,7 @@
   mojom::AppBannerPromptReply reply =
       frame_->DomWindow()->DispatchEvent(BeforeInstallPromptEvent::Create(
           EventTypeNames::beforeinstallprompt, *frame_, std::move(service_ptr),
-          std::move(event_request), platforms)) ==
+          std::move(event_request), platforms, require_gesture)) ==
               DispatchEventResult::kNotCanceled
           ? mojom::AppBannerPromptReply::NONE
           : mojom::AppBannerPromptReply::CANCEL;
diff --git a/third_party/blink/renderer/modules/app_banner/app_banner_controller.h b/third_party/blink/renderer/modules/app_banner/app_banner_controller.h
index 58abec2..144b260 100644
--- a/third_party/blink/renderer/modules/app_banner/app_banner_controller.h
+++ b/third_party/blink/renderer/modules/app_banner/app_banner_controller.h
@@ -26,6 +26,7 @@
   void BannerPromptRequest(mojom::blink::AppBannerServicePtr,
                            mojom::blink::AppBannerEventRequest,
                            const Vector<String>& platforms,
+                           bool require_gesture,
                            BannerPromptRequestCallback) override;
 
  private:
diff --git a/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.cc b/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.cc
index 31de85ea..4b13f92e 100644
--- a/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.cc
+++ b/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.cc
@@ -17,7 +17,8 @@
     LocalFrame& frame,
     mojom::blink::AppBannerServicePtr service_ptr,
     mojom::blink::AppBannerEventRequest event_request,
-    const Vector<String>& platforms)
+    const Vector<String>& platforms,
+    bool require_gesture)
     : Event(name, Bubbles::kNo, Cancelable::kYes),
       ContextClient(&frame),
       banner_service_(std::move(service_ptr)),
@@ -26,7 +27,7 @@
       user_choice_(new UserChoiceProperty(frame.GetDocument(),
                                           this,
                                           UserChoiceProperty::kUserChoice)),
-      prompt_called_(false) {
+      require_gesture_(require_gesture) {
   DCHECK(banner_service_);
   DCHECK(binding_.is_bound());
   UseCounter::Count(&frame, WebFeature::kBeforeInstallPromptEvent);
@@ -39,7 +40,7 @@
     : Event(name, init),
       ContextClient(execution_context),
       binding_(this),
-      prompt_called_(false) {
+      require_gesture_(true) {
   if (init.hasPlatforms())
     platforms_ = init.platforms();
 }
@@ -71,20 +72,26 @@
 ScriptPromise BeforeInstallPromptEvent::prompt(ScriptState* script_state) {
   // |m_bannerService| must be bound to allow us to inform the AppBannerService
   // to display the banner now.
-  if (prompt_called_ || !banner_service_.is_bound()) {
+  if (!banner_service_.is_bound()) {
     return ScriptPromise::RejectWithDOMException(
         script_state,
         DOMException::Create(DOMExceptionCode::kInvalidStateError,
-                             "The prompt() method may only be called once."));
+                             "The prompt() method cannot be called."));
   }
 
   ExecutionContext* context = ExecutionContext::From(script_state);
-  UseCounter::Count(context, WebFeature::kBeforeInstallPromptEventPrompt);
-
   Document* doc = ToDocumentOrNull(context);
-  prompt_called_ = true;
-  banner_service_->DisplayAppBanner(
-      Frame::HasTransientUserActivation(doc ? doc->GetFrame() : nullptr));
+  if (require_gesture_ &&
+      !Frame::HasTransientUserActivation(doc ? doc->GetFrame() : nullptr)) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state,
+        DOMException::Create(
+            DOMExceptionCode::kNotAllowedError,
+            "The prompt() method must be called with a user gesture"));
+  }
+
+  UseCounter::Count(context, WebFeature::kBeforeInstallPromptEventPrompt);
+  banner_service_->DisplayAppBanner();
   return ScriptPromise::CastUndefined(script_state);
 }
 
diff --git a/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.h b/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.h
index e0abec2..58fb4e9 100644
--- a/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.h
+++ b/third_party/blink/renderer/modules/app_banner/before_install_prompt_event.h
@@ -43,9 +43,11 @@
       LocalFrame& frame,
       mojom::blink::AppBannerServicePtr service_ptr,
       mojom::blink::AppBannerEventRequest event_request,
-      const Vector<String>& platforms) {
+      const Vector<String>& platforms,
+      bool require_gesture) {
     return new BeforeInstallPromptEvent(name, frame, std::move(service_ptr),
-                                        std::move(event_request), platforms);
+                                        std::move(event_request), platforms,
+                                        require_gesture);
   }
 
   static BeforeInstallPromptEvent* Create(
@@ -74,7 +76,8 @@
                            LocalFrame&,
                            mojom::blink::AppBannerServicePtr,
                            mojom::blink::AppBannerEventRequest,
-                           const Vector<String>& platforms);
+                           const Vector<String>& platforms,
+                           bool require_gesture);
   BeforeInstallPromptEvent(ExecutionContext*,
                            const AtomicString& name,
                            const BeforeInstallPromptEventInit&);
@@ -87,7 +90,10 @@
   mojo::Binding<mojom::blink::AppBannerEvent> binding_;
   Vector<String> platforms_;
   Member<UserChoiceProperty> user_choice_;
-  bool prompt_called_;
+
+  // TODO(crbug.com/869780): remove this member when the ExperimentalAppBanners
+  // feature is removed.
+  bool require_gesture_;
 };
 
 DEFINE_TYPE_CASTS(BeforeInstallPromptEvent,
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_options.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_options.idl
index c80d491..c30c3686 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_options.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_options.idl
@@ -2,11 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://wicg.github.io/background-fetch/#background-fetch-manager
+// https://wicg.github.io/background-fetch/#dictdef-backgroundfetchoptions
 
-dictionary BackgroundFetchOptions {
-    sequence<ImageResource> icons = [];
-    DOMString title = "";
-    [ImplementedAs=downloadTotal] unsigned long long totalDownloadSize = 0;
+dictionary BackgroundFetchOptions : BackgroundFetchUIOptions  {
     unsigned long long downloadTotal = 0;
 };
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_type_converters.cc b/third_party/blink/renderer/modules/background_fetch/background_fetch_type_converters.cc
index 719b38b..42850b0 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_type_converters.cc
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_type_converters.cc
@@ -42,7 +42,7 @@
 
   mojo_options->icons = std::move(mojo_icons);
   mojo_options->download_total = options.downloadTotal();
-  mojo_options->title = options.title();
+  mojo_options->title = options.hasTitle() ? options.title() : "";
 
   return mojo_options;
 }
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_ui_options.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_ui_options.idl
new file mode 100644
index 0000000..26c20e8c
--- /dev/null
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_ui_options.idl
@@ -0,0 +1,10 @@
+// Copyright 2018 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.
+
+// https://wicg.github.io/background-fetch/#dictdef-backgroundfetchuioptions
+
+dictionary BackgroundFetchUIOptions {
+    sequence<ImageResource> icons = [];
+    DOMString? title;
+};
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.cc b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.cc
index 8f57469d7..c900e6a8 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.cc
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.cc
@@ -13,7 +13,7 @@
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h"
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_icon_loader.h"
 #include "third_party/blink/renderer/modules/background_fetch/background_fetch_settled_fetch.h"
-#include "third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_options.h"
+#include "third_party/blink/renderer/modules/background_fetch/background_fetch_ui_options.h"
 #include "third_party/blink/renderer/modules/event_modules_names.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 
@@ -45,7 +45,7 @@
 
 ScriptPromise BackgroundFetchUpdateEvent::updateUI(
     ScriptState* script_state,
-    const BackgroundFetchUpdateUIOptions& ui_options) {
+    const BackgroundFetchUIOptions& ui_options) {
   if (!registration_) {
     // Return a Promise that will never settle when a developer calls this
     // method on a BackgroundFetchedEvent instance they created themselves.
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.h b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.h
index c060dcff..890bfb1 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.h
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.h
@@ -12,7 +12,7 @@
 namespace blink {
 
 class BackgroundFetchIconLoader;
-class BackgroundFetchUpdateUIOptions;
+class BackgroundFetchUIOptions;
 
 // Event for interacting with fetch requests that have completed.
 class MODULES_EXPORT BackgroundFetchUpdateEvent final
@@ -41,7 +41,7 @@
 
   // Web Exposed method defined in the IDL file.
   ScriptPromise updateUI(ScriptState* script_state,
-                         const BackgroundFetchUpdateUIOptions& ui_options);
+                         const BackgroundFetchUIOptions& ui_options);
 
   void Trace(blink::Visitor* visitor) override;
 
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.idl
index 42273030d..a97acf6 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.idl
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_event.idl
@@ -10,5 +10,5 @@
     RuntimeEnabled=BackgroundFetch
 ]
 interface BackgroundFetchUpdateEvent : BackgroundFetchSettledEvent {
-    [CallWith=ScriptState] Promise<void> updateUI(BackgroundFetchUpdateUIOptions options);
+    [CallWith=ScriptState] Promise<void> updateUI(optional BackgroundFetchUIOptions options);
 };
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_options.idl b/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_options.idl
deleted file mode 100644
index 549c1b70..0000000
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_update_ui_options.idl
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2018 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.
-
-// https://wicg.github.io/background-fetch/#backgroundfetchupdateevent
-
-// TODO(crbug.com/865063): Fix names after the standard is updated.
-// Spec Bug: https://github.com/WICG/background-fetch/issues/86
-
-dictionary BackgroundFetchUpdateUIOptions {
-    DOMString? title;
-    sequence<ImageResource> icons = [];
-};
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_store.cc b/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
index aaec7be..f36fea3 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_store.cc
@@ -127,7 +127,7 @@
 
   return WebCanonicalCookie::Create(
       name, value, domain, options.path(), WTF::Time() /*creation*/, expires,
-      WTF::Time() /*last_access*/, secure, options.httpOnly(), same_site,
+      WTF::Time() /*last_access*/, secure, false /*http_only*/, same_site,
       WebCanonicalCookie::kDefaultPriority);
 }
 
@@ -273,7 +273,6 @@
   set_options.setDomain(options.domain());
   set_options.setPath(options.path());
   set_options.setSecure(options.secure());
-  set_options.setHttpOnly(options.httpOnly());
   set_options.setSameSite(options.sameSite());
   return set(script_state, set_options, exception_state);
 }
diff --git a/third_party/blink/renderer/modules/cookie_store/cookie_store_set_options.idl b/third_party/blink/renderer/modules/cookie_store/cookie_store_set_options.idl
index 2cde6845..4a01a9fa 100644
--- a/third_party/blink/renderer/modules/cookie_store/cookie_store_set_options.idl
+++ b/third_party/blink/renderer/modules/cookie_store/cookie_store_set_options.idl
@@ -15,6 +15,5 @@
   USVString? domain = null;
   USVString path = "/";
   boolean secure = true;
-  boolean httpOnly = false;
   CookieSameSite sameSite = "strict";
 };
diff --git a/third_party/blink/renderer/modules/crypto/normalize_algorithm.h b/third_party/blink/renderer/modules/crypto/normalize_algorithm.h
index 1649646..09be588 100644
--- a/third_party/blink/renderer/modules/crypto/normalize_algorithm.h
+++ b/third_party/blink/renderer/modules/crypto/normalize_algorithm.h
@@ -45,6 +45,8 @@
 
 struct AlgorithmError {
   STACK_ALLOCATED();
+
+ public:
   WebCryptoErrorType error_type;
   WebString error_details;
 };
diff --git a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
index c3f3e7a..12eead2 100644
--- a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
+++ b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc
@@ -90,11 +90,6 @@
     DeleteDatabaseCallback;
 
 namespace blink {
-
-namespace IndexedDBAgentState {
-static const char kIndexedDBAgentEnabled[] = "indexedDBAgentEnabled";
-};
-
 namespace {
 
 const char kIndexedDBObjectGroup[] = "indexeddb";
@@ -754,15 +749,15 @@
 InspectorIndexedDBAgent::InspectorIndexedDBAgent(
     InspectedFrames* inspected_frames,
     v8_inspector::V8InspectorSession* v8_session)
-    : inspected_frames_(inspected_frames), v8_session_(v8_session) {}
+    : inspected_frames_(inspected_frames),
+      v8_session_(v8_session),
+      enabled_(&agent_state_, /*default_value=*/false) {}
 
 InspectorIndexedDBAgent::~InspectorIndexedDBAgent() = default;
 
 void InspectorIndexedDBAgent::Restore() {
-  if (state_->booleanProperty(IndexedDBAgentState::kIndexedDBAgentEnabled,
-                              false)) {
+  if (enabled_.Get())
     enable();
-  }
 }
 
 void InspectorIndexedDBAgent::DidCommitLoadForLocalFrame(LocalFrame* frame) {
@@ -773,12 +768,12 @@
 }
 
 Response InspectorIndexedDBAgent::enable() {
-  state_->setBoolean(IndexedDBAgentState::kIndexedDBAgentEnabled, true);
+  enabled_.Set(true);
   return Response::OK();
 }
 
 Response InspectorIndexedDBAgent::disable() {
-  state_->setBoolean(IndexedDBAgentState::kIndexedDBAgentEnabled, false);
+  enabled_.Clear();
   v8_session_->releaseObjectGroup(
       ToV8InspectorStringView(kIndexedDBObjectGroup));
   return Response::OK();
diff --git a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h
index 9b4cd6d..5c101559 100644
--- a/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h
+++ b/third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h
@@ -86,6 +86,7 @@
  private:
   Member<InspectedFrames> inspected_frames_;
   v8_inspector::V8InspectorSession* v8_session_;
+  InspectorAgentState::Boolean enabled_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 1c29d0cd..d073108e 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -470,7 +470,7 @@
           "background_fetch/background_fetch_fail_event_init.idl",
           "background_fetch/background_fetch_options.idl",
           "background_fetch/background_fetch_settled_event_init.idl",
-          "background_fetch/background_fetch_update_ui_options.idl",
+          "background_fetch/background_fetch_ui_options.idl",
           "background_sync/sync_event_init.idl",
           "bluetooth/bluetooth_le_scan_filter_init.idl",
           "bluetooth/request_device_options.idl",
diff --git a/third_party/blink/renderer/modules/payments/payment_instruments.cc b/third_party/blink/renderer/modules/payments/payment_instruments.cc
index a0a30bf..3c0e57ec 100644
--- a/third_party/blink/renderer/modules/payments/payment_instruments.cc
+++ b/third_party/blink/renderer/modules/payments/payment_instruments.cc
@@ -94,6 +94,52 @@
 
 }  // namespace
 
+// Class used to convert the placement only |PaymentInstrument| to
+// GarbageCollected, so it can be used in WTF::Bind. Otherwise, on-heap objects
+// referenced by |PaymentInstrument| will not be traced through the callback and
+// can be prematurely destroyed.
+// TODO(keishi): Remove this conversion if IDLDictionaryBase situation changes.
+class PaymentInstrumentParameter
+    : public GarbageCollectedFinalized<PaymentInstrumentParameter> {
+ public:
+  explicit PaymentInstrumentParameter(const PaymentInstrument& instrument)
+      : has_icons_(instrument.hasIcons()),
+        has_capabilities_(instrument.hasCapabilities()),
+        has_method_(instrument.hasMethod()),
+        has_name_(instrument.hasName()),
+        capabilities_(instrument.capabilities()),
+        method_(instrument.method()),
+        name_(instrument.name()) {
+    if (has_icons_)
+      icons_ = instrument.icons();
+  }
+
+  bool has_capabilities() const { return has_capabilities_; }
+  ScriptValue capabilities() const { return capabilities_; }
+
+  bool has_icons() const { return has_icons_; }
+  const HeapVector<ImageObject>& icons() const { return icons_; }
+
+  bool has_method() const { return has_method_; }
+  const String& method() const { return method_; }
+
+  bool has_name() const { return has_name_; }
+  const String& name() const { return name_; }
+
+  void Trace(blink::Visitor* visitor) { visitor->Trace(icons_); }
+
+ private:
+  bool has_icons_;
+  bool has_capabilities_;
+  bool has_method_;
+  bool has_name_;
+
+  ScriptValue capabilities_;
+  HeapVector<ImageObject> icons_;
+  String method_;
+  String name_;
+};
+
 PaymentInstruments::PaymentInstruments(
     const payments::mojom::blink::PaymentManagerPtr& manager)
     : manager_(manager) {}
@@ -207,7 +253,8 @@
           Frame::HasTransientUserActivation(doc ? doc->GetFrame() : nullptr),
           WTF::Bind(&PaymentInstruments::OnRequestPermission,
                     WrapPersistent(this), WrapPersistent(resolver),
-                    instrument_key, details));
+                    instrument_key,
+                    WrapPersistent(new PaymentInstrumentParameter(details))));
   return resolver->Promise();
 }
 
@@ -242,7 +289,7 @@
 void PaymentInstruments::OnRequestPermission(
     ScriptPromiseResolver* resolver,
     const String& instrument_key,
-    const PaymentInstrument& details,
+    PaymentInstrumentParameter* details,
     mojom::blink::PermissionStatus status) {
   DCHECK(resolver);
   if (!resolver->GetExecutionContext() ||
@@ -260,11 +307,12 @@
 
   payments::mojom::blink::PaymentInstrumentPtr instrument =
       payments::mojom::blink::PaymentInstrument::New();
-  instrument->name = details.hasName() ? details.name() : WTF::g_empty_string;
-  if (details.hasIcons()) {
+  instrument->name =
+      details->has_name() ? details->name() : WTF::g_empty_string;
+  if (details->has_icons()) {
     ExecutionContext* context =
         ExecutionContext::From(resolver->GetScriptState());
-    for (const ImageObject image_object : details.icons()) {
+    for (const ImageObject image_object : details->icons()) {
       KURL parsed_url = context->CompleteURL(image_object.src());
       if (!parsed_url.IsValid() || !parsed_url.ProtocolIsInHTTPFamily()) {
         resolver->Reject(V8ThrowException::CreateTypeError(
@@ -288,12 +336,12 @@
   }
 
   instrument->method =
-      details.hasMethod() ? details.method() : WTF::g_empty_string;
+      details->has_method() ? details->method() : WTF::g_empty_string;
 
-  if (details.hasCapabilities()) {
+  if (details->has_capabilities()) {
     v8::Local<v8::String> value;
     if (!v8::JSON::Stringify(resolver->GetScriptState()->GetContext(),
-                             details.capabilities().V8Value().As<v8::Object>())
+                             details->capabilities().V8Value().As<v8::Object>())
              .ToLocal(&value)) {
       resolver->Reject(V8ThrowException::CreateTypeError(
           resolver->GetScriptState()->GetIsolate(),
@@ -306,7 +354,7 @@
                                      ExceptionState::kSetterContext,
                                      "PaymentInstruments", "set");
       BasicCardHelper::ParseBasiccardData(
-          details.capabilities(), instrument->supported_networks,
+          details->capabilities(), instrument->supported_networks,
           instrument->supported_types, exception_state);
       if (exception_state.HadException()) {
         resolver->Reject(exception_state);
diff --git a/third_party/blink/renderer/modules/payments/payment_instruments.h b/third_party/blink/renderer/modules/payments/payment_instruments.h
index 02f9046..88fab01 100644
--- a/third_party/blink/renderer/modules/payments/payment_instruments.h
+++ b/third_party/blink/renderer/modules/payments/payment_instruments.h
@@ -20,6 +20,7 @@
 class ScriptPromise;
 class ScriptPromiseResolver;
 class ScriptState;
+class PaymentInstrumentParameter;
 
 class MODULES_EXPORT PaymentInstruments final : public ScriptWrappable {
   DEFINE_WRAPPERTYPEINFO();
@@ -42,7 +43,7 @@
   mojom::blink::PermissionService* GetPermissionService(ScriptState*);
   void OnRequestPermission(ScriptPromiseResolver*,
                            const String&,
-                           const PaymentInstrument&,
+                           PaymentInstrumentParameter*,
                            mojom::blink::PermissionStatus);
 
   void onDeletePaymentInstrument(ScriptPromiseResolver*,
diff --git a/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl b/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
index 42690b59..6390ce4 100644
--- a/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
+++ b/third_party/blink/renderer/modules/shapedetection/barcode_detector.idl
@@ -9,7 +9,7 @@
     ConstructorCallWith=ExecutionContext,
     Exposed=(Window,Worker),
     MeasureAs=ShapeDetection_BarcodeDetectorConstructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface BarcodeDetector {
     [CallWith=ScriptState] Promise<sequence<DetectedBarcode>> detect(ImageBitmapSource image);
 };
diff --git a/third_party/blink/renderer/modules/shapedetection/detected_barcode.idl b/third_party/blink/renderer/modules/shapedetection/detected_barcode.idl
index 56951d2c..8d6aa75 100644
--- a/third_party/blink/renderer/modules/shapedetection/detected_barcode.idl
+++ b/third_party/blink/renderer/modules/shapedetection/detected_barcode.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface DetectedBarcode {
     // TODO(mcasas): Implement missing fields. https://crbug.com/646083
     [SameObject] readonly attribute DOMString rawValue;
diff --git a/third_party/blink/renderer/modules/shapedetection/detected_face.idl b/third_party/blink/renderer/modules/shapedetection/detected_face.idl
index d07c2972..fda03e7 100644
--- a/third_party/blink/renderer/modules/shapedetection/detected_face.idl
+++ b/third_party/blink/renderer/modules/shapedetection/detected_face.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface DetectedFace {
     // TODO(xianglu): Implement any other fields. https://crbug.com/646083
     [SameObject] readonly attribute DOMRectReadOnly boundingBox;
diff --git a/third_party/blink/renderer/modules/shapedetection/detected_text.idl b/third_party/blink/renderer/modules/shapedetection/detected_text.idl
index ec0a22a..8b08cde 100644
--- a/third_party/blink/renderer/modules/shapedetection/detected_text.idl
+++ b/third_party/blink/renderer/modules/shapedetection/detected_text.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface DetectedText {
     [SameObject] readonly attribute DOMString rawValue;
     [SameObject] readonly attribute DOMRectReadOnly boundingBox;
diff --git a/third_party/blink/renderer/modules/shapedetection/face_detector.idl b/third_party/blink/renderer/modules/shapedetection/face_detector.idl
index 6b3b8ab..4ac929e 100644
--- a/third_party/blink/renderer/modules/shapedetection/face_detector.idl
+++ b/third_party/blink/renderer/modules/shapedetection/face_detector.idl
@@ -9,7 +9,7 @@
     ConstructorCallWith=ExecutionContext,
     Exposed=(Window,Worker),
     MeasureAs=ShapeDetection_FaceDetectorConstructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface FaceDetector {
     [CallWith=ScriptState] Promise<sequence<DetectedFace>> detect(ImageBitmapSource image);
 };
diff --git a/third_party/blink/renderer/modules/shapedetection/text_detector.idl b/third_party/blink/renderer/modules/shapedetection/text_detector.idl
index ea26d96..0896eb4 100644
--- a/third_party/blink/renderer/modules/shapedetection/text_detector.idl
+++ b/third_party/blink/renderer/modules/shapedetection/text_detector.idl
@@ -9,7 +9,7 @@
     ConstructorCallWith=ExecutionContext,
     Exposed=(Window,Worker),
     MeasureAs=ShapeDetection_TextDetectorConstructor,
-    RuntimeEnabled=ShapeDetection
+    OriginTrialEnabled=ShapeDetection
 ] interface TextDetector {
     [CallWith=ScriptState] Promise<sequence<DetectedText>> detect(ImageBitmapSource image);
 };
diff --git a/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.cc b/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.cc
index a3b49bc..0f48ab2f 100644
--- a/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.cc
+++ b/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.cc
@@ -42,13 +42,8 @@
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
-
 using protocol::Response;
 
-namespace DOMStorageAgentState {
-static const char kDomStorageAgentEnabled[] = "domStorageAgentEnabled";
-};
-
 static Response ToResponse(ExceptionState& exception_state) {
   if (!exception_state.HadException())
     return Response::OK();
@@ -63,7 +58,8 @@
 
 InspectorDOMStorageAgent::InspectorDOMStorageAgent(
     InspectedFrames* inspected_frames)
-    : inspected_frames_(inspected_frames), is_enabled_(false) {}
+    : inspected_frames_(inspected_frames),
+      enabled_(&agent_state_, /*default_value=*/false) {}
 
 InspectorDOMStorageAgent::~InspectorDOMStorageAgent() = default;
 
@@ -73,28 +69,28 @@
 }
 
 void InspectorDOMStorageAgent::Restore() {
-  if (state_->booleanProperty(DOMStorageAgentState::kDomStorageAgentEnabled,
-                              false)) {
-    enable();
-  }
+  if (enabled_.Get())
+    InnerEnable();
 }
 
-Response InspectorDOMStorageAgent::enable() {
-  if (is_enabled_)
-    return Response::OK();
-  is_enabled_ = true;
-  state_->setBoolean(DOMStorageAgentState::kDomStorageAgentEnabled, true);
+void InspectorDOMStorageAgent::InnerEnable() {
   if (StorageNamespaceController* controller = StorageNamespaceController::From(
           inspected_frames_->Root()->GetPage()))
     controller->SetInspectorAgent(this);
+}
+
+Response InspectorDOMStorageAgent::enable() {
+  if (enabled_.Get())
+    return Response::OK();
+  enabled_.Set(true);
+  InnerEnable();
   return Response::OK();
 }
 
 Response InspectorDOMStorageAgent::disable() {
-  if (!is_enabled_)
+  if (!enabled_.Get())
     return Response::OK();
-  is_enabled_ = false;
-  state_->setBoolean(DOMStorageAgentState::kDomStorageAgentEnabled, false);
+  enabled_.Set(false);
   if (StorageNamespaceController* controller = StorageNamespaceController::From(
           inspected_frames_->Root()->GetPage()))
     controller->SetInspectorAgent(nullptr);
diff --git a/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.h b/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.h
index adc23d1b..f2d68b67 100644
--- a/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.h
+++ b/third_party/blink/renderer/modules/storage/inspector_dom_storage_agent.h
@@ -54,6 +54,7 @@
                                   const SecurityOrigin*);
 
  private:
+  void InnerEnable();
 
   // InspectorBaseAgent overrides.
   void Restore() override;
@@ -84,7 +85,7 @@
       bool is_local_storage);
 
   Member<InspectedFrames> inspected_frames_;
-  bool is_enabled_;
+  InspectorAgentState::Boolean enabled_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.cc b/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.cc
index 95499656..5c77fbdb 100644
--- a/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.cc
+++ b/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.cc
@@ -48,14 +48,9 @@
     ExecuteSQLCallback;
 
 namespace blink {
-
 using protocol::Maybe;
 using protocol::Response;
 
-namespace DatabaseAgentState {
-static const char kDatabaseAgentEnabled[] = "databaseAgentEnabled";
-};
-
 namespace {
 
 class ExecuteSQLCallbackWrapper : public RefCounted<ExecuteSQLCallbackWrapper> {
@@ -230,7 +225,7 @@
       InspectorDatabaseResource::Create(database, domain, name, version);
   resources_.Set(resource->Id(), resource);
   // Resources are only bound while visible.
-  DCHECK(enabled_);
+  DCHECK(enabled_.Get());
   DCHECK(GetFrontend());
   resource->Bind(GetFrontend());
 }
@@ -244,29 +239,31 @@
 }
 
 InspectorDatabaseAgent::InspectorDatabaseAgent(Page* page)
-    : page_(page), enabled_(false) {}
+    : page_(page), enabled_(&agent_state_, /*default_value=*/false) {}
 
 InspectorDatabaseAgent::~InspectorDatabaseAgent() = default;
 
-Response InspectorDatabaseAgent::enable() {
-  if (enabled_)
-    return Response::OK();
-  enabled_ = true;
-  state_->setBoolean(DatabaseAgentState::kDatabaseAgentEnabled, enabled_);
+void InspectorDatabaseAgent::InnerEnable() {
   if (DatabaseClient* client = DatabaseClient::FromPage(page_))
     client->SetInspectorAgent(this);
   DatabaseTracker::Tracker().ForEachOpenDatabaseInPage(
       page_,
       WTF::BindRepeating(&InspectorDatabaseAgent::RegisterDatabaseOnCreation,
                          WrapPersistent(this)));
+}
+
+Response InspectorDatabaseAgent::enable() {
+  if (enabled_.Get())
+    return Response::OK();
+  enabled_.Set(true);
+  InnerEnable();
   return Response::OK();
 }
 
 Response InspectorDatabaseAgent::disable() {
-  if (!enabled_)
+  if (!enabled_.Get())
     return Response::OK();
-  enabled_ = false;
-  state_->setBoolean(DatabaseAgentState::kDatabaseAgentEnabled, enabled_);
+  enabled_.Set(false);
   if (DatabaseClient* client = DatabaseClient::FromPage(page_))
     client->SetInspectorAgent(nullptr);
   resources_.clear();
@@ -274,16 +271,14 @@
 }
 
 void InspectorDatabaseAgent::Restore() {
-  if (state_->booleanProperty(DatabaseAgentState::kDatabaseAgentEnabled,
-                              false)) {
-    enable();
-  }
+  if (enabled_.Get())
+    InnerEnable();
 }
 
 Response InspectorDatabaseAgent::getDatabaseTableNames(
     const String& database_id,
     std::unique_ptr<protocol::Array<String>>* names) {
-  if (!enabled_)
+  if (!enabled_.Get())
     return Response::Error("Database agent is not enabled");
 
   *names = protocol::Array<String>::create();
@@ -305,7 +300,7 @@
   std::unique_ptr<ExecuteSQLCallback> request_callback =
       std::move(prp_request_callback);
 
-  if (!enabled_) {
+  if (!enabled_.Get()) {
     request_callback->sendFailure(
         Response::Error("Database agent is not enabled"));
     return;
diff --git a/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.h b/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.h
index f8d1a7de2..0643e9a 100644
--- a/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.h
+++ b/third_party/blink/renderer/modules/webdatabase/inspector_database_agent.h
@@ -74,6 +74,7 @@
 
  private:
   explicit InspectorDatabaseAgent(Page*);
+  void InnerEnable();
   void RegisterDatabaseOnCreation(blink::Database*);
 
   blink::Database* DatabaseForId(const String& database_id);
@@ -83,7 +84,7 @@
   typedef HeapHashMap<String, Member<InspectorDatabaseResource>>
       DatabaseResourcesHeapMap;
   DatabaseResourcesHeapMap resources_;
-  bool enabled_;
+  InspectorAgentState::Boolean enabled_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/DEPS b/third_party/blink/renderer/modules/webgl/DEPS
index a1d284b..e04241d 100644
--- a/third_party/blink/renderer/modules/webgl/DEPS
+++ b/third_party/blink/renderer/modules/webgl/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
     "+gpu/GLES2/gl2extchromium.h",
     "+gpu/command_buffer/client/gles2_interface.h",
+    "+gpu/command_buffer/common/capabilities.h",
     "+gpu/config/gpu_feature_info.h",
     "+skia/ext",
 ]
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 2c7b940..8a58769c 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -30,6 +30,7 @@
 #include "base/numerics/checked_math.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/common/capabilities.h"
 #include "gpu/config/gpu_feature_info.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -1006,6 +1007,7 @@
     Platform::ContextType context_type)
     : CanvasRenderingContext(host, requested_attributes),
       context_group_(new WebGLContextGroup()),
+      is_origin_top_left_(false),
       is_hidden_(false),
       context_lost_mode_(kNotLostContext),
       auto_recovery_method_(kManual),
@@ -1221,6 +1223,13 @@
       WTF::BindRepeating(&WebGLRenderingContextBase::OnErrorMessage,
                          WrapWeakPersistent(this)));
 
+  // If the context has the flip_y extension, it will behave as having the
+  // origin of coordinates on the top left.
+  is_origin_top_left_ = GetDrawingBuffer()
+                            ->ContextProvider()
+                            ->GetCapabilities()
+                            .mesa_framebuffer_flip_y;
+
   // If WebGL 2, the PRIMITIVE_RESTART_FIXED_INDEX should be always enabled.
   // See the section <Primitive Restart is Always Enabled> in WebGL 2 spec:
   // https://www.khronos.org/registry/webgl/specs/latest/2.0/#4.1.4
@@ -1526,6 +1535,12 @@
     GetDrawingBuffer()->ResetBuffersToAutoClear();
 }
 
+bool WebGLRenderingContextBase::IsOriginTopLeft() const {
+  if (isContextLost())
+    return false;
+  return is_origin_top_left_;
+}
+
 void WebGLRenderingContextBase::SetIsHidden(bool hidden) {
   is_hidden_ = hidden;
   if (GetDrawingBuffer())
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 462e62b2..0c81258 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -653,6 +653,7 @@
   bool Is3d() const override { return true; }
   bool IsComposited() const override { return true; }
   bool IsAccelerated() const override { return true; }
+  bool IsOriginTopLeft() const override;
   void SetIsHidden(bool) override;
   bool PaintRenderingResultsToCanvas(SourceDrawingBuffer) override;
   cc::Layer* CcLayer() const override;
@@ -711,6 +712,8 @@
 
   TraceWrapperMember<WebGLContextGroup> context_group_;
 
+  bool is_origin_top_left_;
+
   bool is_hidden_;
   LostContextMode context_lost_mode_;
   AutoRecoveryMethod auto_recovery_method_;
diff --git a/third_party/blink/renderer/modules/webusb/usb.cc b/third_party/blink/renderer/modules/webusb/usb.cc
index 431d39b..df63696 100644
--- a/third_party/blink/renderer/modules/webusb/usb.cc
+++ b/third_party/blink/renderer/modules/webusb/usb.cc
@@ -276,12 +276,9 @@
   if (!context)
     return false;
 
-  DCHECK(context->IsDocument() || context->IsDedicatedWorkerGlobalScope() ||
-         context->IsSharedWorkerGlobalScope());
+  DCHECK(context->IsDocument() || context->IsDedicatedWorkerGlobalScope());
   DCHECK(!context->IsDedicatedWorkerGlobalScope() ||
          RuntimeEnabledFeatures::WebUSBOnDedicatedWorkersEnabled());
-  DCHECK(!context->IsSharedWorkerGlobalScope() ||
-         RuntimeEnabledFeatures::WebUSBOnSharedWorkersEnabled());
 
   return true;
 }
@@ -289,15 +286,7 @@
 bool USB::IsFeatureEnabled() const {
   ExecutionContext* context = GetExecutionContext();
   FeaturePolicy* policy = context->GetSecurityContext().GetFeaturePolicy();
-  // Feature policy is not yet supported in all contexts.
-  if (policy)
-    return policy->IsFeatureEnabled(mojom::FeaturePolicyFeature::kUsb);
-
-  // TODO(https://crbug.com/843780): Enable this check for shared workers.
-  if (context->IsSharedWorkerGlobalScope())
-    return true;
-
-  return false;
+  return policy->IsFeatureEnabled(mojom::FeaturePolicyFeature::kUsb);
 }
 
 void USB::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/modules/webusb/usb.idl b/third_party/blink/renderer/modules/webusb/usb.idl
index 06b8287..05247015 100644
--- a/third_party/blink/renderer/modules/webusb/usb.idl
+++ b/third_party/blink/renderer/modules/webusb/usb.idl
@@ -5,7 +5,7 @@
 // https://wicg.github.io/webusb/#usb
 
 [
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USB : EventTarget {
     attribute EventHandler onconnect;
diff --git a/third_party/blink/renderer/modules/webusb/usb_alternate_interface.idl b/third_party/blink/renderer/modules/webusb/usb_alternate_interface.idl
index 20cfe45..80de8d9 100644
--- a/third_party/blink/renderer/modules/webusb/usb_alternate_interface.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_alternate_interface.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(USBInterface deviceInterface, octet alternateSetting),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     RaisesException=Constructor,
     SecureContext
 ] interface USBAlternateInterface {
diff --git a/third_party/blink/renderer/modules/webusb/usb_configuration.idl b/third_party/blink/renderer/modules/webusb/usb_configuration.idl
index a4ebe75..62e2d00 100644
--- a/third_party/blink/renderer/modules/webusb/usb_configuration.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_configuration.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(USBDevice device, octet configurationValue),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     RaisesException=Constructor,
     SecureContext
 ] interface USBConfiguration {
diff --git a/third_party/blink/renderer/modules/webusb/usb_connection_event.idl b/third_party/blink/renderer/modules/webusb/usb_connection_event.idl
index ed62a8e..e3c22d17 100644
--- a/third_party/blink/renderer/modules/webusb/usb_connection_event.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_connection_event.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(DOMString type, USBConnectionEventInit eventInitDict),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBConnectionEvent : Event {
     [SameObject] readonly attribute USBDevice device;
diff --git a/third_party/blink/renderer/modules/webusb/usb_device.idl b/third_party/blink/renderer/modules/webusb/usb_device.idl
index bff5d2ed..223fb5f 100644
--- a/third_party/blink/renderer/modules/webusb/usb_device.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_device.idl
@@ -13,7 +13,7 @@
 // https://wicg.github.io/webusb/#device-usage
 
 [
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBDevice {
     readonly attribute octet usbVersionMajor;
diff --git a/third_party/blink/renderer/modules/webusb/usb_endpoint.idl b/third_party/blink/renderer/modules/webusb/usb_endpoint.idl
index 80d5298..679e8825 100644
--- a/third_party/blink/renderer/modules/webusb/usb_endpoint.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_endpoint.idl
@@ -17,7 +17,7 @@
 
 [
     Constructor(USBAlternateInterface alternate, octet endpointNumber, USBDirection direction),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     RaisesException=Constructor,
     SecureContext
 ] interface USBEndpoint {
diff --git a/third_party/blink/renderer/modules/webusb/usb_in_transfer_result.idl b/third_party/blink/renderer/modules/webusb/usb_in_transfer_result.idl
index 3efc62c..2c9fed6 100644
--- a/third_party/blink/renderer/modules/webusb/usb_in_transfer_result.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_in_transfer_result.idl
@@ -5,7 +5,7 @@
 // https://wicg.github.io/webusb/#usbintransferresult
 
 [
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     Constructor(USBTransferStatus status, optional DataView? data),
     SecureContext
 ] interface USBInTransferResult {
diff --git a/third_party/blink/renderer/modules/webusb/usb_interface.idl b/third_party/blink/renderer/modules/webusb/usb_interface.idl
index a25d466..16da8b7 100644
--- a/third_party/blink/renderer/modules/webusb/usb_interface.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_interface.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(USBConfiguration configuration, octet interfaceNumber),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     RaisesException=Constructor,
     SecureContext
 ] interface USBInterface {
diff --git a/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_packet.idl b/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_packet.idl
index 313adbb9..d1f213e 100644
--- a/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_packet.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_packet.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(USBTransferStatus status, optional DataView? data),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBIsochronousInTransferPacket {
     readonly attribute USBTransferStatus status;
diff --git a/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_result.idl b/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_result.idl
index 0d6b8187..ccf61d82 100644
--- a/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_result.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_isochronous_in_transfer_result.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(sequence<USBIsochronousInTransferPacket> packets, optional DataView? data),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBIsochronousInTransferResult {
     readonly attribute DataView? data;
diff --git a/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_packet.idl b/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_packet.idl
index f080b0e4..7473dd03 100644
--- a/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_packet.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_packet.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBIsochronousOutTransferPacket {
     readonly attribute unsigned long bytesWritten;
diff --git a/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_result.idl b/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_result.idl
index 01e6940..3062e08 100644
--- a/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_result.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_isochronous_out_transfer_result.idl
@@ -6,7 +6,7 @@
 
 [
     Constructor(sequence<USBIsochronousOutTransferPacket> packets),
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     SecureContext
 ] interface USBIsochronousOutTransferResult {
     readonly attribute FrozenArray<USBIsochronousOutTransferPacket> packets;
diff --git a/third_party/blink/renderer/modules/webusb/usb_out_transfer_result.idl b/third_party/blink/renderer/modules/webusb/usb_out_transfer_result.idl
index b68561e1..dec458c 100644
--- a/third_party/blink/renderer/modules/webusb/usb_out_transfer_result.idl
+++ b/third_party/blink/renderer/modules/webusb/usb_out_transfer_result.idl
@@ -5,7 +5,7 @@
 // https://wicg.github.io/webusb/#usbouttransferresult
 
 [
-    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, SharedWorker WebUSBOnSharedWorkers, Window WebUSB),
+    Exposed(DedicatedWorker WebUSBOnDedicatedWorkers, Window WebUSB),
     Constructor(USBTransferStatus status, optional unsigned long bytesWritten = 0),
     SecureContext
 ] interface USBOutTransferResult {
diff --git a/third_party/blink/renderer/modules/webusb/worker_navigator_usb.cc b/third_party/blink/renderer/modules/webusb/worker_navigator_usb.cc
index d26f54b..b521d2f 100644
--- a/third_party/blink/renderer/modules/webusb/worker_navigator_usb.cc
+++ b/third_party/blink/renderer/modules/webusb/worker_navigator_usb.cc
@@ -42,11 +42,8 @@
     bool isDedicatedWorkerAndEnabled =
         context->IsDedicatedWorkerGlobalScope() &&
         RuntimeEnabledFeatures::WebUSBOnDedicatedWorkersEnabled();
-    bool isSharedWorkerAndEnabled =
-        context->IsSharedWorkerGlobalScope() &&
-        RuntimeEnabledFeatures::WebUSBOnSharedWorkersEnabled();
 
-    if (isDedicatedWorkerAndEnabled || isSharedWorkerAndEnabled) {
+    if (isDedicatedWorkerAndEnabled) {
       usb_ = USB::Create(*context);
     }
   }
diff --git a/third_party/blink/renderer/modules/xr/xr_device.cc b/third_party/blink/renderer/modules/xr/xr_device.cc
index edd484b..b77c9e5 100644
--- a/third_party/blink/renderer/modules/xr/xr_device.cc
+++ b/third_party/blink/renderer/modules/xr/xr_device.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/renderer/modules/event_target_modules.h"
 #include "third_party/blink/renderer/modules/xr/xr.h"
 #include "third_party/blink/renderer/modules/xr/xr_frame_provider.h"
+#include "third_party/blink/renderer/modules/xr/xr_presentation_context.h"
 #include "third_party/blink/renderer/modules/xr/xr_session.h"
 
 namespace blink {
@@ -178,19 +179,25 @@
       options.environmentIntegration();
   session_options->has_user_activation = has_user_activation;
 
+  XRPresentationContext* output_context =
+      options.hasOutputContext() ? options.outputContext() : nullptr;
+
   // TODO(offenwanger): Once device activation is sorted out for WebXR, either
   // pass in the value for metrics, or remove the value as soon as legacy API
   // has been removed.
   display_->RequestSession(
       std::move(session_options), false /* triggered by display activate */,
       WTF::Bind(&XRDevice::OnRequestSessionReturned, WrapWeakPersistent(this),
-                WrapPersistent(resolver), options));
+                WrapPersistent(resolver), WrapPersistent(output_context),
+                options.environmentIntegration(), options.immersive()));
   return promise;
 }
 
 void XRDevice::OnRequestSessionReturned(
     ScriptPromiseResolver* resolver,
-    const XRSessionCreationOptions& options,
+    XRPresentationContext* output_context,
+    bool environment_integration,
+    bool immersive,
     device::mojom::blink::XRSessionPtr session_ptr) {
   if (!session_ptr) {
     DOMException* exception = DOMException::Create(
@@ -199,22 +206,15 @@
     return;
   }
 
-  XRPresentationContext* output_context = nullptr;
-  if (options.hasOutputContext()) {
-    output_context = options.outputContext();
-  }
-
   XRSession::EnvironmentBlendMode blend_mode = XRSession::kBlendModeOpaque;
-  if (options.environmentIntegration()) {
+  if (environment_integration)
     blend_mode = XRSession::kBlendModeAlphaBlend;
-  }
 
-  XRSession* session =
-      new XRSession(this, options.immersive(), options.environmentIntegration(),
-                    output_context, blend_mode);
+  XRSession* session = new XRSession(this, immersive, environment_integration,
+                                     output_context, blend_mode);
   sessions_.insert(session);
 
-  if (options.immersive()) {
+  if (immersive) {
     frameProvider()->BeginImmersiveSession(session, std::move(session_ptr));
   } else {
     magic_window_provider_.Bind(std::move(session_ptr->data_provider));
diff --git a/third_party/blink/renderer/modules/xr/xr_device.h b/third_party/blink/renderer/modules/xr/xr_device.h
index 2e83621..4e7d08cb 100644
--- a/third_party/blink/renderer/modules/xr/xr_device.h
+++ b/third_party/blink/renderer/modules/xr/xr_device.h
@@ -88,7 +88,9 @@
   const char* checkSessionSupport(const XRSessionCreationOptions&) const;
 
   void OnRequestSessionReturned(ScriptPromiseResolver* resolver,
-                                const XRSessionCreationOptions& options,
+                                XRPresentationContext* output_context,
+                                bool environment_integration,
+                                bool immersive,
                                 device::mojom::blink::XRSessionPtr session);
   void OnSupportsSessionReturned(ScriptPromiseResolver* resolver,
                                  bool supports_session);
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 7dd411e..696801e 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1173,9 +1173,6 @@
     "mac/graphics_context_canvas.mm",
     "mac/local_current_graphics_context.h",
     "mac/local_current_graphics_context.mm",
-    "mac/ns_scroller_imp_details.h",
-    "mac/scroll_animator_mac.h",
-    "mac/scroll_animator_mac.mm",
     "mac/theme_mac.h",
     "mac/theme_mac.mm",
     "mac/version_util_mac.h",
@@ -1225,7 +1222,6 @@
     "peerconnection/rtc_stats_request.h",
     "peerconnection/rtc_stats_response_base.h",
     "peerconnection/rtc_void_request.h",
-    "platform_chrome_client.h",
     "plugins/plugin_data.cc",
     "plugins/plugin_data.h",
     "plugins/plugin_script_forbidden_scope.cc",
@@ -1246,43 +1242,10 @@
     "scoped_orientation_change_indicator.cc",
     "scoped_orientation_change_indicator.h",
     "scroll/main_thread_scrolling_reason.h",
-    "scroll/programmatic_scroll_animator.cc",
-    "scroll/programmatic_scroll_animator.h",
     "scroll/scroll_alignment.cc",
     "scroll/scroll_alignment.h",
-    "scroll/scroll_animator.cc",
-    "scroll/scroll_animator.h",
-    "scroll/scroll_animator_base.cc",
-    "scroll/scroll_animator_base.h",
-    "scroll/scroll_animator_compositor_coordinator.cc",
-    "scroll/scroll_animator_compositor_coordinator.h",
-    "scroll/scroll_customization.cc",
-    "scroll/scroll_customization.h",
     "scroll/scroll_snap_data.h",
-    "scroll/scroll_state_data.h",
     "scroll/scroll_types.h",
-    "scroll/scrollable_area.cc",
-    "scroll/scrollable_area.h",
-    "scroll/scrollbar.cc",
-    "scroll/scrollbar.h",
-    "scroll/scrollbar_layer_delegate.cc",
-    "scroll/scrollbar_layer_delegate.h",
-    "scroll/scrollbar_theme.cc",
-    "scroll/scrollbar_theme.h",
-    "scroll/scrollbar_theme_android.cc",
-    "scroll/scrollbar_theme_aura.cc",
-    "scroll/scrollbar_theme_aura.h",
-    "scroll/scrollbar_theme_mac.h",
-    "scroll/scrollbar_theme_mac.mm",
-    "scroll/scrollbar_theme_mock.cc",
-    "scroll/scrollbar_theme_mock.h",
-    "scroll/scrollbar_theme_overlay.cc",
-    "scroll/scrollbar_theme_overlay.h",
-    "scroll/scrollbar_theme_overlay_mock.h",
-    "scroll/smooth_scroll_sequencer.cc",
-    "scroll/smooth_scroll_sequencer.h",
-    "scroll/web_scroll_into_view_params.cc",
-    "scroll/web_scrollbar_theme.mm",
     "scroll/web_scrollbar_theme_client.h",
     "serialized_resource.h",
     "shared_buffer.cc",
@@ -1516,8 +1479,6 @@
     sources -= [
       "fonts/skia/font_cache_skia.cc",
       "fonts/web_font_render_style.cc",
-      "scroll/scroll_animator.cc",
-      "scroll/scroll_animator.h",
 
       # Uses LocaleMac instead.
       "text/locale_icu.cc",
@@ -1572,13 +1533,6 @@
     ]
   }
 
-  if (!use_default_render_theme) {
-    sources -= [
-      "scroll/scrollbar_theme_aura.cc",
-      "scroll/scrollbar_theme_aura.h",
-    ]
-  }
-
   if (current_cpu == "arm") {
     deps += [ ":blink_arm_neon" ]
   }
@@ -1613,7 +1567,6 @@
 
   sources = [
     "graphics/gpu/drawing_buffer_test_helpers.h",
-    "scroll/scrollbar_test_suite.h",
     "testing/compositor_test.cc",
     "testing/compositor_test.h",
     "testing/empty_web_media_player.cc",
@@ -1653,7 +1606,6 @@
     "testing/unit_test_helpers.h",
     "testing/url_test_helpers.cc",
     "testing/url_test_helpers.h",
-    "testing/use_mock_scrollbar_settings.h",
     "testing/viewport_layers_setup.cc",
     "testing/viewport_layers_setup.h",
     "testing/weburl_loader_mock.cc",
@@ -1838,8 +1790,6 @@
     "pod_interval_tree_test.cc",
     "pod_red_black_tree_test.cc",
     "scoped_orientation_change_indicator_test.cc",
-    "scroll/scrollable_area_test.cc",
-    "scroll/scrollbar_theme_overlay_test.cc",
     "shared_buffer_test.cc",
     "testing/arena_test_helpers.h",
     "testing/tree_test_helpers.cc",
@@ -1890,14 +1840,6 @@
     sources += [ "text/locale_icu_test.cc" ]
   }
 
-  if (!is_mac) {
-    sources += [ "scroll/scroll_animator_test.cc" ]
-  }
-
-  if (use_default_render_theme) {
-    sources += [ "scroll/scrollbar_theme_aura_test.cc" ]
-  }
-
   sources += [ "testing/run_all_tests.cc" ]
 
   configs += [
diff --git a/third_party/blink/renderer/platform/fonts/font_description.h b/third_party/blink/renderer/platform/fonts/font_description.h
index 140e099..15a999c 100644
--- a/third_party/blink/renderer/platform/fonts/font_description.h
+++ b/third_party/blink/renderer/platform/fonts/font_description.h
@@ -100,6 +100,8 @@
 
   struct VariantLigatures {
     STACK_ALLOCATED();
+
+   public:
     VariantLigatures(LigaturesState state = kNormalLigaturesState)
         : common(state),
           discretionary(state),
@@ -116,6 +118,8 @@
 
   struct Size {
     STACK_ALLOCATED();
+
+   public:
     Size(unsigned keyword, float value, bool is_absolute)
         : keyword(keyword), is_absolute(is_absolute), value(value) {}
 
@@ -128,6 +132,8 @@
 
   struct FamilyDescription {
     STACK_ALLOCATED();
+
+   public:
     FamilyDescription(GenericFamilyType generic_family)
         : generic_family(generic_family) {}
     FamilyDescription(GenericFamilyType generic_family,
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
index 71eca39f..314bbf51 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result.h
@@ -260,6 +260,7 @@
   struct GlyphIndexResult {
     STACK_ALLOCATED();
 
+   public:
     unsigned run_index = 0;
     // The total number of characters of runs_[0..run_index - 1].
     unsigned characters_on_left_runs = 0;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h
index 239a015..e58db43 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h
@@ -53,6 +53,7 @@
   struct Result {
     STACK_ALLOCATED();
 
+   public:
     // Indicates the resulting break offset.
     unsigned break_offset;
 
@@ -97,6 +98,7 @@
   struct BreakOpportunity {
     STACK_ALLOCATED();
 
+   public:
     unsigned offset;
     bool is_hyphenated;
   };
diff --git a/third_party/blink/renderer/platform/fonts/simple_font_data.h b/third_party/blink/renderer/platform/fonts/simple_font_data.h
index 8e14be72..a1e85e7 100644
--- a/third_party/blink/renderer/platform/fonts/simple_font_data.h
+++ b/third_party/blink/renderer/platform/fonts/simple_font_data.h
@@ -54,6 +54,8 @@
 // character.
 struct GlyphData {
   STACK_ALLOCATED();
+
+ public:
   GlyphData(
       Glyph g = 0,
       const SimpleFontData* f = nullptr,
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
index 427b328..0133cc0 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc
@@ -129,10 +129,11 @@
 void CanvasResourceDispatcher::DispatchFrameSync(
     scoped_refptr<CanvasResource> canvas_resource,
     base::TimeTicks commit_start_time,
-    const SkIRect& damage_rect) {
+    const SkIRect& damage_rect,
+    bool needs_vertical_flip) {
   viz::CompositorFrame frame;
   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
-                    &frame)) {
+                    needs_vertical_flip, &frame)) {
     return;
   }
 
@@ -147,10 +148,11 @@
 void CanvasResourceDispatcher::DispatchFrame(
     scoped_refptr<CanvasResource> canvas_resource,
     base::TimeTicks commit_start_time,
-    const SkIRect& damage_rect) {
+    const SkIRect& damage_rect,
+    bool needs_vertical_flip) {
   viz::CompositorFrame frame;
   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
-                    &frame)) {
+                    needs_vertical_flip, &frame)) {
     return;
   }
 
@@ -164,6 +166,7 @@
     scoped_refptr<CanvasResource> canvas_resource,
     base::TimeTicks commit_start_time,
     const SkIRect& damage_rect,
+    bool needs_vertical_flip,
     viz::CompositorFrame* frame) {
   if (!canvas_resource || !VerifyImageSize(canvas_resource->Size()))
     return false;
@@ -205,9 +208,6 @@
   sqs->SetAll(gfx::Transform(), bounds, bounds, bounds, is_clipped,
               are_contents_opaque, 1.f, SkBlendMode::kSrcOver, 0);
 
-  viz::TransferableResource resource;
-
-  bool yflipped = false;
   OffscreenCanvasCommitType commit_type;
   DEFINE_THREAD_SAFE_STATIC_LOCAL(
       EnumerationHistogram, commit_type_histogram,
@@ -221,7 +221,6 @@
     if (SharedGpuContext::IsGpuCompositingEnabled()) {
       // Case 1: both canvas and compositor are gpu accelerated.
       commit_type = kCommitGPUCanvasGPUCompositing;
-      yflipped = true;
     } else {
       // Case 2: canvas is accelerated but gpu compositing is disabled.
       commit_type = kCommitGPUCanvasSoftwareCompositing;
@@ -236,6 +235,7 @@
     }
   }
 
+  viz::TransferableResource resource;
   offscreen_canvas_resource_provider_->SetTransferableResource(&resource,
                                                                canvas_resource);
   // TODO(crbug.com/869913): add unit testing for this.
@@ -266,6 +266,11 @@
   // TODO(crbug.com/645590): filter should respect the image-rendering CSS
   // property of associated canvas element.
   const bool kNearestNeighbor = false;
+  // Accelerated resources have the origin of coordinates in the upper left
+  // corner while canvases have it in the lower left corner. The DrawQuad is
+  // marked as vertically flipped unless someone else has done the flip for us.
+  const bool yflipped =
+      SharedGpuContext::IsGpuCompositingEnabled() && needs_vertical_flip;
   quad->SetAll(sqs, bounds, bounds, kNeedsBlending, resource.id,
                canvas_resource_size, kPremultipliedAlpha, uv_top_left,
                uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity, yflipped,
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
index 0de60f33..862bc3b4 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h
@@ -49,11 +49,13 @@
   bool IsAnimationSuspended() const { return suspend_animation_; }
   void DispatchFrame(scoped_refptr<CanvasResource>,
                      base::TimeTicks commit_start_time,
-                     const SkIRect& damage_rect);
+                     const SkIRect& damage_rect,
+                     bool needs_vertical_flip);
   void ReclaimResource(viz::ResourceId);
   void DispatchFrameSync(scoped_refptr<CanvasResource>,
                          base::TimeTicks commit_start_time,
-                         const SkIRect& damage_rect);
+                         const SkIRect& damage_rect,
+                         bool needs_vertical_flip);
 
   void Reshape(const IntSize&);
 
@@ -88,6 +90,7 @@
   bool PrepareFrame(scoped_refptr<CanvasResource>,
                     base::TimeTicks commit_start_time,
                     const SkIRect& damage_rect,
+                    bool needs_vertical_flip,
                     viz::CompositorFrame* frame);
 
   // Surface-related
diff --git a/third_party/blink/renderer/platform/graphics/logging_canvas.cc b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
index 09dbc7eb..b5d37e1e 100644
--- a/third_party/blink/renderer/platform/graphics/logging_canvas.cc
+++ b/third_party/blink/renderer/platform/graphics/logging_canvas.cc
@@ -53,6 +53,8 @@
 
 struct VerbParams {
   STACK_ALLOCATED();
+
+ public:
   String name;
   unsigned point_count;
   unsigned point_offset;
diff --git a/third_party/blink/renderer/platform/graphics/offscreen_canvas_frame_dispatcher_test.cc b/third_party/blink/renderer/platform/graphics/offscreen_canvas_frame_dispatcher_test.cc
index eea63df..7079af8 100644
--- a/third_party/blink/renderer/platform/graphics/offscreen_canvas_frame_dispatcher_test.cc
+++ b/third_party/blink/renderer/platform/graphics/offscreen_canvas_frame_dispatcher_test.cc
@@ -68,7 +68,8 @@
 
 void CanvasResourceDispatcherTest::DispatchOneFrame() {
   dispatcher_->DispatchFrame(resource_provider_->ProduceFrame(),
-                             base::TimeTicks(), SkIRect::MakeEmpty());
+                             base::TimeTicks(), SkIRect::MakeEmpty(),
+                             false /* needs_vertical_flip */);
 }
 
 TEST_F(CanvasResourceDispatcherTest, PlaceholderRunsNormally) {
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
index 178d297..41f126a1 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.cc
@@ -211,10 +211,8 @@
 void PaintController::ProcessNewItem(DisplayItem& display_item) {
   DCHECK(!construction_disabled_);
 
-  if (IsSkippingCache()) {
-    DCHECK_EQ(usage_, kMultiplePaints);
+  if (IsSkippingCache() && usage_ == kMultiplePaints)
     display_item.Client().Invalidate(PaintInvalidationReason::kUncacheable);
-  }
 
 #if DCHECK_IS_ON()
   bool chunk_added =
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
index c64e6fb..183561d 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller.h
@@ -157,7 +157,9 @@
     DCHECK(skipping_cache_count_ > 0);
     --skipping_cache_count_;
   }
-  bool IsSkippingCache() const { return skipping_cache_count_; }
+  bool IsSkippingCache() const {
+    return usage_ == kTransient || skipping_cache_count_;
+  }
 
   // Must be called when a painting is finished. Updates the current paint
   // artifact with the new paintings.
diff --git a/third_party/blink/renderer/platform/heap/garbage_collected.h b/third_party/blink/renderer/platform/heap/garbage_collected.h
index 3111f5c..d07e1e4 100644
--- a/third_party/blink/renderer/platform/heap/garbage_collected.h
+++ b/third_party/blink/renderer/platform/heap/garbage_collected.h
@@ -49,6 +49,8 @@
 
 struct TraceDescriptor {
   STACK_ALLOCATED();
+
+ public:
   void* base_object_payload;
   TraceCallback callback;
   bool can_trace_eagerly;
diff --git a/third_party/blink/renderer/platform/heap/thread_state.h b/third_party/blink/renderer/platform/heap/thread_state.h
index 053e89b..cec56295 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.h
+++ b/third_party/blink/renderer/platform/heap/thread_state.h
@@ -466,6 +466,8 @@
 
   struct GCSnapshotInfo {
     STACK_ALLOCATED();
+
+   public:
     GCSnapshotInfo(size_t num_object_types);
 
     // Map from gcInfoIndex (vector-index) to count/size.
diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache.h b/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
index 950ae3f9..a31faed 100644
--- a/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
+++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache.h
@@ -85,6 +85,8 @@
 
   struct TypeStatistic {
     STACK_ALLOCATED();
+
+   public:
     size_t count;
     size_t size;
     size_t decoded_size;
@@ -105,6 +107,8 @@
 
   struct Statistics {
     STACK_ALLOCATED();
+
+   public:
     TypeStatistic images;
     TypeStatistic css_style_sheets;
     TypeStatistic scripts;
diff --git a/third_party/blink/renderer/platform/mac/theme_mac.mm b/third_party/blink/renderer/platform/mac/theme_mac.mm
index b5308fd..50f2f9eb 100644
--- a/third_party/blink/renderer/platform/mac/theme_mac.mm
+++ b/third_party/blink/renderer/platform/mac/theme_mac.mm
@@ -31,7 +31,6 @@
 #import "third_party/blink/renderer/platform/mac/local_current_graphics_context.h"
 #import "third_party/blink/renderer/platform/mac/version_util_mac.h"
 #import "third_party/blink/renderer/platform/mac/web_core_ns_cell_extras.h"
-#import "third_party/blink/renderer/platform/scroll/scrollable_area.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 // This is a view whose sole purpose is to tell AppKit that it's flipped.
diff --git a/third_party/blink/renderer/platform/platform_chrome_client.h b/third_party/blink/renderer/platform/platform_chrome_client.h
deleted file mode 100644
index 4626ea6d..0000000
--- a/third_party/blink/renderer/platform/platform_chrome_client.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2008 Apple Inc.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_PLATFORM_CHROME_CLIENT_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_PLATFORM_CHROME_CLIENT_H_
-
-#include "third_party/blink/renderer/platform/heap/handle.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-#include "third_party/blink/renderer/platform/wtf/allocator.h"
-#include "third_party/blink/renderer/platform/wtf/noncopyable.h"
-
-namespace blink {
-
-class PLATFORM_EXPORT PlatformChromeClient
-    : public GarbageCollectedFinalized<PlatformChromeClient> {
-  WTF_MAKE_NONCOPYABLE(PlatformChromeClient);
-
- public:
-  PlatformChromeClient() = default;
-  virtual ~PlatformChromeClient() = default;
-  virtual void Trace(blink::Visitor* visitor) {}
-
-  // Converts the scalar value from the window coordinates to the viewport
-  // scale.
-  virtual float WindowToViewportScalar(const float) const = 0;
-
-  virtual bool IsPopup() { return false; }
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_PLATFORM_CHROME_CLIENT_H_
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index d8d7b4f..fdcd6d0 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -556,6 +556,10 @@
       status: "experimental",
     },
     {
+      name: "GamepadButtonAxisEvents",
+      status: "experimental",
+    },
+    {
       name: "GamepadExtensions",
       status: "experimental",
     },
@@ -1096,7 +1100,7 @@
     },
     {
       name: "RestrictAppCacheToSecureContexts",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "RestrictCanRequestURLCharacterSet",
@@ -1190,6 +1194,7 @@
     },
     {
       name: "ShapeDetection",
+      origin_trial_feature_name: "ShapeDetection",
       status: "experimental",
     },
     {
@@ -1400,11 +1405,6 @@
       depends_on: ["WebUSB"],
     },
     {
-      name: "WebUSBOnSharedWorkers",
-      status: "test",
-      depends_on: ["WebUSB"],
-    },
-    {
       name: "WebVR",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/scroll/DEPS b/third_party/blink/renderer/platform/scroll/DEPS
index 4ae5c6e..67597e2 100644
--- a/third_party/blink/renderer/platform/scroll/DEPS
+++ b/third_party/blink/renderer/platform/scroll/DEPS
@@ -7,19 +7,7 @@
 
     # Dependencies.
     "+cc",
-    "+third_party/blink/renderer/platform/animation",
     "+third_party/blink/renderer/platform/geometry",
-    "+third_party/blink/renderer/platform/graphics",
-    "+third_party/blink/renderer/platform/heap",
-    "+third_party/blink/renderer/platform/instrumentation",
-    "+third_party/blink/renderer/platform/layout_test_support.h",
-    "+third_party/blink/renderer/platform/mac",
-    "+third_party/blink/renderer/platform/platform_chrome_client.h",
     "+third_party/blink/renderer/platform/platform_export.h",
-    "+third_party/blink/renderer/platform/runtime_enabled_features.h",
-    "+third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h",
-    "+third_party/blink/renderer/platform/timer.h",
-    "+third_party/blink/renderer/platform/transforms/transformation_matrix.h",
-    "+third_party/blink/renderer/platform/testing",
     "+third_party/blink/renderer/platform/wtf",
 ]
diff --git a/third_party/blink/renderer/platform/scroll/scroll_alignment.h b/third_party/blink/renderer/platform/scroll/scroll_alignment.h
index 7a4d5e3..26d57bd 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_alignment.h
+++ b/third_party/blink/renderer/platform/scroll/scroll_alignment.h
@@ -64,6 +64,8 @@
 
 struct PLATFORM_EXPORT ScrollAlignment {
   STACK_ALLOCATED();
+
+ public:
   static ScrollAlignmentBehavior GetVisibleBehavior(const ScrollAlignment& s) {
     return s.rect_visible_;
   }
diff --git a/third_party/blink/renderer/platform/scroll/scroll_types.h b/third_party/blink/renderer/platform/scroll/scroll_types.h
index 4f4884b..6be6f516 100644
--- a/third_party/blink/renderer/platform/scroll/scroll_types.h
+++ b/third_party/blink/renderer/platform/scroll/scroll_types.h
@@ -213,6 +213,8 @@
 // by scrolling.
 struct ScrollResult {
   STACK_ALLOCATED();
+
+ public:
   explicit ScrollResult()
       : did_scroll_x(false),
         did_scroll_y(false),
diff --git a/third_party/blink/renderer/platform/timer_test.cc b/third_party/blink/renderer/platform/timer_test.cc
index e60defe1..f7760e11 100644
--- a/third_party/blink/renderer/platform/timer_test.cc
+++ b/third_party/blink/renderer/platform/timer_test.cc
@@ -119,8 +119,9 @@
 };
 
 class GCForbiddenScope final {
- public:
   STACK_ALLOCATED();
+
+ public:
   GCForbiddenScope() { ThreadState::Current()->EnterGCForbiddenScope(); }
   ~GCForbiddenScope() { ThreadState::Current()->LeaveGCForbiddenScope(); }
 };
diff --git a/third_party/blink/renderer/platform/wtf/BUILD.gn b/third_party/blink/renderer/platform/wtf/BUILD.gn
index 54accfc5..9f73fef 100644
--- a/third_party/blink/renderer/platform/wtf/BUILD.gn
+++ b/third_party/blink/renderer/platform/wtf/BUILD.gn
@@ -46,6 +46,7 @@
   sources = [
     "address_sanitizer.h",
     "alignment.h",
+    "allocator.cc",
     "allocator.h",
     "allocator/partition_allocator.cc",
     "allocator/partition_allocator.h",
diff --git a/third_party/blink/renderer/platform/wtf/allocator.cc b/third_party/blink/renderer/platform/wtf/allocator.cc
new file mode 100644
index 0000000..927e7e0
--- /dev/null
+++ b/third_party/blink/renderer/platform/wtf/allocator.cc
@@ -0,0 +1,20 @@
+// Copyright 2018 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 "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace {
+
+struct Empty {};
+
+struct StackAllocatedType {
+  STACK_ALLOCATED();
+};
+
+static_assert(!WTF::IsStackAllocatedType<Empty>::value,
+              "Failed to detect STACK_ALLOCATED macro.");
+static_assert(WTF::IsStackAllocatedType<StackAllocatedType>::value,
+              "Failed to detect STACK_ALLOCATED macro.");
+
+}  // namespace
diff --git a/third_party/blink/renderer/platform/wtf/allocator.h b/third_party/blink/renderer/platform/wtf/allocator.h
index ff81a049..8fa9e5f 100644
--- a/third_party/blink/renderer/platform/wtf/allocator.h
+++ b/third_party/blink/renderer/platform/wtf/allocator.h
@@ -72,6 +72,10 @@
 
 #if defined(__clang__)
 #define STACK_ALLOCATED()                                                \
+ public:                                                                 \
+  using IsStackAllocatedTypeMarker[[maybe_unused]] = int;                \
+                                                                         \
+ private:                                                                \
   __attribute__((annotate("blink_stack_allocated"))) void* operator new( \
       size_t) = delete;                                                  \
   void* operator new(size_t, NotNullTag, void*) = delete;                \
diff --git a/third_party/blink/renderer/platform/wtf/functional.h b/third_party/blink/renderer/platform/wtf/functional.h
index 8fa025863..85b0502 100644
--- a/third_party/blink/renderer/platform/wtf/functional.h
+++ b/third_party/blink/renderer/platform/wtf/functional.h
@@ -208,7 +208,9 @@
                 "WrapWeakPersistent, WrapCrossThreadPersistent or "
                 "WrapCrossThreadWeakPersistent.");
   static_assert(!WTF::IsGarbageCollectedType<T>::value,
-                "GCed type is forbidden as a bound parameters.");
+                "GCed types are forbidden as bound parameters.");
+  static_assert(!WTF::IsStackAllocatedType<T>::value,
+                "Stack allocated types are forbidden as bound parameters.");
 };
 
 template <typename Index, typename... Args>
diff --git a/third_party/blink/renderer/platform/wtf/hash_table.h b/third_party/blink/renderer/platform/wtf/hash_table.h
index f47d1d6a..26173045 100644
--- a/third_party/blink/renderer/platform/wtf/hash_table.h
+++ b/third_party/blink/renderer/platform/wtf/hash_table.h
@@ -607,6 +607,8 @@
 template <typename HashTableType, typename ValueType>
 struct HashTableAddResult final {
   STACK_ALLOCATED();
+
+ public:
   HashTableAddResult(const HashTableType* container,
                      ValueType* stored_value,
                      bool is_new_entry)
@@ -2166,6 +2168,8 @@
 template <typename HashTableType, typename Traits>
 struct HashTableConstIteratorAdapter {
   STACK_ALLOCATED();
+
+ public:
   HashTableConstIteratorAdapter() = default;
   HashTableConstIteratorAdapter(
       const typename HashTableType::const_iterator& impl)
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 794a22e..293687a 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -195,6 +195,8 @@
 
   struct AddResult final {
     STACK_ALLOCATED();
+
+   public:
     AddResult(const typename ImplType::AddResult& hash_table_add_result)
         : stored_value(&hash_table_add_result.stored_value->value_),
           is_new_entry(hash_table_add_result.is_new_entry) {}
diff --git a/third_party/blink/renderer/platform/wtf/list_hash_set.h b/third_party/blink/renderer/platform/wtf/list_hash_set.h
index ddc89aa..1cc4ed47 100644
--- a/third_party/blink/renderer/platform/wtf/list_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/list_hash_set.h
@@ -129,6 +129,8 @@
 
   struct AddResult final {
     STACK_ALLOCATED();
+
+   public:
     friend class ListHashSet<ValueArg, inlineCapacity, HashArg, AllocatorArg>;
     AddResult(Node* node, bool is_new_entry)
         : stored_value(&node->value_),
diff --git a/third_party/blink/renderer/platform/wtf/type_traits.h b/third_party/blink/renderer/platform/wtf/type_traits.h
index 7fd0ecc..6d1603d 100644
--- a/third_party/blink/renderer/platform/wtf/type_traits.h
+++ b/third_party/blink/renderer/platform/wtf/type_traits.h
@@ -25,6 +25,7 @@
 #include <cstddef>
 #include <type_traits>
 #include <utility>
+#include "base/template_util.h"
 #include "build/build_config.h"
 #include "third_party/blink/renderer/platform/wtf/compiler.h"
 
@@ -262,6 +263,14 @@
   static const bool value = IsGarbageCollectedType<T>::value;
 };
 
+template <typename T, typename = void>
+struct IsStackAllocatedType : std::false_type {};
+
+template <typename T>
+struct IsStackAllocatedType<
+    T,
+    base::void_t<typename T::IsStackAllocatedTypeMarker>> : std::true_type {};
+
 }  // namespace WTF
 
 using WTF::IsGarbageCollectedType;
diff --git a/third_party/blink/tools/audit_non_blink_usage.py b/third_party/blink/tools/audit_non_blink_usage.py
index d8dc773b..a60c1da 100755
--- a/third_party/blink/tools/audit_non_blink_usage.py
+++ b/third_party/blink/tools/audit_non_blink_usage.py
@@ -28,7 +28,6 @@
             'gfx::CubicBezier',
             'gfx::ICCProfile',
             'gfx::RadToDeg',
-            'gfx::ScrollOffset',
 
             # //base constructs that are allowed everywhere
             'base::AdoptRef',
@@ -197,6 +196,12 @@
             'cc::EventListenerClass',
             'cc::EventListenerProperties',
 
+            # Scrolling
+            'cc::ScrollOffsetAnimationCurve',
+            'cc::ScrollStateData',
+            'gfx::RectToSkRect',
+            'gfx::ScrollOffset',
+
             # Standalone utility libraries that only depend on //base
             'skia::.+',
             'url::.+',
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 6e2a668..eee4f37 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -176,6 +176,7 @@
   IMAGE_MAP: 'imageMap',
   INLINE_TEXT_BOX: 'inlineTextBox',
   INPUT_TIME: 'inputTime',
+  KEYBOARD: 'keyboard',
   LABEL_TEXT: 'labelText',
   LAYOUT_TABLE: 'layoutTable',
   LAYOUT_TABLE_CELL: 'layoutTableCell',
@@ -1581,6 +1582,7 @@
  */
 chrome.automation.AutomationNode.prototype.getNextTextMatch = function(searchStr, backward) {};
 
+
 /**
  * Get the automation tree for the tab with the given tabId, or the current tab
  * if no tabID is given, enabling automation if necessary. Returns a tree with a
diff --git a/third_party/gvr-android-sdk/display_synchronizer_jni.h b/third_party/gvr-android-sdk/display_synchronizer_jni.h
index 98bfee9..dc6a01e 100644
--- a/third_party/gvr-android-sdk/display_synchronizer_jni.h
+++ b/third_party/gvr-android-sdk/display_synchronizer_jni.h
@@ -21,6 +21,7 @@
 // Native JNI methods
 // ----------------------------------------------------------------------------
 #include <jni.h>
+#include <atomic>
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 
@@ -32,8 +33,8 @@
 const char kDisplaySynchronizerClassPath[] =
     "com/google/vr/cardboard/DisplaySynchronizer";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-base::subtle::AtomicWord g_DisplaySynchronizer_clazz __attribute__((unused)) =
-    0;
+std::atomic<jclass> g_DisplaySynchronizer_clazz __attribute__((unused))
+    (nullptr);
 #define DisplaySynchronizer_clazz(env)                            \
   base::android::LazyGetClass(env, kDisplaySynchronizerClassPath, \
                               &g_DisplaySynchronizer_clazz)
diff --git a/third_party/gvr-android-sdk/gvr_api_jni.h b/third_party/gvr-android-sdk/gvr_api_jni.h
index f8f7a45..fe24eda 100644
--- a/third_party/gvr-android-sdk/gvr_api_jni.h
+++ b/third_party/gvr-android-sdk/gvr_api_jni.h
@@ -23,6 +23,7 @@
 // Native JNI methods
 // ----------------------------------------------------------------------------
 #include <jni.h>
+#include <atomic>
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 
@@ -32,7 +33,7 @@
 namespace {
 const char kGvrApiClassPath[] = "com/google/vr/ndk/base/GvrApi";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-base::subtle::AtomicWord g_GvrApi_clazz __attribute__((unused)) = 0;
+std::atomic<jclass> g_GvrApi_clazz __attribute__((unused)) (nullptr);
 #define GvrApi_clazz(env) \
   base::android::LazyGetClass(env, kGvrApiClassPath, &g_GvrApi_clazz)
 
diff --git a/third_party/gvr-android-sdk/native_callbacks_jni.h b/third_party/gvr-android-sdk/native_callbacks_jni.h
index 825077b3..631756a 100644
--- a/third_party/gvr-android-sdk/native_callbacks_jni.h
+++ b/third_party/gvr-android-sdk/native_callbacks_jni.h
@@ -26,6 +26,7 @@
 // Native JNI methods
 // ----------------------------------------------------------------------------
 #include <jni.h>
+#include <atomic>
 
 #include "base/android/jni_generator/jni_generator_helper.h"
 
@@ -36,7 +37,7 @@
 const char kNativeCallbacksClassPath[] =
     "com/google/vr/internal/controller/NativeCallbacks";
 // Leaking this jclass as we cannot use LazyInstance from some threads.
-base::subtle::AtomicWord g_NativeCallbacks_clazz __attribute__((unused)) = 0;
+std::atomic<jclass> g_NativeCallbacks_clazz __attribute__((unused)) (nullptr);
 #define NativeCallbacks_clazz(env)                            \
   base::android::LazyGetClass(env, kNativeCallbacksClassPath, \
                               &g_NativeCallbacks_clazz)
diff --git a/third_party/tcmalloc/chromium/src/libc_override.h b/third_party/tcmalloc/chromium/src/libc_override.h
index c981c3d..2fd58bf0 100644
--- a/third_party/tcmalloc/chromium/src/libc_override.h
+++ b/third_party/tcmalloc/chromium/src/libc_override.h
@@ -68,17 +68,31 @@
 
 static void ReplaceSystemAlloc();  // defined in the .h files below
 
+#if defined(TCMALLOC_DONT_REPLACE_SYSTEM_ALLOC)
+// TCMALLOC_DONT_REPLACE_SYSTEM_ALLOC has the following semantic:
+//  - tcmalloc with all its tc_* (tc_malloc, tc_free) symbols is being built
+//    and linked as usual.
+//  - the default system allocator symbols (malloc, free, operator new) are NOT
+//    overridden. The embedded must take care of routing them to tc_* symbols.
+// This no-op #if block effectively prevents the inclusion of the
+// libc_override_* headers below.
+static void ReplaceSystemAlloc() {}
+
 // For windows, there are two ways to get tcmalloc.  If we're
 // patching, then src/windows/patch_function.cc will do the necessary
 // overriding here.  Otherwise, we doing the 'redefine' trick, where
 // we remove malloc/new/etc from mscvcrt.dll, and just need to define
 // them now.
-#if defined(_WIN32) && defined(WIN32_DO_PATCHING)
+#elif defined(_WIN32) && defined(WIN32_DO_PATCHING)
 void PatchWindowsFunctions();   // in src/windows/patch_function.cc
 static void ReplaceSystemAlloc() { PatchWindowsFunctions(); }
 
 #elif defined(_WIN32) && !defined(WIN32_DO_PATCHING)
-#include "libc_override_redefine.h"
+// "libc_override_redefine.h" is included in the original gperftools.  But,
+// we define allocator functions in Chromium's base/allocator/allocator_shim.cc
+// on Windows.  We don't include libc_override_redefine.h here.
+// ReplaceSystemAlloc() is defined here instead.
+static void ReplaceSystemAlloc() {}
 
 #elif defined(__APPLE__)
 #include "libc_override_osx.h"
diff --git a/tools/android/native_lib_memory/code_pages_pss.py b/tools/android/native_lib_memory/code_pages_pss.py
index 4a77682..8e723d1a 100755
--- a/tools/android/native_lib_memory/code_pages_pss.py
+++ b/tools/android/native_lib_memory/code_pages_pss.py
@@ -5,7 +5,7 @@
 
 """Prints the total PSS attributed to Chrome's code pages in an application.
 
-This scripts assumes a device with Monochrome, and requires root access.
+This script assumes a device with Monochrome, and requires root access.
 For instance, to get chrome's code page memory footprint:
 $ tools/android/native_lib_memory/code_pages_pss.py
     --app-package com.android.chrome
diff --git a/tools/android/native_lib_memory/java_code_pages_pss.py b/tools/android/native_lib_memory/java_code_pages_pss.py
new file mode 100755
index 0000000..06b8baa
--- /dev/null
+++ b/tools/android/native_lib_memory/java_code_pages_pss.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+# Copyright 2018 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.
+
+"""Prints the total PSS attributed to another app in Chrome's mappings.
+
+This script assumes a device with Monochrome, and requires root access.
+For instance, to get the part of Chrome's memory footprint coming from GMSCore
+code and bytecode pages:
+$ tools/android/native_lib_memory/java_code_pages_pss.py
+    --chrome-package com.android.chrome
+    --app-package com.google.android.gms --verbose
+"""
+
+import argparse
+import logging
+import os
+import re
+import sys
+
+import parse_smaps
+
+_SRC_PATH = os.path.join(
+    os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
+from devil.android import device_utils
+
+
+def _GetPssInKb(mappings, app_package, verbose):
+  """Returns the total PSS from mappings.
+
+  Args:
+    mappings: ([parse_smaps.Mapping]) List of mappings.
+    app_package: (str) App package to look for.
+    verbose: (bool) Verbose output or not.
+
+  Returns:
+    (executable_pss (int), other_pss (int)) Executable mappings and others,
+                                            in kB.
+  """
+  executable_pss, other_pss = (0, 0)
+  for mapping in mappings:
+    if app_package in mapping.pathname:
+      if mapping.permissions == 'r-xp':
+        executable_pss += mapping.fields['Pss']
+      else:
+        other_pss += mapping.fields['Pss']
+      if verbose:
+        print mapping.ToString()
+  return (executable_pss, other_pss)
+
+
+def _CreateArgumentParser():
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--chrome-package', help='Chrome package to look for.',
+                      required=True)
+  parser.add_argument('--app-package', help='Application to inspect.',
+                      required=True)
+  parser.add_argument('--verbose', help='Verbose output.',
+                      action='store_true')
+  return parser
+
+
+def main():
+  parser = _CreateArgumentParser()
+  args = parser.parse_args()
+  devices = device_utils.DeviceUtils.HealthyDevices()
+  if not devices:
+    logging.error('No connected devices')
+    return
+  device = devices[0]
+  device.EnableRoot()
+  processes = device.ListProcesses(args.chrome_package)
+  logging.basicConfig(level=logging.INFO)
+  logging.info('Processes:\n\t' + '\n\t'.join(p.name for p in processes))
+  total_executable_pss_kb, total_other_pss_kb = (0, 0)
+  for process in processes:
+    mappings = parse_smaps.ParseProcSmaps(device, process.pid)
+    executable_pss_kb, other_pss_kb = _GetPssInKb(
+        mappings, args.app_package, args.verbose)
+    total_executable_pss_kb += executable_pss_kb
+    total_other_pss_kb += other_pss_kb
+
+  print 'Total executable PSS = %dkB' % total_executable_pss_kb
+  print 'Total other mappings PSS = %dkB' % total_other_pss_kb
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/android/native_lib_memory/parse_smaps.py b/tools/android/native_lib_memory/parse_smaps.py
index f33c5623..6cb711c 100755
--- a/tools/android/native_lib_memory/parse_smaps.py
+++ b/tools/android/native_lib_memory/parse_smaps.py
@@ -80,7 +80,7 @@
       # Offset
       '([0-9a-f]{1,16}) '
       # Device
-      '([0-9a-f]{2}:[0-9a-f]{2}) '
+      '([0-9a-f]{2,3}:[0-9a-f]{2,3}) '
       # Inode
       '([0-9]*) '
       # Pathname
diff --git a/tools/binary_size/libsupersize/generate_milestone_report.py b/tools/binary_size/libsupersize/generate_milestone_report.py
index 2bb4255..fd06ca41 100755
--- a/tools/binary_size/libsupersize/generate_milestone_report.py
+++ b/tools/binary_size/libsupersize/generate_milestone_report.py
@@ -105,14 +105,14 @@
 def _SetPushedReports(directory):
   outpath = os.path.join(directory, 'milestones.json')
   with codecs.open(outpath, 'w', encoding='ascii') as out_file:
-    json.dump(out_file, {
+    pushed_reports_obj = {
       'pushed': {
         'cpu': DESIRED_CPUS,
         'apk': DESIRED_APKS,
         'version': DESIRED_VERSION,
       },
-    })
-
+    }
+    json.dump(pushed_reports_obj, out_file)
 
 def _GetReportPaths(directory, template, report):
   report_dict = report._asdict()
@@ -194,7 +194,7 @@
                       format='%(levelname).1s %(relativeCreated)6d %(message)s')
 
   size_file_bucket = args.size_file_bucket
-  if not size_file_bucket.startwith('gs://'):
+  if not size_file_bucket.startswith('gs://'):
     parser.error('Size file bucket must be located in Google Cloud Storage.')
   elif size_file_bucket.endswith('/'):
     # Remove trailing slash
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 6ae24fa..04835f7 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -750,26 +750,26 @@
   else:
     assert sys.platform.startswith('linux')
     platform = 'linux'
-  asan_rt_lib_src_dir = os.path.join(COMPILER_RT_BUILD_DIR, 'lib', platform)
+  rt_lib_src_dir = os.path.join(COMPILER_RT_BUILD_DIR, 'lib', platform)
   if sys.platform == 'win32':
     # TODO(thakis): This too is due to compiler-rt being part of the checkout
     # on Windows, see TODO above COMPILER_RT_DIR.
-    asan_rt_lib_src_dir = os.path.join(COMPILER_RT_BUILD_DIR, 'lib', 'clang',
-                                       VERSION, 'lib', platform)
-  asan_rt_lib_dst_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang',
-                                     VERSION, 'lib', platform)
+    rt_lib_src_dir = os.path.join(COMPILER_RT_BUILD_DIR, 'lib', 'clang',
+                                  VERSION, 'lib', platform)
+  rt_lib_dst_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang', VERSION, 'lib',
+                                platform)
   # Blacklists:
-  CopyDirectoryContents(os.path.join(asan_rt_lib_src_dir, '..', '..', 'share'),
-                        os.path.join(asan_rt_lib_dst_dir, '..', '..', 'share'))
+  CopyDirectoryContents(os.path.join(rt_lib_src_dir, '..', '..', 'share'),
+                        os.path.join(rt_lib_dst_dir, '..', '..', 'share'))
   # Headers:
   if sys.platform != 'win32':
     CopyDirectoryContents(
         os.path.join(COMPILER_RT_BUILD_DIR, 'include/sanitizer'),
         os.path.join(LLVM_BUILD_DIR, 'lib/clang', VERSION, 'include/sanitizer'))
   # Static and dynamic libraries:
-  CopyDirectoryContents(asan_rt_lib_src_dir, asan_rt_lib_dst_dir)
+  CopyDirectoryContents(rt_lib_src_dir, rt_lib_dst_dir)
   if sys.platform == 'darwin':
-    for dylib in glob.glob(os.path.join(asan_rt_lib_dst_dir, '*.dylib')):
+    for dylib in glob.glob(os.path.join(rt_lib_dst_dir, '*.dylib')):
       # Fix LC_ID_DYLIB for the ASan dynamic libraries to be relative to
       # @executable_path.
       # TODO(glider): this is transitional. We'll need to fix the dylib
@@ -828,15 +828,16 @@
       RunCommand(['ninja', 'asan', 'ubsan', 'profile'])
 
       # And copy them into the main build tree.
-      want_archs = '{aarch64,arm}'
-      want = [
-          'lib/linux/libclang_rt.asan-%s-android.so' % want_archs,
-          'lib/linux/libclang_rt.ubsan_standalone-%s-android.so' % want_archs,
-          'lib/linux/libclang_rt.profile-%s-android.a' % want_archs,
+      libs_want = [
+          'lib/linux/libclang_rt.asan-{0}-android.so',
+          'lib/linux/libclang_rt.ubsan_standalone-{0}-android.so',
+          'lib/linux/libclang_rt.profile-{0}-android.a',
       ]
-      for p in want:
-        for f in glob.glob(os.path.join(build_dir, p)):
-          shutil.copy(f, asan_rt_lib_dst_dir)
+      for arch in ['aarch64', 'arm']:
+        for p in libs_want:
+          lib_path = os.path.join(build_dir, p.format(arch))
+          if os.path.exists(lib_path):
+            shutil.copy(lib_path, rt_lib_dst_dir)
 
   # Run tests.
   if args.run_tests or use_head_revision:
diff --git a/tools/determinism/deterministic_build_blacklist.json b/tools/determinism/deterministic_build_blacklist.json
index 0b70055..0d4f101c 100644
--- a/tools/determinism/deterministic_build_blacklist.json
+++ b/tools/determinism/deterministic_build_blacklist.json
@@ -1,9 +1,2 @@
 [
-  "base_i18n_nacl_win64.dll",
-  "base_win64.dll",
-  "crypto_nacl_win64.dll",
-  "ipc_win64.dll",
-  "latency_info_nacl_win64.dll",
-  "nacl64.exe",
-  "ppapi_shared_win64.dll"
 ]
diff --git a/tools/determinism/remove_build_metadata.py b/tools/determinism/remove_build_metadata.py
index ecf1765..7083799 100755
--- a/tools/determinism/remove_build_metadata.py
+++ b/tools/determinism/remove_build_metadata.py
@@ -21,11 +21,6 @@
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 SRC_DIR = os.path.dirname(os.path.dirname(BASE_DIR))
 
-# Files that can't be processed by zap_timestamp.exe.
-_ZAP_TIMESTAMP_BLACKLIST = {
-  'mini_installer.exe',
-}
-
 
 def build_bitness(build_dir):
   # This function checks whether the target (not host) word size is 64-bits.
@@ -58,7 +53,7 @@
 def get_files_to_clean(build_dir, recursive=False):
   """Get the list of files to clean."""
   allowed = frozenset(
-      ('', '.app', '.dll', '.dylib', '.exe', '.nexe', '.so'))
+      ('', '.app', '.dylib', '.nexe', '.so'))
   non_x_ok_exts = frozenset(('.isolated', '.jar'))
   min_timestamp = 0
   if os.path.exists(os.path.join(build_dir, 'build.ninja')):
@@ -81,32 +76,6 @@
   return ret_files
 
 
-def run_zap_timestamp(filepath):
-  """Run zap_timestamp.exe on a PE binary."""
-  assert sys.platform == 'win32'
-  syzygy_dir = os.path.join(
-      SRC_DIR, 'third_party', 'syzygy', 'binaries', 'exe')
-  zap_timestamp_exe = os.path.join(syzygy_dir, 'zap_timestamp.exe')
-  sys.stdout.write('Processing: %s\n' % os.path.basename(filepath))
-  proc = subprocess.Popen(
-      [zap_timestamp_exe, '--input-image=%s' % filepath, '--overwrite'],
-      stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-  log, _ = proc.communicate()
-  if proc.returncode != 0:
-    sys.stderr.write('%s failed:\n%s\n' % (os.path.basename(filepath), log))
-  return proc.returncode
-
-
-def remove_pe_metadata(filename, bitness):
-  """Remove the build metadata from a PE file."""
-  # Only run zap_timestamp on the 32-bit PE files for which we have a PDB.
-  ret = 0
-  if ((not os.path.basename(filename) in _ZAP_TIMESTAMP_BLACKLIST) and
-      os.path.exists(filename + '.pdb') and bitness != 64):
-    ret = run_zap_timestamp(filename)
-  return ret
-
-
 def remove_zip_timestamps(filename):
   """Remove the timestamps embedded in a zip archive."""
   sys.stdout.write('Processing: %s\n' % os.path.basename(filename))
@@ -133,10 +102,7 @@
   """Worker thread for the remove_metadata function."""
   while True:
     f = file_queue.get()
-    if f.endswith(('.dll', '.exe')):
-      if remove_pe_metadata(os.path.join(build_dir, f), bitness):
-        failed_queue.put(f)
-    elif f.endswith('.jar'):
+    if f.endswith('.jar'):
       remove_zip_timestamps(os.path.join(build_dir, f))
     file_queue.task_done()
 
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index fb9e8cf7..0fd7a60 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -708,7 +708,7 @@
     ],
 
     'android_binary_size': [
-      'android', 'chrome_with_codecs', 'goma', 'minimal_symbols', 'official_optimize',
+      'android', 'chrome_with_codecs', 'goma', 'no_symbols', 'official_optimize',
     ],
 
     'android_cast_debug_static_bot': [
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 2f01cf4f..77541c6c7 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -19628,6 +19628,7 @@
   <suffix name="BadgedReadingList" label="For BadgedReadingList feature."/>
   <suffix name="Bookmark" label="For Bookmark feature."/>
   <suffix name="BottomToolbarTip" label="For BottomToolbarTip feature."/>
+  <suffix name="ChromeDuet" label="For ChromeDuet feature."/>
   <suffix name="ChromeHomeExpand" label="For ChromeHomeExpand feature."/>
   <suffix name="ChromeHomeMenuHeader"
       label="For ChromeHomeMenuHeader feature."/>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 436f584d..bea7466 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -360,6 +360,30 @@
   <int value="157" label="UMA_API_UNSELECT_ROW">unselectRow</int>
 </enum>
 
+<enum name="AccessoryAction">
+  <int value="0" label="Automatic password generation selected"/>
+  <int value="1" label="'Manage all passwords' link selected"/>
+</enum>
+
+<enum name="AccessoryBarContents">
+  <int value="0" label="Without contents (should never be logged)"/>
+  <int value="1" label="With any content (should always be logged)"/>
+  <int value="2" label="With Tabs"/>
+  <int value="3" label="With Actions"/>
+  <int value="4" label="With Autofill Suggestions"/>
+</enum>
+
+<enum name="AccessorySheetTrigger">
+  <int value="0" label="Sheet was closed (user-triggered or automatic)"/>
+  <int value="1" label="User closed an accessory sheet"/>
+  <int value="2" label="User opened an accessory sheet"/>
+</enum>
+
+<enum name="AccessorySuggestionType">
+  <int value="0" label="USERNAME"/>
+  <int value="1" label="PASSWORD"/>
+</enum>
+
 <enum name="AccessPasswordInSettingsEvent">
   <int value="0" label="Viewed"/>
   <int value="1" label="Copied"/>
@@ -1184,7 +1208,8 @@
   <int value="29" label="Service worker not offline capable"/>
   <int value="30" label="Waiting for manifest to be fetched"/>
   <int value="31" label="Waiting for installability check"/>
-  <int value="32" label="No gesture associated with call to prompt"/>
+  <int value="32"
+      label="No gesture associated with call to prompt (removed Aug 2018)"/>
   <int value="33" label="Waiting for native data"/>
   <int value="34" label="App install dialog shown"/>
 </enum>
@@ -13930,6 +13955,7 @@
   <int value="460" label="TabLifecyclesEnabled"/>
   <int value="461" label="UrlKeyedAnonymizedDataCollectionEnabled"/>
   <int value="462" label="NetworkFileSharesAllowed"/>
+  <int value="463" label="DeviceLocalAccountManagedSessionEnabled"/>
 </enum>
 
 <enum name="EnterprisePolicyInvalidations">
@@ -27621,9 +27647,11 @@
   <int value="-1961497025" label="tint-gl-composited-content"/>
   <int value="-1961062505" label="VrBrowsingInCustomTab:disabled"/>
   <int value="-1960567385" label="KeepPrefetchedContentSuggestions:enabled"/>
+  <int value="-1958315092" label="EnableGamepadButtonAxisEvents:disabled"/>
   <int value="-1957328398" label="MacSystemShareMenu:disabled"/>
   <int value="-1956747298" label="LayeredAPI:enabled"/>
   <int value="-1956349722" label="disable-smooth-scrolling"/>
+  <int value="-1955923385" label="EnableGamepadButtonAxisEvents:enabled"/>
   <int value="-1948540128" label="disable-webrtc-hw-encoding (deprecated)"/>
   <int value="-1946595906" label="enable-push-api-background-mode"/>
   <int value="-1946522787" label="VrCustomTabBrowsing:disabled"/>
@@ -27641,6 +27669,7 @@
   <int value="-1939003674" label="NetworkServiceInProcess:disabled"/>
   <int value="-1938263248" label="enable-extension-info-dialog"/>
   <int value="-1937077699" label="http-form-warning"/>
+  <int value="-1934661084" label="ForceUnifiedConsentBump:disabled"/>
   <int value="-1933425042" label="OfflinePreviews:enabled"/>
   <int value="-1930720286" label="nacl-debug-mask"/>
   <int value="-1928198763" label="enable-async-dns"/>
@@ -28691,6 +28720,7 @@
   <int value="200347243" label="WebVRExperimentalRendering:disabled"/>
   <int value="201343576" label="enable-password-change-support:enabled"/>
   <int value="203776499" label="enable-virtual-keyboard-overscroll"/>
+  <int value="217455219" label="SyncStandaloneTransport:enabled"/>
   <int value="218890378" label="ManualSaving:disabled"/>
   <int value="219117936" label="AllowReaderForAccessibility:enabled"/>
   <int value="222184258"
@@ -28791,6 +28821,7 @@
   <int value="422307097" label="PhysicalWeb:disabled"/>
   <int value="423615350" label="enable-tab-audio-muting"/>
   <int value="423855924" label="enable-tab-switcher-theme-colors"/>
+  <int value="430959979" label="SyncStandaloneTransport:disabled"/>
   <int value="431691805" label="MediaDocumentDownloadButton:enabled"/>
   <int value="434033638" label="PwaPersistentNotification:disabled"/>
   <int value="438048339" label="WarnBeforeQuitting:disabled"/>
@@ -29123,6 +29154,7 @@
   <int value="1105439588" label="enable-swipe-selection"/>
   <int value="1107543566" label="enable-one-copy"/>
   <int value="1108663108" label="disable-device-discovery-notifications"/>
+  <int value="1111871757" label="ForceUnifiedConsentBump:enabled"/>
   <int value="1112051724" label="DetectingHeavyPages:enabled"/>
   <int value="1112885300" label="VizHitTestDrawQuad:disabled"/>
   <int value="1113365156" label="tab-management-experiment-type-chive"/>
@@ -31408,6 +31440,12 @@
   <int value="3" label="Exceeded Cache Size"/>
 </enum>
 
+<enum name="MemoryMeasurementOutcome">
+  <int value="0" label="kMeasurementSuccess"/>
+  <int value="1" label="kMeasurementPartialSuccess"/>
+  <int value="2" label="kMeasurementFailure"/>
+</enum>
+
 <enum name="MemoryPressureLevel">
   <int value="0" label="No memory pressure"/>
   <int value="1" label="Moderate memory pressure"/>
@@ -32234,6 +32272,12 @@
   <int value="17" label="externalfile"/>
 </enum>
 
+<enum name="NavigationSuggestionEvent">
+  <int value="0" label="None"/>
+  <int value="1" label="Infobar shown"/>
+  <int value="2" label="Link clicked on the infobar"/>
+</enum>
+
 <enum name="NavigationWasServedFromCache">
   <int value="0" label="Navigation wasn't served from cache"/>
   <int value="1" label="Navigation was served from cache"/>
@@ -47432,6 +47476,7 @@
   <int value="14" label="Disabled by initialized, connected, passphrase"/>
   <int value="15"
       label="Disabled by history, initialized, connected, passphrase"/>
+  <int value="16" label="Disabled by URL-keyed anonymized data collection"/>
 </enum>
 
 <enum name="UmaCleanExitConsistency">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 97fcb57..6c0c3d1 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -25,6 +25,18 @@
 
 <histograms>
 
+<histogram name="Accessibility.Android.TabSwitcherPreferenceEnabled"
+    enum="BooleanEnabled">
+  <owner>twellington@chromium.org</owner>
+  <owner>tedchoc@chromium.org</owner>
+  <summary>
+    Tracks whether the accessibility tab switcher is enabled when an
+    accessibility service (e.g. TalkBack or Switch Access) is enabled that would
+    typically cause the accessibility tab switcher to be used. Recorded when the
+    activity is resumed or accessibility services are turned on.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.AndroidServiceInfo"
     enum="AccessibilityAndroidServiceInfoEnum">
   <owner>dmazzoni@chromium.org</owner>
@@ -37736,6 +37748,61 @@
   </summary>
 </histogram>
 
+<histogram name="KeyboardAccessory.AccessoryActionImpression"
+    enum="AccessoryAction">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records whenever users faces an action in the accessory bar or
+    one of its sheets.
+  </summary>
+</histogram>
+
+<histogram name="KeyboardAccessory.AccessoryActionSelected"
+    enum="AccessoryAction">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records whenever users select an action in the accessory bar
+    or one of its sheets.
+  </summary>
+</histogram>
+
+<histogram name="KeyboardAccessory.AccessoryBarShown"
+    enum="AccessoryBarContents">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records how often users encounter the keyboard accessory bar.
+    Its buckets show the contents when it came up. Every bucket may be logged up
+    to one time per impression.
+  </summary>
+</histogram>
+
+<histogram name="KeyboardAccessory.AccessorySheetSuggestionCount" units="count">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records how many suggestions a user faced when opening a
+    sheet. The base histogram counts impressions across all sheets.
+  </summary>
+</histogram>
+
+<histogram name="KeyboardAccessory.AccessorySheetSuggestionsSelected"
+    enum="AccessorySuggestionType">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records which type of suggestion was selected from an open
+    sheet.
+  </summary>
+</histogram>
+
+<histogram name="KeyboardAccessory.AccessorySheetTriggered"
+    enum="AccessorySheetTrigger">
+  <owner>fhorschig@chromium.org</owner>
+  <summary>
+    Android only. Records how often the bottom sheet was opened or closed by a
+    user and the overall count of closures. Closing buckets may be logged up to
+    one time per trigger. There are suffixes for each specific sheet type.
+  </summary>
+</histogram>
+
 <histogram name="Kiosk.Launch.CryptohomeFailure" enum="LoginFailureReason">
   <owner>xiyuan@chromium.org</owner>
   <summary>Tracks cryptohome failure during kiosk launch.</summary>
@@ -45457,6 +45524,14 @@
   </summary>
 </histogram>
 
+<histogram name="Memory.Total.RendererPrivateMemoryFootprint" units="MB">
+  <owner>tommckee@chromium.org</owner>
+  <summary>
+    A rough estimate of the private memory footprint of all renderer processes.
+    Recorded once per UMA ping.
+  </summary>
+</histogram>
+
 <histogram name="Memory.Total.SharedMemoryFootprint" units="MB">
   <owner>erikchen@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
@@ -47926,6 +48001,15 @@
   </summary>
 </histogram>
 
+<histogram name="NavigationSuggestion.Event" enum="NavigationSuggestionEvent">
+  <owner>meacer@chromium.org</owner>
+  <summary>
+    Tracks events when the currently navigated domain name is visually similar
+    to one of the top 10K domains, resulting in a navigation suggestion in the
+    form of &quot;Did you mean to go to ...&quot;.
+  </summary>
+</histogram>
+
 <histogram name="NCN.CellularConnectionSubtype" enum="ConnectionSubtype">
   <owner>bmcquade@chromium.org</owner>
   <summary>
@@ -82459,6 +82543,47 @@
   </summary>
 </histogram>
 
+<histogram name="ResourceCoordinator.Measurement.Duration" units="ms">
+  <owner>siggi@chromium.org</owner>
+  <summary>The amount of wall-clock time a measurement cycle occupied.</summary>
+</histogram>
+
+<histogram name="ResourceCoordinator.Measurement.Memory.ExtraProcesses"
+    units="processes">
+  <owner>siggi@chromium.org</owner>
+  <summary>
+    The number of unexpected processes encountered during memory measurement.
+    This is expected to be non-zero as new processes can be created after a
+    measurement cycle is initiated, plus non-renderer processes are counted here
+    at the moment.
+  </summary>
+</histogram>
+
+<histogram name="ResourceCoordinator.Measurement.Memory.Outcome"
+    enum="MemoryMeasurementOutcome">
+  <owner>siggi@chromium.org</owner>
+  <summary>
+    An enumeration indicating the outcome of each memory measurement attempt.
+  </summary>
+</histogram>
+
+<histogram name="ResourceCoordinator.Measurement.Memory.UnmeasuredProcesses"
+    units="processes">
+  <owner>siggi@chromium.org</owner>
+  <summary>
+    The number of processes that didn't get a memory measurement. This can
+    happen if processes die as or after measurement is initiated.
+  </summary>
+</histogram>
+
+<histogram name="ResourceCoordinator.Measurement.TotalProcesses"
+    units="processes">
+  <owner>siggi@chromium.org</owner>
+  <summary>
+    The number of processes that were measured for CPU at least.
+  </summary>
+</histogram>
+
 <histogram name="ResourceLoadingHints.CountBlockedSubresourcePatterns"
     units="pattern count">
   <owner>tbansal@chromium.org</owner>
@@ -116083,6 +116208,14 @@
 
 <histogram_suffixes_list>
 
+<histogram_suffixes name="AccessorySheetType" separator=".">
+  <suffix name="Passwords" label="Password suggestions and generation."/>
+  <affected-histogram name="KeyboardAccessory.AccessorySheetSuggestionCount"/>
+  <affected-histogram
+      name="KeyboardAccessory.AccessorySheetSuggestionsSelected"/>
+  <affected-histogram name="KeyboardAccessory.AccessorySheetTriggered"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="AccountInvestigationReportingType" separator="_">
   <suffix name="OnChange"
       label="Driven from a change in signin status or change in content area
@@ -119851,6 +119984,9 @@
       label="In product help badged reading list."/>
   <suffix name="IPH_Bookmark" label="In product help bookmark."/>
   <suffix name="IPH_BottomToolbarTip" label="In product help bottom toolbar."/>
+  <suffix name="IPH_ChromeDuet"
+      label="In product help notifying users that their buttons have moved to
+             the bottom of the screen."/>
   <suffix name="IPH_ChromeHomeExpand"
       label="In product help for Chrome Home shown on cold start."/>
   <suffix name="IPH_ChromeHomeMenuHeader"
diff --git a/tools/perf/benchmarks/dromaeo.py b/tools/perf/benchmarks/dromaeo.py
index 9084d80a..4af0b6aa 100644
--- a/tools/perf/benchmarks/dromaeo.py
+++ b/tools/perf/benchmarks/dromaeo.py
@@ -85,31 +85,10 @@
       AddResult(key, math.exp(value['sum'] / value['count']))
 
 
-class _DromaeoBenchmark(perf_benchmark.PerfBenchmark):
-  """A base class for Dromaeo benchmarks."""
-  test = _DromaeoMeasurement
-
-  def CreateStorySet(self, options):
-    """Makes a PageSet for Dromaeo benchmarks."""
-    # Subclasses are expected to define class members called query_param and
-    # tag.
-    if not hasattr(self, 'query_param') or not hasattr(self, 'tag'):
-      raise NotImplementedError('query_param or tag not in Dromaeo benchmark.')
-    archive_data_file = '../page_sets/data/dromaeo.%s.json' % self.tag
-    ps = story.StorySet(
-        archive_data_file=archive_data_file,
-        base_dir=os.path.dirname(os.path.abspath(__file__)),
-        cloud_storage_bucket=story.PUBLIC_BUCKET)
-    url = 'http://dromaeo.com?%s' % self.query_param
-    ps.AddStory(page_module.Page(
-        url, ps, ps.base_dir, make_javascript_deterministic=False, name=url))
-    return ps
-
-
 @benchmark.Info(emails=['jbroman@chromium.org',
                          'yukishiino@chromium.org',
                          'haraken@chromium.org'])
-class DromaeoBenchmark(_DromaeoBenchmark):
+class DromaeoBenchmark(perf_benchmark.PerfBenchmark):
 
   test = _DromaeoMeasurement
 
diff --git a/tools/perf/chrome_telemetry_build/BUILD.gn b/tools/perf/chrome_telemetry_build/BUILD.gn
index 5a14c29..12b83ef 100644
--- a/tools/perf/chrome_telemetry_build/BUILD.gn
+++ b/tools/perf/chrome_telemetry_build/BUILD.gn
@@ -52,7 +52,10 @@
   }
 
   if (is_chromeos && cros_board != "") {
-    data_deps += [ "//chromeos:cros_vm_launcher" ]
+    data_deps += [
+      "//chromeos:cros_chrome_deploy",
+      "//chromeos:cros_vm_launcher",
+    ]
   }
 
   if (is_win && (symbol_level == 1 || symbol_level == 2)) {
diff --git a/tools/perf/core/retrieve_story_timing.py b/tools/perf/core/retrieve_story_timing.py
index 294ce7a..2f92f64 100755
--- a/tools/perf/core/retrieve_story_timing.py
+++ b/tools/perf/core/retrieve_story_timing.py
@@ -28,33 +28,25 @@
 QUERY_LAST_RUNS = """
 SELECT
   name,
-  ROUND(AVG(time)) AS duration
+  ROUND(AVG(time)) AS duration,
 FROM (
   SELECT
-    name,
+    run.name AS name,
     start_time,
-    time,
-    ROW_NUMBER() OVER (PARTITION BY name ORDER BY start_time DESC)
-      AS row_num
-  FROM (
-    SELECT
-      run.name AS name,
-      start_time,
-      AVG(run.times) AS time
-    FROM
-      [test-results-hrd:events.test_results]
-    WHERE
-      buildbot_info.builder_name IN ({configuration_names})
-      AND run.time IS NOT NULL
-      AND run.time != 0
-      AND run.is_unexpected IS FALSE
-    GROUP BY
-      name,
-      start_time
-    ORDER BY
-      start_time DESC ))
-WHERE
-  row_num < {num_last_builds}
+    AVG(run.times) AS time
+  FROM
+    [test-results-hrd:events.test_results]
+  WHERE
+    buildbot_info.builder_name IN ({configuration_names})
+    AND run.time IS NOT NULL
+    AND run.time != 0
+    AND run.is_unexpected IS FALSE
+    AND DATEDIFF(CURRENT_DATE(), DATE(start_time)) < {num_last_days}
+  GROUP BY
+    name,
+    start_time
+  ORDER BY
+    start_time DESC)
 GROUP BY
   name
 ORDER BY
@@ -64,14 +56,15 @@
 
 def _run_query(query):
   args = ["bq", "query", "--format=json", "--max_rows=100000", query]
+
   p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-  if p.wait() == 0:
-    json_result = p.stdout.read().strip()
-    return json.loads(json_result)
+  stdout, stderr = p.communicate()
+  if p.returncode == 0:
+    return json.loads(stdout)
   else:
     raise RuntimeError(
         'Error generating authentication token.\nStdout: %s\nStder:%s' %
-        (p.stdout.read(), p.stderr.read()))
+        (stdout, stderr))
 
 
 def FetchStoryTimingDataForSingleBuild(configurations, build_number):
@@ -79,9 +72,9 @@
       configurations, build_number))
 
 
-def FetchAverageStortyTimingData(configurations, num_last_builds):
+def FetchAverageStortyTimingData(configurations, num_last_days):
   return _run_query(QUERY_LAST_RUNS.format(
-      configuration_names=configurations, num_last_builds=num_last_builds))
+      configuration_names=configurations, num_last_days=num_last_days))
 
 
 def main(args):
@@ -115,7 +108,7 @@
     data = FetchStoryTimingDataForSingleBuild(configurations,
         opts.build_number)
   else:
-    data = FetchAverageStortyTimingData(configurations, num_last_builds=10)
+    data = FetchAverageStortyTimingData(configurations, num_last_days=5)
 
   with open(opts.output_file, 'w') as output_file:
     json.dump(data, output_file, indent = 4, separators=(',', ': '))
diff --git a/tools/perf/core/shard_maps/android_go_shard_map.json b/tools/perf/core/shard_maps/android_go_shard_map.json
index 2b2302fe..cc8f3dba 100644
--- a/tools/perf/core/shard_maps/android_go_shard_map.json
+++ b/tools/perf/core/shard_maps/android_go_shard_map.json
@@ -2,14 +2,14 @@
     "0": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "end": 16
+                "end": 14
             }
         }
     },
     "1": {
         "benchmarks": {
             "system_health.common_mobile": {
-                "begin": 16,
+                "begin": 14,
                 "end": 29
             }
         }
@@ -26,92 +26,106 @@
         "benchmarks": {
             "system_health.common_mobile": {
                 "begin": 53
+            },
+            "memory.top_10_mobile": {
+                "end": 1
             }
         }
     },
     "4": {
         "benchmarks": {
+            "memory.top_10_mobile": {
+                "begin": 1,
+                "end": 10
+            }
+        }
+    },
+    "5": {
+        "benchmarks": {
+            "memory.top_10_mobile": {
+                "begin": 10,
+                "end": 19
+            }
+        }
+    },
+    "6": {
+        "benchmarks": {
+            "memory.top_10_mobile": {
+                "begin": 19
+            },
             "start_with_url.warm.startup_pages": {},
             "start_with_url.cold.startup_pages": {
                 "end": 1
             }
         }
     },
-    "5": {
+    "7": {
         "benchmarks": {
             "start_with_url.cold.startup_pages": {
                 "begin": 1
             },
             "system_health.memory_mobile": {
-                "end": 7
-            }
-        }
-    },
-    "6": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 7,
-                "end": 14
-            }
-        }
-    },
-    "7": {
-        "benchmarks": {
-            "system_health.memory_mobile": {
-                "begin": 14,
-                "end": 17
+                "end": 8
             }
         }
     },
     "8": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 17,
-                "end": 22
+                "begin": 8,
+                "end": 13
             }
         }
     },
     "9": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 22,
-                "end": 30
+                "begin": 13,
+                "end": 17
             }
         }
     },
     "10": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 30,
-                "end": 41
+                "begin": 17,
+                "end": 23
             }
         }
     },
     "11": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 41,
-                "end": 50
+                "begin": 23,
+                "end": 35
             }
         }
     },
     "12": {
         "benchmarks": {
             "system_health.memory_mobile": {
-                "begin": 50,
-                "end": 53
+                "begin": 35,
+                "end": 46
             }
         }
     },
     "13": {
         "benchmarks": {
             "system_health.memory_mobile": {
+                "begin": 46,
+                "end": 53
+            }
+        }
+    },
+    "14": {
+        "benchmarks": {
+            "system_health.memory_mobile": {
                 "begin": 53,
                 "end": 56
             }
         }
     },
-    "14": {
+    "15": {
         "benchmarks": {
             "system_health.memory_mobile": {
                 "begin": 56,
@@ -119,46 +133,36 @@
             }
         }
     },
-    "15": {
+    "16": {
         "benchmarks": {
             "system_health.memory_mobile": {
                 "begin": 61
             },
-            "power.typical_10_mobile": {
-                "end": 8
-            }
-        }
-    },
-    "16": {
-        "benchmarks": {
-            "power.typical_10_mobile": {
-                "begin": 8
-            },
-            "memory.top_10_mobile": {
-                "end": 6
-            }
+            "system_health.webview_startup": {},
+            "power.typical_10_mobile": {}
         }
     },
     "17": {
         "benchmarks": {
-            "memory.top_10_mobile": {
-                "begin": 6,
-                "end": 13
+            "speedometer2": {},
+            "speedometer": {},
+            "v8.browsing_mobile": {
+                "end": 10
             }
         }
     },
     "18": {
         "benchmarks": {
-            "memory.top_10_mobile": {
-                "begin": 13
+            "v8.browsing_mobile": {
+                "begin": 10
             }
         }
     },
     "extra_infos": {
-        "num_stories": 159,
-        "predicted_min_shard_time": 1566.0,
-        "predicted_min_shard_index": 7,
-        "predicted_max_shard_time": 2400.0,
-        "predicted_max_shard_index": 6
+        "num_stories": 187,
+        "predicted_min_shard_time": 1794.0,
+        "predicted_min_shard_index": 8,
+        "predicted_max_shard_time": 2778.0,
+        "predicted_max_shard_index": 13
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/android_go_timing.json b/tools/perf/core/shard_maps/timing_data/android_go_timing.json
index 8141d80..e19e27f 100644
--- a/tools/perf/core/shard_maps/timing_data/android_go_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/android_go_timing.json
@@ -1,1558 +1,106 @@
 [
     {
-        "duration": "33.0",
-        "name": "blink_perf.bindings/append-child.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/create-element.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/document-implementation.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.bindings/dom-attribute-on-prototoype.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/first-child.html"
-    },
-    {
-        "duration": "32.0",
-        "name": "blink_perf.bindings/gc-forest.html"
-    },
-    {
-        "duration": "41.0",
-        "name": "blink_perf.bindings/gc-mini-tree.html"
-    },
-    {
-        "duration": "126.0",
-        "name": "blink_perf.bindings/gc-tree.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/get-attribute-rare.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/get-attribute.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.bindings/get-element-by-id.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.bindings/get-elements-by-tag-name.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/id-getter.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/id-setter.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.bindings/indexed-getter.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/insert-before.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/named-property-enumerator.html"
-    },
-    {
-        "duration": "130.0",
-        "name": "blink_perf.bindings/node-list-access.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/node-type.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.bindings/post-message.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/sequence-conversion-array.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.bindings/sequence-conversion-custom-iterator.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.bindings/serialize-array.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.bindings/serialize-long-string.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "blink_perf.bindings/serialize-map.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.bindings/serialize-nested-array.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/set-attribute-rare.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/set-attribute.html"
-    },
-    {
-        "duration": "69.0",
-        "name": "blink_perf.bindings/structured-clone-json-deserialize.html"
-    },
-    {
-        "duration": "69.0",
-        "name": "blink_perf.bindings/structured-clone-json-serialize.html"
-    },
-    {
         "duration": "24.0",
-        "name": "blink_perf.bindings/structured-clone-long-string-deserialize.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.bindings/structured-clone-long-string-serialize.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/typed-array-construct-from-array.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.bindings/typed-array-construct-from-same-type.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/typed-array-construct-from-typed.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/typed-array-set-from-typed.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/undefined-first-child.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.bindings/undefined-get-element-by-id.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.bindings/undefined-id-getter.html"
-    },
-    {
-        "duration": "35.0",
-        "name": "blink_perf.canvas/createImageBitmapFromImageData.html"
-    },
-    {
-        "duration": "52.0",
-        "name": "blink_perf.canvas/draw-dynamic-canvas-2d-to-hw-accelerated-canvas-2d.html"
-    },
-    {
-        "duration": "74.0",
-        "name": "blink_perf.canvas/draw-dynamic-webgl-to-hw-accelerated-canvas-2d.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.canvas/draw-hw-accelerated-canvas-2d-to-sw-canvas-2d.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "blink_perf.canvas/draw-static-canvas-2d-to-hw-accelerated-canvas-2d.html"
-    },
-    {
-        "duration": "31.0",
-        "name": "blink_perf.canvas/draw-static-webgl-to-hw-accelerated-canvas-2d.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "blink_perf.canvas/draw-video-to-hw-accelerated-canvas-2d.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.canvas/drawimage-not-pixelaligned.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.canvas/drawimage.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "blink_perf.canvas/getImageData.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.canvas/getImageDataColorManaged.html"
-    },
-    {
-        "duration": "31.0",
-        "name": "blink_perf.canvas/putImageData.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "blink_perf.canvas/toBlob_duration.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.canvas/toBlob_duration_jpeg.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.canvas/transferFromImageBitmap.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "blink_perf.canvas/upload-canvas-2d-to-texture.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.canvas/upload-video-to-sub-texture.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.canvas/upload-video-to-texture.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "blink_perf.canvas/upload-webgl-to-texture.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "blink_perf.css/AttributeDescendantSelector.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.css/CSSPropertySetterGetter.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.css/CSSPropertySetterGetterMethods.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.css/CSSPropertyUpdateValue.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "blink_perf.css/ChangeStyleChildClassSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleChildElementSelectors.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleElementSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleGrandChildElementSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleMultipleClassSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleMultipleQualifiedDataAttributesWithValuesSelector.html"
-    },
-    {
-        "duration": "13.0",
-        "name": "blink_perf.css/ChangeStyleNestedPseudoSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStylePairOfNthChildSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStylePartialAttributeMatchingSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleQualifiedDataAttributeSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleQualifiedDataAttributeWithValueSelector.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.css/ChangeStyleShallowTree.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleSingleClassSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleSingleNthChildSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleSinglePseudoSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleUniversalSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleUnqualifiedDataAttributeSelector.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.css/ChangeStyleUnqualifiedDataAttributeWithValueSelector.html"
-    },
-    {
-        "duration": "39.0",
-        "name": "blink_perf.css/ClassDescendantSelector.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "blink_perf.css/ClassInvalidation.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "blink_perf.css/FocusUpdate.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.css/LoadBootstrapBlog.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.css/LoadMaterializeStarterPage.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.css/LoadSemanticPageExample.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.css/PseudoClassSelectors.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.css/SelectorCountScaling.html"
-    },
-    {
-        "duration": "64.0",
-        "name": "blink_perf.dom/addRange.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.dom/delete-in-password-field.html"
-    },
-    {
-        "duration": "38.0",
-        "name": "blink_perf.dom/div-editable.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.dom/inner_html_with_selection.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "blink_perf.dom/long-sibling-list.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.dom/modify-element-classname.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.dom/modify-element-id.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.dom/modify-element-title.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.dom/move-down-with-hidden-elements.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.dom/move-up-with-hidden-elements.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.dom/remove_child_with_selection.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.dom/select-multiple-add.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "blink_perf.dom/select-single-add.html"
-    },
-    {
-        "duration": "25.0",
-        "name": "blink_perf.dom/select-single-remove.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.dom/textarea-dom.html"
-    },
-    {
-        "duration": "47.0",
-        "name": "blink_perf.dom/textarea-edit.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "blink_perf.events/EventsDispatching.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.events/EventsDispatchingInDeeplyNestedShadowTrees.html"
-    },
-    {
-        "duration": "89.0",
-        "name": "blink_perf.events/EventsDispatchingInShadowTrees.html"
-    },
-    {
-        "duration": "131.0",
-        "name": "blink_perf.events/hit-test-lots-of-layers.html"
-    },
-    {
-        "duration": "65.0",
-        "name": "blink_perf.image_decoder/decode-gif.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "blink_perf.image_decoder/decode-jpeg.html"
-    },
-    {
-        "duration": "63.0",
-        "name": "blink_perf.image_decoder/decode-lossless-webp.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.image_decoder/decode-lossy-webp.html"
-    },
-    {
-        "duration": "34.0",
-        "name": "blink_perf.image_decoder/decode-png-palette-opaque.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.image_decoder/decode-png-palette.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "blink_perf.image_decoder/decode-png.html"
-    },
-    {
-        "duration": "34.0",
-        "name": "blink_perf.layout/ArabicLineLayout.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.layout/Shapes/MultipleShapes.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.layout/SimpleTextPathLineLayout.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/add-remove-inline-floats.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.layout/attach-inlines-2.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/attach-inlines.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.layout/auto-grid-lots-of-data.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.layout/chapter-reflow-once-random.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/chapter-reflow-once.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/chapter-reflow-thrice.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/chapter-reflow-twice.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/chapter-reflow.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/character_fallback.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.layout/character_fallback_aat.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "blink_perf.layout/fixed-grid-lots-of-data.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.layout/fixed-grid-lots-of-stretched-data.html"
-    },
-    {
-        "duration": "34.0",
-        "name": "blink_perf.layout/flexbox-column-nowrap.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/flexbox-column-wrap.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/flexbox-deeply-nested-column-flow.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.layout/flexbox-lots-of-data.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/flexbox-row-nowrap.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/flexbox-row-wrap.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.layout/flexbox-with-stretch-layout.html"
-    },
-    {
-        "duration": "154.0",
-        "name": "blink_perf.layout/floats_100_100.html"
-    },
-    {
-        "duration": "155.0",
-        "name": "blink_perf.layout/floats_100_100_nested.html"
-    },
-    {
-        "duration": "58.0",
-        "name": "blink_perf.layout/floats_10_1000.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "blink_perf.layout/floats_20_100.html"
-    },
-    {
-        "duration": "35.0",
-        "name": "blink_perf.layout/floats_20_100_nested.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.layout/floats_2_100.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.layout/floats_2_100_nested.html"
-    },
-    {
-        "duration": "54.0",
-        "name": "blink_perf.layout/floats_50_100.html"
-    },
-    {
-        "duration": "57.0",
-        "name": "blink_perf.layout/floats_50_100_nested.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.layout/hindi-line-layout.html"
-    },
-    {
-        "duration": "59.0",
-        "name": "blink_perf.layout/large-table-with-collapsed-borders-and-colspans-wider-than-table.html"
-    },
-    {
-        "duration": "59.0",
-        "name": "blink_perf.layout/large-table-with-collapsed-borders-and-colspans.html"
-    },
-    {
-        "duration": "58.0",
-        "name": "blink_perf.layout/large-table-with-collapsed-borders-and-no-colspans.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.layout/latin-complex-text.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.layout/layers_overlap_2d.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/layers_overlap_3d.html"
-    },
-    {
-        "duration": "61.0",
-        "name": "blink_perf.layout/line-layout-line-height.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.layout/line-layout-repeat-append.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/line-layout.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.layout/long-line-nowrap-collapse.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.layout/long-line-nowrap-spans-collapse.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "blink_perf.layout/long-line-nowrap.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.layout/multicol/deeply-nested-tables.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.layout/multicol/fixed-height-with-spanner-and-nested-tables.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.layout/multicol/lots-of-text-autofill.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.layout/multicol/lots-of-text-balanced-orphans-widows.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.layout/multicol/lots-of-text-balanced.html"
-    },
-    {
-        "duration": "31.0",
-        "name": "blink_perf.layout/multicol/tall-content-short-columns-realistic.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.layout/multicol/tall-content-short-columns.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.layout/nested-blocks-with-percent-height-and-max-height.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.layout/nested-grid.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.layout/nested-percent-height-tables.html"
-    },
-    {
-        "duration": "445.0",
-        "name": "blink_perf.layout/subtree-detaching.html"
-    },
-    {
-        "duration": "32.0",
-        "name": "blink_perf.layout/vertical-japanese-kokoro-insert.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "blink_perf.layout/word-break-break-all.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "blink_perf.layout/word-break-break-word.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "blink_perf.layout/word-wrap-break-word.html"
-    },
-    {
-        "duration": "43.0",
-        "name": "blink_perf.owp_storage/blob-perf-files.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "blink_perf.owp_storage/blob-perf-ipc.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.owp_storage/blob-perf-shm.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "blink_perf.owp_storage/blob-perf-tiny.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "blink_perf.owp_storage/idb-load-docs.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "blink_perf.paint/appending-text.html"
-    },
-    {
-        "duration": "65.0",
-        "name": "blink_perf.paint/color-changes.html"
-    },
-    {
-        "duration": "74.0",
-        "name": "blink_perf.paint/complex-content-slow-scroll.html"
-    },
-    {
-        "duration": "63.0",
-        "name": "blink_perf.paint/containment-resize.html"
-    },
-    {
-        "duration": "54.0",
-        "name": "blink_perf.paint/fixed-and-many-layers-scroll.html"
-    },
-    {
-        "duration": "78.0",
-        "name": "blink_perf.paint/large-table-background-change-with-invisible-collapsed-borders.html"
-    },
-    {
-        "duration": "48.0",
-        "name": "blink_perf.paint/large-table-background-change-with-visible-collapsed-borders.html"
-    },
-    {
-        "duration": "81.0",
-        "name": "blink_perf.paint/large-table-background-change-with-zero-width-collapsed-borders.html"
-    },
-    {
-        "duration": "58.0",
-        "name": "blink_perf.paint/large-table-collapsed-border-change-with-backgrounds.html"
-    },
-    {
-        "duration": "66.0",
-        "name": "blink_perf.paint/large-table-collapsed-border-change-with-text.html"
-    },
-    {
-        "duration": "42.0",
-        "name": "blink_perf.paint/large-table-collapsed-border-change.html"
-    },
-    {
-        "duration": "63.0",
-        "name": "blink_perf.paint/large-table-repaint.html"
-    },
-    {
-        "duration": "36.0",
-        "name": "blink_perf.paint/move-text-with-mask.html"
-    },
-    {
-        "duration": "59.0",
-        "name": "blink_perf.paint/paint-offset-changes.html"
-    },
-    {
-        "duration": "54.0",
-        "name": "blink_perf.paint/transform-changes.html"
-    },
-    {
-        "duration": "39.0",
-        "name": "blink_perf.parser/css-parser-yui.html"
-    },
-    {
-        "duration": "68.0",
-        "name": "blink_perf.parser/html-parser-threaded.html"
-    },
-    {
-        "duration": "84.0",
-        "name": "blink_perf.parser/html-parser.html"
-    },
-    {
-        "duration": "326.0",
-        "name": "blink_perf.parser/html5-full-render.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.parser/iframe-append-remove.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/innerHTML-setter-siblings.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.parser/innerHTML-setter.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.parser/query-selector-all-attribute-complex.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.parser/query-selector-all-attribute.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/query-selector-all-class-deep.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.parser/query-selector-all-class-first.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.parser/query-selector-all-class-last.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.parser/query-selector-all-class.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/query-selector-all-deep.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/query-selector-all-first.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "blink_perf.parser/query-selector-all-id-deep.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.parser/query-selector-all-id-first.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.parser/query-selector-all-id-last.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.parser/query-selector-all-last.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.parser/query-selector-deep.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.parser/query-selector-first.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "blink_perf.parser/query-selector-id-deep.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "blink_perf.parser/query-selector-id-last.html"
-    },
-    {
-        "duration": "17.0",
-        "name": "blink_perf.parser/query-selector-last.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "blink_perf.parser/simple-url.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/textarea-parsing.html"
-    },
-    {
-        "duration": "34.0",
-        "name": "blink_perf.parser/tiny-innerHTML.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "blink_perf.parser/url-parser.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.parser/xml-parser.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "blink_perf.shadow_dom/shadow-style-share-attr-selectors.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.shadow_dom/shadow-style-share-media-query.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.shadow_dom/shadow-style-share-with-distribution.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/shadow-style-share.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.shadow_dom/style-sheet-insert.html"
-    },
-    {
-        "duration": "13.0",
-        "name": "blink_perf.shadow_dom/v0-changing-classname-with-shadow-dom.html"
-    },
-    {
-        "duration": "36.0",
-        "name": "blink_perf.shadow_dom/v0-changing-classname-without-shadow-dom.html"
-    },
-    {
-        "duration": "37.0",
-        "name": "blink_perf.shadow_dom/v0-changing-select-with-shadow-dom.html"
-    },
-    {
-        "duration": "41.0",
-        "name": "blink_perf.shadow_dom/v0-changing-select-without-shadow-dom.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.shadow_dom/v0-content-reprojection.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v0-large-distribution-without-layout.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v0-multiple-insertion-points.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.shadow_dom/v0-shadow-reprojection.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.shadow_dom/v0-small-distribution-with-layout.html"
-    },
-    {
-        "duration": "100.0",
-        "name": "blink_perf.shadow_dom/v1-distribution-disconnected-and-reconnected.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.shadow_dom/v1-distribution.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-host-child-append.html"
-    },
-    {
-        "duration": "100.0",
-        "name": "blink_perf.shadow_dom/v1-large-deep-distribution.html"
-    },
-    {
-        "duration": "249.0",
-        "name": "blink_perf.shadow_dom/v1-large-deep-layout.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.shadow_dom/v1-large-shallow-distribution.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-large-shallow-layout.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-deep-tree-then-re-layout.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-deep-tree-then-slot-assigned-nodes.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-deep-tree-then-slot-flatten.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-shallow-tree-then-re-layout.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-shallow-tree-then-slot-assigned-nodes.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.shadow_dom/v1-mutate-shallow-tree-then-slot-flatten.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.shadow_dom/v1-slot-append.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.shadow_dom/v1-small-deep-distribution.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "blink_perf.shadow_dom/v1-small-deep-layout.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.shadow_dom/v1-small-shallow-distribution.html"
-    },
-    {
-        "duration": "9.0",
-        "name": "blink_perf.shadow_dom/v1-small-shallow-layout.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "blink_perf.svg/AzLizardBenjiPark.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.svg/Bamboo.html"
-    },
-    {
-        "duration": "13.0",
-        "name": "blink_perf.svg/Cactus.html"
-    },
-    {
-        "duration": "13.0",
-        "name": "blink_perf.svg/Cowboy.html"
-    },
-    {
-        "duration": "13.0",
-        "name": "blink_perf.svg/Cowboy_transform.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.svg/CrawFishGanson.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.svg/Debian.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.svg/DropsOnABlade.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.svg/FlowerFromMyGarden.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.svg/FoodLeifLodahl.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.svg/France.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.svg/FrancoBolloGnomeEzechi.html"
-    },
-    {
-        "duration": "12.0",
-        "name": "blink_perf.svg/GearFlowers.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.svg/HarveyRayner.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.svg/HereGear.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "blink_perf.svg/MtSaintHelens.html"
-    },
-    {
-        "duration": "10.0",
-        "name": "blink_perf.svg/Samurai.html"
-    },
-    {
-        "duration": "696.0",
-        "name": "blink_perf.svg/SierpinskiCarpet.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "blink_perf.svg/SvgCubics.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "blink_perf.svg/SvgHitTesting.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "blink_perf.svg/SvgNestedUse.html"
-    },
-    {
-        "duration": "11.0",
-        "name": "blink_perf.svg/UnderTheSee.html"
-    },
-    {
-        "duration": "14.0",
-        "name": "blink_perf.svg/WorldIso.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "blink_perf.svg/Worldcup.html"
-    },
-    {
-        "duration": "55.0",
-        "name": "dromaeo/http://dromaeo.com?dom-attr"
-    },
-    {
-        "duration": "43.0",
-        "name": "dromaeo/http://dromaeo.com?dom-modify"
-    },
-    {
-        "duration": "57.0",
-        "name": "dromaeo/http://dromaeo.com?dom-query"
-    },
-    {
-        "duration": "36.0",
-        "name": "dromaeo/http://dromaeo.com?dom-traverse"
-    },
-    {
-        "duration": "31.0",
-        "name": "dummy_benchmark.histogram_benchmark_1/dummy_page.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "dummy_benchmark.noisy_benchmark_1/dummy_page.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "dummy_benchmark.stable_benchmark_1/dummy_page.html"
-    },
-    {
-        "duration": "932.0",
-        "name": "jetstream/http://browserbench.org/JetStream/"
-    },
-    {
-        "duration": "315.0",
-        "name": "kraken/http://krakenbenchmark.mozilla.org/kraken-1.1/driver.html"
-    },
-    {
-        "duration": "42.0",
-        "name": "loading.mobile/58Pic"
-    },
-    {
-        "duration": "63.0",
-        "name": "loading.mobile/Amazon"
-    },
-    {
-        "duration": "63.0",
-        "name": "loading.mobile/BOLNoticias"
-    },
-    {
-        "duration": "25.0",
-        "name": "loading.mobile/Baidu"
-    },
-    {
-        "duration": "71.0",
-        "name": "loading.mobile/Baidu_3g"
-    },
-    {
-        "duration": "80.0",
-        "name": "loading.mobile/Bradesco"
-    },
-    {
-        "duration": "58.0",
-        "name": "loading.mobile/Dailymotion"
-    },
-    {
-        "duration": "65.0",
-        "name": "loading.mobile/Dawn"
-    },
-    {
-        "duration": "34.0",
-        "name": "loading.mobile/DevOpera"
-    },
-    {
-        "duration": "80.0",
-        "name": "loading.mobile/Dramaq"
-    },
-    {
-        "duration": "50.0",
-        "name": "loading.mobile/EnquiryIndianRail"
-    },
-    {
-        "duration": "29.0",
-        "name": "loading.mobile/Facebook"
-    },
-    {
-        "duration": "77.0",
-        "name": "loading.mobile/Facebook_3g"
-    },
-    {
-        "duration": "46.0",
-        "name": "loading.mobile/FlipBoard"
-    },
-    {
-        "duration": "55.0",
-        "name": "loading.mobile/FlipKart"
-    },
-    {
-        "duration": "52.0",
-        "name": "loading.mobile/FranceTVInfo"
-    },
-    {
-        "duration": "142.0",
-        "name": "loading.mobile/G1_3g"
-    },
-    {
-        "duration": "128.0",
-        "name": "loading.mobile/GSShop"
-    },
-    {
-        "duration": "20.0",
-        "name": "loading.mobile/GoogleBrazil"
-    },
-    {
-        "duration": "70.0",
-        "name": "loading.mobile/GoogleBrazil_3g"
-    },
-    {
-        "duration": "22.0",
-        "name": "loading.mobile/GoogleIndia"
-    },
-    {
-        "duration": "64.0",
-        "name": "loading.mobile/GoogleIndia_3g"
-    },
-    {
-        "duration": "20.0",
-        "name": "loading.mobile/GoogleIndonesia"
-    },
-    {
-        "duration": "63.0",
-        "name": "loading.mobile/GoogleIndonesia_3g"
-    },
-    {
-        "duration": "29.0",
-        "name": "loading.mobile/GoogleRedirectToGoogleJapan"
-    },
-    {
-        "duration": "88.0",
-        "name": "loading.mobile/GoogleRedirectToGoogleJapan_3g"
-    },
-    {
-        "duration": "64.0",
-        "name": "loading.mobile/Hongkiat"
-    },
-    {
-        "duration": "71.0",
-        "name": "loading.mobile/KapanLagi"
-    },
-    {
-        "duration": "86.0",
-        "name": "loading.mobile/Kaskus"
-    },
-    {
-        "duration": "67.0",
-        "name": "loading.mobile/LocalMoxie"
-    },
-    {
-        "duration": "35.0",
-        "name": "loading.mobile/Locanto"
-    },
-    {
-        "duration": "68.0",
-        "name": "loading.mobile/OLX"
-    },
-    {
-        "duration": "47.0",
-        "name": "loading.mobile/QQNews"
-    },
-    {
-        "duration": "62.0",
-        "name": "loading.mobile/SlideShare"
-    },
-    {
-        "duration": "28.0",
-        "name": "loading.mobile/Suumo"
-    },
-    {
-        "duration": "89.0",
-        "name": "loading.mobile/Thairath"
-    },
-    {
-        "duration": "82.0",
-        "name": "loading.mobile/TheStar"
-    },
-    {
-        "duration": "70.0",
-        "name": "loading.mobile/TribunNews"
-    },
-    {
-        "duration": "51.0",
-        "name": "loading.mobile/Twitter"
-    },
-    {
-        "duration": "40.0",
-        "name": "loading.mobile/VoiceMemos"
-    },
-    {
-        "duration": "55.0",
-        "name": "loading.mobile/Wikipedia"
-    },
-    {
-        "duration": "36.0",
-        "name": "loading.mobile/YahooNews"
-    },
-    {
-        "duration": "102.0",
-        "name": "loading.mobile/YahooNews_3g"
-    },
-    {
-        "duration": "21.0",
-        "name": "loading.mobile/Youtube"
-    },
-    {
-        "duration": "83.0",
-        "name": "loading.mobile/Youtube_3g"
-    },
-    {
-        "duration": "33.0",
-        "name": "media.mobile/mse.html?media=aac_audio.mp4"
-    },
-    {
-        "duration": "44.0",
-        "name": "media.mobile/mse.html?media=aac_audio.mp4,h264_video.mp4"
-    },
-    {
-        "duration": "43.0",
-        "name": "media.mobile/mse.html?media=aac_audio.mp4,h264_video.mp4&waitForPageLoaded=true"
-    },
-    {
-        "duration": "40.0",
-        "name": "media.mobile/mse.html?media=h264_video.mp4"
-    },
-    {
-        "duration": "58.0",
-        "name": "media.mobile/video.html?src=crowd.ogg&type=audio"
-    },
-    {
-        "duration": "42.0",
-        "name": "media.mobile/video.html?src=crowd1080_vp9.webm"
-    },
-    {
-        "duration": "29.0",
-        "name": "media.mobile/video.html?src=crowd1080_vp9.webm&seek"
-    },
-    {
-        "duration": "53.0",
-        "name": "media.mobile/video.html?src=crowd720_vp9.webm"
-    },
-    {
-        "duration": "39.0",
-        "name": "media.mobile/video.html?src=tulip2.m4a&type=audio"
-    },
-    {
-        "duration": "39.0",
-        "name": "media.mobile/video.html?src=tulip2.mp3&type=audio"
-    },
-    {
-        "duration": "19.0",
-        "name": "media.mobile/video.html?src=tulip2.mp3&type=audio&seek"
-    },
-    {
-        "duration": "50.0",
-        "name": "media.mobile/video.html?src=tulip2.mp4"
-    },
-    {
-        "duration": "49.0",
-        "name": "media.mobile/video.html?src=tulip2.mp4&busyjs"
-    },
-    {
-        "duration": "25.0",
-        "name": "media.mobile/video.html?src=tulip2.mp4&seek"
-    },
-    {
-        "duration": "39.0",
-        "name": "media.mobile/video.html?src=tulip2.ogg&type=audio"
-    },
-    {
-        "duration": "20.0",
-        "name": "media.mobile/video.html?src=tulip2.ogg&type=audio&seek"
-    },
-    {
-        "duration": "48.0",
-        "name": "media.mobile/video.html?src=tulip2.vp9.webm"
-    },
-    {
-        "duration": "39.0",
-        "name": "media.mobile/video.html?src=tulip2.vp9.webm&background"
-    },
-    {
-        "duration": "35.0",
-        "name": "media.mobile/video.html?src=tulip2.vp9.webm&seek"
-    },
-    {
-        "duration": "80.0",
-        "name": "media.mobile/video.html?src=tulip2.vp9.webm_Regular-3G"
-    },
-    {
-        "duration": "40.0",
-        "name": "media.mobile/video.html?src=tulip2.wav&type=audio"
-    },
-    {
-        "duration": "20.0",
-        "name": "media.mobile/video.html?src=tulip2.wav&type=audio&seek"
-    },
-    {
-        "duration": "398.0",
-        "name": "memory.long_running_idle_gmail_background_tbmv2/https://mail.google.com/mail/"
-    },
-    {
-        "duration": "352.0",
-        "name": "memory.long_running_idle_gmail_tbmv2/https://mail.google.com/mail/"
-    },
-    {
-        "duration": "25.0",
         "name": "memory.top_10_mobile/after_http_en_m_wikipedia_org_wiki_Science"
     },
     {
-        "duration": "24.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_m_intl_taobao_com_group_purchase_html"
     },
     {
-        "duration": "31.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_m_youtube_com_results_q_science"
     },
     {
-        "duration": "24.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_search_yahoo_com_search__ylt_p_google"
     },
     {
-        "duration": "25.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_www_amazon_com_gp_aw_s_k_nexus"
     },
     {
-        "duration": "24.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_www_baidu_com_s_word_google"
     },
     {
-        "duration": "24.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_http_yandex_ru_touchsearch_text_science"
     },
     {
-        "duration": "24.0",
+        "duration": "23.0",
         "name": "memory.top_10_mobile/after_https_m_facebook_com_rihanna"
     },
     {
-        "duration": "36.0",
+        "duration": "35.0",
         "name": "memory.top_10_mobile/after_https_mobile_twitter_com_justinbieber_skip_interstitial_true"
     },
     {
-        "duration": "25.0",
+        "duration": "24.0",
         "name": "memory.top_10_mobile/after_https_www_google_co_uk_hl_en_q_science"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "memory.top_10_mobile/http_en_m_wikipedia_org_wiki_Science"
     },
     {
-        "duration": "28.0",
+        "duration": "27.0",
         "name": "memory.top_10_mobile/http_m_intl_taobao_com_group_purchase_html"
     },
     {
-        "duration": "30.0",
+        "duration": "28.0",
         "name": "memory.top_10_mobile/http_m_youtube_com_results_q_science"
     },
     {
-        "duration": "28.0",
+        "duration": "27.0",
         "name": "memory.top_10_mobile/http_search_yahoo_com_search__ylt_p_google"
     },
     {
-        "duration": "29.0",
+        "duration": "28.0",
         "name": "memory.top_10_mobile/http_www_amazon_com_gp_aw_s_k_nexus"
     },
     {
-        "duration": "31.0",
+        "duration": "29.0",
         "name": "memory.top_10_mobile/http_www_baidu_com_s_word_google"
     },
     {
-        "duration": "30.0",
+        "duration": "28.0",
         "name": "memory.top_10_mobile/http_yandex_ru_touchsearch_text_science"
     },
     {
-        "duration": "30.0",
+        "duration": "28.0",
         "name": "memory.top_10_mobile/https_m_facebook_com_rihanna"
     },
     {
-        "duration": "29.0",
+        "duration": "28.0",
         "name": "memory.top_10_mobile/https_mobile_twitter_com_justinbieber_skip_interstitial_true"
     },
     {
-        "duration": "28.0",
+        "duration": "33.0",
         "name": "memory.top_10_mobile/https_www_google_co_uk_hl_en_q_science"
     },
     {
-        "duration": "183.0",
-        "name": "octane/http://chromium.github.io/octane/index.html?auto=1"
-    },
-    {
-        "duration": "31.0",
-        "name": "oortonline_tbmv2/http://oortonline.gl/#run"
-    },
-    {
-        "duration": "33.0",
-        "name": "power.idle_platform/IdleStory_10s"
-    },
-    {
-        "duration": "131.0",
-        "name": "power.idle_platform/IdleStory_120s"
-    },
-    {
-        "duration": "70.0",
-        "name": "power.idle_platform/IdleStory_60s"
-    },
-    {
-        "duration": "76.0",
+        "duration": "75.0",
         "name": "power.typical_10_mobile/http://de.m.wikipedia.org/wiki/K%C3%B6lner_Dom"
     },
     {
-        "duration": "83.0",
+        "duration": "81.0",
         "name": "power.typical_10_mobile/http://m.chiebukuro.yahoo.co.jp/detail/q10136829180"
     },
     {
-        "duration": "75.0",
+        "duration": "70.0",
         "name": "power.typical_10_mobile/http://m.ebay.com/itm/351157205404"
     },
     {
-        "duration": "92.0",
+        "duration": "93.0",
         "name": "power.typical_10_mobile/http://m.facebook.com/barackobama"
     },
     {
-        "duration": "78.0",
+        "duration": "75.0",
         "name": "power.typical_10_mobile/http://m.huffpost.com/us/entry/6004486"
     },
     {
-        "duration": "76.0",
+        "duration": "77.0",
         "name": "power.typical_10_mobile/http://m.ynet.co.il"
     },
     {
@@ -1560,15 +108,15 @@
         "name": "power.typical_10_mobile/http://siriuslymeg.tumblr.com/"
     },
     {
-        "duration": "71.0",
+        "duration": "72.0",
         "name": "power.typical_10_mobile/http://wapbaike.baidu.com/"
     },
     {
-        "duration": "73.0",
+        "duration": "75.0",
         "name": "power.typical_10_mobile/http://www.cnn.com/2014/03/31/showbiz/tv/himym-finale/index.html"
     },
     {
-        "duration": "85.0",
+        "duration": "84.0",
         "name": "power.typical_10_mobile/http://www.rg.ru/2014/10/21/cska-site.html"
     },
     {
@@ -1576,2743 +124,123 @@
         "name": "power.typical_10_mobile/https://en.wikipedia.org/wiki/File:Rotating_earth_(large).gif"
     },
     {
-        "duration": "33.0",
-        "name": "rasterize_and_record_micro.partial_invalidation/800_relpos_divs.html"
-    },
-    {
-        "duration": "53.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/amazon.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/blogger.html"
-    },
-    {
-        "duration": "46.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/booking.html"
-    },
-    {
         "duration": "39.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/cnn.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/ebay.html"
-    },
-    {
-        "duration": "83.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/espn.html"
-    },
-    {
-        "duration": "31.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/facebook.html"
-    },
-    {
-        "duration": "58.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/gmail.html"
-    },
-    {
-        "duration": "49.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/google.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/googlecalendar.html"
-    },
-    {
-        "duration": "32.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/googledocs.html"
-    },
-    {
-        "duration": "47.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/googleimagesearch.html"
-    },
-    {
-        "duration": "62.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/googleplus.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/linkedin.html"
-    },
-    {
-        "duration": "15.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/pinterest.html"
-    },
-    {
-        "duration": "67.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/techcrunch.html"
-    },
-    {
-        "duration": "74.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/twitter.html"
-    },
-    {
-        "duration": "34.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/weather.html"
-    },
-    {
-        "duration": "0.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/wikipedia.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/wordpress.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/yahooanswers.html"
-    },
-    {
-        "duration": "58.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/yahoogames.html"
-    },
-    {
-        "duration": "144.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/yahoonews.html"
-    },
-    {
-        "duration": "128.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/yahoosports.html"
-    },
-    {
-        "duration": "64.0",
-        "name": "rasterize_and_record_micro.top_25/file://static_top_25/youtube.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/amazon_pinch"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/amazon_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/analog_clock_svg"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/androidpolice_mobile"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/androidpolice_mobile_sync_scroll"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/animometer_webgl"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/aquarium"
-    },
-    {
-        "duration": "49.0",
-        "name": "rendering.mobile/background_color_animation"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/background_color_animation_with_gradient"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/baidu_mobile"
-    },
-    {
-        "duration": "23.0",
-        "name": "rendering.mobile/baidu_mobile_sync_scroll"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/balls_css_key_frame_animations"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/balls_css_transition_2_properties"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/balls_css_transition_40_properties"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/balls_css_transition_all_properties"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/balls_javascript_canvas"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/balls_javascript_css"
-    },
-    {
-        "duration": "86.0",
-        "name": "rendering.mobile/balls_svg_animations"
-    },
-    {
-        "duration": "23.0",
-        "name": "rendering.mobile/bing_mobile"
-    },
-    {
-        "duration": "21.0",
-        "name": "rendering.mobile/bing_mobile_sync_scroll"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/blob"
-    },
-    {
-        "duration": "46.0",
-        "name": "rendering.mobile/blogspot"
-    },
-    {
-        "duration": "45.0",
-        "name": "rendering.mobile/blogspot_desktop_gpu_raster"
-    },
-    {
-        "duration": "24.0",
-        "name": "rendering.mobile/blogspot_mobile"
-    },
-    {
-        "duration": "22.0",
-        "name": "rendering.mobile/blogspot_mobile_sync_scroll"
-    },
-    {
-        "duration": "52.0",
-        "name": "rendering.mobile/boingboing_mobile"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/booking.com"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/booking.com_desktop_gpu_raster"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/booking.com_mobile"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/booking.com_mobile_sync_scroll"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/booking_pinch"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/booking_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/bouncing_balls_15"
-    },
-    {
-        "duration": "49.0",
-        "name": "rendering.mobile/bouncing_balls_shadow"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/bouncing_clipped_rectangles"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/bouncing_gradient_circles"
-    },
-    {
-        "duration": "21.0",
-        "name": "rendering.mobile/bouncing_png_images"
-    },
-    {
-        "duration": "41.0",
-        "name": "rendering.mobile/bouncing_svg_images"
-    },
-    {
-        "duration": "24.0",
-        "name": "rendering.mobile/canvas_animation_no_clear"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/canvas_arcs"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/canvas_font_cycler"
-    },
-    {
-        "duration": "25.0",
-        "name": "rendering.mobile/canvas_lines"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/canvas_to_blob"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/capitolvolkswagen_mobile"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/capitolvolkswagen_mobile_sync_scroll"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/chip_tune"
-    },
-    {
-        "duration": "53.0",
-        "name": "rendering.mobile/cnn_article_mobile"
-    },
-    {
-        "duration": "52.0",
-        "name": "rendering.mobile/cnn_article_mobile_sync_scroll"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/cnn_mobile"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/cnn_mobile_sync_scroll"
-    },
-    {
-        "duration": "54.0",
-        "name": "rendering.mobile/cnn_pathological"
-    },
-    {
-        "duration": "54.0",
-        "name": "rendering.mobile/cnn_pinch"
-    },
-    {
-        "duration": "54.0",
-        "name": "rendering.mobile/cnn_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/compositor_heavy_animation"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/crafty_mind"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/css_animations_many_keyframes"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_animations_simultaneous_inline_style"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/css_animations_simultaneous_new_element"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_animations_simultaneous_style_element"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/css_animations_simultaneous_updating_class"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/css_animations_staggered_infinite_iterations"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/css_animations_staggered_inline_style"
-    },
-    {
-        "duration": "40.0",
-        "name": "rendering.mobile/css_animations_staggered_new_element"
-    },
-    {
-        "duration": "45.0",
-        "name": "rendering.mobile/css_animations_staggered_style_element"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/css_animations_staggered_updating_class"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/css_animations_triggered_inline_style"
-    },
-    {
-        "duration": "41.0",
-        "name": "rendering.mobile/css_animations_triggered_new_element"
-    },
-    {
-        "duration": "59.0",
-        "name": "rendering.mobile/css_animations_triggered_style_element"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/css_animations_triggered_updating_class"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_inline_style"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_new_element"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_staggered_inline_style"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/css_transitions_staggered_new_element"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_staggered_style_element"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_staggered_updating_class"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/css_transitions_style_element"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/css_transitions_triggered_inline_style"
-    },
-    {
-        "duration": "40.0",
-        "name": "rendering.mobile/css_transitions_triggered_new_element"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/css_transitions_triggered_style_element"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/css_transitions_triggered_updating_class"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/css_transitions_updating_class"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/css_value_type_color"
-    },
-    {
-        "duration": "50.0",
-        "name": "rendering.mobile/css_value_type_filter"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/css_value_type_length"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/css_value_type_length_complex"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/css_value_type_length_simple"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/css_value_type_path"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/css_value_type_shadow"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/css_value_type_transform_complex"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/css_value_type_transform_simple"
-    },
-    {
-        "duration": "51.0",
-        "name": "rendering.mobile/cuteoverload_mobile"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/deviantart_mobile"
-    },
-    {
-        "duration": "40.0",
-        "name": "rendering.mobile/deviantart_mobile_sync_scroll"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/dynamic_cube_map"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/earth"
-    },
-    {
-        "duration": "25.0",
-        "name": "rendering.mobile/ebay"
-    },
-    {
-        "duration": "23.0",
-        "name": "rendering.mobile/ebay_desktop_gpu_raster"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/ebay_mobile"
-    },
-    {
-        "duration": "29.0",
-        "name": "rendering.mobile/ebay_mobile_sync_scroll"
-    },
-    {
-        "duration": "50.0",
-        "name": "rendering.mobile/ebay_pinch"
-    },
-    {
-        "duration": "49.0",
-        "name": "rendering.mobile/ebay_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/effect_games"
-    },
-    {
-        "duration": "20.0",
-        "name": "rendering.mobile/espn"
-    },
-    {
-        "duration": "19.0",
-        "name": "rendering.mobile/espn_desktop_gpu_raster"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/espn_pathological"
-    },
-    {
-        "duration": "58.0",
-        "name": "rendering.mobile/espn_pinch"
-    },
-    {
-        "duration": "56.0",
-        "name": "rendering.mobile/espn_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "38.0",
-        "name": "rendering.mobile/extra_large_texture_uploads"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/facebook"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/facebook_desktop_gpu_raster"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/facebook_mobile"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/facebook_mobile_sync_scroll"
-    },
-    {
-        "duration": "40.0",
-        "name": "rendering.mobile/facebook_pinch"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/facebook_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/fill_shapes"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/filter_terrain_svg"
-    },
-    {
-        "duration": "24.0",
-        "name": "rendering.mobile/geo_apis"
-    },
-    {
-        "duration": "71.0",
-        "name": "rendering.mobile/gmail"
-    },
-    {
-        "duration": "90.0",
-        "name": "rendering.mobile/gmail_desktop_gpu_raster"
-    },
-    {
-        "duration": "64.0",
-        "name": "rendering.mobile/gmail_pinch"
-    },
-    {
-        "duration": "66.0",
-        "name": "rendering.mobile/gmail_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "44.0",
-        "name": "rendering.mobile/google_calendar"
-    },
-    {
-        "duration": "44.0",
-        "name": "rendering.mobile/google_calendar_desktop_gpu_raster"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/google_calendar_pinch"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/google_calendar_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "69.0",
-        "name": "rendering.mobile/google_docs"
-    },
-    {
-        "duration": "68.0",
-        "name": "rendering.mobile/google_docs_desktop_gpu_raster"
-    },
-    {
-        "duration": "41.0",
-        "name": "rendering.mobile/google_image_pinch"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/google_image_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "54.0",
-        "name": "rendering.mobile/google_image_search"
-    },
-    {
-        "duration": "54.0",
-        "name": "rendering.mobile/google_image_search_desktop_gpu_raster"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/google_news_mobile"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/google_news_mobile_sync_scroll"
-    },
-    {
-        "duration": "62.0",
-        "name": "rendering.mobile/google_plus"
-    },
-    {
-        "duration": "61.0",
-        "name": "rendering.mobile/google_plus_desktop_gpu_raster"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/google_plus_mobile"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/google_plus_mobile_sync_scroll"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/google_search_pinch"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/google_search_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/google_web_search"
-    },
-    {
-        "duration": "24.0",
-        "name": "rendering.mobile/google_web_search_desktop_gpu_raster"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/google_web_search_mobile"
-    },
-    {
-        "duration": "24.0",
-        "name": "rendering.mobile/google_web_search_mobile_sync_scroll"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/gsp.ro_mobile"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/gsp.ro_mobile_sync_scroll"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/guardian_pathological"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/guimark_vector_chart"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/gws_boogie_expansion"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/gws_google_expansion"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/hakim"
-    },
-    {
-        "duration": "47.0",
-        "name": "rendering.mobile/ie_chalkboard"
-    },
-    {
-        "duration": "33.0",
-        "name": "rendering.mobile/jarro_doverson"
-    },
-    {
-        "duration": "31.0",
-        "name": "rendering.mobile/kevs_3d"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/keyframed_animations"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/large_texture_uploads"
-    },
-    {
-        "duration": "42.0",
-        "name": "rendering.mobile/latimes_pathological"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/linkedin"
-    },
-    {
-        "duration": "25.0",
-        "name": "rendering.mobile/linkedin_desktop_gpu_raster"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/linkedin_mobile"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/linkedin_mobile_sync_scroll"
-    },
-    {
-        "duration": "36.0",
-        "name": "rendering.mobile/linkedin_pathological"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/linkedin_pinch"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/linkedin_pinch_desktop_gpu_raster"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/man_in_blue"
-    },
-    {
-        "duration": "41.0",
-        "name": "rendering.mobile/many_images"
-    },
-    {
-        "duration": "9.0",
-        "name": "rendering.mobile/many_planets_deep"
-    },
-    {
-        "duration": "8.0",
-        "name": "rendering.mobile/maps_perf_test"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/masonry"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/medium_texture_uploads"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/megi_dish"
-    },
-    {
-        "duration": "35.0",
-        "name": "rendering.mobile/microsoft_asteroid_belt"
-    },
-    {
-        "duration": "37.0",
-        "name": "rendering.mobile/microsoft_fireflies"
-    },
-    {
-        "duration": "26.0",
-        "name": "rendering.mobile/microsoft_fish_ie_tank"
-    },
-    {
-        "duration": "28.0",
-        "name": "rendering.mobile/microsoft_snow"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/microsoft_speed_reading"
-    },
-    {
-        "duration": "32.0",
-        "name": "rendering.mobile/microsoft_tweet_map"
-    },
-    {
-        "duration": "30.0",
-        "name": "rendering.mobile/microsoft_video_city"
-    },
-    {
-        "duration": "27.0",
-        "name": "rendering.mobile/microsoft_worker_fountains"
-    },
-    {
-        "duration": "29.0",
-        "name": "rendering.mobile/mix_10k"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/mix_blend_mode_animation_difference"
-    },
-    {
-        "duration": "34.0",
-        "name": "rendering.mobile/mix_blend_mode_animation_hue"
-    },
-    {
-        "duration": "39.0",
-        "name": "rendering.mobile/mix_blend_mode_animation_propagating_isolation"
-    },
-    {
-        "duration": "22.0",
-        "name": "rendering.mobile/mlb_mobile"
-    },
-    {
-        "duration": "22.0",
-        "name": "rendering.mobile/mlb_mobile_sync_scroll"
-    },
-    {
-        "duration": "26.0",
-        "name": "scheduler.tough_scheduling_cases/raf.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "scheduler.tough_scheduling_cases/raf_animation.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "scheduler.tough_scheduling_cases/raf_canvas.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "scheduler.tough_scheduling_cases/raf_touch_animation.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "scheduler.tough_scheduling_cases/second_batch_js.html?heavy"
-    },
-    {
-        "duration": "18.0",
-        "name": "scheduler.tough_scheduling_cases/second_batch_js.html?light"
-    },
-    {
-        "duration": "18.0",
-        "name": "scheduler.tough_scheduling_cases/second_batch_js.html?medium"
-    },
-    {
-        "duration": "40.0",
-        "name": "scheduler.tough_scheduling_cases/simple_text_page.html"
-    },
-    {
-        "duration": "18.0",
-        "name": "scheduler.tough_scheduling_cases/simple_touch_drag.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "scheduler.tough_scheduling_cases/sync_scroll_offset.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "scheduler.tough_scheduling_cases/touch_handler_scrolling.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/blogspot"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/booking.com"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/ebay"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/espn"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/facebook"
-    },
-    {
-        "duration": "65.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/gmail"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/google_calendar"
-    },
-    {
-        "duration": "50.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/google_docs"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/google_image_search"
-    },
-    {
-        "duration": "45.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/google_plus"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/google_web_search"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/linkedin"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/techcrunch"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/twitter"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/weather.com"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/wikipedia"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/wordpress"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/yahoo_answers"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/yahoo_games"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/yahoo_news"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/yahoo_sports"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.gpu_rasterization.top_25_smooth/youtube"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.gpu_rasterization.tough_filters_cases/Analog_Clock_SVG"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.gpu_rasterization.tough_filters_cases/Filter_Terrain_SVG"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.gpu_rasterization.tough_filters_cases/IE_PirateMark"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.gpu_rasterization.tough_filters_cases/MotionMark_Focus"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.gpu_rasterization.tough_path_rendering_cases/GUIMark_Vector_Chart_Test"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.gpu_rasterization.tough_path_rendering_cases/IE_Chalkboard"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_path_rendering_cases/MotionMark_Canvas_Fill_Shapes"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_path_rendering_cases/MotionMark_Canvas_Stroke_Shapes"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/Blogger"
-    },
-    {
-        "duration": "39.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/ESPN"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/Facebook"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/LinkedIn"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/Twitter"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/Weather.com"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://booking.com"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://games.yahoo.com"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://news.yahoo.com"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://sports.yahoo.com/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://www.amazon.com"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://www.cnn.com"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://www.ebay.com"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/http://www.youtube.com"
-    },
-    {
-        "duration": "48.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/https://mail.google.com/mail/"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/https://www.google.com/#hl=en&q=barack+obama"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/https://www.google.com/calendar/"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.gpu_rasterization.tough_pinch_zoom_cases/https://www.google.com/search?q=cats&tbm=isch"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_05000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_10000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_15000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_20000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_30000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_40000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_50000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_60000_pixels_per_second"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_75000_pixels_per_second"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/canvas_90000_pixels_per_second"
-    },
-    {
-        "duration": "40.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_05000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_10000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_15000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_20000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_30000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_40000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_50000_pixels_per_second"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_60000_pixels_per_second"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_75000_pixels_per_second"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_90000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_05000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_10000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_15000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_20000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_30000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_40000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_50000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_60000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_75000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_constant_full_page_raster_90000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_05000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_10000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_15000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_20000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_30000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_40000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_50000_pixels_per_second"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_60000_pixels_per_second"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_75000_pixels_per_second"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.gpu_rasterization.tough_scrolling_cases/text_hover_90000_pixels_per_second"
-    },
-    {
-        "duration": "67.0",
-        "name": "smoothness.gpu_rasterization_and_decoding.image_decoding_cases/yuv_decoding.html"
-    },
-    {
-        "duration": "67.0",
-        "name": "smoothness.image_decoding_cases/yuv_decoding.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.key_mobile_sites_smooth/androidpolice"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.key_mobile_sites_smooth/baidu"
-    },
-    {
-        "duration": "17.0",
-        "name": "smoothness.key_mobile_sites_smooth/bing"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_mobile_sites_smooth/blogspot"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.key_mobile_sites_smooth/boingboing"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.key_mobile_sites_smooth/booking.com"
-    },
-    {
-        "duration": "41.0",
-        "name": "smoothness.key_mobile_sites_smooth/capitolvolkswagen"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.key_mobile_sites_smooth/cnn"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.key_mobile_sites_smooth/cnn_article"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.key_mobile_sites_smooth/cuteoverload"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.key_mobile_sites_smooth/deviantart"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_mobile_sites_smooth/ebay"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_mobile_sites_smooth/facebook"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.key_mobile_sites_smooth/google_news"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_mobile_sites_smooth/google_plus"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.key_mobile_sites_smooth/google_web_search"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_mobile_sites_smooth/gsp.ro"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.key_mobile_sites_smooth/linkedin"
-    },
-    {
-        "duration": "18.0",
-        "name": "smoothness.key_mobile_sites_smooth/mlb"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.key_mobile_sites_smooth/nytimes"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.key_mobile_sites_smooth/pinterest"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.key_mobile_sites_smooth/reddit"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.key_mobile_sites_smooth/sfgate"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.key_mobile_sites_smooth/slashdot"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_mobile_sites_smooth/techcrunch"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_mobile_sites_smooth/theverge"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.key_mobile_sites_smooth/theverge_article"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.key_mobile_sites_smooth/usatoday"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.key_mobile_sites_smooth/wikipedia"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.key_mobile_sites_smooth/wikipedia_delayed_scroll_start"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.key_mobile_sites_smooth/wordpress"
-    },
-    {
-        "duration": "45.0",
-        "name": "smoothness.key_mobile_sites_smooth/worldjournal"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.key_mobile_sites_smooth/wowwiki"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.key_mobile_sites_smooth/wsj"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.key_mobile_sites_smooth/yahoo_answers"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_mobile_sites_smooth/yahoo_news"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.key_mobile_sites_smooth/youtube"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_silk_cases/font_wipe.html"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.key_silk_cases/http://groupcloned.com/test/plain/list-recycle-transform.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_silk_cases/http://groupcloned.com/test/plain/sticky-using-webkit-backface-visibility.html"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.key_silk_cases/http://jsbin.com/UVIgUTa/38/quiet"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/3yDKh/15/show/"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/3yDKh/16/show/"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/R8DX9/4/show/"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/TLXLu/3/show/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/bNp2h/3/show/"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/cKB9D/7/show/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/jx5De/14/show/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/rF9Gh/7/show/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/ugkd4/10/show/"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/vBQHH/11/show/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://jsfiddle.net/xLuvC/1/show/"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.key_silk_cases/http://mobile-news.sandbox.google.com/news/pt0?scroll"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_silk_cases/http://mobile-news.sandbox.google.com/news/pt0?swipe"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.key_silk_cases/http://plus.google.com/app/basic/stream"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.key_silk_cases/http://wiltzius.github.io/shape-shifter/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/http://www.google.com/#q=google"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.key_silk_cases/https://www.google.com/search?hl=en&q=define%3Aboogie"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.key_silk_cases/inbox_app.html?stress_hidey_bars"
-    },
-    {
-        "duration": "18.0",
-        "name": "smoothness.key_silk_cases/inbox_app.html?swipe_to_dismiss"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.key_silk_cases/inbox_app.html?toggle_drawer"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.key_silk_cases/infinite_scrolling.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.key_silk_cases/list_animation_simple.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.key_silk_cases/masonry.html"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.key_silk_cases/pushState.html"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.key_silk_cases/silk_finance.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.maps/maps_perf_test"
-    },
-    {
-        "duration": "54.0",
-        "name": "smoothness.pathological_mobile_sites/http://edition.cnn.com"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.pathological_mobile_sites/http://m.espn.go.com/nhl/rankings"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.pathological_mobile_sites/http://recode.net"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.pathological_mobile_sites/http://sports.yahoo.com/"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.pathological_mobile_sites/http://www.latimes.com"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.pathological_mobile_sites/http://www.pbs.org/newshour/bb/much-really-cost-live-city-like-seattle/#the-rundown"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.pathological_mobile_sites/http://www.theguardian.com/politics/2015/mar/09/ed-balls-tory-spending-plans-nhs-charging"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.pathological_mobile_sites/http://www.wowwiki.com/World_of_Warcraft:_Mists_of_Pandaria"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.pathological_mobile_sites/http://www.zdnet.com"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.pathological_mobile_sites/https://www.linkedin.com/in/linustorvalds"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.simple_mobile_sites/http://m.nytimes.com/"
-    },
-    {
-        "duration": "52.0",
-        "name": "smoothness.simple_mobile_sites/http://www.ebay.co.uk/"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.simple_mobile_sites/http://www.nyc.gov"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.simple_mobile_sites/https://www.flickr.com/"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/amazon"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/androidpolice"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/baidu"
-    },
-    {
-        "duration": "18.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/bing"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/blogspot"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/boingboing"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/booking.com"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/capitolvolkswagen"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/cnn"
-    },
-    {
-        "duration": "39.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/cnn_article"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/cuteoverload"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/deviantart"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/digg"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/ebay"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/espn"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/facebook"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/forecast.io"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/google_news"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/google_plus"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/google_web_search"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/gsp.ro"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/linkedin"
-    },
-    {
-        "duration": "18.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/mlb"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/nytimes"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/pinterest"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/reddit"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/sfgate"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/slashdot"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/techcrunch"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/theverge"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/theverge_article"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/twitter"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/usatoday"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/wikipedia"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/wikipedia_delayed_scroll_start"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/wordpress"
-    },
-    {
-        "duration": "49.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/worldjournal"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/wowwiki"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/wsj"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/yahoo_answers"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/yahoo_news"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.sync_scroll.key_mobile_sites_smooth/youtube"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.top_25_smooth/amazon"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.top_25_smooth/blogspot"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.top_25_smooth/booking.com"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.top_25_smooth/cnn"
-    },
-    {
-        "duration": "19.0",
-        "name": "smoothness.top_25_smooth/ebay"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.top_25_smooth/espn"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.top_25_smooth/facebook"
-    },
-    {
-        "duration": "64.0",
-        "name": "smoothness.top_25_smooth/gmail"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.top_25_smooth/google_calendar"
-    },
-    {
-        "duration": "51.0",
-        "name": "smoothness.top_25_smooth/google_docs"
-    },
-    {
-        "duration": "39.0",
-        "name": "smoothness.top_25_smooth/google_image_search"
-    },
-    {
-        "duration": "46.0",
-        "name": "smoothness.top_25_smooth/google_plus"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.top_25_smooth/google_web_search"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.top_25_smooth/linkedin"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.top_25_smooth/pinterest"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.top_25_smooth/techcrunch"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.top_25_smooth/twitter"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.top_25_smooth/weather.com"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.top_25_smooth/wikipedia"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.top_25_smooth/wordpress"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.top_25_smooth/yahoo_answers"
-    },
-    {
-        "duration": "39.0",
-        "name": "smoothness.top_25_smooth/yahoo_games"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.top_25_smooth/yahoo_news"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.top_25_smooth/yahoo_sports"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.top_25_smooth/youtube"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CICAgICQ15a9NxDIARjIASgBMghBC1XuTk8ezw.swiffy72.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CICAgIDQ2Pb-MxCsAhj6ASgBMgi5DLoSO0gPbQ.swiffy72.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CICAgKCN39CopQEQoAEY2AQoATIID59gK5hjjIg.swiffy72.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CICAgKCNj4HgyAEQeBjYBCgBMgjQpPkOjyWNdw.1.swiffy72.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CICAgMDOrcnRGRB4GNgEKAEyCP_ZBSfwUFsj.swiffy72.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/CNP2xe_LmqPEKBCsAhj6ASgBMggnyMqth81h8Q.swiffy72.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/clip-paths-CICAgMDO7Ye9-gEQ2AUYWigBMgjZxDii6aoK9w.swiffy72.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/clip-paths-CILZhLqO_-27bxB4GNgEKAEyCC46kMLBXnMT.swiffy72.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/filters-CNLa0t2T47qJ_wEQoAEY2AQoATIIFaIdc7VMBr4.swiffy72.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/shapes-CICAgMDO7cfIzwEQ1AMYPCgBMghqY8tqyRCArQ.swiffy72.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_ad_cases/http://localhost:8000/shapes-CK7ptO3F8bi2KxDQAhiYAigBMgij6QBQtD2gyA.swiffy72.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_animation_cases/balls_css_keyframe_animations.html"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.tough_animation_cases/balls_css_keyframe_animations_composited_transform.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/balls_css_transition_2_properties.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/balls_css_transition_40_properties.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/balls_css_transition_all_properties.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/balls_javascript_canvas.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/balls_javascript_css.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "smoothness.tough_animation_cases/balls_svg_animations.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/compositor_heavy_animation.html?N=0200"
-    },
-    {
-        "duration": "136.0",
-        "name": "smoothness.tough_animation_cases/css_animations_many_keyframes.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_animations_simultaneous_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.tough_animation_cases/css_animations_simultaneous_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.tough_animation_cases/css_animations_simultaneous_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_animations_simultaneous_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_chaining_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_chaining_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_chaining_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_chaining_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_infinite_iterations.html?N=0316"
-    },
-    {
-        "duration": "34.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_triggering_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_triggering_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_triggering_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_animation_cases/css_animations_staggered_triggering_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_simultaneous_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_simultaneous_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_simultaneous_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_simultaneous_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_chaining_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_chaining_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_chaining_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_chaining_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_triggering_by_inserting_new_element.html?N=0316"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_triggering_by_inserting_style_element.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_triggering_by_updating_class.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/css_transitions_staggered_triggering_by_updating_inline_style.html?N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_color.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_color.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_filter.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_3d.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_3d.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_complex.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_complex.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_simple.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_length_simple.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_path.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_path.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_shadow.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_shadow.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_transform_complex.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_transform_complex.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_transform_simple.html?api=css_animations&N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/css_value_type_transform_simple.html?api=web_animations&N=0316"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/keyframed_animations.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/mix_blend_mode_animation_difference.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/mix_blend_mode_animation_hue.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_animation_cases/mix_blend_mode_animation_screen.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/mix_blend_mode_propagating_isolation.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/overlay_background_color_css_transitions.html"
-    },
-    {
-        "duration": "0.0",
-        "name": "smoothness.tough_animation_cases/robohornetpro"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/transform_transition_js_block.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/transform_transitions.html"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.tough_animation_cases/web_animations_many_keyframes.html?N=0316"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_animation_cases/web_animations_set_current_time_in_raf.html?N=0316"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.tough_animation_cases/web_animations_simultaneous.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/web_animations_staggered_chaining.html?N=0316"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_animation_cases/web_animations_staggered_infinite_iterations.html?N=0316"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_animation_cases/web_animations_staggered_triggering.html?N=0316"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_canvas_cases/../../../chrome/test/data/perf/canvas_bench/many_images.html"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.tough_canvas_cases/http://geoapis.appspot.com/agdnZW9hcGlzchMLEgtFeGFtcGxlQ29kZRjh1wIM"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/http://hakim.se/experiments/html5/magnetic/02/"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Graphics/TweetMap/Default.html"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Graphics/VideoCity/Default.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Graphics/WorkerFountains/Default.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Performance/AsteroidBelt/Default.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Performance/FishIETank/Default.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Performance/LetItSnow/"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://ie.microsoft.com/testdrive/Performance/SpeedReading/Default.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/http://jarrodoverson.com/static/demos/particleSystem/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/http://mix10k.visitmix.com/Entry/Details/169"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://runway.countlessprojects.com/prototype/performance_test.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://spielzeugz.de/html5/liquid-particles.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/http://themaninblue.com/experiment/AnimationBenchmark/canvas/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/http://www.chiptune.com/starfield/starfield.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/http://www.craftymind.com/factory/guimark2/HTML5ChartingTest.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/http://www.effectgames.com/demos/canvascycle/"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/http://www.kevs3d.co.uk/dev/canvask3d/k3d_test.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/http://www.megidish.net/awjs/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/http://www.smashcat.org/av/canvas_test/"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/canvas-animation-no-clear.html"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/canvas-font-cycler.html"
-    },
-    {
-        "duration": "44.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/canvas2d_balls_common/bouncing_balls.html?ball=image_with_shadow&back=image"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/canvas2d_balls_common/bouncing_balls.html?ball=text&back=white&ball_count=15"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/canvas_toBlob.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/bouncing_clipped_rectangles.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/bouncing_gradient_circles.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/bouncing_png_images.html"
-    },
-    {
-        "duration": "36.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/bouncing_svg_images.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/canvas_arcs.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/canvas_lines.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/fill_shapes.html"
-    },
-    {
-        "duration": "68.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/put_get_image_data.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_canvas_cases/tough_canvas_cases/rendering_throughput/stroke_shapes.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_filters_cases/Analog_Clock_SVG"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_filters_cases/Filter_Terrain_SVG"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_filters_cases/IE_PirateMark"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.tough_filters_cases/MotionMark_Focus"
-    },
-    {
-        "duration": "32.0",
-        "name": "smoothness.tough_image_decode_cases/http://localhost:9000/cats-unscaled.html"
-    },
-    {
-        "duration": "16.0",
-        "name": "smoothness.tough_image_decode_cases/http://localhost:9000/cats-viewport-width.html"
-    },
-    {
-        "duration": "43.0",
-        "name": "smoothness.tough_path_rendering_cases/GUIMark_Vector_Chart_Test"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.tough_path_rendering_cases/IE_Chalkboard"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_path_rendering_cases/MotionMark_Canvas_Fill_Shapes"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_path_rendering_cases/MotionMark_Canvas_Stroke_Shapes"
-    },
-    {
-        "duration": "31.0",
-        "name": "smoothness.tough_pinch_zoom_cases/Blogger"
-    },
-    {
-        "duration": "38.0",
-        "name": "smoothness.tough_pinch_zoom_cases/ESPN"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_pinch_zoom_cases/Facebook"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_pinch_zoom_cases/LinkedIn"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_pinch_zoom_cases/Twitter"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.tough_pinch_zoom_cases/Weather.com"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://booking.com"
-    },
-    {
-        "duration": "37.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://games.yahoo.com"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://news.yahoo.com"
-    },
-    {
-        "duration": "41.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://sports.yahoo.com/"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://www.amazon.com"
-    },
-    {
-        "duration": "35.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://www.cnn.com"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://www.ebay.com"
-    },
-    {
-        "duration": "30.0",
-        "name": "smoothness.tough_pinch_zoom_cases/http://www.youtube.com"
-    },
-    {
-        "duration": "47.0",
-        "name": "smoothness.tough_pinch_zoom_cases/https://mail.google.com/mail/"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.tough_pinch_zoom_cases/https://www.google.com/#hl=en&q=barack+obama"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_pinch_zoom_cases/https://www.google.com/calendar/"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_pinch_zoom_cases/https://www.google.com/search?q=cats&tbm=isch"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_05000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_10000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_15000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_20000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_30000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_40000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_50000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_60000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_75000_pixels_per_second"
-    },
-    {
-        "duration": "22.0",
-        "name": "smoothness.tough_scrolling_cases/canvas_90000_pixels_per_second"
-    },
-    {
-        "duration": "40.0",
-        "name": "smoothness.tough_scrolling_cases/text_05000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_10000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_15000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_20000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_30000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_40000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_scrolling_cases/text_50000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_scrolling_cases/text_60000_pixels_per_second"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_scrolling_cases/text_75000_pixels_per_second"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.tough_scrolling_cases/text_90000_pixels_per_second"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_05000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_10000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_15000_pixels_per_second"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_20000_pixels_per_second"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_30000_pixels_per_second"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_40000_pixels_per_second"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_50000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_60000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_75000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_scrolling_cases/text_constant_full_page_raster_90000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_05000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_10000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_15000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_20000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_30000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_40000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_50000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_60000_pixels_per_second"
-    },
-    {
-        "duration": "21.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_75000_pixels_per_second"
-    },
-    {
-        "duration": "20.0",
-        "name": "smoothness.tough_scrolling_cases/text_hover_90000_pixels_per_second"
-    },
-    {
-        "duration": "46.0",
-        "name": "smoothness.tough_texture_upload_cases/background_color_animation.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_texture_upload_cases/background_color_animation_with_gradient.html"
-    },
-    {
-        "duration": "33.0",
-        "name": "smoothness.tough_texture_upload_cases/extra_large_texture_uploads.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_texture_upload_cases/large_texture_uploads.html"
-    },
-    {
-        "duration": "28.0",
-        "name": "smoothness.tough_texture_upload_cases/medium_texture_uploads.html"
-    },
-    {
-        "duration": "29.0",
-        "name": "smoothness.tough_texture_upload_cases/small_texture_uploads.html"
-    },
-    {
-        "duration": "42.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CICAgICQ15a9NxDIARjIASgBMghBC1XuTk8ezw.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CICAgIDQ2Pb-MxCsAhj6ASgBMgi5DLoSO0gPbQ.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CICAgKCN39CopQEQoAEY2AQoATIID59gK5hjjIg.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CICAgKCNj4HgyAEQeBjYBCgBMgjQpPkOjyWNdw.1.swf.webglbeta.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CICAgMDOrcnRGRB4GNgEKAEyCP_ZBSfwUFsj.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/CNP2xe_LmqPEKBCsAhj6ASgBMggnyMqth81h8Q.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/clip-paths-CICAgMDO7Ye9-gEQ2AUYWigBMgjZxDii6aoK9w.swf.webglbeta.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/clip-paths-CILZhLqO_-27bxB4GNgEKAEyCC46kMLBXnMT.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/filters-CNLa0t2T47qJ_wEQoAEY2AQoATIIFaIdc7VMBr4.swf.webglbeta.html"
-    },
-    {
-        "duration": "27.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/shapes-CICAgMDO7cfIzwEQ1AMYPCgBMghqY8tqyRCArQ.swf.webglbeta.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "smoothness.tough_webgl_ad_cases/http://localhost:8000/shapes-CK7ptO3F8bi2KxDQAhiYAigBMgij6QBQtD2gyA.swf.webglbeta.html"
-    },
-    {
-        "duration": "38.0",
         "name": "start_with_url.cold.startup_pages/about:blank"
     },
     {
-        "duration": "43.0",
+        "duration": "42.0",
         "name": "start_with_url.cold.startup_pages/http://bbc.co.uk"
     },
     {
-        "duration": "36.0",
+        "duration": "35.0",
         "name": "start_with_url.warm.startup_pages/about:blank"
     },
     {
-        "duration": "38.0",
+        "duration": "36.0",
         "name": "start_with_url.warm.startup_pages/http://bbc.co.uk"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.common_mobile/background:media:imgur"
     },
     {
-        "duration": "33.0",
+        "duration": "66.0",
+        "name": "system_health.common_mobile/background:news:nytimes"
+    },
+    {
+        "duration": "32.0",
         "name": "system_health.common_mobile/background:search:google"
     },
     {
-        "duration": "54.0",
+        "duration": "55.0",
         "name": "system_health.common_mobile/background:social:facebook"
     },
     {
-        "duration": "49.0",
+        "duration": "48.0",
         "name": "system_health.common_mobile/background:tools:gmail"
     },
     {
-        "duration": "210.0",
+        "duration": "209.0",
         "name": "system_health.common_mobile/browse:chrome:newtab"
     },
     {
-        "duration": "56.0",
+        "duration": "54.0",
         "name": "system_health.common_mobile/browse:chrome:omnibox"
     },
     {
-        "duration": "128.0",
+        "duration": "149.0",
         "name": "system_health.common_mobile/browse:media:facebook_photos"
     },
     {
-        "duration": "70.0",
+        "duration": "69.0",
         "name": "system_health.common_mobile/browse:media:flickr_infinite_scroll"
     },
     {
-        "duration": "108.0",
+        "duration": "105.0",
         "name": "system_health.common_mobile/browse:media:imgur"
     },
     {
-        "duration": "182.0",
+        "duration": "156.0",
         "name": "system_health.common_mobile/browse:media:youtube"
     },
     {
-        "duration": "259.0",
+        "duration": "247.0",
         "name": "system_health.common_mobile/browse:news:cnn"
     },
     {
-        "duration": "90.0",
+        "duration": "91.0",
         "name": "system_health.common_mobile/browse:news:cricbuzz"
     },
     {
-        "duration": "78.0",
+        "duration": "77.0",
         "name": "system_health.common_mobile/browse:news:qq"
     },
     {
-        "duration": "88.0",
+        "duration": "84.0",
         "name": "system_health.common_mobile/browse:news:reddit"
     },
     {
-        "duration": "78.0",
+        "duration": "307.0",
+        "name": "system_health.common_mobile/browse:news:toi"
+    },
+    {
+        "duration": "75.0",
         "name": "system_health.common_mobile/browse:news:washingtonpost"
     },
     {
-        "duration": "117.0",
+        "duration": "115.0",
         "name": "system_health.common_mobile/browse:shopping:amazon"
     },
     {
-        "duration": "117.0",
+        "duration": "115.0",
         "name": "system_health.common_mobile/browse:shopping:avito"
     },
     {
-        "duration": "58.0",
+        "duration": "57.0",
         "name": "system_health.common_mobile/browse:shopping:lazada"
     },
     {
-        "duration": "122.0",
+        "duration": "97.0",
         "name": "system_health.common_mobile/browse:social:facebook"
     },
     {
-        "duration": "159.0",
+        "duration": "156.0",
         "name": "system_health.common_mobile/browse:social:facebook_infinite_scroll"
     },
     {
-        "duration": "121.0",
+        "duration": "120.0",
         "name": "system_health.common_mobile/browse:social:instagram"
     },
     {
-        "duration": "128.0",
+        "duration": "126.0",
         "name": "system_health.common_mobile/browse:social:pinterest_infinite_scroll"
     },
     {
-        "duration": "160.0",
+        "duration": "154.0",
         "name": "system_health.common_mobile/browse:social:tumblr_infinite_scroll"
     },
     {
-        "duration": "78.0",
+        "duration": "75.0",
         "name": "system_health.common_mobile/browse:social:twitter"
     },
     {
@@ -4320,47 +248,51 @@
         "name": "system_health.common_mobile/browse:tech:discourse_infinite_scroll"
     },
     {
-        "duration": "69.0",
+        "duration": "68.0",
         "name": "system_health.common_mobile/browse:tools:maps"
     },
     {
-        "duration": "27.0",
+        "duration": "26.0",
         "name": "system_health.common_mobile/load:chrome:blank"
     },
     {
-        "duration": "38.0",
+        "duration": "37.0",
         "name": "system_health.common_mobile/load:games:bubbles"
     },
     {
-        "duration": "28.0",
+        "duration": "27.0",
         "name": "system_health.common_mobile/load:games:lazors"
     },
     {
-        "duration": "38.0",
+        "duration": "50.0",
+        "name": "system_health.common_mobile/load:games:spychase"
+    },
+    {
+        "duration": "37.0",
         "name": "system_health.common_mobile/load:media:dailymotion"
     },
     {
-        "duration": "33.0",
+        "duration": "32.0",
         "name": "system_health.common_mobile/load:media:facebook_photos"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:media:google_images"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:media:imgur"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.common_mobile/load:media:soundcloud"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:media:youtube"
     },
     {
-        "duration": "72.0",
+        "duration": "69.0",
         "name": "system_health.common_mobile/load:news:cnn"
     },
     {
@@ -4368,99 +300,103 @@
         "name": "system_health.common_mobile/load:news:irctc"
     },
     {
-        "duration": "43.0",
+        "duration": "41.0",
         "name": "system_health.common_mobile/load:news:nytimes"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:news:qq"
     },
     {
-        "duration": "34.0",
+        "duration": "33.0",
         "name": "system_health.common_mobile/load:news:reddit"
     },
     {
-        "duration": "37.0",
+        "duration": "36.0",
         "name": "system_health.common_mobile/load:news:washingtonpost"
     },
     {
-        "duration": "34.0",
+        "duration": "33.0",
         "name": "system_health.common_mobile/load:news:wikipedia"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:search:baidu"
     },
     {
-        "duration": "33.0",
+        "duration": "55.0",
         "name": "system_health.common_mobile/load:search:ebay"
     },
     {
-        "duration": "30.0",
+        "duration": "29.0",
         "name": "system_health.common_mobile/load:search:google"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.common_mobile/load:search:taobao"
     },
     {
-        "duration": "30.0",
+        "duration": "29.0",
         "name": "system_health.common_mobile/load:search:yahoo"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.common_mobile/load:search:yandex"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.common_mobile/load:social:twitter"
     },
     {
-        "duration": "31.0",
+        "duration": "29.0",
         "name": "system_health.common_mobile/load:tools:docs"
     },
     {
-        "duration": "59.0",
+        "duration": "36.0",
         "name": "system_health.common_mobile/load:tools:drive"
     },
     {
-        "duration": "29.0",
+        "duration": "28.0",
         "name": "system_health.common_mobile/load:tools:dropbox"
     },
     {
-        "duration": "33.0",
+        "duration": "48.0",
+        "name": "system_health.common_mobile/load:tools:gmail"
+    },
+    {
+        "duration": "32.0",
         "name": "system_health.common_mobile/load:tools:stackoverflow"
     },
     {
-        "duration": "47.0",
+        "duration": "45.0",
         "name": "system_health.common_mobile/load:tools:weather"
     },
     {
-        "duration": "148.0",
+        "duration": "146.0",
         "name": "system_health.common_mobile/long_running:tools:gmail-background"
     },
     {
-        "duration": "146.0",
+        "duration": "145.0",
         "name": "system_health.common_mobile/long_running:tools:gmail-foreground"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/background:media:imgur"
     },
     {
-        "duration": "33.0",
+        "duration": "31.0",
         "name": "system_health.memory_mobile/background:search:google"
     },
     {
-        "duration": "38.0",
+        "duration": "37.0",
         "name": "system_health.memory_mobile/background:social:facebook"
     },
     {
-        "duration": "44.0",
+        "duration": "42.0",
         "name": "system_health.memory_mobile/background:tools:gmail"
     },
     {
-        "duration": "182.0",
+        "duration": "180.0",
         "name": "system_health.memory_mobile/browse:chrome:newtab"
     },
     {
@@ -4468,87 +404,91 @@
         "name": "system_health.memory_mobile/browse:chrome:omnibox"
     },
     {
-        "duration": "102.0",
+        "duration": "100.0",
         "name": "system_health.memory_mobile/browse:media:facebook_photos"
     },
     {
-        "duration": "56.0",
+        "duration": "55.0",
         "name": "system_health.memory_mobile/browse:media:flickr_infinite_scroll"
     },
     {
-        "duration": "82.0",
+        "duration": "81.0",
         "name": "system_health.memory_mobile/browse:media:imgur"
     },
     {
-        "duration": "108.0",
+        "duration": "106.0",
         "name": "system_health.memory_mobile/browse:media:youtube"
     },
     {
-        "duration": "187.0",
+        "duration": "178.0",
         "name": "system_health.memory_mobile/browse:news:cnn"
     },
     {
-        "duration": "70.0",
+        "duration": "68.0",
         "name": "system_health.memory_mobile/browse:news:cricbuzz"
     },
     {
-        "duration": "62.0",
+        "duration": "61.0",
         "name": "system_health.memory_mobile/browse:news:qq"
     },
     {
-        "duration": "82.0",
+        "duration": "79.0",
         "name": "system_health.memory_mobile/browse:news:reddit"
     },
     {
-        "duration": "60.0",
+        "duration": "184.0",
+        "name": "system_health.memory_mobile/browse:news:toi"
+    },
+    {
+        "duration": "59.0",
         "name": "system_health.memory_mobile/browse:news:washingtonpost"
     },
     {
-        "duration": "80.0",
+        "duration": "78.0",
         "name": "system_health.memory_mobile/browse:shopping:amazon"
     },
     {
-        "duration": "95.0",
+        "duration": "94.0",
         "name": "system_health.memory_mobile/browse:shopping:avito"
     },
     {
-        "duration": "48.0",
+        "duration": "47.0",
         "name": "system_health.memory_mobile/browse:shopping:lazada"
     },
     {
-        "duration": "76.0",
+        "duration": "75.0",
         "name": "system_health.memory_mobile/browse:social:facebook"
     },
     {
-        "duration": "97.0",
+        "duration": "98.0",
         "name": "system_health.memory_mobile/browse:social:facebook_infinite_scroll"
     },
     {
-        "duration": "98.0",
+        "duration": "89.0",
         "name": "system_health.memory_mobile/browse:social:instagram"
     },
     {
-        "duration": "96.0",
+        "duration": "94.0",
         "name": "system_health.memory_mobile/browse:social:pinterest_infinite_scroll"
     },
     {
-        "duration": "106.0",
+        "duration": "104.0",
         "name": "system_health.memory_mobile/browse:social:tumblr_infinite_scroll"
     },
     {
-        "duration": "63.0",
+        "duration": "61.0",
         "name": "system_health.memory_mobile/browse:social:twitter"
     },
     {
-        "duration": "83.0",
+        "duration": "82.0",
         "name": "system_health.memory_mobile/browse:tech:discourse_infinite_scroll"
     },
     {
-        "duration": "60.0",
+        "duration": "58.0",
         "name": "system_health.memory_mobile/browse:tools:maps"
     },
     {
-        "duration": "28.0",
+        "duration": "27.0",
         "name": "system_health.memory_mobile/load:chrome:blank"
     },
     {
@@ -4556,111 +496,111 @@
         "name": "system_health.memory_mobile/load:games:bubbles"
     },
     {
-        "duration": "29.0",
+        "duration": "28.0",
         "name": "system_health.memory_mobile/load:games:lazors"
     },
     {
-        "duration": "39.0",
+        "duration": "47.0",
         "name": "system_health.memory_mobile/load:games:spychase"
     },
     {
-        "duration": "38.0",
+        "duration": "36.0",
         "name": "system_health.memory_mobile/load:media:dailymotion"
     },
     {
-        "duration": "33.0",
+        "duration": "40.0",
         "name": "system_health.memory_mobile/load:media:facebook_photos"
     },
     {
-        "duration": "33.0",
+        "duration": "32.0",
         "name": "system_health.memory_mobile/load:media:google_images"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:media:imgur"
     },
     {
-        "duration": "40.0",
+        "duration": "31.0",
         "name": "system_health.memory_mobile/load:media:soundcloud"
     },
     {
-        "duration": "41.0",
+        "duration": "40.0",
         "name": "system_health.memory_mobile/load:media:youtube"
     },
     {
-        "duration": "62.0",
+        "duration": "59.0",
         "name": "system_health.memory_mobile/load:news:cnn"
     },
     {
-        "duration": "38.0",
+        "duration": "37.0",
         "name": "system_health.memory_mobile/load:news:irctc"
     },
     {
-        "duration": "40.0",
+        "duration": "39.0",
         "name": "system_health.memory_mobile/load:news:nytimes"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.memory_mobile/load:news:qq"
     },
     {
-        "duration": "35.0",
+        "duration": "34.0",
         "name": "system_health.memory_mobile/load:news:reddit"
     },
     {
-        "duration": "35.0",
+        "duration": "34.0",
         "name": "system_health.memory_mobile/load:news:washingtonpost"
     },
     {
-        "duration": "34.0",
+        "duration": "33.0",
         "name": "system_health.memory_mobile/load:news:wikipedia"
     },
     {
-        "duration": "33.0",
+        "duration": "40.0",
         "name": "system_health.memory_mobile/load:search:baidu"
     },
     {
-        "duration": "40.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:search:ebay"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:search:google"
     },
     {
-        "duration": "32.0",
+        "duration": "31.0",
         "name": "system_health.memory_mobile/load:search:taobao"
     },
     {
-        "duration": "31.0",
+        "duration": "29.0",
         "name": "system_health.memory_mobile/load:search:yahoo"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:search:yandex"
     },
     {
-        "duration": "32.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:social:twitter"
     },
     {
-        "duration": "31.0",
+        "duration": "30.0",
         "name": "system_health.memory_mobile/load:tools:docs"
     },
     {
-        "duration": "34.0",
+        "duration": "33.0",
         "name": "system_health.memory_mobile/load:tools:drive"
     },
     {
-        "duration": "30.0",
+        "duration": "29.0",
         "name": "system_health.memory_mobile/load:tools:dropbox"
     },
     {
-        "duration": "33.0",
+        "duration": "32.0",
         "name": "system_health.memory_mobile/load:tools:stackoverflow"
     },
     {
-        "duration": "42.0",
+        "duration": "41.0",
         "name": "system_health.memory_mobile/load:tools:weather"
     },
     {
@@ -4668,635 +608,71 @@
         "name": "system_health.memory_mobile/long_running:tools:gmail-foreground"
     },
     {
-        "duration": "45.0",
-        "name": "thread_times.key_idle_power_cases/animated-gif.html"
-    },
-    {
-        "duration": "57.0",
-        "name": "thread_times.key_idle_power_cases/blank.html"
-    },
-    {
-        "duration": "46.0",
-        "name": "thread_times.key_idle_power_cases/css-animation.html"
-    },
-    {
-        "duration": "46.0",
-        "name": "thread_times.key_idle_power_cases/request-animation-frame.html"
-    },
-    {
-        "duration": "47.0",
-        "name": "thread_times.key_idle_power_cases/set-timeout.html"
-    },
-    {
-        "duration": "117.0",
-        "name": "thread_times.key_idle_power_cases/set-timeout.html (Long Idle)"
-    },
-    {
-        "duration": "47.0",
-        "name": "thread_times.key_mobile_sites_smooth/boingboing"
-    },
-    {
-        "duration": "45.0",
-        "name": "thread_times.key_mobile_sites_smooth/cuteoverload"
-    },
-    {
-        "duration": "51.0",
-        "name": "thread_times.key_mobile_sites_smooth/nytimes"
-    },
-    {
-        "duration": "34.0",
-        "name": "thread_times.key_mobile_sites_smooth/reddit"
-    },
-    {
-        "duration": "36.0",
-        "name": "thread_times.key_mobile_sites_smooth/slashdot"
-    },
-    {
-        "duration": "42.0",
-        "name": "thread_times.key_noop_cases/no_op_raf.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "thread_times.key_noop_cases/no_op_scroll.html"
-    },
-    {
-        "duration": "20.0",
-        "name": "thread_times.key_noop_cases/no_op_settimeout.html"
-    },
-    {
-        "duration": "26.0",
-        "name": "thread_times.key_noop_cases/no_op_touch_handler.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "thread_times.key_silk_cases/font_wipe.html"
-    },
-    {
-        "duration": "51.0",
-        "name": "thread_times.key_silk_cases/http://groupcloned.com/test/plain/list-recycle-transform.html"
-    },
-    {
-        "duration": "22.0",
-        "name": "thread_times.key_silk_cases/http://groupcloned.com/test/plain/sticky-using-webkit-backface-visibility.html"
-    },
-    {
-        "duration": "21.0",
-        "name": "thread_times.key_silk_cases/http://jsbin.com/UVIgUTa/38/quiet"
-    },
-    {
-        "duration": "28.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/3yDKh/15/show/"
-    },
-    {
-        "duration": "24.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/3yDKh/16/show/"
-    },
-    {
-        "duration": "28.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/R8DX9/4/show/"
-    },
-    {
-        "duration": "28.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/TLXLu/3/show/"
-    },
-    {
-        "duration": "28.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/bNp2h/3/show/"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/cKB9D/7/show/"
-    },
-    {
-        "duration": "31.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/jx5De/14/show/"
-    },
-    {
-        "duration": "31.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/rF9Gh/7/show/"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/ugkd4/10/show/"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/vBQHH/11/show/"
-    },
-    {
-        "duration": "26.0",
-        "name": "thread_times.key_silk_cases/http://jsfiddle.net/xLuvC/1/show/"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.key_silk_cases/http://mobile-news.sandbox.google.com/news/pt0?scroll"
-    },
-    {
-        "duration": "22.0",
-        "name": "thread_times.key_silk_cases/http://mobile-news.sandbox.google.com/news/pt0?swipe"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/http://plus.google.com/app/basic/stream"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/http://s.codepen.io/befamous/fullpage/pFsqb?scroll"
-    },
-    {
-        "duration": "35.0",
-        "name": "thread_times.key_silk_cases/http://wiltzius.github.io/shape-shifter/"
-    },
-    {
-        "duration": "24.0",
-        "name": "thread_times.key_silk_cases/http://www.google.com/#q=google"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/https://polymer-topeka.appspot.com/"
-    },
-    {
-        "duration": "24.0",
-        "name": "thread_times.key_silk_cases/https://www.google.com/search?hl=en&q=define%3Aboogie"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/inbox_app.html?slide_drawer"
-    },
-    {
-        "duration": "46.0",
-        "name": "thread_times.key_silk_cases/inbox_app.html?stress_hidey_bars"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/inbox_app.html?swipe_to_dismiss"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.key_silk_cases/inbox_app.html?toggle_drawer"
-    },
-    {
-        "duration": "34.0",
-        "name": "thread_times.key_silk_cases/infinite_scrolling.html"
-    },
-    {
-        "duration": "23.0",
-        "name": "thread_times.key_silk_cases/list_animation_simple.html"
-    },
-    {
-        "duration": "0.0",
-        "name": "thread_times.key_silk_cases/masonry.html"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.key_silk_cases/pushState.html"
-    },
-    {
-        "duration": "45.0",
-        "name": "thread_times.key_silk_cases/silk_finance.html"
-    },
-    {
-        "duration": "37.0",
-        "name": "thread_times.simple_mobile_sites/http://m.nytimes.com/"
-    },
-    {
-        "duration": "64.0",
-        "name": "thread_times.simple_mobile_sites/http://www.ebay.co.uk/"
-    },
-    {
-        "duration": "42.0",
-        "name": "thread_times.simple_mobile_sites/http://www.nyc.gov"
-    },
-    {
-        "duration": "40.0",
-        "name": "thread_times.simple_mobile_sites/https://www.flickr.com/"
-    },
-    {
-        "duration": "50.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/beqojupo/1/quiet?JS_FULL_SCREEN_INVALIDATION"
-    },
-    {
-        "duration": "40.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/covoqi/1/quiet?NEW_TILINGS"
-    },
-    {
-        "duration": "35.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/falefice/1/quiet?CC_POSTER_CIRCLE"
-    },
-    {
-        "duration": "38.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/giqafofe/1/quiet?JS_POSTER_CIRCLE"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/jevibahi/4/quiet?JS_SCROLL_200_LAYER_GRID"
-    },
-    {
-        "duration": "54.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/pixavefe/1/quiet?CC_SCROLL_TEXT_ONLY"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/wixadinu/2/quiet?JS_SCROLL_TEXT_ONLY"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_compositor_cases/http://jsbin.com/yakagevo/1/quiet?CC_SCROLL_200_LAYER_GRID"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_05000_pixels_per_second"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_10000_pixels_per_second"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_15000_pixels_per_second"
-    },
-    {
-        "duration": "34.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_20000_pixels_per_second"
-    },
-    {
-        "duration": "34.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_30000_pixels_per_second"
-    },
-    {
-        "duration": "34.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_40000_pixels_per_second"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_50000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_60000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_75000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "thread_times.tough_scrolling_cases/canvas_90000_pixels_per_second"
-    },
-    {
-        "duration": "44.0",
-        "name": "thread_times.tough_scrolling_cases/text_05000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_10000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_15000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_20000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_30000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_40000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_50000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "thread_times.tough_scrolling_cases/text_60000_pixels_per_second"
-    },
-    {
-        "duration": "24.0",
-        "name": "thread_times.tough_scrolling_cases/text_75000_pixels_per_second"
-    },
-    {
-        "duration": "22.0",
-        "name": "thread_times.tough_scrolling_cases/text_90000_pixels_per_second"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_05000_pixels_per_second"
-    },
-    {
-        "duration": "33.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_10000_pixels_per_second"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_15000_pixels_per_second"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_20000_pixels_per_second"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_30000_pixels_per_second"
-    },
-    {
-        "duration": "32.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_40000_pixels_per_second"
-    },
-    {
-        "duration": "31.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_50000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_60000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_75000_pixels_per_second"
-    },
-    {
-        "duration": "26.0",
-        "name": "thread_times.tough_scrolling_cases/text_constant_full_page_raster_90000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_05000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_10000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_15000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_20000_pixels_per_second"
-    },
-    {
-        "duration": "29.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_30000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_40000_pixels_per_second"
-    },
-    {
-        "duration": "30.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_50000_pixels_per_second"
-    },
-    {
-        "duration": "27.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_60000_pixels_per_second"
-    },
-    {
-        "duration": "25.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_75000_pixels_per_second"
-    },
-    {
-        "duration": "23.0",
-        "name": "thread_times.tough_scrolling_cases/text_hover_90000_pixels_per_second"
-    },
-    {
-        "duration": "19.0",
-        "name": "tracing.tracing_with_background_memory_infra/Facebook"
-    },
-    {
-        "duration": "22.0",
-        "name": "tracing.tracing_with_background_memory_infra/Wikipedia"
-    },
-    {
-        "duration": "15.0",
-        "name": "tracing.tracing_with_background_memory_infra/http://www.amazon.com"
-    },
-    {
-        "duration": "16.0",
-        "name": "tracing.tracing_with_background_memory_infra/http://www.ask.com/"
-    },
-    {
-        "duration": "16.0",
-        "name": "tracing.tracing_with_background_memory_infra/http://www.bing.com/"
-    },
-    {
-        "duration": "17.0",
-        "name": "tracing.tracing_with_background_memory_infra/http://www.yahoo.com/"
-    },
-    {
-        "duration": "18.0",
-        "name": "tracing.tracing_with_background_memory_infra/http://www.youtube.com"
-    },
-    {
-        "duration": "33.0",
-        "name": "tracing.tracing_with_background_memory_infra/https://www.google.com/#hl=en&q=barack+obama"
-    },
-    {
-        "duration": "20.0",
-        "name": "tracing.tracing_with_background_memory_infra/https://www.google.com/calendar/"
-    },
-    {
-        "duration": "207.0",
-        "name": "v8.browsing_mobile-future/browse:chrome:newtab"
-    },
-    {
-        "duration": "59.0",
-        "name": "v8.browsing_mobile-future/browse:chrome:omnibox"
-    },
-    {
-        "duration": "158.0",
-        "name": "v8.browsing_mobile-future/browse:media:facebook_photos"
-    },
-    {
-        "duration": "85.0",
-        "name": "v8.browsing_mobile-future/browse:media:flickr_infinite_scroll"
-    },
-    {
-        "duration": "133.0",
-        "name": "v8.browsing_mobile-future/browse:media:imgur"
-    },
-    {
-        "duration": "205.0",
-        "name": "v8.browsing_mobile-future/browse:media:youtube"
-    },
-    {
-        "duration": "381.0",
-        "name": "v8.browsing_mobile-future/browse:news:cnn"
-    },
-    {
-        "duration": "109.0",
-        "name": "v8.browsing_mobile-future/browse:news:cricbuzz"
-    },
-    {
-        "duration": "0.0",
-        "name": "v8.browsing_mobile-future/browse:news:globo"
-    },
-    {
-        "duration": "93.0",
-        "name": "v8.browsing_mobile-future/browse:news:qq"
-    },
-    {
-        "duration": "111.0",
-        "name": "v8.browsing_mobile-future/browse:news:reddit"
-    },
-    {
-        "duration": "0.0",
-        "name": "v8.browsing_mobile-future/browse:news:toi"
-    },
-    {
-        "duration": "93.0",
-        "name": "v8.browsing_mobile-future/browse:news:washingtonpost"
-    },
-    {
-        "duration": "180.0",
-        "name": "v8.browsing_mobile-future/browse:shopping:amazon"
-    },
-    {
-        "duration": "175.0",
-        "name": "v8.browsing_mobile-future/browse:shopping:avito"
-    },
-    {
-        "duration": "0.0",
-        "name": "v8.browsing_mobile-future/browse:shopping:flipkart"
-    },
-    {
-        "duration": "74.0",
-        "name": "v8.browsing_mobile-future/browse:shopping:lazada"
-    },
-    {
-        "duration": "138.0",
-        "name": "v8.browsing_mobile-future/browse:social:facebook"
-    },
-    {
-        "duration": "203.0",
-        "name": "v8.browsing_mobile-future/browse:social:facebook_infinite_scroll"
-    },
-    {
-        "duration": "184.0",
-        "name": "v8.browsing_mobile-future/browse:social:instagram"
-    },
-    {
-        "duration": "176.0",
-        "name": "v8.browsing_mobile-future/browse:social:pinterest_infinite_scroll"
-    },
-    {
-        "duration": "207.0",
-        "name": "v8.browsing_mobile-future/browse:social:tumblr_infinite_scroll"
-    },
-    {
-        "duration": "88.0",
-        "name": "v8.browsing_mobile-future/browse:social:twitter"
-    },
-    {
-        "duration": "136.0",
-        "name": "v8.browsing_mobile-future/browse:tech:discourse_infinite_scroll"
-    },
-    {
-        "duration": "89.0",
-        "name": "v8.browsing_mobile-future/browse:tools:maps"
-    },
-    {
-        "duration": "208.0",
+        "duration": "141.0",
         "name": "v8.browsing_mobile/browse:chrome:newtab"
     },
     {
-        "duration": "57.0",
+        "duration": "50.0",
         "name": "v8.browsing_mobile/browse:chrome:omnibox"
     },
     {
-        "duration": "155.0",
+        "duration": "146.0",
         "name": "v8.browsing_mobile/browse:media:facebook_photos"
     },
     {
-        "duration": "85.0",
+        "duration": "81.0",
         "name": "v8.browsing_mobile/browse:media:flickr_infinite_scroll"
     },
     {
-        "duration": "134.0",
+        "duration": "125.0",
         "name": "v8.browsing_mobile/browse:media:imgur"
     },
     {
-        "duration": "206.0",
-        "name": "v8.browsing_mobile/browse:media:youtube"
-    },
-    {
-        "duration": "375.0",
-        "name": "v8.browsing_mobile/browse:news:cnn"
-    },
-    {
-        "duration": "108.0",
+        "duration": "103.0",
         "name": "v8.browsing_mobile/browse:news:cricbuzz"
     },
     {
-        "duration": "0.0",
-        "name": "v8.browsing_mobile/browse:news:globo"
-    },
-    {
-        "duration": "93.0",
+        "duration": "84.0",
         "name": "v8.browsing_mobile/browse:news:qq"
     },
     {
-        "duration": "109.0",
+        "duration": "93.0",
         "name": "v8.browsing_mobile/browse:news:reddit"
     },
     {
-        "duration": "0.0",
+        "duration": "423.0",
         "name": "v8.browsing_mobile/browse:news:toi"
     },
     {
-        "duration": "94.0",
+        "duration": "88.0",
         "name": "v8.browsing_mobile/browse:news:washingtonpost"
     },
     {
-        "duration": "175.0",
+        "duration": "167.0",
         "name": "v8.browsing_mobile/browse:shopping:amazon"
     },
     {
-        "duration": "171.0",
-        "name": "v8.browsing_mobile/browse:shopping:avito"
-    },
-    {
-        "duration": "0.0",
-        "name": "v8.browsing_mobile/browse:shopping:flipkart"
-    },
-    {
-        "duration": "73.0",
-        "name": "v8.browsing_mobile/browse:shopping:lazada"
-    },
-    {
-        "duration": "135.0",
+        "duration": "121.0",
         "name": "v8.browsing_mobile/browse:social:facebook"
     },
     {
-        "duration": "206.0",
-        "name": "v8.browsing_mobile/browse:social:facebook_infinite_scroll"
-    },
-    {
-        "duration": "184.0",
+        "duration": "146.0",
         "name": "v8.browsing_mobile/browse:social:instagram"
     },
     {
-        "duration": "173.0",
-        "name": "v8.browsing_mobile/browse:social:pinterest_infinite_scroll"
-    },
-    {
-        "duration": "213.0",
-        "name": "v8.browsing_mobile/browse:social:tumblr_infinite_scroll"
-    },
-    {
-        "duration": "89.0",
+        "duration": "82.0",
         "name": "v8.browsing_mobile/browse:social:twitter"
     },
     {
-        "duration": "134.0",
-        "name": "v8.browsing_mobile/browse:tech:discourse_infinite_scroll"
+        "duration": "83.0",
+        "name": "v8.browsing_mobile/browse:tools:maps"
     },
     {
-        "duration": "89.0",
-        "name": "v8.browsing_mobile/browse:tools:maps"
+        "duration": "133.0",
+        "name": "speedometer/http://browserbench.org/Speedometer/"
+    },
+    {
+        "duration": "250.0",
+        "name": "speedometer2/Speedometer2"
     }
-]
\ No newline at end of file
+]
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 91894b2..fc36b93 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -212,10 +212,7 @@
 
 # Benchmark: system_health.common_desktop
 crbug.com/828917 [ Mac ] system_health.common_desktop/multitab:misc:typical24 [ Skip ]
-crbug.com/649392 [ Win ] system_health.common_desktop/play:media:soundcloud [ Skip ]
-crbug.com/649392 [ All ] system_health.common_desktop/play:media:google_play_music [ Skip ]
 crbug.com/773084 [ Mac ] system_health.common_desktop/browse:tools:maps [ Skip ]
-crbug.com/673775 [ Win ] system_health.common_desktop/browse:search:google [ Skip ]
 crbug.com/839411 [ Win ] system_health.common_desktop/browse:social:twitter_infinite_scroll [ Skip ]
 crbug.com/846022 [ Linux ] system_health.common_desktop/browse:social:twitter_infinite_scroll [ Skip ]
 
diff --git a/tools/perf/page_sets/data/desktop_power_stories.json b/tools/perf/page_sets/data/desktop_power_stories.json
index ef2df58..2e4f935 100644
--- a/tools/perf/page_sets/data/desktop_power_stories.json
+++ b/tools/perf/page_sets/data/desktop_power_stories.json
@@ -1,51 +1,27 @@
 {
     "archives": {
-        "http://abcnews.go.com/": {
+        "abcnews": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://bbc.com/news/uk/": {
+        "indiatimes": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://bgr.com": {
+        "microsoft": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://capitalone.com": {
+        "sina": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://tumblr.all-that-is-interesting.com/": {
+        "slideshare": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://www.androidpolice.com/2014/10/20/animation-bonanza-android-5-0-lollipop-in-gifs/": {
+        "uol": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         },
-        "http://www.indiatimes.com": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.labradortraininghq.com/labrador-training/how-to-crate-train-a-puppy/#How_Long_DoesIt_Take_To_Crate_Train_A_Puppy": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.microsoft.com": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.sina.com.cn": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.slideshare.net/patrickmeenan": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.uol.com.br": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "http://www.w3schools.com/html/default.asp": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "https://instagram.com/cnn/": {
-            "DEFAULT": "idle_after_loading_stories_002.wprgo"
-        },
-        "https://twitter.com/katyperry": {
+        "instagram": {
             "DEFAULT": "idle_after_loading_stories_002.wprgo"
         }
     },
     "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
     "platform_specific": true
-}
\ No newline at end of file
+}
diff --git a/tools/perf/page_sets/data/dromaeo.cssqueryjquery.json b/tools/perf/page_sets/data/dromaeo.cssqueryjquery.json
deleted file mode 100644
index 1e8c7b2..0000000
--- a/tools/perf/page_sets/data/dromaeo.cssqueryjquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?cssquery-jquery": {
-            "DEFAULT": "dromaeo.cssqueryjquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.domcoreattr.json b/tools/perf/page_sets/data/dromaeo.domcoreattr.json
deleted file mode 100644
index 83a021b2..0000000
--- a/tools/perf/page_sets/data/dromaeo.domcoreattr.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?dom-attr": {
-            "DEFAULT": "dromaeo.domcoreattr_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.domcoremodify.json b/tools/perf/page_sets/data/dromaeo.domcoremodify.json
deleted file mode 100644
index 0241f81..0000000
--- a/tools/perf/page_sets/data/dromaeo.domcoremodify.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?dom-modify": {
-            "DEFAULT": "dromaeo.domcoremodify_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.domcorequery.json b/tools/perf/page_sets/data/dromaeo.domcorequery.json
deleted file mode 100644
index 04d2b974..0000000
--- a/tools/perf/page_sets/data/dromaeo.domcorequery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?dom-query": {
-            "DEFAULT": "dromaeo.domcorequery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.domcoretraverse.json b/tools/perf/page_sets/data/dromaeo.domcoretraverse.json
deleted file mode 100644
index 616bb8b..0000000
--- a/tools/perf/page_sets/data/dromaeo.domcoretraverse.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?dom-traverse": {
-            "DEFAULT": "dromaeo.domcoretraverse_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibattrjquery.json b/tools/perf/page_sets/data/dromaeo.jslibattrjquery.json
deleted file mode 100644
index cc7be62..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibattrjquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-attr-jquery": {
-            "DEFAULT": "dromaeo.jslibattrjquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibattrprototype.json b/tools/perf/page_sets/data/dromaeo.jslibattrprototype.json
deleted file mode 100644
index b4e0a512..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibattrprototype.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-attr-prototype": {
-            "DEFAULT": "dromaeo.jslibattrprototype_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibeventjquery.json b/tools/perf/page_sets/data/dromaeo.jslibeventjquery.json
deleted file mode 100644
index fd5f0a0..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibeventjquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-event-jquery": {
-            "DEFAULT": "dromaeo.jslibeventjquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibeventprototype.json b/tools/perf/page_sets/data/dromaeo.jslibeventprototype.json
deleted file mode 100644
index 56657e91..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibeventprototype.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-event-prototype": {
-            "DEFAULT": "dromaeo.jslibeventprototype_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibmodifyjquery.json b/tools/perf/page_sets/data/dromaeo.jslibmodifyjquery.json
deleted file mode 100644
index 2a209b8..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibmodifyjquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-modify-jquery": {
-            "DEFAULT": "dromaeo.jslibmodifyjquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibmodifyprototype.json b/tools/perf/page_sets/data/dromaeo.jslibmodifyprototype.json
deleted file mode 100644
index 12e2503..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibmodifyprototype.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-modify-prototype": {
-            "DEFAULT": "dromaeo.jslibmodifyprototype_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibstylejquery.json b/tools/perf/page_sets/data/dromaeo.jslibstylejquery.json
deleted file mode 100644
index 12e3ea3..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibstylejquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-style-jquery": {
-            "DEFAULT": "dromaeo.jslibstylejquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibstyleprototype.json b/tools/perf/page_sets/data/dromaeo.jslibstyleprototype.json
deleted file mode 100644
index 5a7aa28..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibstyleprototype.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-style-prototype": {
-            "DEFAULT": "dromaeo.jslibstyleprototype_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibtraversejquery.json b/tools/perf/page_sets/data/dromaeo.jslibtraversejquery.json
deleted file mode 100644
index df0dfbb6..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibtraversejquery.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-traverse-jquery": {
-            "DEFAULT": "dromaeo.jslibtraversejquery_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/dromaeo.jslibtraverseprototype.json b/tools/perf/page_sets/data/dromaeo.jslibtraverseprototype.json
deleted file mode 100644
index 1d830cdb..0000000
--- a/tools/perf/page_sets/data/dromaeo.jslibtraverseprototype.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-    "archives": {
-        "http://dromaeo.com?jslib-traverse-prototype": {
-            "DEFAULT": "dromaeo.jslibtraverseprototype_000.wprgo"
-        }
-    },
-    "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
-    "platform_specific": true
-}
\ No newline at end of file
diff --git a/tools/perf/page_sets/data/top_10.json b/tools/perf/page_sets/data/top_10.json
index b1e9edb0..cf7fdf38 100644
--- a/tools/perf/page_sets/data/top_10.json
+++ b/tools/perf/page_sets/data/top_10.json
@@ -3,7 +3,7 @@
         "Facebook": {
             "DEFAULT": "top_10_001.wprgo"
         },
-        "http://en.wikipedia.org/wiki/Wikipedia": {
+        "Wikipedia": {
             "DEFAULT": "top_10_000.wprgo"
         },
         "http://www.amazon.com": {
@@ -36,4 +36,4 @@
     },
     "description": "Describes the Web Page Replay archives for a story set. Don't edit by hand! Use record_wpr for updating.",
     "platform_specific": true
-}
\ No newline at end of file
+}
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 04dee61..f5a06d7 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -273,4 +273,6 @@
  <item id="webstore_installer" hash_code="18764319" type="0" content_hash_code="11030110" os_list="linux,windows" file_path="chrome/browser/extensions/webstore_installer.cc"/>
  <item id="webui_content_scripts_download" hash_code="100545943" type="0" content_hash_code="119898059" os_list="linux,windows" file_path="extensions/browser/guest_view/web_view/web_ui/web_ui_url_fetcher.cc"/>
  <item id="xmpp_signal_strategy" hash_code="88906454" type="0" content_hash_code="88958321" os_list="linux,windows" file_path="remoting/signaling/xmpp_signal_strategy.cc"/>
+ <item id="chrome_cleanup_report" hash_code="71102679" type="0" content_hash_code="26537237" os_list="windows" file_path="chrome/chrome_cleaner/logging/cleaner_logging_service.cc"/>
+ <item id="unwanted_software_report" hash_code="43759504" type="0" content_hash_code="111455033" os_list="windows" file_path="chrome/chrome_cleaner/logging/reporter_logging_service.cc"/>
 </annotations>
diff --git a/tools/v8_context_snapshot/v8_context_snapshot_generator.cc b/tools/v8_context_snapshot/v8_context_snapshot_generator.cc
index cd74429..3f343181 100644
--- a/tools/v8_context_snapshot/v8_context_snapshot_generator.cc
+++ b/tools/v8_context_snapshot/v8_context_snapshot_generator.cc
@@ -19,6 +19,8 @@
 
 namespace {
 
+constexpr char kPredictableFlag[] = "--predictable";
+
 class SnapshotThread : public blink::WebThread {
  public:
   bool IsCurrentThread() const override { return true; }
@@ -57,6 +59,9 @@
   base::TaskScheduler::CreateAndStartWithDefaultParams("TakeSnapshot");
   mojo::core::Init();
 
+  // Set predictable flag in V8 to generate identical snapshot file.
+  v8::V8::SetFlagsFromString(kPredictableFlag, sizeof(kPredictableFlag) - 1);
+
   // Take a snapshot.
   SnapshotPlatform platform;
   service_manager::BinderRegistry empty_registry;
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index 96383eb1..32dba84 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -419,6 +419,8 @@
       return "inlineTextBox";
     case ax::mojom::Role::kInputTime:
       return "inputTime";
+    case ax::mojom::Role::kKeyboard:
+      return "keyboard";
     case ax::mojom::Role::kLabelText:
       return "labelText";
     case ax::mojom::Role::kLayoutTable:
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index 8cc2cfc..0e4cf91 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -197,6 +197,7 @@
   kImageMap,
   kInlineTextBox,
   kInputTime,
+  kKeyboard,
   kLabelText,
   kLayoutTable,
   kLayoutTableCell,
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index ae3dcdd..ce8dbda 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -1304,6 +1304,7 @@
       return ATK_ROLE_PANEL;
     case ax::mojom::Role::kFooter:
       return ATK_ROLE_FOOTER;
+    case ax::mojom::Role::kKeyboard:
     case ax::mojom::Role::kNone:
     case ax::mojom::Role::kPresentational:
     case ax::mojom::Role::kUnknown:
@@ -1591,13 +1592,10 @@
   ATK_AURALINUX_RETURN_STRING(base::UTF16ToUTF8(action_verb));
 }
 
-AtkAttributeSet* AXPlatformNodeAuraLinux::GetAtkAttributes() const {
-  AtkAttributeSet* atk_attributes = nullptr;
-
-  atk_attributes = AddIntAttributeToAtkAttributeSet(
-      atk_attributes, ax::mojom::IntAttribute::kHierarchicalLevel, "level");
-
-  return atk_attributes;
+AtkAttributeSet* AXPlatformNodeAuraLinux::GetAtkAttributes() {
+  AtkAttributeSet* attribute_list = nullptr;
+  ComputeAttributes(&attribute_list);
+  return attribute_list;
 }
 
 // AtkDocumentHelpers
@@ -1617,9 +1615,9 @@
 }
 
 static AtkAttributeSet* PrependAtkAttributeToAtkAttributeSet(
-    AtkAttributeSet* attribute_set,
     const char* name,
-    const char* value) {
+    const char* value,
+    AtkAttributeSet* attribute_set) {
   AtkAttribute* attribute =
       static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
   attribute->name = g_strdup(name);
@@ -1636,7 +1634,7 @@
     value = GetDocumentAttributeValue(doc_attributes[i]);
     if (value) {
       attribute_set = PrependAtkAttributeToAtkAttributeSet(
-          attribute_set, doc_attributes[i], value);
+          doc_attributes[i], value, attribute_set);
     }
   }
 
@@ -1676,24 +1674,10 @@
   }
 }
 
-AtkAttributeSet* AXPlatformNodeAuraLinux::AddIntAttributeToAtkAttributeSet(
-    AtkAttributeSet* attributes,
-    ax::mojom::IntAttribute attribute,
-    const char* atk_attribute) const {
-  int value;
-  if (GetIntAttribute(attribute, &value)) {
-    attributes = PrependAtkAttributeToAtkAttributeSet(
-        attributes, atk_attribute, base::IntToString(value).c_str());
-  }
-
-  return attributes;
-}
-
-void AXPlatformNodeAuraLinux::AddAttributeToList(
-    const char* name,
-    const char* value,
-    PlatformAttributeList* attributes) {
-  NOTREACHED();
+void AXPlatformNodeAuraLinux::AddAttributeToList(const char* name,
+                                                 const char* value,
+                                                 AtkAttributeSet** attributes) {
+  *attributes = PrependAtkAttributeToAtkAttributeSet(name, value, *attributes);
 }
 
 }  // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.h b/ui/accessibility/platform/ax_platform_node_auralinux.h
index 0c6f9b50..f7be935 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -59,7 +59,7 @@
   bool GrabFocus();
   bool DoDefaultAction();
   const gchar* GetDefaultActionName();
-  AtkAttributeSet* GetAtkAttributes() const;
+  AtkAttributeSet* GetAtkAttributes();
 
   void SetExtentsRelativeToAtkCoordinateType(
       gint* x, gint* y, gint* width, gint* height,
@@ -74,10 +74,6 @@
 
   // Misc helpers
   void GetFloatAttributeInGValue(ax::mojom::FloatAttribute attr, GValue* value);
-  AtkAttributeSet* AddIntAttributeToAtkAttributeSet(
-      AtkAttributeSet* attributes,
-      ax::mojom::IntAttribute attribute,
-      const char* atk_attribute) const;
 
   // Event helpers
   void OnFocused();
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index 3a94cf3..7dde4be7 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -69,10 +69,93 @@
   while (current) {
     AtkAttribute* attribute = static_cast<AtkAttribute*>(current->data);
     ASSERT_NE(0, strcmp(attribute_name, attribute->name));
+    current = current->next;
   }
   atk_attribute_set_free(attributes);
 }
 
+static void TestAtkObjectIntAttribute(
+    AXNode* ax_node,
+    AtkObject* atk_object,
+    ax::mojom::IntAttribute mojom_attribute,
+    const gchar* attribute_name,
+    base::Optional<ax::mojom::Role> role = base::nullopt) {
+  AXNodeData new_data = AXNodeData();
+  new_data.role = role.value_or(ax::mojom::Role::kApplication);
+  ax_node->SetData(new_data);
+  EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name);
+
+  std::pair<int, const char*> tests[] = {
+      std::make_pair(0, "0"),       std::make_pair(1, "1"),
+      std::make_pair(2, "2"),       std::make_pair(-100, "-100"),
+      std::make_pair(1000, "1000"),
+  };
+
+  for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) {
+    AXNodeData new_data = AXNodeData();
+    new_data.role = role.value_or(ax::mojom::Role::kApplication);
+    new_data.id = ax_node->data().id;
+    new_data.AddIntAttribute(mojom_attribute, tests[i].first);
+    ax_node->SetData(new_data);
+    EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name,
+                                         tests[i].second);
+  }
+}
+
+static void TestAtkObjectStringAttribute(
+    AXNode* ax_node,
+    AtkObject* atk_object,
+    ax::mojom::StringAttribute mojom_attribute,
+    const gchar* attribute_name,
+    base::Optional<ax::mojom::Role> role = base::nullopt) {
+  AXNodeData new_data = AXNodeData();
+  new_data.role = role.value_or(ax::mojom::Role::kApplication);
+  ax_node->SetData(new_data);
+  EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name);
+
+  const char* tests[] = {
+      "",
+      "a string with spaces"
+      "a string with , a comma",
+      "\xE2\x98\xBA",  // The smiley emoji.
+  };
+
+  for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) {
+    AXNodeData new_data = AXNodeData();
+    new_data.role = role.value_or(ax::mojom::Role::kApplication);
+    new_data.id = ax_node->data().id;
+    new_data.AddStringAttribute(mojom_attribute, tests[i]);
+    ax_node->SetData(new_data);
+    EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, tests[i]);
+  }
+}
+
+static void TestAtkObjectBoolAttribute(
+    AXNode* ax_node,
+    AtkObject* atk_object,
+    ax::mojom::BoolAttribute mojom_attribute,
+    const gchar* attribute_name,
+    base::Optional<ax::mojom::Role> role = base::nullopt) {
+  AXNodeData new_data = AXNodeData();
+  new_data.role = role.value_or(ax::mojom::Role::kApplication);
+  ax_node->SetData(new_data);
+  EnsureAtkObjectDoesNotHaveAttribute(atk_object, attribute_name);
+
+  new_data = AXNodeData();
+  new_data.role = role.value_or(ax::mojom::Role::kApplication);
+  new_data.id = ax_node->data().id;
+  new_data.AddBoolAttribute(mojom_attribute, true);
+  ax_node->SetData(new_data);
+  EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, "true");
+
+  new_data = AXNodeData();
+  new_data.role = role.value_or(ax::mojom::Role::kApplication);
+  new_data.id = ax_node->data().id;
+  new_data.AddBoolAttribute(mojom_attribute, false);
+  ax_node->SetData(new_data);
+  EnsureAtkObjectHasAttributeWithValue(atk_object, attribute_name, "false");
+}
+
 //
 // AtkObject tests
 //
@@ -403,7 +486,7 @@
   g_object_unref(root_obj);
 }
 
-TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectAttributes) {
+TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectStringAttributes) {
   AXNodeData root_data;
   root_data.id = 1;
 
@@ -413,25 +496,104 @@
   AtkObject* root_atk_object(AtkObjectFromNode(root_node));
   ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object));
   g_object_ref(root_atk_object);
-  EnsureAtkObjectDoesNotHaveAttribute(root_atk_object, "level");
 
-  root_data = AXNodeData();
-  root_data.id = 1;
-  root_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 1);
-  root_node->SetData(root_data);
-  EnsureAtkObjectHasAttributeWithValue(root_atk_object, "level", "1");
+  std::pair<ax::mojom::StringAttribute, const char*> tests[] = {
+      std::make_pair(ax::mojom::StringAttribute::kDisplay, "display"),
+      std::make_pair(ax::mojom::StringAttribute::kHtmlTag, "tag"),
+      std::make_pair(ax::mojom::StringAttribute::kRole, "xml-roles"),
+      std::make_pair(ax::mojom::StringAttribute::kPlaceholder, "placeholder"),
+      std::make_pair(ax::mojom::StringAttribute::kRoleDescription,
+                     "roledescription"),
+      std::make_pair(ax::mojom::StringAttribute::kKeyShortcuts, "keyshortcuts"),
+      std::make_pair(ax::mojom::StringAttribute::kLiveStatus, "live"),
+      std::make_pair(ax::mojom::StringAttribute::kLiveRelevant, "relevant"),
+      std::make_pair(ax::mojom::StringAttribute::kContainerLiveStatus,
+                     "container-live"),
+      std::make_pair(ax::mojom::StringAttribute::kContainerLiveRelevant,
+                     "container-relevant"),
+  };
 
-  root_data = AXNodeData();
-  root_data.id = 1;
-  root_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 2);
-  root_node->SetData(root_data);
-  EnsureAtkObjectHasAttributeWithValue(root_atk_object, "level", "2");
+  for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) {
+    TestAtkObjectStringAttribute(root_node, root_atk_object, tests[i].first,
+                                 tests[i].second);
+  }
 
-  root_data = AXNodeData();
+  g_object_unref(root_atk_object);
+}
+
+TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectBoolAttributes) {
+  AXNodeData root_data;
   root_data.id = 1;
-  root_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 34);
-  root_node->SetData(root_data);
-  EnsureAtkObjectHasAttributeWithValue(root_atk_object, "level", "34");
+
+  Init(root_data);
+
+  AXNode* root_node = GetRootNode();
+  AtkObject* root_atk_object(AtkObjectFromNode(root_node));
+  ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object));
+  g_object_ref(root_atk_object);
+
+  std::pair<ax::mojom::BoolAttribute, const char*> tests[] = {
+      std::make_pair(ax::mojom::BoolAttribute::kLiveAtomic, "atomic"),
+      std::make_pair(ax::mojom::BoolAttribute::kBusy, "busy"),
+      std::make_pair(ax::mojom::BoolAttribute::kContainerLiveAtomic,
+                     "container-atomic"),
+      std::make_pair(ax::mojom::BoolAttribute::kContainerLiveBusy,
+                     "container-busy"),
+  };
+
+  for (unsigned i = 0; i < G_N_ELEMENTS(tests); i++) {
+    TestAtkObjectBoolAttribute(root_node, root_atk_object, tests[i].first,
+                               tests[i].second);
+  }
+
+  g_object_unref(root_atk_object);
+}
+
+TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectIntAttributes) {
+  AXNodeData root_data;
+  root_data.id = 1;
+
+  Init(root_data);
+
+  AXNode* root_node = GetRootNode();
+  AtkObject* root_atk_object(AtkObjectFromNode(root_node));
+  ASSERT_TRUE(ATK_IS_OBJECT(root_atk_object));
+  g_object_ref(root_atk_object);
+
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kHierarchicalLevel,
+                            "level");
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kSetSize, "setsize");
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kPosInSet, "posinset");
+
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaColumnCount,
+                            "colcount", ax::mojom::Role::kTable);
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaColumnCount,
+                            "colcount", ax::mojom::Role::kGrid);
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaColumnCount,
+                            "colcount", ax::mojom::Role::kTreeGrid);
+
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaRowCount, "rowcount",
+                            ax::mojom::Role::kTable);
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaRowCount, "rowcount",
+                            ax::mojom::Role::kGrid);
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaRowCount, "rowcount",
+                            ax::mojom::Role::kTreeGrid);
+
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaCellColumnIndex,
+                            "colindex", ax::mojom::Role::kCell);
+  TestAtkObjectIntAttribute(root_node, root_atk_object,
+                            ax::mojom::IntAttribute::kAriaCellRowIndex,
+                            "rowindex", ax::mojom::Role::kCell);
 
   g_object_unref(root_atk_object);
 }
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index e9c1867f..122c525 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -11,9 +11,14 @@
 #include "base/macros.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/base/ui_features.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
+#if BUILDFLAG(USE_ATK)
+#include <atk/atk.h>
+#endif
+
 namespace ui {
 
 struct AXNodeData;
@@ -194,10 +199,15 @@
   // Sets the text selection in this object if possible.
   bool SetTextSelection(int start_offset, int end_offset);
 
+#if BUILDFLAG(USE_ATK)
+  using PlatformAttributeList = AtkAttributeSet*;
+#else
+  using PlatformAttributeList = std::vector<base::string16>;
+#endif
+
   // Compute the attributes exposed via platform accessibility objects and put
   // them into an attribute list, |attributes|. Currently only used by
-  // IAccessible2 on Windows, but will soon be shared with other ports.
-  using PlatformAttributeList = std::vector<base::string16>;
+  // IAccessible2 on Windows and ATK on Aura Linux.
   void ComputeAttributes(PlatformAttributeList* attributes);
 
   // If the string attribute |attribute| is present, add its value as an
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index f7dec6a..ed5b650 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -3921,6 +3921,7 @@
       return ROLE_SYSTEM_PANE;
 
     case ax::mojom::Role::kIgnored:
+    case ax::mojom::Role::kKeyboard:
     case ax::mojom::Role::kNone:
     case ax::mojom::Role::kPresentational:
     case ax::mojom::Role::kUnknown:
@@ -4474,6 +4475,9 @@
     case ax::mojom::Role::kInlineTextBox:
       return L"textbox";
 
+    case ax::mojom::Role::kKeyboard:
+      return L"group";
+
     case ax::mojom::Role::kLabelText:
     case ax::mojom::Role::kLegend:
       return L"description";
@@ -5323,6 +5327,7 @@
     case ax::mojom::Role::kPane:
     case ax::mojom::Role::kWindow:
     case ax::mojom::Role::kIgnored:
+    case ax::mojom::Role::kKeyboard:
     case ax::mojom::Role::kNone:
     case ax::mojom::Role::kPresentational:
     case ax::mojom::Role::kUnknown:
diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc
index 40df096..0bea6fc 100644
--- a/ui/android/delegated_frame_host_android.cc
+++ b/ui/android/delegated_frame_host_android.cc
@@ -58,7 +58,8 @@
       client_(client),
       begin_frame_source_(this),
       enable_surface_synchronization_(enable_surface_synchronization),
-      enable_viz_(features::IsVizDisplayCompositorEnabled()),
+      enable_viz_(
+          base::FeatureList::IsEnabled(features::kVizDisplayCompositor)),
       frame_evictor_(std::make_unique<viz::FrameEvictor>(this)) {
   DCHECK(view_);
   DCHECK(client_);
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 88a9ed0..ac310da 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -55,6 +55,7 @@
     "ENABLE_HIDPI=$enable_hidpi",
     "ENABLE_MESSAGE_CENTER=$enable_message_center",
     "ENABLE_MUS=$enable_mus",
+    "USE_ATK=$use_atk",
     "USE_XKBCOMMON=$use_xkbcommon",
     "MAC_VIEWS_BROWSER=$mac_views_browser",
     "HAS_NATIVE_ACCESSIBILITY=$has_native_accessibility",
@@ -860,6 +861,7 @@
       "accelerators/accelerator_manager_unittest.cc",
       "accelerators/accelerator_unittest.cc",
       "accelerators/menu_label_accelerator_util_linux_unittest.cc",
+      "accelerators/platform_accelerator_cocoa_unittest.mm",
       "clipboard/custom_data_helper_unittest.cc",
       "cocoa/base_view_unittest.mm",
       "cocoa/bubble_closer_unittest.mm",
diff --git a/ui/base/accelerators/platform_accelerator_cocoa.mm b/ui/base/accelerators/platform_accelerator_cocoa.mm
index 9786168..66b12301 100644
--- a/ui/base/accelerators/platform_accelerator_cocoa.mm
+++ b/ui/base/accelerators/platform_accelerator_cocoa.mm
@@ -24,10 +24,18 @@
     cocoa_modifiers |= NSEventModifierFlagOption;
   if (accelerator.IsCmdDown())
     cocoa_modifiers |= NSEventModifierFlagCommand;
+
   unichar shifted_character;
+  unichar character;
   int result = ui::MacKeyCodeForWindowsKeyCode(
-      accelerator.key_code(), cocoa_modifiers, &shifted_character, nullptr);
-  DCHECK(result != -1);
+      accelerator.key_code(), cocoa_modifiers, &shifted_character, &character);
+  DCHECK_NE(result, -1);
+
+  // If the key equivalent is itself shifted, then drop Shift from the modifier
+  // flags, otherwise Shift will be required. E.g., curly braces and plus are
+  // both inherently shifted, so the key equivalents shouldn't require Shift.
+  if (shifted_character != character)
+    cocoa_modifiers &= ~NSEventModifierFlagShift;
   *key_equivalent = [NSString stringWithFormat:@"%C", shifted_character];
   *modifier_mask = cocoa_modifiers;
 }
diff --git a/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm b/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm
new file mode 100644
index 0000000..4b0f1b5
--- /dev/null
+++ b/ui/base/accelerators/platform_accelerator_cocoa_unittest.mm
@@ -0,0 +1,48 @@
+// Copyright 2018 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 "ui/base/accelerators/platform_accelerator_cocoa.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+#include "ui/base/accelerators/accelerator.h"
+
+TEST(PlatformAcceleratorCocoaTest,
+     GetKeyEquivalentAndModifierMaskFromAccelerator) {
+  static const struct {
+    ui::Accelerator accelerator;
+    NSString* expected_key_equivalent;
+    NSUInteger expected_modifier_mask;
+  } kTestCases[] = {
+      {{ui::VKEY_OEM_PLUS, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN},
+       @"+",
+       NSEventModifierFlagCommand},
+      {{ui::VKEY_T, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN},
+       @"T",
+       NSEventModifierFlagCommand},
+      {{ui::VKEY_BACK, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN},
+       // \x08 is kBackspaceCharCode in Carbon, which NSMenuItem will translate
+       // into U+232B.
+       @"\x08",
+       NSEventModifierFlagCommand | NSEventModifierFlagShift},
+      {{ui::VKEY_F, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN},
+       @"f",
+       NSEventModifierFlagCommand | NSEventModifierFlagControl},
+  };
+
+  for (const auto& test : kTestCases) {
+    SCOPED_TRACE(base::StringPrintf("key_code='%c', modifiers=0x%x",
+                                    test.accelerator.key_code(),
+                                    test.accelerator.modifiers()));
+    NSString* actual_key_equivalent;
+    NSUInteger actual_modifier_mask;
+    ui::GetKeyEquivalentAndModifierMaskFromAccelerator(
+        test.accelerator, &actual_key_equivalent, &actual_modifier_mask);
+    EXPECT_NSEQ(test.expected_key_equivalent, actual_key_equivalent);
+    EXPECT_EQ(test.expected_modifier_mask, actual_modifier_mask);
+  }
+}
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index 72a6781..33698ee 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -191,10 +191,10 @@
                             # uninteresting.
 
     sources += [
-      "input_method_win.cc",
-      "input_method_win.h",
       "input_method_win_base.cc",
       "input_method_win_base.h",
+      "input_method_win_imm32.cc",
+      "input_method_win_imm32.h",
       "input_method_win_tsf.cc",
       "input_method_win_tsf.h",
     ]
diff --git a/ui/base/ime/input_method_factory.cc b/ui/base/ime/input_method_factory.cc
index d93dd27..45546b3 100644
--- a/ui/base/ime/input_method_factory.cc
+++ b/ui/base/ime/input_method_factory.cc
@@ -14,7 +14,7 @@
 #if defined(OS_CHROMEOS)
 #include "ui/base/ime/input_method_chromeos.h"
 #elif defined(OS_WIN)
-#include "ui/base/ime/input_method_win.h"
+#include "ui/base/ime/input_method_win_imm32.h"
 #include "ui/base/ime/input_method_win_tsf.h"
 #elif defined(OS_MACOSX)
 #include "ui/base/ime/input_method_mac.h"
@@ -59,7 +59,7 @@
 #elif defined(OS_WIN)
   if (base::FeatureList::IsEnabled(features::kTSFImeSupport))
     return std::make_unique<InputMethodWinTSF>(delegate, widget);
-  return std::make_unique<InputMethodWin>(delegate, widget);
+  return std::make_unique<InputMethodWinImm32>(delegate, widget);
 #elif defined(OS_MACOSX)
   return std::make_unique<InputMethodMac>(delegate);
 #elif defined(USE_AURA) && defined(USE_X11)
diff --git a/ui/base/ime/input_method_win.cc b/ui/base/ime/input_method_win_imm32.cc
similarity index 80%
rename from ui/base/ime/input_method_win.cc
rename to ui/base/ime/input_method_win_imm32.cc
index 9c644a2c..92528b2f 100644
--- a/ui/base/ime/input_method_win.cc
+++ b/ui/base/ime/input_method_win_imm32.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 "ui/base/ime/input_method_win.h"
+#include "ui/base/ime/input_method_win_imm32.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -33,8 +33,9 @@
 
 }  // namespace
 
-InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate,
-                               HWND toplevel_window_handle)
+InputMethodWinImm32::InputMethodWinImm32(
+    internal::InputMethodDelegate* delegate,
+    HWND toplevel_window_handle)
     : InputMethodWinBase(delegate, toplevel_window_handle),
       pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION),
       enabled_(false),
@@ -44,14 +45,14 @@
   imm32_manager_.SetInputLanguage();
 }
 
-InputMethodWin::~InputMethodWin() {}
+InputMethodWinImm32::~InputMethodWinImm32() {}
 
-void InputMethodWin::OnFocus() {
+void InputMethodWinImm32::OnFocus() {
   InputMethodBase::OnFocus();
   RefreshInputLanguage();
 }
 
-bool InputMethodWin::OnUntranslatedIMEMessage(
+bool InputMethodWinImm32::OnUntranslatedIMEMessage(
     const MSG event,
     InputMethod::NativeEventResult* result) {
   LRESULT original_result = 0;
@@ -59,24 +60,24 @@
 
   switch (event.message) {
     case WM_IME_SETCONTEXT:
-      original_result = OnImeSetContext(
-          event.hwnd, event.message, event.wParam, event.lParam, &handled);
+      original_result = OnImeSetContext(event.hwnd, event.message, event.wParam,
+                                        event.lParam, &handled);
       break;
     case WM_IME_STARTCOMPOSITION:
       original_result = OnImeStartComposition(
           event.hwnd, event.message, event.wParam, event.lParam, &handled);
       break;
     case WM_IME_COMPOSITION:
-      original_result = OnImeComposition(
-          event.hwnd, event.message, event.wParam, event.lParam, &handled);
+      original_result = OnImeComposition(event.hwnd, event.message,
+                                         event.wParam, event.lParam, &handled);
       break;
     case WM_IME_ENDCOMPOSITION:
       original_result = OnImeEndComposition(
           event.hwnd, event.message, event.wParam, event.lParam, &handled);
       break;
     case WM_IME_REQUEST:
-      original_result = OnImeRequest(
-          event.message, event.wParam, event.lParam, &handled);
+      original_result =
+          OnImeRequest(event.message, event.wParam, event.lParam, &handled);
       break;
     case WM_CHAR:
     case WM_SYSCHAR:
@@ -84,8 +85,8 @@
                                event.lParam, event, &handled);
       break;
     case WM_IME_NOTIFY:
-      original_result = OnImeNotify(
-          event.message, event.wParam, event.lParam, &handled);
+      original_result =
+          OnImeNotify(event.message, event.wParam, event.lParam, &handled);
       break;
     default:
       NOTREACHED() << "Unknown IME message:" << event.message;
@@ -96,7 +97,8 @@
   return !!handled;
 }
 
-ui::EventDispatchDetails InputMethodWin::DispatchKeyEvent(ui::KeyEvent* event) {
+ui::EventDispatchDetails InputMethodWinImm32::DispatchKeyEvent(
+    ui::KeyEvent* event) {
   MSG native_key_event = MSGFromKeyEvent(event);
   if (native_key_event.message == WM_CHAR) {
     auto ref = weak_ptr_factory_.GetWeakPtr();
@@ -128,8 +130,8 @@
     if (msg.message == WM_CHAR)
       char_msgs.push_back(msg);
   }
-  while (::PeekMessage(&msg, native_key_event.hwnd, WM_SYSCHAR,
-                       WM_SYSDEADCHAR, PM_REMOVE)) {
+  while (::PeekMessage(&msg, native_key_event.hwnd, WM_SYSCHAR, WM_SYSDEADCHAR,
+                       PM_REMOVE)) {
     if (msg.message == WM_SYSCHAR)
       char_msgs.push_back(msg);
   }
@@ -168,7 +170,7 @@
   if (char_msgs.size() <= 1 && GetEngine() &&
       GetEngine()->IsInterestedInKeyEvent()) {
     ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback =
-        base::BindOnce(&InputMethodWin::ProcessKeyEventDone,
+        base::BindOnce(&InputMethodWinImm32::ProcessKeyEventDone,
                        weak_ptr_factory_.GetWeakPtr(),
                        base::Owned(new ui::KeyEvent(*event)),
                        base::Owned(new std::vector<MSG>(char_msgs)));
@@ -179,15 +181,15 @@
   return ProcessUnhandledKeyEvent(event, &char_msgs);
 }
 
-void InputMethodWin::ProcessKeyEventDone(ui::KeyEvent* event,
-                                         const std::vector<MSG>* char_msgs,
-                                         bool is_handled) {
+void InputMethodWinImm32::ProcessKeyEventDone(ui::KeyEvent* event,
+                                              const std::vector<MSG>* char_msgs,
+                                              bool is_handled) {
   if (is_handled)
     return;
   ProcessUnhandledKeyEvent(event, char_msgs);
 }
 
-ui::EventDispatchDetails InputMethodWin::ProcessUnhandledKeyEvent(
+ui::EventDispatchDetails InputMethodWinImm32::ProcessUnhandledKeyEvent(
     ui::KeyEvent* event,
     const std::vector<MSG>* char_msgs) {
   DCHECK(event);
@@ -207,14 +209,15 @@
   return details;
 }
 
-void InputMethodWin::OnTextInputTypeChanged(const TextInputClient* client) {
+void InputMethodWinImm32::OnTextInputTypeChanged(
+    const TextInputClient* client) {
   if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
     return;
   imm32_manager_.CancelIME(toplevel_window_handle_);
   UpdateIMEState();
 }
 
-void InputMethodWin::OnCaretBoundsChanged(const TextInputClient* client) {
+void InputMethodWinImm32::OnCaretBoundsChanged(const TextInputClient* client) {
   if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
     return;
   NotifyTextInputCaretBoundsChanged(client);
@@ -234,23 +237,22 @@
   // Tentatively assume that the returned value is DIP (Density Independent
   // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
   const gfx::Rect dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
-  const gfx::Rect screen_bounds =
-      display::win::ScreenWin::DIPToScreenRect(toplevel_window_handle_,
-                                               dip_screen_bounds);
+  const gfx::Rect screen_bounds = display::win::ScreenWin::DIPToScreenRect(
+      toplevel_window_handle_, dip_screen_bounds);
 
   HWND attached_window = toplevel_window_handle_;
   // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
   // conversion shouldn't be necessary.
   RECT r = {};
   GetClientRect(attached_window, &r);
-  POINT window_point = { screen_bounds.x(), screen_bounds.y() };
+  POINT window_point = {screen_bounds.x(), screen_bounds.y()};
   ScreenToClient(attached_window, &window_point);
   gfx::Rect caret_rect(gfx::Point(window_point.x, window_point.y),
                        screen_bounds.size());
   imm32_manager_.UpdateCaretRect(attached_window, caret_rect);
 }
 
-void InputMethodWin::CancelComposition(const TextInputClient* client) {
+void InputMethodWinImm32::CancelComposition(const TextInputClient* client) {
   if (IsTextInputClientFocused(client)) {
     // |enabled_| == false could be faked, and the engine should rely on the
     // real type get from GetTextInputType().
@@ -265,10 +267,10 @@
   }
 }
 
-void InputMethodWin::OnInputLocaleChanged() {
+void InputMethodWinImm32::OnInputLocaleChanged() {
   // Note: OnInputLocaleChanged() is for capturing the input language which can
   // be used to determine the appropriate TextInputType for Omnibox.
-  // See crbug.com/344834.
+  // See https://crbug.com/344834.
   // Currently OnInputLocaleChanged() on Windows relies on WM_INPUTLANGCHANGED,
   // which is known to be incompatible with TSF.
   // TODO(shuchen): Use ITfLanguageProfileNotifySink instead.
@@ -276,21 +278,22 @@
   RefreshInputLanguage();
 }
 
-bool InputMethodWin::IsInputLocaleCJK() const {
+bool InputMethodWinImm32::IsInputLocaleCJK() const {
   return imm32_manager_.IsInputLanguageCJK();
 }
 
-bool InputMethodWin::IsCandidatePopupOpen() const {
+bool InputMethodWinImm32::IsCandidatePopupOpen() const {
   return is_candidate_popup_open_;
 }
 
-void InputMethodWin::OnWillChangeFocusedClient(TextInputClient* focused_before,
-                                               TextInputClient* focused) {
+void InputMethodWinImm32::OnWillChangeFocusedClient(
+    TextInputClient* focused_before,
+    TextInputClient* focused) {
   if (IsWindowFocused(focused_before))
     ConfirmCompositionText();
 }
 
-void InputMethodWin::OnDidChangeFocusedClient(
+void InputMethodWinImm32::OnDidChangeFocusedClient(
     TextInputClient* focused_before,
     TextInputClient* focused) {
   if (IsWindowFocused(focused)) {
@@ -306,32 +309,32 @@
   InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused);
 }
 
-LRESULT InputMethodWin::OnImeSetContext(HWND window_handle,
-                                        UINT message,
-                                        WPARAM wparam,
-                                        LPARAM lparam,
-                                        BOOL* handled) {
+LRESULT InputMethodWinImm32::OnImeSetContext(HWND window_handle,
+                                             UINT message,
+                                             WPARAM wparam,
+                                             LPARAM lparam,
+                                             BOOL* handled) {
   if (!!wparam) {
     imm32_manager_.CreateImeWindow(window_handle);
     // Delay initialize the tsf to avoid perf regression.
     // Loading tsf dll causes some time, so doing it in UpdateIMEState() will
     // slow down the browser window creation.
-    // See crbug.com/509984.
+    // See https://crbug.com/509984.
     tsf_inputscope::InitializeTsfForInputScopes();
     tsf_inputscope::SetInputScopeForTsfUnawareWindow(
         toplevel_window_handle_, GetTextInputType(), GetTextInputMode());
   }
 
   OnInputMethodChanged();
-  return imm32_manager_.SetImeWindowStyle(
-      window_handle, message, wparam, lparam, handled);
+  return imm32_manager_.SetImeWindowStyle(window_handle, message, wparam,
+                                          lparam, handled);
 }
 
-LRESULT InputMethodWin::OnImeStartComposition(HWND window_handle,
-                                              UINT message,
-                                              WPARAM wparam,
-                                              LPARAM lparam,
-                                              BOOL* handled) {
+LRESULT InputMethodWinImm32::OnImeStartComposition(HWND window_handle,
+                                                   UINT message,
+                                                   WPARAM wparam,
+                                                   LPARAM lparam,
+                                                   BOOL* handled) {
   // We have to prevent WTL from calling ::DefWindowProc() because the function
   // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
   // over-write the position of IME windows.
@@ -344,11 +347,11 @@
   return 0;
 }
 
-LRESULT InputMethodWin::OnImeComposition(HWND window_handle,
-                                         UINT message,
-                                         WPARAM wparam,
-                                         LPARAM lparam,
-                                         BOOL* handled) {
+LRESULT InputMethodWinImm32::OnImeComposition(HWND window_handle,
+                                              UINT message,
+                                              WPARAM wparam,
+                                              LPARAM lparam,
+                                              BOOL* handled) {
   // We have to prevent WTL from calling ::DefWindowProc() because we do not
   // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
   *handled = TRUE;
@@ -377,17 +380,17 @@
   return 0;
 }
 
-LRESULT InputMethodWin::OnImeEndComposition(HWND window_handle,
-                                            UINT message,
-                                            WPARAM wparam,
-                                            LPARAM lparam,
-                                            BOOL* handled) {
+LRESULT InputMethodWinImm32::OnImeEndComposition(HWND window_handle,
+                                                 UINT message,
+                                                 WPARAM wparam,
+                                                 LPARAM lparam,
+                                                 BOOL* handled) {
   // Let WTL call ::DefWindowProc() and release its resources.
   *handled = FALSE;
 
   composing_window_handle_ = NULL;
 
-  // This is a hack fix for MS Korean IME issue (crbug.com/647150).
+  // This is a hack fix for MS Korean IME issue (https://crbug.com/647150).
   // Messages received when hitting Space key during composition:
   //   1. WM_IME_ENDCOMPOSITION (we usually clear composition for this MSG)
   //   2. WM_IME_COMPOSITION with GCS_RESULTSTR (we usually commit composition)
@@ -415,26 +418,26 @@
   return 0;
 }
 
-LRESULT InputMethodWin::OnImeNotify(UINT message,
-                                    WPARAM wparam,
-                                    LPARAM lparam,
-                                    BOOL* handled) {
+LRESULT InputMethodWinImm32::OnImeNotify(UINT message,
+                                         WPARAM wparam,
+                                         LPARAM lparam,
+                                         BOOL* handled) {
   *handled = FALSE;
 
   // Update |is_candidate_popup_open_|, whether a candidate window is open.
   switch (wparam) {
-  case IMN_OPENCANDIDATE:
-    is_candidate_popup_open_ = true;
-    break;
-  case IMN_CLOSECANDIDATE:
-    is_candidate_popup_open_ = false;
-    break;
+    case IMN_OPENCANDIDATE:
+      is_candidate_popup_open_ = true;
+      break;
+    case IMN_CLOSECANDIDATE:
+      is_candidate_popup_open_ = false;
+      break;
   }
 
   return 0;
 }
 
-void InputMethodWin::RefreshInputLanguage() {
+void InputMethodWinImm32::RefreshInputLanguage() {
   TextInputType type_original = GetTextInputType();
   imm32_manager_.SetInputLanguage();
   if (type_original != GetTextInputType()) {
@@ -443,12 +446,12 @@
     // 1) Switching betweeen 2 top-level windows, and the switched-away window
     //    receives OnInputLocaleChanged.
     // 2) The text input type is not changed by |SetInputLanguage|.
-    // Please refer to crbug.com/679564.
+    // Please refer to https://crbug.com/679564.
     UpdateIMEState();
   }
 }
 
-void InputMethodWin::ConfirmCompositionText() {
+void InputMethodWinImm32::ConfirmCompositionText() {
   if (composing_window_handle_)
     imm32_manager_.CleanupComposition(composing_window_handle_);
 
@@ -463,7 +466,7 @@
   }
 }
 
-void InputMethodWin::UpdateIMEState() {
+void InputMethodWinImm32::UpdateIMEState() {
   // Use switch here in case we are going to add more text input types.
   // We disable input method in password field.
   const HWND window_handle = toplevel_window_handle_;
diff --git a/ui/base/ime/input_method_win.h b/ui/base/ime/input_method_win_imm32.h
similarity index 88%
rename from ui/base/ime/input_method_win.h
rename to ui/base/ime/input_method_win_imm32.h
index 5b732e4..1d43a29 100644
--- a/ui/base/ime/input_method_win.h
+++ b/ui/base/ime/input_method_win_imm32.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 UI_BASE_IME_INPUT_METHOD_WIN_H_
-#define UI_BASE_IME_INPUT_METHOD_WIN_H_
+#ifndef UI_BASE_IME_INPUT_METHOD_WIN_IMM32_H_
+#define UI_BASE_IME_INPUT_METHOD_WIN_IMM32_H_
 
 #include <windows.h>
 
@@ -17,11 +17,11 @@
 namespace ui {
 
 // A common InputMethod implementation based on IMM32.
-class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodWinBase {
+class UI_BASE_IME_EXPORT InputMethodWinImm32 : public InputMethodWinBase {
  public:
-  InputMethodWin(internal::InputMethodDelegate* delegate,
-                 HWND toplevel_window_handle);
-  ~InputMethodWin() override;
+  InputMethodWinImm32(internal::InputMethodDelegate* delegate,
+                      HWND toplevel_window_handle);
+  ~InputMethodWinImm32() override;
 
   // Overridden from InputMethodBase:
   void OnFocus() override;
@@ -109,11 +109,11 @@
   HWND composing_window_handle_;
 
   // Used for making callbacks.
-  base::WeakPtrFactory<InputMethodWin> weak_ptr_factory_;
+  base::WeakPtrFactory<InputMethodWinImm32> weak_ptr_factory_;
 
-  DISALLOW_COPY_AND_ASSIGN(InputMethodWin);
+  DISALLOW_COPY_AND_ASSIGN(InputMethodWinImm32);
 };
 
 }  // namespace ui
 
-#endif  // UI_BASE_IME_INPUT_METHOD_WIN_H_
+#endif  // UI_BASE_IME_INPUT_METHOD_WIN_IMM32_H_
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 63c6e01..fa21564e 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -16,7 +16,7 @@
 // text areas.
 const base::Feature kEnableEmojiContextMenu {
   "EnableEmojiContextMenu",
-#if defined(OS_MACOSX) || defined(OS_WIN)
+#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_CHROMEOS)
       base::FEATURE_ENABLED_BY_DEFAULT
 #else
       base::FEATURE_DISABLED_BY_DEFAULT
@@ -25,7 +25,7 @@
 
 // Enables the floating virtual keyboard behavior.
 const base::Feature kEnableFloatingVirtualKeyboard = {
-    "enable-floating-virtual-keyboard", base::FEATURE_DISABLED_BY_DEFAULT};
+    "enable-floating-virtual-keyboard", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables the full screen handwriting virtual keyboard behavior.
 const base::Feature kEnableFullscreenHandwritingVirtualKeyboard = {
@@ -37,7 +37,7 @@
 
 // If enabled, uses the Material Design UI for virtual keyboard.
 const base::Feature kEnableVirtualKeyboardMdUi = {
-    "EnableVirtualKeyboardMdUi", base::FEATURE_DISABLED_BY_DEFAULT};
+    "EnableVirtualKeyboardMdUi", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kEnableVirtualKeyboardUkm = {
     "EnableVirtualKeyboardUkm", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ui/events/event_handler.cc b/ui/events/event_handler.cc
index 179420f..66279d4 100644
--- a/ui/events/event_handler.cc
+++ b/ui/events/event_handler.cc
@@ -4,6 +4,7 @@
 
 #include "ui/events/event_handler.h"
 
+#include "base/debug/alias.h"
 #include "ui/events/event.h"
 #include "ui/events/event_dispatcher.h"
 
@@ -21,6 +22,10 @@
 }
 
 void EventHandler::OnEvent(Event* event) {
+  // TODO(sky): remove |event_type|. Temporary while tracking down crash.
+  // https://crbug.com/867035
+  const EventType event_type = event->type();
+  base::debug::Alias(&event_type);
   if (event->IsKeyEvent())
     OnKeyEvent(event->AsKeyEvent());
   else if (event->IsMouseEvent())
diff --git a/ui/events/mojo/event_struct_traits.cc b/ui/events/mojo/event_struct_traits.cc
index e2663f6..aa2ec74 100644
--- a/ui/events/mojo/event_struct_traits.cc
+++ b/ui/events/mojo/event_struct_traits.cc
@@ -76,6 +76,36 @@
   return false;
 }
 
+bool ReadScrollData(ui::mojom::EventDataView* event,
+                    base::TimeTicks time_stamp,
+                    EventUniquePtr* out) {
+  ui::mojom::ScrollDataPtr scroll_data;
+  if (!event->ReadScrollData<ui::mojom::ScrollDataPtr>(&scroll_data))
+    return false;
+
+  *out = std::make_unique<ui::ScrollEvent>(
+      mojo::ConvertTo<ui::EventType>(event->action()),
+      gfx::Point(scroll_data->location->x, scroll_data->location->y),
+      time_stamp, event->flags(), scroll_data->x_offset, scroll_data->y_offset,
+      scroll_data->x_offset_ordinal, scroll_data->y_offset_ordinal,
+      scroll_data->finger_count, scroll_data->momentum_phase);
+  return true;
+}
+
+bool ReadGestureData(ui::mojom::EventDataView* event,
+                     base::TimeTicks time_stamp,
+                     EventUniquePtr* out) {
+  ui::mojom::GestureDataPtr gesture_data;
+  if (!event->ReadGestureData<ui::mojom::GestureDataPtr>(&gesture_data))
+    return false;
+
+  *out = std::make_unique<ui::GestureEvent>(
+      gesture_data->location->x, gesture_data->location->y, event->flags(),
+      time_stamp,
+      ui::GestureEventDetails(ConvertTo<ui::EventType>(event->action())));
+  return true;
+}
+
 }  // namespace
 
 static_assert(ui::mojom::kEventFlagNone == static_cast<int32_t>(ui::EF_NONE),
@@ -421,32 +451,25 @@
           pointer_details, time_stamp);
       break;
     }
-    case ui::mojom::EventType::GESTURE_TAP: {
-      ui::mojom::GestureDataPtr gesture_data;
-      if (!event.ReadGestureData<ui::mojom::GestureDataPtr>(&gesture_data))
+    case ui::mojom::EventType::GESTURE_TAP:
+      if (!ReadGestureData(&event, time_stamp, out))
         return false;
-
-      *out = std::make_unique<ui::GestureEvent>(
-          gesture_data->location->x, gesture_data->location->y, event.flags(),
-          time_stamp, ui::GestureEventDetails(ui::ET_GESTURE_TAP));
       break;
-    }
     case ui::mojom::EventType::SCROLL:
-    case ui::mojom::EventType::SCROLL_FLING_START:
-    case ui::mojom::EventType::SCROLL_FLING_CANCEL: {
-      ui::mojom::ScrollDataPtr scroll_data;
-      if (!event.ReadScrollData<ui::mojom::ScrollDataPtr>(&scroll_data))
+      if (!ReadScrollData(&event, time_stamp, out))
         return false;
-
-      *out = std::make_unique<ui::ScrollEvent>(
-          mojo::ConvertTo<ui::EventType>(event.action()),
-          gfx::Point(scroll_data->location->x, scroll_data->location->y),
-          time_stamp, event.flags(), scroll_data->x_offset,
-          scroll_data->y_offset, scroll_data->x_offset_ordinal,
-          scroll_data->y_offset_ordinal, scroll_data->finger_count,
-          scroll_data->momentum_phase);
       break;
-    }
+    case ui::mojom::EventType::SCROLL_FLING_START:
+    case ui::mojom::EventType::SCROLL_FLING_CANCEL:
+      // SCROLL_FLING_START/CANCEL is represented by a GestureEvent if
+      // EF_FROM_TOUCH is set.
+      if ((event.flags() & ui::EF_FROM_TOUCH) != 0) {
+        if (!ReadGestureData(&event, time_stamp, out))
+          return false;
+      } else if (!ReadScrollData(&event, time_stamp, out)) {
+        return false;
+      }
+      break;
     case ui::mojom::EventType::CANCEL_MODE:
       *out = std::make_unique<ui::CancelModeEvent>();
       break;
diff --git a/ui/events/mojo/struct_traits_unittest.cc b/ui/events/mojo/struct_traits_unittest.cc
index 851f9c7..c7b7df5 100644
--- a/ui/events/mojo/struct_traits_unittest.cc
+++ b/ui/events/mojo/struct_traits_unittest.cc
@@ -242,6 +242,9 @@
   GestureEvent kTestData[] = {
       {10, 20, EF_NONE,
        base::TimeTicks() + base::TimeDelta::FromMicroseconds(401),
+       GestureEventDetails(ET_SCROLL_FLING_START)},
+      {10, 20, EF_NONE,
+       base::TimeTicks() + base::TimeDelta::FromMicroseconds(401),
        GestureEventDetails(ET_GESTURE_TAP)},
   };
 
diff --git a/ui/file_manager/externs/command_handler_deps.js b/ui/file_manager/externs/command_handler_deps.js
index a3647b4..2e5ff96 100644
--- a/ui/file_manager/externs/command_handler_deps.js
+++ b/ui/file_manager/externs/command_handler_deps.js
@@ -102,3 +102,8 @@
  * @return {FileSelection}
  */
 CommandHandlerDeps.prototype.getSelection = function() {};
+
+/**
+ * @type {MetadataModel}
+ */
+CommandHandlerDeps.prototype.metadataModel;
diff --git a/ui/file_manager/file_manager/foreground/js/directory_model.js b/ui/file_manager/file_manager/foreground/js/directory_model.js
index 0acd42c..c49b169a 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_model.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_model.js
@@ -563,9 +563,9 @@
   }
   this.metadataModel_.notifyEntriesRemoved(removedUrls);
 
-  // Retrieve metadata information for the new (visible) items in the list.
+  // Retrieve metadata information for the new directory.
   this.metadataModel_.get(
-      newDirContents.fileList_.array_,
+      [this.currentDirContents_.getDirectoryEntry()],
       constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES);
 
   // Clear the table, and start scanning.
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index d67f2cf9..14755c9 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -349,6 +349,8 @@
   selectionHandler.addEventListener(
       FileSelectionHandler.EventType.CHANGE_THROTTLED,
       this.updateAvailability.bind(this));
+  fileManager.metadataModel.addEventListener(
+      'update', this.updateAvailability.bind(this));
 
   chrome.commandLinePrivate.hasSwitch(
       'disable-zip-archiver-packer', function(disabled) {
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/exif_parser.js b/ui/file_manager/file_manager/foreground/js/metadata/exif_parser.js
index d969dad..d03fd8f 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata/exif_parser.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata/exif_parser.js
@@ -20,6 +20,11 @@
     FILE_MANAGER_HOST__EXIF_PARSER +
     '/foreground/js/metadata/exif_constants.js');
 
+/**
+ * @param {MetadataParserLogger} parent Parent object.
+ * @constructor
+ * @extends {ImageParser}
+ */
 function ExifParser(parent) {
   ImageParser.call(this, parent, 'jpeg', /\.jpe?g$/i);
 }
@@ -52,8 +57,10 @@
   var self = this;
   var reader = new FileReader();
   reader.onerror = errorCallback;
-  reader.onload = function() { self.parseSlice(
-      file, callback, errorCallback, metadata, filePos, reader.result);
+  reader.onload = function() {
+    self.parseSlice(
+        file, callback, errorCallback, metadata, filePos,
+        /** @type{ArrayBuffer} */ (reader.result));
   };
   reader.readAsArrayBuffer(file.slice(filePos, filePos + opt_length));
 };
@@ -61,7 +68,7 @@
 /**
  * @param {File} file File object to parse.
  * @param {function(!Object)} callback Callback to be called on success.
- * @param {function(string)} errorCallback Error callback.
+ * @param {function((Event|string))} errorCallback Error callback.
  * @param {!Object} metadata Metadata object.
  * @param {number} filePos Position to slice at.
  * @param {ArrayBuffer} buf Buffer to be parsed.
@@ -274,13 +281,13 @@
 
 /**
  * @param {ByteReader} br Byte reader to be used for reading.
- * @param {Array<Object>} tags Array of tags to be written to.
+ * @param {Object<number, Object>} tags Map of tags to be written to.
  * @return {number} Directory offset.
  */
 ExifParser.prototype.readDirectory = function(br, tags) {
   var entryCount = br.readScalar(2);
   for (var i = 0; i < entryCount; i++) {
-    var tagId = br.readScalar(2);
+    var tagId = /** @type Exif.Tag<number> */ (br.readScalar(2));
     var tag = tags[tagId] = {id: tagId};
     tag.format = br.readScalar(2);
     tag.componentCount = br.readScalar(4);
@@ -292,7 +299,7 @@
 
 /**
  * @param {ByteReader} br Byte reader to be used for reading.
- * @param {Object} tag Tag object.
+ * @param {ExifEntry} tag Tag object.
  */
 ExifParser.prototype.readTagValue = function(br, tag) {
   var self = this;
@@ -367,9 +374,10 @@
       if (tag.componentCount === 0) {
         tag.value = '';
       } else if (tag.componentCount === 1) {
-        tag.value = String.fromCharCode(tag.value);
+        tag.value = String.fromCharCode(/** @type {number} */ (tag.value));
       } else {
-        tag.value = String.fromCharCode.apply(null, tag.value);
+        tag.value = String.fromCharCode.apply(
+            null, /** @type{Array<number>} */ (tag.value));
       }
       this.validateAndFixStringTag_(tag);
       break;
diff --git a/ui/file_manager/gallery/js/image_editor/BUILD.gn b/ui/file_manager/gallery/js/image_editor/BUILD.gn
index 36d0d2c..9a2e4a9 100644
--- a/ui/file_manager/gallery/js/image_editor/BUILD.gn
+++ b/ui/file_manager/gallery/js/image_editor/BUILD.gn
@@ -9,6 +9,7 @@
     ":closure_compile_externs",
     ":commands",
     ":exif_encoder",
+    ":exif_encoder_unittest",
     ":filter",
     ":image_adjust",
     ":image_buffer",
@@ -71,6 +72,13 @@
   ]
 }
 
+js_library("exif_encoder_unittest") {
+  deps = [
+    ":exif_encoder",
+    "../../../file_manager/foreground/js/metadata:exif_parser",
+  ]
+}
+
 js_library("filter") {
   deps = [
     ":image_util",
diff --git a/ui/file_manager/gallery/js/image_editor/exif_encoder_unittest.js b/ui/file_manager/gallery/js/image_editor/exif_encoder_unittest.js
index 514dac9..cd2f3d7 100644
--- a/ui/file_manager/gallery/js/image_editor/exif_encoder_unittest.js
+++ b/ui/file_manager/gallery/js/image_editor/exif_encoder_unittest.js
@@ -11,53 +11,34 @@
   var canvas = getSampleCanvas();
   var data = canvas.toDataURL('image/jpeg');
 
-  var metadata = {
+  var metadata = /** @type {!MetadataItem} */ ({
     mediaMimeType: 'image/jpeg',
     modificationTime: new Date(2015, 0, 7, 15, 30, 6),
     ifd: {
       image: {
         // Manufacture
-        271: {
-          id: 0x10f,
-          format: 2,
-          componentCount: 12,
-          value: 'Manufacture\0'
-        },
+        271: {id: 0x10f, format: 2, componentCount: 12, value: 'Manufacture\0'},
         // Device model
-        272: {
-          id: 0x110,
-          format: 2,
-          componentCount: 12,
-          value: 'DeviceModel\0'
-        },
+        272: {id: 0x110, format: 2, componentCount: 12, value: 'DeviceModel\0'},
         // GPS Pointer
         34853: {
           id: 0x8825,
           format: 4,
           componentCount: 1,
-          value: 0 // The value is set by the encoder.
+          value: 0  // The value is set by the encoder.
         }
       },
       exif: {
         // Lens model
-        42036: {
-          id: 0xa434,
-          format: 2,
-          componentCount: 10,
-          value: 'LensModel\0'
-        }
+        42036:
+            {id: 0xa434, format: 2, componentCount: 10, value: 'LensModel\0'}
       },
       gps: {
         // GPS latitude ref
-        1: {
-          id: 0x1,
-          format: 2,
-          componentCount: 2,
-          value: 'N\0'
-        }
+        1: {id: 0x1, format: 2, componentCount: 2, value: 'N\0'}
       }
     }
-  };
+  });
 
   var encoder = ImageEncoder.encodeMetadata(metadata, canvas, 1);
 
@@ -116,14 +97,18 @@
  */
 function measureExpectedThumbnailSize_() {
   var canvas = getSampleCanvas();
-  var metadata = {
+  var metadata = /** @type {!MetadataItem} */ ({
     mediaMimeType: 'image/jpeg',
     modificationTime: new Date(2015, 0, 7, 15, 30, 6)
-  };
+  });
 
   var encoder = ImageEncoder.encodeMetadata(metadata, canvas, 1);
-  return ImageEncoder.decodeDataURL(encoder.thumbnailDataUrl).length;
-};
+  /** @suppress {accessControls} */
+  function getThumbnailDataUrlForTest() {
+    return encoder.thumbnailDataUrl;
+  }
+  return ImageEncoder.decodeDataURL(getThumbnailDataUrlForTest()).length;
+}
 
 /**
  * Helper function for testing that exif encoder drops thumbnail data if there
@@ -139,7 +124,7 @@
   var longString = '0'.repeat(largeFieldValueSize - 1);
   longString += '\0';
 
-  var metadata = {
+  var metadata = /** @type{!MetadataItem} */ ({
     mediaMimeType: 'image/jpeg',
     modificationTime: new Date(2015, 0, 7, 15, 30, 6),
     ifd: {
@@ -153,7 +138,7 @@
         }
       }
     }
-  };
+  });
 
   var encoder = ImageEncoder.encodeMetadata(metadata, canvas, 1);
 
@@ -161,7 +146,8 @@
   var encodedResult = encoder.encode();
 
   // Decode encoded exif data and check thumbnail is written or not.
-  var exifParser = new ExifParser({verbose: false});
+  var exifParser =
+      new ExifParser(/** @type{MetadataParserLogger} */ ({verbose: false}));
   var parsedMetadata = {};
   var byteReader = new ByteReader(encodedResult);
   byteReader.readString(2 + 2); // Skip marker and size.
diff --git a/ui/gfx/mac/display_icc_profiles.cc b/ui/gfx/mac/display_icc_profiles.cc
index 8e663ec1..45bbb4b 100644
--- a/ui/gfx/mac/display_icc_profiles.cc
+++ b/ui/gfx/mac/display_icc_profiles.cc
@@ -48,9 +48,12 @@
   error = CGGetActiveDisplayList(0, nullptr, &display_count);
   if (error != kCGErrorSuccess)
     return;
+  if (!display_count)
+    return;
 
   std::vector<CGDirectDisplayID> displays(display_count);
-  error = CGGetActiveDisplayList(displays.size(), &displays[0], &display_count);
+  error =
+      CGGetActiveDisplayList(displays.size(), displays.data(), &display_count);
   if (error != kCGErrorSuccess)
     return;
 
diff --git a/ui/gl/gl_context.cc b/ui/gl/gl_context.cc
index 466a321..67ac664 100644
--- a/ui/gl/gl_context.cc
+++ b/ui/gl/gl_context.cc
@@ -194,6 +194,8 @@
 }
 
 void GLContext::BackpressureFenceWait(uint64_t fence) {}
+
+void GLContext::FlushForDebugging() {}
 #endif
 
 bool GLContext::HasExtension(const char* name) {
diff --git a/ui/gl/gl_context.h b/ui/gl/gl_context.h
index 11e6fc8d..6187b44 100644
--- a/ui/gl/gl_context.h
+++ b/ui/gl/gl_context.h
@@ -202,6 +202,8 @@
   virtual uint64_t BackpressureFenceCreate();
   // Perform a client-side wait on a previously-created fence.
   virtual void BackpressureFenceWait(uint64_t fence);
+  // Temporary instrumentation for https://crbug.com/863817
+  virtual void FlushForDebugging();
 #endif
 
  protected:
diff --git a/ui/gl/gl_context_cgl.cc b/ui/gl/gl_context_cgl.cc
index 6392f65..c2f167d 100644
--- a/ui/gl/gl_context_cgl.cc
+++ b/ui/gl/gl_context_cgl.cc
@@ -286,6 +286,12 @@
     backpressure_fences_.erase(backpressure_fences_.begin());
 }
 
+void GLContextCGL::FlushForDebugging() {
+  if (!context_ || CGLGetCurrentContext() != context_)
+    return;
+  glFlush();
+}
+
 bool GLContextCGL::MakeCurrent(GLSurface* surface) {
   DCHECK(context_);
 
diff --git a/ui/gl/gl_context_cgl.h b/ui/gl/gl_context_cgl.h
index 6e3c4b1..e757639 100644
--- a/ui/gl/gl_context_cgl.h
+++ b/ui/gl/gl_context_cgl.h
@@ -38,6 +38,7 @@
       const gfx::ColorSpace& color_space) override;
   uint64_t BackpressureFenceCreate() override;
   void BackpressureFenceWait(uint64_t fence) override;
+  void FlushForDebugging() override;
 
  protected:
   ~GLContextCGL() override;
diff --git a/ui/latency/mojo/latency_info.mojom b/ui/latency/mojo/latency_info.mojom
index ad48d18..a39a8ed 100644
--- a/ui/latency/mojo/latency_info.mojom
+++ b/ui/latency/mojo/latency_info.mojom
@@ -56,8 +56,6 @@
   // Timestamp when the frame is swapped (i.e. when the rendering caused by
   // input event actually takes effect).
   INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT,
-  LATENCY_COMPONENT_TYPE_LAST =
-      INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT,
 };
 
 enum SourceEventType {
@@ -70,7 +68,6 @@
   TOUCHPAD,
   FRAME,
   OTHER,
-  SOURCE_EVENT_TYPE_LAST = OTHER,
 };
 
 // See ui/latency/latency_info.h
diff --git a/ui/latency/mojo/latency_info_struct_traits.cc b/ui/latency/mojo/latency_info_struct_traits.cc
index e5ef36e8..a1ac873 100644
--- a/ui/latency/mojo/latency_info_struct_traits.cc
+++ b/ui/latency/mojo/latency_info_struct_traits.cc
@@ -201,7 +201,7 @@
           INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT;
   }
   NOTREACHED();
-  return ui::mojom::LatencyComponentType::LATENCY_COMPONENT_TYPE_LAST;
+  return ui::mojom::LatencyComponentType::kMaxValue;
 }
 
 // static
diff --git a/ui/latency/mojo/latency_info_struct_traits.h b/ui/latency/mojo/latency_info_struct_traits.h
index c69086c..e668e4d 100644
--- a/ui/latency/mojo/latency_info_struct_traits.h
+++ b/ui/latency/mojo/latency_info_struct_traits.h
@@ -11,16 +11,13 @@
 
 namespace mojo {
 
-static_assert(
-    static_cast<int>(
-        ui::mojom::LatencyComponentType::LATENCY_COMPONENT_TYPE_LAST) ==
-        static_cast<int>(ui::LATENCY_COMPONENT_TYPE_LAST),
-    "Enum size mismatch");
+static_assert(static_cast<int>(ui::mojom::LatencyComponentType::kMaxValue) ==
+                  static_cast<int>(ui::LATENCY_COMPONENT_TYPE_LAST),
+              "Enum size mismatch");
 
-static_assert(
-    static_cast<int>(ui::mojom::SourceEventType::SOURCE_EVENT_TYPE_LAST) ==
-        static_cast<int>(ui::SOURCE_EVENT_TYPE_LAST),
-    "Enum size mismatch");
+static_assert(static_cast<int>(ui::mojom::SourceEventType::kMaxValue) ==
+                  static_cast<int>(ui::SOURCE_EVENT_TYPE_LAST),
+              "Enum size mismatch");
 
 template <>
 struct ArrayTraits<ui::LatencyInfo::LatencyMap> {
diff --git a/ui/views/controls/styled_label.cc b/ui/views/controls/styled_label.cc
index 1f2d6fbc..2c12e6e7 100644
--- a/ui/views/controls/styled_label.cc
+++ b/ui/views/controls/styled_label.cc
@@ -82,6 +82,16 @@
 
 }  // namespace
 
+// StyledLabel::TestApi ------------------------------------------------
+
+StyledLabel::TestApi::TestApi(StyledLabel* view) : view_(view) {}
+
+StyledLabel::TestApi::~TestApi() = default;
+
+const std::map<View*, gfx::Range>& StyledLabel::TestApi::link_targets() {
+  return view_->link_targets_;
+}
+
 // StyledLabel::RangeStyleInfo ------------------------------------------------
 
 StyledLabel::RangeStyleInfo::RangeStyleInfo() = default;
diff --git a/ui/views/controls/styled_label.h b/ui/views/controls/styled_label.h
index ae5bac55..43544ef 100644
--- a/ui/views/controls/styled_label.h
+++ b/ui/views/controls/styled_label.h
@@ -37,6 +37,20 @@
   // Internal class name.
   static const char kViewClassName[];
 
+  // TestApi is used for tests to get internal implementation details.
+  class VIEWS_EXPORT TestApi {
+   public:
+    explicit TestApi(StyledLabel* view);
+    ~TestApi();
+
+    const std::map<View*, gfx::Range>& link_targets();
+
+   private:
+    StyledLabel* const view_;
+
+    DISALLOW_COPY_AND_ASSIGN(TestApi);
+  };
+
   // Parameters that define label style for a styled label's text range.
   struct VIEWS_EXPORT RangeStyleInfo {
     RangeStyleInfo();
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index af89fb3..d077d45 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -22,6 +22,7 @@
 #include "ui/events/event.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/mus/mus_client.h"
+#include "ui/views/mus/mus_client_test_api.h"
 #include "ui/views/mus/screen_mus.h"
 #include "ui/views/mus/window_manager_frame_values.h"
 #include "ui/views/test/views_test_base.h"
@@ -402,7 +403,7 @@
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, GetWindowBoundsInScreen) {
-  ScreenMus* screen = MusClient::Get()->screen();
+  ScreenMus* screen = MusClientTestApi::screen();
 
   // Add a second display to the right of the primary.
   const int64_t kSecondDisplayId = 222;
diff --git a/ui/views/mus/mus_client.h b/ui/views/mus/mus_client.h
index 4e2c384..e8742c19 100644
--- a/ui/views/mus/mus_client.h
+++ b/ui/views/mus/mus_client.h
@@ -119,9 +119,6 @@
 
   AXRemoteHost* ax_remote_host() { return ax_remote_host_.get(); }
 
-  // Getter for type safety. Most code can use display::Screen::GetScreen().
-  ScreenMus* screen() { return screen_.get(); }
-
   // Creates DesktopNativeWidgetAura with DesktopWindowTreeHostMus. This is
   // set as the factory function used for creating NativeWidgets when a
   //  NativeWidget has not been explicitly set.
diff --git a/ui/views/mus/mus_client_test_api.h b/ui/views/mus/mus_client_test_api.h
index e305227..bdf20dc 100644
--- a/ui/views/mus/mus_client_test_api.h
+++ b/ui/views/mus/mus_client_test_api.h
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/macros.h"
+#include "ui/views/mus/ax_remote_host.h"
 #include "ui/views/mus/mus_client.h"
 
 namespace views {
@@ -21,6 +22,8 @@
     MusClient::Get()->ax_remote_host_ = std::move(client);
   }
 
+  static ScreenMus* screen() { return MusClient::Get()->screen_.get(); }
+
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(MusClientTestApi);
 };
diff --git a/ui/views/mus/pointer_watcher_event_router_unittest.cc b/ui/views/mus/pointer_watcher_event_router_unittest.cc
index 821cefdc..dd50ba05 100644
--- a/ui/views/mus/pointer_watcher_event_router_unittest.cc
+++ b/ui/views/mus/pointer_watcher_event_router_unittest.cc
@@ -11,6 +11,7 @@
 #include "ui/aura/test/mus/window_tree_client_private.h"
 #include "ui/events/event.h"
 #include "ui/views/mus/mus_client.h"
+#include "ui/views/mus/mus_client_test_api.h"
 #include "ui/views/mus/screen_mus.h"
 #include "ui/views/pointer_watcher.h"
 #include "ui/views/test/scoped_views_test_helper.h"
@@ -250,7 +251,7 @@
   TestPointerWatcher watcher;
   pointer_watcher_event_router->AddPointerWatcher(&watcher, false);
 
-  ScreenMus* screen = MusClient::Get()->screen();
+  ScreenMus* screen = MusClientTestApi::screen();
   const uint64_t kFirstDisplayId = screen->GetPrimaryDisplay().id();
 
   // The first display is at 0,0.
diff --git a/ui/views/selection_controller.cc b/ui/views/selection_controller.cc
index 61d83ff..b04a9930 100644
--- a/ui/views/selection_controller.cc
+++ b/ui/views/selection_controller.cc
@@ -75,7 +75,8 @@
         initial_focus_state == InitialFocusStateOnMousePress::UNFOCUSED) {
       SelectAll();
     } else if (PlatformStyle::kSelectWordOnRightClick &&
-               !render_text->IsPointInSelection(event.location())) {
+               !render_text->IsPointInSelection(event.location()) &&
+               IsInsideText(event.location())) {
       SelectWord(event.location());
     }
   }
@@ -223,4 +224,16 @@
   delegate_->OnAfterPointerAction(false, true);
 }
 
+bool SelectionController::IsInsideText(const gfx::Point& point) {
+  gfx::RenderText* render_text = GetRenderText();
+  std::vector<gfx::Rect> bounds_rects = render_text->GetSubstringBounds(
+      gfx::Range(0, render_text->text().length()));
+
+  for (const auto& bounds : bounds_rects)
+    if (bounds.Contains(point))
+      return true;
+
+  return false;
+}
+
 }  // namespace views
diff --git a/ui/views/selection_controller.h b/ui/views/selection_controller.h
index 46ec060..c4382f1b 100644
--- a/ui/views/selection_controller.h
+++ b/ui/views/selection_controller.h
@@ -82,6 +82,9 @@
   // |last_drag_location_|. Can be called asynchronously, through a timer.
   void SelectThroughLastDragLocation();
 
+  // Returns whether |point| is inside any substring of the text.
+  bool IsInsideText(const gfx::Point& point);
+
   // A timer and point used to modify the selection when dragging.
   base::RepeatingTimer drag_selection_timer_;
   gfx::Point last_drag_location_;
diff --git a/ui/views/selection_controller_unittest.cc b/ui/views/selection_controller_unittest.cc
index 6313eef08..e28d530 100644
--- a/ui/views/selection_controller_unittest.cc
+++ b/ui/views/selection_controller_unittest.cc
@@ -168,18 +168,22 @@
     EXPECT_EQ("", GetSelectedText());
 }
 
-// This tests the current incorrect behavior. When the behavior is fixed this
-// test can be updated.
-// TODO(ellyjones): Do that - https://crbug.com/856609
-TEST_F(SelectionControllerTest, RightClickPastEndSelectsLastWord) {
+TEST_F(SelectionControllerTest, RightClickSelectsWord) {
   SetText("abc def");
-
-  RightMouseDown(CenterRight(BoundsOfChar(6)), true);
+  RightMouseDown(CenterRight(BoundsOfChar(5)), true);
   if (PlatformStyle::kSelectWordOnRightClick)
     EXPECT_EQ("def", GetSelectedText());
   else
     EXPECT_EQ("", GetSelectedText());
 }
 
+// Regression test for https://crbug.com/856609
+TEST_F(SelectionControllerTest, RightClickPastEndDoesntSelectLastWord) {
+  SetText("abc def");
+
+  RightMouseDown(CenterRight(BoundsOfChar(6)), true);
+  EXPECT_EQ("", GetSelectedText());
+}
+
 }  // namespace
 }  // namespace views
diff --git a/ui/views/window/frame_background.cc b/ui/views/window/frame_background.cc
index 7bbbb8d..a5e5775b 100644
--- a/ui/views/window/frame_background.cc
+++ b/ui/views/window/frame_background.cc
@@ -4,6 +4,7 @@
 
 #include "ui/views/window/frame_background.h"
 
+#include "build/build_config.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/theme_provider.h"
@@ -54,17 +55,12 @@
 
 void FrameBackground::PaintRestored(gfx::Canvas* canvas,
                                     const View* view) const {
-  // Fill with the frame color first so we have a constant background for
-  // areas not covered by the theme image.
-  PaintFrameColor(canvas, view);
+  // Restored window painting is a superset of maximized window painting; let
+  // the maximized code paint the frame color and images.
+  PaintMaximized(canvas, view);
 
-  // Draw the theme frame and overlay, if available.
-  if (!theme_image_.isNull()) {
-    canvas->TileImageInt(theme_image_, 0, 0, view->width(),
-                         theme_image_.height());
-  }
-  if (!theme_overlay_image_.isNull())
-    canvas->DrawImageInt(theme_overlay_image_, 0, 0);
+  // Fill the frame borders with the frame color before drawing the edge images.
+  FillFrameBorders(canvas, view);
 
   // Draw the top corners and edge, scaling the corner images down if they
   // are too big and relative to the vertical space available.
@@ -123,30 +119,37 @@
 
 void FrameBackground::PaintMaximized(gfx::Canvas* canvas,
                                      const View* view) const {
-  // We will be painting from -|maximized_top_inset_| to
-  // -|maximized_top_inset_| + |theme_image_|.height(). If this is less than
-  // |top_area_height_|, we need to paint the frame color to fill in the area
-  // beneath the image.
-  int theme_frame_bottom = -maximized_top_inset_ +
-                           (theme_image_.isNull() ? 0 : theme_image_.height());
-  if (top_area_height_ > theme_frame_bottom)
-    PaintFrameTopArea(canvas, view);
+// Fill the top with the frame color first so we have a constant background
+// for areas not covered by the theme image.
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  auto* native_theme = view->GetNativeTheme();
+  ui::NativeTheme::ExtraParams params;
+  params.frame_top_area.use_custom_frame = use_custom_frame_;
+  params.frame_top_area.is_active = is_active_;
+  params.frame_top_area.incognito = incognito_;
+  params.frame_top_area.default_background_color = frame_color_;
+  native_theme->Paint(canvas->sk_canvas(), ui::NativeTheme::kFrameTopArea,
+                      ui::NativeTheme::kNormal,
+                      gfx::Rect(0, 0, view->width(), top_area_height_), params);
+#else
+  canvas->FillRect(gfx::Rect(0, 0, view->width(), top_area_height_),
+                   frame_color_);
+#endif
 
-  // Draw the theme frame.
+  // Draw the theme frame and overlay, if available.
   if (!theme_image_.isNull()) {
-    canvas->TileImageInt(theme_image_, 0, -maximized_top_inset_, view->width(),
-                         theme_image_.height());
+    canvas->TileImageInt(theme_image_, 0, 0, 0, -maximized_top_inset_,
+                         view->width(), top_area_height_, 1.0f,
+                         SkShader::kRepeat_TileMode,
+                         SkShader::kMirror_TileMode);
   }
-  // Draw the theme frame overlay, if available.
   if (!theme_overlay_image_.isNull())
     canvas->DrawImageInt(theme_overlay_image_, 0, -maximized_top_inset_);
 }
 
-void FrameBackground::PaintFrameColor(gfx::Canvas* canvas,
-                                      const View* view) const {
-  PaintFrameTopArea(canvas, view);
-
-  // If the window is very short, we're done.
+void FrameBackground::FillFrameBorders(gfx::Canvas* canvas,
+                                       const View* view) const {
+  // If the window is very short, we don't need to fill any borders.
   int remaining_height = view->height() - top_area_height_;
   if (remaining_height <= 0)
     return;
@@ -171,22 +174,4 @@
                              frame_color_);
 }
 
-void FrameBackground::PaintFrameTopArea(gfx::Canvas* canvas,
-                                        const View* view) const {
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
-  auto* native_theme = view->GetNativeTheme();
-  ui::NativeTheme::ExtraParams params;
-  params.frame_top_area.use_custom_frame = use_custom_frame_;
-  params.frame_top_area.is_active = is_active_;
-  params.frame_top_area.incognito = incognito_;
-  params.frame_top_area.default_background_color = frame_color_;
-  native_theme->Paint(canvas->sk_canvas(), ui::NativeTheme::kFrameTopArea,
-                      ui::NativeTheme::kNormal,
-                      gfx::Rect(0, 0, view->width(), top_area_height_), params);
-#else
-  canvas->FillRect(gfx::Rect(0, 0, view->width(), top_area_height_),
-                   frame_color_);
-#endif
-}
-
 }  // namespace views
diff --git a/ui/views/window/frame_background.h b/ui/views/window/frame_background.h
index 191e5209d..bfd565f 100644
--- a/ui/views/window/frame_background.h
+++ b/ui/views/window/frame_background.h
@@ -81,11 +81,8 @@
   void PaintMaximized(gfx::Canvas* canvas, const View* view) const;
 
  private:
-  // Fills the frame area with the frame color.
-  void PaintFrameColor(gfx::Canvas* canvas, const View* view) const;
-
-  // Paints the background of the tab strip.
-  void PaintFrameTopArea(gfx::Canvas* canvas, const View* view) const;
+  // Fills the frame side and bottom borders with the frame color.
+  void FillFrameBorders(gfx::Canvas* canvas, const View* view) const;
 
   SkColor frame_color_;
   bool use_custom_frame_;
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js b/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
index 13deebe..8bbb66fa 100644
--- a/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
+++ b/ui/webui/resources/cr_components/chromeos/bluetooth_dialog.js
@@ -238,6 +238,9 @@
 
   /** @private */
   pairingChanged_: function() {
+    if (this.pairingDevice === undefined)
+      return;
+
     // Auto-close the dialog when pairing completes.
     if (this.pairingDevice.paired && !this.pairingDevice.connecting &&
         this.pairingDevice.connected) {
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config.js b/ui/webui/resources/cr_components/chromeos/network/network_config.js
index cf5f094..f1ac994 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config.js
@@ -875,6 +875,9 @@
 
   /** @private */
   updateVpnType_: function() {
+    if (this.configProperties_ === undefined)
+      return;
+
     var vpn = this.configProperties_.VPN;
     if (!vpn) {
       this.showVpn_ = null;