diff --git a/AUTHORS b/AUTHORS
index cd603bd0..7c0c78e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -511,6 +511,7 @@
 Kristof Kosztyo <kkosztyo.u-szeged@partner.samsung.com>
 Krzysztof Czech <k.czech@samsung.com>
 Krzysztof Wolanski <k.wolanski@samsung.com>
+Kui Tan <tk1061178@gmail.com>
 Kunal Thakar <kunalt@gmail.com>
 Kushal Pisavadia <kushi.p@gmail.com>
 Kwangho Shin <k_h.shin@samsung.com>
diff --git a/DEPS b/DEPS
index d5554f62..46ccfb0 100644
--- a/DEPS
+++ b/DEPS
@@ -129,11 +129,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': '59b733715141b732ce3fb73b8870851f616c3163',
+  'skia_revision': 'acb4829c1be4bd6be53ea750effd94c07da95632',
   # 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': 'c3553b10c8f8d018da015b448e6f2e72168741b4',
+  'v8_revision': '804cfc5fb2ab8c49facc7a5f486c1555c2cbad63',
   # 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.
@@ -145,11 +145,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '555c33311d83b09b09045c837a7feace4966f906',
+  'swiftshader_revision': '52a67b6495ce4973c4e17830107f09c35f7abbcc',
   # 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': 'bc36bade1e160e34415fa445ba456e56ab69336b',
+  'pdfium_revision': '21d000094adeb6be3b97f3758523c6117f334e82',
   # 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.
@@ -805,7 +805,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a46c6ae7a64f28c2871f1fd042866e4583548549',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '507bf397d15ce43076ded2298db4a23bdecc2b1d',
       'condition': 'checkout_linux',
   },
 
@@ -1343,7 +1343,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '688fbfe33779392aa210d67d4aa12cb012f112c2',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '3295c01df233c0106c3358414ade2b61e3cd36fc',
+    Var('webrtc_git') + '/src.git' + '@' + '708eccc1bd2d0163e35df9605649e6cadd68f589',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1384,7 +1384,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d6512b3755d97e324dae3da952032ec8d77670f2',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2523c15caf5fee59aa11611e62ec46c83019562a',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 50eb18a..7b2d605 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1527,6 +1527,7 @@
     # TODO(stevenjb): Investigate whether this is OK. https://crbug.com/644336.
     "//chromeos/audio",
     "//chromeos/dbus",
+    "//chromeos/dbus/biod",
     "//components/discardable_memory/public/interfaces",
     "//components/services/font:lib",
     "//components/services/font/public/interfaces",
diff --git a/ash/DEPS b/ash/DEPS
index 029e03c..92dd1c8 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -59,6 +59,7 @@
   "+chromeos/audio",
   "+chromeos/components/multidevice/logging/logging.h",
   "+chromeos/constants",
+  "+chromeos/dbus/biod/biod_client.h",
   "+chromeos/dbus/dbus_thread_manager.h",
   "+chromeos/dbus/fake_power_manager_client.h",
   "+chromeos/dbus/hammerd",
diff --git a/ash/app_menu/notification_menu_view.cc b/ash/app_menu/notification_menu_view.cc
index 1cfe140..f3c8e44 100644
--- a/ash/app_menu/notification_menu_view.cc
+++ b/ash/app_menu/notification_menu_view.cc
@@ -132,20 +132,22 @@
   const auto i = NotificationIterForId(notification_id);
   if (i == notification_item_views_.end())
     return;
+  const bool removed_displayed_notification =
+      i->get() == GetDisplayedNotificationItemView();
 
-  // Erase the notification from |notification_item_views_| and
-  // |overflow_view_|.
   notification_item_views_.erase(i);
-  if (overflow_view_)
-    overflow_view_->RemoveIcon(notification_id);
   header_view_->UpdateCounter(notification_item_views_.size());
 
-  // Display the next notification.
-  auto* item = GetDisplayedNotificationItemView();
-  if (item) {
-    AddChildView(item);
-    if (overflow_view_)
-      overflow_view_->RemoveIcon(item->notification_id());
+  if (removed_displayed_notification) {
+    // Display the next notification.
+    auto* item = GetDisplayedNotificationItemView();
+    if (item) {
+      AddChildView(item);
+      if (overflow_view_)
+        overflow_view_->RemoveIcon(item->notification_id());
+    }
+  } else if (overflow_view_) {
+    overflow_view_->RemoveIcon(notification_id);
   }
 
   if (overflow_view_ && overflow_view_->is_empty()) {
diff --git a/ash/shell/content/client/shell_browser_main_parts.cc b/ash/shell/content/client/shell_browser_main_parts.cc
index 548a75a..f55bdb3 100644
--- a/ash/shell/content/client/shell_browser_main_parts.cc
+++ b/ash/shell/content/client/shell_browser_main_parts.cc
@@ -32,7 +32,9 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/dbus/biod/biod_client.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/dbus/power/power_policy_controller.h"
 #include "components/exo/file_helper.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -72,6 +74,8 @@
 
 void ShellBrowserMainParts::PostMainMessageLoopStart() {
   chromeos::DBusThreadManager::Initialize(chromeos::DBusThreadManager::kShared);
+  chromeos::PowerManagerClient::InitializeFake();
+  chromeos::BiodClient::InitializeFake();
 
   // WindowTreeClient needs to do some shutdown while the IO thread is alive.
   if (mus_client_)
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index c6481c5..e6e84a2 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -241,7 +241,11 @@
   // On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory.
   if (DirectoryExists(path))
     flags |= File::FLAG_BACKUP_SEMANTICS;
-#endif  // OS_WIN
+#elif defined(OS_FUCHSIA)
+  // On Fuchsia, we need O_RDONLY for directories, or O_WRONLY for files.
+  // TODO(https://crbug.com/947802): Find a cleaner workaround for this.
+  flags |= (DirectoryExists(path) ? File::FLAG_READ : File::FLAG_WRITE);
+#endif
 
   File file(path, flags);
   if (!file.IsValid())
diff --git a/base/fuchsia/service_directory_test_base.cc b/base/fuchsia/service_directory_test_base.cc
index c476c6d..7067d94 100644
--- a/base/fuchsia/service_directory_test_base.cc
+++ b/base/fuchsia/service_directory_test_base.cc
@@ -24,8 +24,9 @@
 
   // Create the ServiceDirectoryClient, connected to the "public" sub-directory.
   fidl::InterfaceHandle<::fuchsia::io::Directory> public_directory;
-  CHECK_EQ(fdio_open_at(directory.channel().get(), "public", 0,
-                        public_directory.NewRequest().TakeChannel().release()),
+  CHECK_EQ(fdio_service_connect_at(
+               directory.channel().get(), "/public/.",
+               public_directory.NewRequest().TakeChannel().release()),
            ZX_OK);
   public_service_directory_client_ =
       std::make_unique<ServiceDirectoryClient>(std::move(public_directory));
diff --git a/build/android/pylib/symbols/symbol_utils_unittest.py b/build/android/pylib/symbols/symbol_utils_unittest.py
index e0c1405..82a7e313 100644
--- a/build/android/pylib/symbols/symbol_utils_unittest.py
+++ b/build/android/pylib/symbols/symbol_utils_unittest.py
@@ -34,7 +34,6 @@
   (0x0155d000, 0x015ab98c, 0x0004e98c, 'libbase_i18n.cr.so'),
   (0x015ac000, 0x015dff4c, 0x00033f4c, 'libbindings.cr.so'),
   (0x015e0000, 0x015f5a54, 0x00015a54, 'libbindings_base.cr.so'),
-  (0x015f6000, 0x0160d770, 0x00017770, 'libblink_android_mojo_bindings_shared.cr.so'),
   (0x0160e000, 0x01731960, 0x00123960, 'libblink_common.cr.so'),
   (0x01732000, 0x0174ce54, 0x0001ae54, 'libblink_controller.cr.so'),
   (0x0174d000, 0x0318c528, 0x01a3f528, 'libblink_core.cr.so'),
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index c51a474..947d51c 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8917613372240707536
\ No newline at end of file
+8917444505235389856
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 4e755571..b1f8579 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8917620309259302576
\ No newline at end of file
+8917450276044616784
\ No newline at end of file
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index ccd512f..80e2a36 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -319,6 +319,7 @@
 
     tool("cc") {
       depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
       command = "$cc -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_c}}${extra_cppflags}${extra_cflags} -c {{source}} -o {{output}}"
       depsformat = "gcc"
       description = "CC {{output}}"
@@ -329,6 +330,7 @@
 
     tool("cxx") {
       depfile = "{{output}}.d"
+      precompiled_header_type = "gcc"
       command = "$cxx -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}}${extra_cppflags}${extra_cxxflags} -c {{source}} -o {{output}}"
       depsformat = "gcc"
       description = "CXX {{output}}"
diff --git a/chrome/VERSION b/chrome/VERSION
index c0b240c..d4f1fe7b 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=75
 MINOR=0
-BUILD=3752
+BUILD=3753
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 43f4909..b968a96 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -347,7 +347,6 @@
     "//third_party/android_media:android_media_java",
     "//third_party/android_sdk:android_gcm_java",
     "//third_party/android_swipe_refresh:android_swipe_refresh_java",
-    "//third_party/blink/public:android_mojo_bindings_java",
     "//third_party/blink/public:blink_headers_java",
     "//third_party/blink/public/mojom:android_mojo_bindings_java",
     "//third_party/blink/public/mojom:mojom_platform_java",
@@ -635,7 +634,6 @@
     "//third_party/android_deps:com_android_support_mediarouter_v7_java",
     "//third_party/android_deps:com_android_support_recyclerview_v7_java",
     "//third_party/android_deps:com_android_support_support_annotations_java",
-    "//third_party/blink/public:android_mojo_bindings_java",
     "//third_party/blink/public:blink_headers_java",
     "//third_party/blink/public/mojom:android_mojo_bindings_java",
     "//third_party/cacheinvalidation:cacheinvalidation_javalib",
@@ -782,7 +780,6 @@
     "//third_party/android_sdk:android_test_runner_java",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
-    "//third_party/blink/public:android_mojo_bindings_java",
     "//third_party/blink/public:blink_headers_java",
     "//third_party/blink/public/mojom:android_mojo_bindings_java",
     "//third_party/blink/public/mojom:mojom_mhtml_load_result_java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index abd7c24f..4d276b2f 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1380,6 +1380,7 @@
   "java/src/org/chromium/chrome/browser/rappor/RapporServiceBridge.java",
   "java/src/org/chromium/chrome/browser/rlz/RevenueStats.java",
   "java/src/org/chromium/chrome/browser/rlz/RlzPingHandler.java",
+  "java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java",
   "java/src/org/chromium/chrome/browser/search_engines/TemplateUrl.java",
   "java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java",
   "java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 2875d4b..17f6f75 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -156,6 +156,7 @@
   "junit/src/org/chromium/chrome/browser/preferences/password/SingleThreadBarrierClosureTest.java",
   "junit/src/org/chromium/chrome/browser/preferences/password/TimedCallbackDelayerTest.java",
   "junit/src/org/chromium/chrome/browser/preferences/privacy/PrivacyPreferencesManagerTest.java",
+  "junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotificationTest.java",
   "junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfAndroidBridgeTest.java",
   "junit/src/org/chromium/chrome/browser/send_tab_to_self/SendTabToSelfShareActivityTest.java",
   "junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index db66043..6046950 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -164,6 +164,8 @@
     public static final String ANDROID_PAY_INTEGRATION_V1 = "AndroidPayIntegrationV1";
     public static final String ANDROID_PAY_INTEGRATION_V2 = "AndroidPayIntegrationV2";
     public static final String ANDROID_PAYMENT_APPS = "AndroidPaymentApps";
+    public static final String ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION =
+            "AndroidSearchEngineChoiceNotification";
     public static final String ANDROID_SITE_SETTINGS_UI_REFRESH = "AndroidSiteSettingsUIRefresh";
     public static final String APP_NOTIFICATION_STATUS_MESSAGING = "AppNotificationStatusMessaging";
     public static final String AUTOFILL_ASSISTANT = "AutofillAssistant";
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 72874d2..35542b15 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -117,6 +117,7 @@
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.search_engines.SearchEngineChoiceNotification;
 import org.chromium.chrome.browser.signin.SigninPromoUtil;
 import org.chromium.chrome.browser.snackbar.undo.UndoBarController;
 import org.chromium.chrome.browser.suggestions.SuggestionsEventReporterBridge;
@@ -793,6 +794,9 @@
             }
         }
 
+        // This call is not guarded by a feature flag.
+        SearchEngineChoiceNotification.handleSearchEngineChoice(this, getSnackbarManager());
+
         if (!isWarmOnResume()) {
             SuggestionsMetrics.recordArticlesListVisible();
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
index 99992e9..e07f06d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
@@ -46,6 +46,7 @@
 import org.chromium.content_public.browser.BrowserStartupController;
 import org.chromium.content_public.browser.DeviceUtils;
 import org.chromium.content_public.browser.SpeechRecognition;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
 import org.chromium.policy.CombinedPolicyProvider;
 import org.chromium.ui.resources.ResourceExtractor;
@@ -258,6 +259,7 @@
         // Check to see if we need to extract any new resources from the APK. This could
         // be on first run when we need to extract all the .pak files we need, or after
         // the user has switched locale, in which case we want new locale resources.
+        ResourceExtractor.get().setResultTraits(UiThreadTaskTraits.BOOTSTRAP);
         ResourceExtractor.get().startExtractingResources(LocaleUtils.toLanguage(
                 ChromeLocalizationUtils.getUiLocaleStringForCompressedPak()));
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
index 6856afb..b3ea22b5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEngineAdapter.java
@@ -129,6 +129,7 @@
             TemplateUrlService.getInstance().unregisterLoadListener(this);
             mHasLoadObserver = false;
         }
+
         TemplateUrlService.getInstance().removeObserver(this);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/SavePasswordsPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/SavePasswordsPreferences.java
index abdba9d..28551675fa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/SavePasswordsPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/SavePasswordsPreferences.java
@@ -27,6 +27,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference;
 import org.chromium.chrome.browser.preferences.ChromeBasePreference;
 import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
@@ -34,6 +35,7 @@
 import org.chromium.chrome.browser.preferences.PreferencesLauncher;
 import org.chromium.chrome.browser.preferences.SearchUtils;
 import org.chromium.chrome.browser.preferences.TextMessagePreference;
+import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.ui.text.SpanApplier;
 
 import java.util.Locale;
@@ -448,6 +450,12 @@
     }
 
     private void displayManageAccountLink() {
+        // See http://crbug/946332
+        if (LocaleManager.getInstance().isSpecialUser()
+                && !ChromeSigninController.get().isSignedIn()) {
+            // Don't add the Manage Account link if this is a special user and not signed in.
+            return;
+        }
         if (mSearchQuery != null && !mNoPasswords) {
             return; // Don't add the Manage Account link if there is a search going on.
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java
new file mode 100644
index 0000000..ed359bef
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java
@@ -0,0 +1,239 @@
+// Copyright 2019 The Chromium 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.search_engines;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeVersionInfo;
+import org.chromium.chrome.browser.omaha.VersionNumber;
+import org.chromium.chrome.browser.preferences.PreferencesLauncher;
+import org.chromium.chrome.browser.preferences.SearchEnginePreference;
+import org.chromium.chrome.browser.snackbar.Snackbar;
+import org.chromium.chrome.browser.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class that prompts the user to change their search engine at the browser startup.
+ * User is only meant to be propmpted once, hence the fact of prompting is saved to preferences.
+ */
+public final class SearchEngineChoiceNotification {
+    /** Key used to store the date of when search engine choice was requested. */
+    static final String PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP =
+            "search_engine_choice_requested_timestamp";
+
+    /** Key used to store the version of Chrome in which the choice was presented. */
+    static final String PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION =
+            "search_engine_choice_presented_version";
+
+    /** Key used to store the default Search Engine Type before choice is presented. */
+    static final String PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE =
+            "search_engine_choice_default_type_before";
+
+    /** Variations parameter name for notification snackbar duration (in seconds). */
+    private static final String PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS =
+            "notification-snackbar-duration-seconds";
+
+    /** Default value for notification snackbar duration (in seconds). */
+    private static final int PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT = 10;
+
+    /** Variations parameter name for invalidating version number. */
+    private static final String PARAM_NOTIFICATION_INVALIDATING_VERSION_NUMBER =
+            "notification-invalidating-version-number";
+
+    // AndroidSearchEngineChoiceEvents defined in tools/metrics/histograms/enums.xml.
+    // These values are persisted to logs. Entries should not be renumbered and numeric values
+    // should never be reused.
+    @IntDef({Events.SNACKBAR_SHOWN, Events.PROMPT_FOLLOWED, Events.SEARCH_ENGINE_CHANGED})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Events {
+        int SNACKBAR_SHOWN = 0;
+        int PROMPT_FOLLOWED = 1;
+        int SEARCH_ENGINE_CHANGED = 2;
+        int MAX = 3;
+    }
+
+    /**
+     * Snackbar controller for search engine choice notification. It takes the user to the settings
+     * page responsible for search engine choice, when button is clicked.
+     */
+    public static class NotificationSnackbarController implements SnackbarController {
+        private Context mContext;
+
+        private NotificationSnackbarController(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void onAction(Object actionData) {
+            PreferencesLauncher.launchSettingsPage(mContext, SearchEnginePreference.class);
+            recordEvent(Events.PROMPT_FOLLOWED);
+            recordSearchEngineTypeBeforeChoicePresented();
+        }
+    }
+
+    private SearchEngineChoiceNotification() {}
+
+    /**
+     * When called for the first time, it will save a preference that search engine choice was
+     * requested.
+     */
+    public static void receiveSearchEngineChoiceRequest() {
+        if (wasSearchEngineChoiceRequested()) return;
+
+        updateSearchEngineChoiceRequested();
+    }
+
+    /**
+     * Shows a search engine change notification, in form of a Snackbar. When run for the first time
+     * after showing a prompt, it reports metrics about Search Engine change.
+     *
+     * @param context Context in which to show the Snackbar.
+     * @param snackbarManager Snackbar manager which will shown and manage the Snackbar.
+     */
+    public static void handleSearchEngineChoice(Context context, SnackbarManager snackbarManager) {
+        boolean searchEngineChoiceRequested = wasSearchEngineChoiceRequested();
+        boolean searchEngineChoicePresented = wasSearchEngineChoicePresented();
+
+        if (searchEngineChoiceRequested && !searchEngineChoicePresented) {
+            snackbarManager.showSnackbar(buildSnackbarNotification(context));
+            updateSearchEngineChoicePresented();
+            recordEvent(Events.SNACKBAR_SHOWN);
+        } else if (isSearchEnginePossiblyDifferent()) {
+            @SearchEngineType
+            int previousSearchEngineType = getPreviousSearchEngineType();
+            @SearchEngineType
+            int currentSearchEngineType = getDefaultSearchEngineType();
+            if (previousSearchEngineType != currentSearchEngineType) {
+                recordEvent(Events.SEARCH_ENGINE_CHANGED);
+                RecordHistogram.recordEnumeratedHistogram(
+                        "Android.SearchEngineChoice.ChosenSearchEngine", currentSearchEngineType,
+                        SearchEngineType.SEARCH_ENGINE_MAX);
+            }
+            removePreviousSearchEngineType();
+        }
+    }
+
+    private static void recordEvent(@Events int event) {
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.SearchEngineChoice.Events", event, Events.MAX);
+    }
+
+    private static Snackbar buildSnackbarNotification(Context context) {
+        int durationSeconds = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
+                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS,
+                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT);
+
+        return Snackbar
+                .make(context.getString(R.string.search_engine_choice_prompt),
+                        new NotificationSnackbarController(context), Snackbar.TYPE_NOTIFICATION,
+                        Snackbar.UMA_SEARCH_ENGINE_CHOICE_NOTIFICATION)
+                .setAction(context.getString(R.string.preferences), null)
+                .setDuration((int) TimeUnit.SECONDS.toMillis(durationSeconds))
+                .setSingleLine(false)
+                .setTheme(Snackbar.Theme.GOOGLE);
+    }
+
+    private static void updateSearchEngineChoiceRequested() {
+        long now = System.currentTimeMillis();
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putLong(PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP, now)
+                .apply();
+    }
+
+    private static boolean wasSearchEngineChoiceRequested() {
+        return ContextUtils.getAppSharedPreferences().contains(
+                PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP);
+    }
+
+    private static void updateSearchEngineChoicePresented() {
+        String productVersion = ChromeVersionInfo.getProductVersion();
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putString(PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION, productVersion)
+                .apply();
+    }
+
+    private static boolean wasSearchEngineChoicePresented() {
+        VersionNumber lastPresentedVersionNumber = getLastPresentedVersionNumber();
+        if (lastPresentedVersionNumber == null) return false;
+
+        VersionNumber lowestAcceptedVersionNumber = getLowestAcceptedVersionNumber();
+        if (lowestAcceptedVersionNumber == null) return true;
+
+        return !lastPresentedVersionNumber.isSmallerThan(lowestAcceptedVersionNumber);
+    }
+
+    @Nullable
+    private static VersionNumber getLastPresentedVersionNumber() {
+        return VersionNumber.fromString(ContextUtils.getAppSharedPreferences().getString(
+                PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION, null));
+    }
+
+    @Nullable
+    private static VersionNumber getLowestAcceptedVersionNumber() {
+        return VersionNumber.fromString(ChromeFeatureList.getFieldTrialParamByFeature(
+                ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
+                PARAM_NOTIFICATION_INVALIDATING_VERSION_NUMBER));
+    }
+
+    @SearchEngineType
+    private static int getDefaultSearchEngineType() {
+        TemplateUrlService templateUrlService = TemplateUrlService.getInstance();
+        TemplateUrl currentSearchEngine = templateUrlService.getDefaultSearchEngineTemplateUrl();
+        if (currentSearchEngine == null) return SearchEngineType.SEARCH_ENGINE_UNKNOWN;
+        return templateUrlService.getSearchEngineTypeFromTemplateUrl(
+                currentSearchEngine.getKeyword());
+    }
+
+    private static void recordSearchEngineTypeBeforeChoicePresented() {
+        @SearchEngineType
+        int currentSearchEngineType = getDefaultSearchEngineType();
+        RecordHistogram.recordEnumeratedHistogram(
+                "Android.SearchEngineChoice.SearchEngineBeforeChoicePrompt",
+                currentSearchEngineType, SearchEngineType.SEARCH_ENGINE_MAX);
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putInt(PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE, currentSearchEngineType)
+                .apply();
+    }
+
+    private static boolean isSearchEnginePossiblyDifferent() {
+        return ContextUtils.getAppSharedPreferences().contains(
+                PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE);
+    }
+
+    @SearchEngineType
+    private static int getPreviousSearchEngineType() {
+        return ContextUtils.getAppSharedPreferences().getInt(
+                PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE,
+                SearchEngineType.SEARCH_ENGINE_UNKNOWN);
+    }
+
+    private static void removePreviousSearchEngineType() {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .remove(PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE)
+                .apply();
+    }
+
+    private static int getNotificationSnackbarDuration() {
+        return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
+                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS,
+                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
index 3aa6620a..55c72cd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
@@ -332,6 +332,11 @@
         return nativeUpdateLastVisitedForTesting(mNativeTemplateUrlServiceAndroid, keyword);
     }
 
+    @VisibleForTesting
+    static void setInstanceForTesting(TemplateUrlService service) {
+        sService = service;
+    }
+
     private native long nativeInit();
     private native void nativeLoad(long nativeTemplateUrlServiceAndroid);
     private native boolean nativeIsLoaded(long nativeTemplateUrlServiceAndroid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/Snackbar.java b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/Snackbar.java
index 344a402..9ea8a47 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/snackbar/Snackbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/snackbar/Snackbar.java
@@ -82,6 +82,7 @@
     public static final int UMA_TWA_PRIVACY_DISCLOSURE = 28;
     public static final int UMA_AUTOFILL_ASSISTANT_STOP_UNDO = 29;
     public static final int UMA_TAB_CLOSE_MULTIPLE_UNDO = 30;
+    public static final int UMA_SEARCH_ENGINE_CHOICE_NOTIFICATION = 31;
 
     private SnackbarController mController;
     private CharSequence mText;
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 07d7746..35723b3 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -452,6 +452,10 @@
         You can change this later in Settings
       </message>
 
+      <message name="IDS_SEARCH_ENGINE_CHOICE_PROMPT" desc="Prompt asking users whether they want to change the search engine">
+        You can choose your search engine
+      </message>
+
       <!-- Autofill and Payments preferences -->
       <message name="IDS_AUTOFILL_KEYBOARD_ACCESSORY_CONTENT_DESCRIPTION" desc="The text announced by the screen reader when the autofill suggestions are shown.">
         Passwords available
diff --git a/chrome/android/java/strings/android_chrome_strings_grd/IDS_SEARCH_ENGINE_CHOICE_PROMPT.png.sha1 b/chrome/android/java/strings/android_chrome_strings_grd/IDS_SEARCH_ENGINE_CHOICE_PROMPT.png.sha1
new file mode 100644
index 0000000..ff70abc
--- /dev/null
+++ b/chrome/android/java/strings/android_chrome_strings_grd/IDS_SEARCH_ENGINE_CHOICE_PROMPT.png.sha1
@@ -0,0 +1 @@
+13f3afa89a013f58ba2ce9a13c4da6b09e2cca1e
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
index 71e7711..a7fe6ca 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/privacy/ClearBrowsingDataPreferencesTest.java
@@ -70,10 +70,7 @@
  * Integration tests for ClearBrowsingDataPreferences.
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
-// Disable notifications for the default search engine so that it doesn't interfere with important
-// sites tests.
-@CommandLineFlags.
-Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "disable-features=GrantNotificationsToDSE"})
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @RetryOnFailure
 public class ClearBrowsingDataPreferencesTest {
     @Rule
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotificationTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotificationTest.java
new file mode 100644
index 0000000..33efdaf
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotificationTest.java
@@ -0,0 +1,291 @@
+// Copyright 2019 The Chromium 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.search_engines;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.metrics.test.ShadowRecordHistogram;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeVersionInfo;
+import org.chromium.chrome.browser.snackbar.Snackbar;
+import org.chromium.chrome.browser.snackbar.SnackbarManager;
+import org.chromium.chrome.test.support.DisableHistogramsRule;
+
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link SearchEngineChoiceNotification}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, shadows = {ShadowRecordHistogram.class})
+public final class SearchEngineChoiceNotificationTest {
+    private static final String TEST_INITIAL_ENGINE = "google.com";
+    private static final String TEST_ALTERNATIVE_ENGINE = "duckduckgo.com";
+
+    @Rule
+    public DisableHistogramsRule mDisableHistogramsRule = new DisableHistogramsRule();
+    @Spy
+    private Context mContext = RuntimeEnvironment.application.getApplicationContext();
+    @Mock
+    private SnackbarManager mSnackbarManager;
+    @Mock
+    private TemplateUrlService mTemplateUrlService;
+    @Mock
+    private TemplateUrl mInitialSearchEngine;
+    @Mock
+    private TemplateUrl mAlternativeSearchEngine;
+    @Captor
+    private ArgumentCaptor<Snackbar> mSnackbarArgument;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ContextUtils.initApplicationContextForTests(mContext);
+        ContextUtils.getAppSharedPreferences().edit().clear().apply();
+
+        ChromeFeatureList.setTestFeatures(new HashMap<String, Boolean>());
+        ShadowRecordHistogram.reset();
+
+        // Sets up appropriate responses from Template URL service.
+        TemplateUrlService.setInstanceForTesting(mTemplateUrlService);
+        doReturn(TEST_ALTERNATIVE_ENGINE).when(mAlternativeSearchEngine).getKeyword();
+        doReturn(SearchEngineType.SEARCH_ENGINE_DUCKDUCKGO)
+                .when(mTemplateUrlService)
+                .getSearchEngineTypeFromTemplateUrl(TEST_ALTERNATIVE_ENGINE);
+        doReturn(TEST_INITIAL_ENGINE).when(mInitialSearchEngine).getKeyword();
+        doReturn(SearchEngineType.SEARCH_ENGINE_GOOGLE)
+                .when(mTemplateUrlService)
+                .getSearchEngineTypeFromTemplateUrl(TEST_INITIAL_ENGINE);
+        doReturn(mInitialSearchEngine)
+                .when(mTemplateUrlService)
+                .getDefaultSearchEngineTemplateUrl();
+    }
+
+    @Test
+    @SmallTest
+    public void receiveSearchEngineChoiceRequest() {
+        assertFalse(ContextUtils.getAppSharedPreferences().contains(
+                SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP));
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        assertTrue(ContextUtils.getAppSharedPreferences().contains(
+                SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP));
+
+        long firstTimestamp = ContextUtils.getAppSharedPreferences().getLong(
+                SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP, 0);
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        long secondTimestamp = ContextUtils.getAppSharedPreferences().getLong(
+                SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_REQUESTED_TIMESTAMP, 0);
+
+        assertEquals(firstTimestamp, secondTimestamp);
+    }
+
+    @Test
+    @SmallTest
+    public void handleSearchEngineChoice_ignoredWhenNotRequested() {
+        assertFalse(ContextUtils.getAppSharedPreferences().contains(
+                SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION));
+
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, null);
+
+        assertFalse("When not requested, the call should have been ignored.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION));
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SNACKBAR_SHOWN));
+    }
+
+    @Test
+    @SmallTest
+    public void handleSearchEngineChoice_performedFirstTime() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        // TODO(fgorski): Snackbar content is scoped to its package, therefore cannot be verified
+        // here at this time. See whether that can be fixed.
+        verify(mSnackbarManager, times(1)).showSnackbar(any(Snackbar.class));
+
+        assertEquals("We are expecting exactly one snackbar shown event.", 1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SNACKBAR_SHOWN));
+
+        assertTrue("Version of the app should be persisted upon prompting.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION));
+
+        assertEquals("Presented version should be set to the current product version.",
+                ChromeVersionInfo.getProductVersion(),
+                ContextUtils.getAppSharedPreferences().getString(
+                        SearchEngineChoiceNotification.PREF_SEARCH_ENGINE_CHOICE_PRESENTED_VERSION,
+                        null));
+    }
+
+    @Test
+    @SmallTest
+    public void handleSearchEngineChoice_ignoredOnSubsequentCalls() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        verify(mSnackbarManager, times(1)).showSnackbar(any(Snackbar.class));
+
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        assertFalse("Second call removes the preference for search engine choice before.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE));
+
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+
+        // No increase in execution counter means it was not called again.
+        verify(mSnackbarManager, times(1)).showSnackbar(any(Snackbar.class));
+        assertEquals(1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SNACKBAR_SHOWN));
+    }
+
+    @Test
+    @SmallTest
+    public void snackbarClicked() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        verify(mSnackbarManager, times(1)).showSnackbar(mSnackbarArgument.capture());
+
+        mSnackbarArgument.getValue().getController().onAction(null);
+        assertEquals(1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.PROMPT_FOLLOWED));
+        verify(mContext, times(1)).startActivity(any(Intent.class), isNull());
+    }
+
+    @Test
+    @SmallTest
+    public void reportSearchEngineChanged_whenNoChange() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        verify(mSnackbarManager, times(1)).showSnackbar(mSnackbarArgument.capture());
+        mSnackbarArgument.getValue().getController().onAction(null);
+
+        // Simulates no change.
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+
+        assertFalse(
+                "First handleSearchEngineChoice call after prompt removes SE choice before pref.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE));
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SEARCH_ENGINE_CHANGED));
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.ChosenSearchEngine",
+                        SearchEngineType.SEARCH_ENGINE_DUCKDUCKGO));
+    }
+
+    @Test
+    @SmallTest
+    public void reportSearchEngineChanged_whenNoChangeOnFirstVisitToSettings() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        verify(mSnackbarManager, times(1)).showSnackbar(mSnackbarArgument.capture());
+        mSnackbarArgument.getValue().getController().onAction(null);
+
+        // Simulates a change between the initialization, but reporting happens only the first time.
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        assertFalse(
+                "First handleSearchEngineChoice call after prompt removes SE choice before pref.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE));
+
+        doReturn(mAlternativeSearchEngine)
+                .when(mTemplateUrlService)
+                .getDefaultSearchEngineTemplateUrl();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SEARCH_ENGINE_CHANGED));
+        assertEquals(0,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.ChosenSearchEngine",
+                        SearchEngineType.SEARCH_ENGINE_DUCKDUCKGO));
+    }
+
+    @Test
+    @SmallTest
+    public void reportSearchEngineChanged_onlyFirstTime() {
+        SearchEngineChoiceNotification.receiveSearchEngineChoiceRequest();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+        verify(mSnackbarManager, times(1)).showSnackbar(mSnackbarArgument.capture());
+        mSnackbarArgument.getValue().getController().onAction(null);
+
+        // Simulates a change of search engine on the first visit to settings.
+        doReturn(mAlternativeSearchEngine)
+                .when(mTemplateUrlService)
+                .getDefaultSearchEngineTemplateUrl();
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+
+        assertEquals("Event is recorded when search engine was changed.", 1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SEARCH_ENGINE_CHANGED));
+        assertEquals("Newly chosen search engine type should be recoreded.", 1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.ChosenSearchEngine",
+                        SearchEngineType.SEARCH_ENGINE_DUCKDUCKGO));
+
+        assertFalse(
+                "First handleSearchEngineChoice call after prompt removes SE choice before pref.",
+                ContextUtils.getAppSharedPreferences().contains(
+                        SearchEngineChoiceNotification
+                                .PREF_SEARCH_ENGINE_CHOICE_DEFAULT_TYPE_BEFORE));
+
+        SearchEngineChoiceNotification.handleSearchEngineChoice(mContext, mSnackbarManager);
+
+        assertEquals("Event should only be recorded once, therefore count should be still 1.", 1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.Events",
+                        SearchEngineChoiceNotification.Events.SEARCH_ENGINE_CHANGED));
+        assertEquals("New Search Engine shoudl only be reported once, therefore count should be 1",
+                1,
+                ShadowRecordHistogram.getHistogramValueCountForTesting(
+                        "Android.SearchEngineChoice.ChosenSearchEngine",
+                        SearchEngineType.SEARCH_ENGINE_DUCKDUCKGO));
+    }
+}
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 4c6f29c..39c5b8d 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-75.0.3750.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-75.0.3752.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 16e2dea..75e297a 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -195,7 +195,6 @@
   # Allow mojo generated files in WebKit. These files use STL types and
   # don't use WTF types.
   "+third_party/blink/public/platform/modules/budget_service/budget_service.mojom.h",
-  "+third_party/blink/public/platform/modules/installation/installation.mojom.h",
   "+third_party/blink/public/platform/modules/presentation/presentation.mojom.h",
 
   # The following restrictions are for ChromeOS and in particular mus/mash where
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 97f6413..9a3961d 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -108,6 +108,7 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/common/feature_h264_with_openh264_ffmpeg.h"
 #include "device/base/features.h"
+#include "device/fido/features.h"
 #include "device/vr/buildflags/buildflags.h"
 #include "extensions/buildflags/buildflags.h"
 #include "gpu/config/gpu_finch_features.h"
@@ -3088,6 +3089,13 @@
      kOsDesktop, FEATURE_VALUE_TYPE(features::kWebAuthCable)},
 #endif  // !defined(OS_ANDROID)
 
+#if !defined(OS_ANDROID)
+    {"enable-web-authentication-pin-support",
+     flag_descriptions::kEnableWebAuthenticationPINSupportName,
+     flag_descriptions::kEnableWebAuthenticationPINSupportDescription,
+     kOsDesktop, FEATURE_VALUE_TYPE(device::kWebAuthPINSupport)},
+#endif  // !defined(OS_ANDROID)
+
 #if defined(OS_ANDROID)
     {"enable-sole-integration", flag_descriptions::kSoleIntegrationName,
      flag_descriptions::kSoleIntegrationDescription, kOsAndroid,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 3a943b8..68952c3 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -94,6 +94,7 @@
     &kAndroidPayIntegrationV1,
     &kAndroidPayIntegrationV2,
     &kAndroidPaymentApps,
+    &kAndroidSearchEngineChoiceNotification,
     &kAndroidSiteSettingsUIRefresh,
     &kBackgroundTaskSchedulerForBackgroundSync,
     &kCastDeviceFilter,
@@ -244,6 +245,10 @@
 // TODO(rouslan): Remove this.
 const base::Feature kAndroidPaymentApps{"AndroidPaymentApps",
                                         base::FEATURE_ENABLED_BY_DEFAULT};
+
+const base::Feature kAndroidSearchEngineChoiceNotification{
+    "AndroidSearchEngineChoiceNotification", base::FEATURE_ENABLED_BY_DEFAULT};
+
 const base::Feature kAndroidSiteSettingsUIRefresh{
     "AndroidSiteSettingsUIRefresh", base::FEATURE_ENABLED_BY_DEFAULT};
 
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 879d274..234540f 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -20,6 +20,7 @@
 extern const base::Feature kAndroidPayIntegrationV1;
 extern const base::Feature kAndroidPayIntegrationV2;
 extern const base::Feature kAndroidPaymentApps;
+extern const base::Feature kAndroidSearchEngineChoiceNotification;
 extern const base::Feature kAndroidSiteSettingsUIRefresh;
 extern const base::Feature kAndroidWebContentsDarkMode;
 extern const base::Feature kBackgroundTaskComponentUpdate;
diff --git a/chrome/browser/android/webapps/add_to_homescreen_manager.cc b/chrome/browser/android/webapps/add_to_homescreen_manager.cc
index 615a5c6..afdc489 100644
--- a/chrome/browser/android/webapps/add_to_homescreen_manager.cc
+++ b/chrome/browser/android/webapps/add_to_homescreen_manager.cc
@@ -22,7 +22,7 @@
 #include "jni/AddToHomescreenManager_jni.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
-#include "third_party/blink/public/platform/modules/installation/installation.mojom.h"
+#include "third_party/blink/public/mojom/installation/installation.mojom.h"
 #include "ui/gfx/android/java_bitmap.h"
 
 using base::android::JavaParamRef;
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index 9b9ef20..d45a738 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -30,7 +30,7 @@
 #include "content/public/browser/web_contents.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
-#include "third_party/blink/public/platform/modules/installation/installation.mojom.h"
+#include "third_party/blink/public/mojom/installation/installation.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 8a118cc8..588f8f58 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -413,9 +413,7 @@
       FROM_HERE, base::BindOnce(&InitializeModuleDatabase,
                                 third_party_blocking_policy_enabled));
 
-  *module_watcher =
-      ModuleWatcher::Create(base::BindRepeating(&OnModuleEvent),
-                            /* report_background_loaded_modules = */ true);
+  *module_watcher = ModuleWatcher::Create(base::BindRepeating(&OnModuleEvent));
 }
 
 void ShowCloseBrowserFirstMessageBox() {
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index f922aba..27e4ef5 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1286,8 +1286,6 @@
     "login/screens/network_screen_view.h",
     "login/screens/recommend_apps/recommend_apps_fetcher.cc",
     "login/screens/recommend_apps/recommend_apps_fetcher.h",
-    "login/screens/recommend_apps/recommend_apps_fetcher_impl.cc",
-    "login/screens/recommend_apps/recommend_apps_fetcher_impl.h",
     "login/screens/recommend_apps_screen.cc",
     "login/screens/recommend_apps_screen.h",
     "login/screens/recommend_apps_screen_view.h",
diff --git a/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.cc b/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.cc
index 066d453..361a87f 100644
--- a/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.cc
+++ b/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.cc
@@ -116,11 +116,6 @@
   StopObserveNetwork();
 }
 
-KerberosFilesHandler*
-AuthPolicyCredentialsManager::GetKerberosFilesHandlerForTesting() {
-  return &kerberos_files_handler_;
-}
-
 void AuthPolicyCredentialsManager::GetUserStatus() {
   DCHECK(!is_get_status_in_progress_);
   is_get_status_in_progress_ = true;
diff --git a/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.h b/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.h
index 389f7f99..a4e98a49f 100644
--- a/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.h
+++ b/chrome/browser/chromeos/authpolicy/auth_policy_credentials_manager.h
@@ -53,8 +53,6 @@
       const chromeos::NetworkState* network) override;
   void OnShuttingDown() override;
 
-  KerberosFilesHandler* GetKerberosFilesHandlerForTesting();
-
  private:
   friend class AuthPolicyCredentialsManagerTest;
   // Calls AuthPolicyClient::GetUserStatus method.
@@ -130,7 +128,6 @@
   friend struct base::DefaultSingletonTraits<
       AuthPolicyCredentialsManagerFactory>;
   friend class AuthPolicyCredentialsManagerTest;
-  friend class ExistingUserControllerActiveDirectoryTest;
 
   AuthPolicyCredentialsManagerFactory();
   ~AuthPolicyCredentialsManagerFactory() override;
diff --git a/chrome/browser/chromeos/authpolicy/kerberos_files_handler.cc b/chrome/browser/chromeos/authpolicy/kerberos_files_handler.cc
index d847d9b..939febf6 100644
--- a/chrome/browser/chromeos/authpolicy/kerberos_files_handler.cc
+++ b/chrome/browser/chromeos/authpolicy/kerberos_files_handler.cc
@@ -41,17 +41,7 @@
 
 // Writes |blob| into file <UserPath>/kerberos/|file_name|. First writes into
 // temporary file and then replaces existing one.
-void WriteFile(const base::FilePath& path, base::Optional<std::string> blob) {
-  if (!blob.has_value())
-    return;
-  if (!base::ImportantFileWriter::WriteFileAtomically(path, blob.value()))
-    LOG(ERROR) << "Failed to write file " << path.value();
-}
-
-// Writes |krb5cc| to <DIR_HOME>/kerberos/krb5cc and |krb5config| to
-// <DIR_HOME>/kerberos/krb5.conf if set. Creates directories if necessary.
-void WriteFiles(base::Optional<std::string> krb5cc,
-                base::Optional<std::string> krb5config) {
+void WriteFile(const std::string& file_name, const std::string& blob) {
   base::FilePath dir;
   base::PathService::Get(base::DIR_HOME, &dir);
   dir = dir.Append(kKrb5Directory);
@@ -61,22 +51,19 @@
                << "' directory: " << base::File::ErrorToString(error);
     return;
   }
-
-  WriteFile(dir.Append(kKrb5CCFile), std::move(krb5cc));
-  WriteFile(dir.Append(kKrb5ConfFile), std::move(krb5config));
+  base::FilePath dest_file = dir.Append(file_name);
+  if (!base::ImportantFileWriter::WriteFileAtomically(dest_file, blob)) {
+    LOG(ERROR) << "Failed to write file " << dest_file.value();
+  }
 }
 
-// If |config| has a value, puts canonicalization settings first depending on
-// user policy. Whatever setting comes first wins, so even if krb5.conf sets
-// rdns or dns_canonicalize_hostname below, it would get overridden.
-base::Optional<std::string> MaybeAdjustConfig(
-    base::Optional<std::string> config,
-    bool is_dns_cname_enabled) {
-  if (!config.has_value())
-    return base::nullopt;
+// Put canonicalization settings first depending on user policy. Whatever
+// setting comes first wins, so even if krb5.conf sets rdns or
+// dns_canonicalize_hostname below, it would get overridden.
+std::string AdjustConfig(const std::string& config, bool is_dns_cname_enabled) {
   std::string adjusted_config = base::StringPrintf(
       kKrb5CnameSettings, is_dns_cname_enabled ? "true" : "false");
-  adjusted_config.append(config.value());
+  adjusted_config.append(config);
   return adjusted_config;
 }
 
@@ -125,20 +112,23 @@
 
 void KerberosFilesHandler::SetFiles(base::Optional<std::string> krb5cc,
                                     base::Optional<std::string> krb5conf) {
-  krb5conf =
-      MaybeAdjustConfig(krb5conf, !negotiate_disable_cname_lookup_.GetValue());
-  base::PostTaskWithTraitsAndReply(
-      FROM_HERE,
-      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
-       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
-      base::BindOnce(&WriteFiles, std::move(krb5cc), std::move(krb5conf)),
-      base::BindOnce(&KerberosFilesHandler::OnFilesChanged,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void KerberosFilesHandler::SetFilesChangedForTesting(
-    base::OnceClosure callback) {
-  files_changed_for_testing_ = std::move(callback);
+  if (krb5cc.has_value()) {
+    base::PostTaskWithTraits(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+        base::BindOnce(&WriteFile, kKrb5CCFile, krb5cc.value()));
+  }
+  if (krb5conf.has_value()) {
+    base::PostTaskWithTraits(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+        base::BindOnce(
+            &WriteFile, kKrb5ConfFile,
+            AdjustConfig(krb5conf.value(),
+                         !negotiate_disable_cname_lookup_.GetValue())));
+  }
 }
 
 void KerberosFilesHandler::OnDisabledAuthNegotiateCnameLookupChanged() {
@@ -146,9 +136,4 @@
   get_kerberos_files_.Run();
 }
 
-void KerberosFilesHandler::OnFilesChanged() {
-  if (files_changed_for_testing_)
-    std::move(files_changed_for_testing_).Run();
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/authpolicy/kerberos_files_handler.h b/chrome/browser/chromeos/authpolicy/kerberos_files_handler.h
index df223e2d..2f93805 100644
--- a/chrome/browser/chromeos/authpolicy/kerberos_files_handler.h
+++ b/chrome/browser/chromeos/authpolicy/kerberos_files_handler.h
@@ -30,24 +30,15 @@
   void SetFiles(base::Optional<std::string> krb5cc,
                 base::Optional<std::string> krb5conf);
 
-  // Sets a callback for when disk IO task posted by SetFiles has finished.
-  void SetFilesChangedForTesting(base::OnceClosure callback);
-
  private:
   // Called whenever prefs::kDisableAuthNegotiateCnameLookup is changed.
   void OnDisabledAuthNegotiateCnameLookupChanged();
 
-  // Forwards to |files_changed_for_testing_| if set.
-  void OnFilesChanged();
-
   PrefMember<bool> negotiate_disable_cname_lookup_;
 
   // Triggers a fetch of Kerberos files. Called when the watched pref changes.
   base::RepeatingClosure get_kerberos_files_;
 
-  // Called when disk IO queued by SetFiles has finished.
-  base::OnceClosure files_changed_for_testing_;
-
   base::WeakPtrFactory<KerberosFilesHandler> weak_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(KerberosFilesHandler);
 };
diff --git a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
index 7d3b97c..81d363c2 100644
--- a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
@@ -5,14 +5,15 @@
 #include <string>
 #include <vector>
 
+#include "base/barrier_closure.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/callback.h"
 #include "base/command_line.h"
+#include "base/files/file_path_watcher.h"
 #include "base/location.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_restrictions.h"
@@ -35,8 +36,6 @@
 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/browser.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chromeos/constants/chromeos_switches.h"
@@ -70,13 +69,13 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using ::testing::_;
 using ::testing::AnyNumber;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
 using ::testing::Return;
 using ::testing::ReturnNull;
 using ::testing::WithArg;
+using ::testing::_;
 
 namespace em = enterprise_management;
 
@@ -91,6 +90,7 @@
 const char kSupervisedUserID[] = "supervised_user@locally-managed.localhost";
 const char kPassword[] = "test_password";
 const char kActiveDirectoryRealm[] = "active.directory.realm";
+const char kKrb5CCFilePrefix[] = "FILE:";
 
 const char kPublicSessionUserEmail[] = "public_session_user@localhost";
 const int kAutoLoginNoDelay = 0;
@@ -116,18 +116,78 @@
   }
 }
 
-base::FilePath GetKerberosConfigPath() {
-  base::FilePath path;
-  EXPECT_TRUE(base::PathService::Get(base::DIR_HOME, &path));
-  return path.Append("kerberos").Append("krb5.conf");
+std::string GetKerberosConfigFileName() {
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  std::string config_file;
+  EXPECT_TRUE(env->GetVar("KRB5_CONFIG", &config_file));
+  return config_file;
 }
 
-base::FilePath GetKerberosCredentialsCachePath() {
-  base::FilePath path;
-  EXPECT_TRUE(base::PathService::Get(base::DIR_HOME, &path));
-  return path.Append("kerberos").Append("krb5cc");
+std::string GetKerberosCredentialsCacheFileName() {
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  std::string creds_file;
+  EXPECT_TRUE(env->GetVar("KRB5CCNAME", &creds_file));
+  EXPECT_EQ(kKrb5CCFilePrefix, creds_file.substr(0, strlen(kKrb5CCFilePrefix)));
+  return creds_file.substr(strlen(kKrb5CCFilePrefix));
 }
 
+// Helper class to wait when both Kerberos credentials cache and config file
+// changed.
+class KerberosFilesChangeWaiter {
+ public:
+  // If |files_must_exist| is true and a file already exists the class does not
+  // wait when it changes.
+  explicit KerberosFilesChangeWaiter(bool files_must_exist) {
+    barrier_closure_ = base::BarrierClosure(2, loop_.QuitClosure());
+
+    watch_callback_ = base::BindRepeating(
+        [](const base::RepeatingClosure& barrier_closure,
+           const base::FilePath& path, bool error) -> void {
+          EXPECT_FALSE(error);
+          barrier_closure.Run();
+        },
+        barrier_closure_);
+
+    config_watcher_ = std::make_unique<base::FilePathWatcher>();
+    MaybeStartWatch(&config_watcher_,
+                    base::FilePath(GetKerberosConfigFileName()),
+                    files_must_exist);
+
+    creds_watcher_ = std::make_unique<base::FilePathWatcher>();
+    MaybeStartWatch(&creds_watcher_,
+                    base::FilePath(GetKerberosCredentialsCacheFileName()),
+                    files_must_exist);
+  }
+
+  // Should be called once.
+  void Wait() {
+    base::ScopedAllowBlockingForTesting allow_io;
+    loop_.Run();
+    config_watcher_.reset();
+    creds_watcher_.reset();
+  }
+
+ private:
+  void MaybeStartWatch(std::unique_ptr<base::FilePathWatcher>* watcher,
+                       const base::FilePath& path,
+                       bool files_must_exist) {
+    base::ScopedAllowBlockingForTesting allow_io;
+    (*watcher)->Watch(path, false /* recursive */, watch_callback_);
+    if (!files_must_exist && base::PathExists(path)) {
+      watch_callback_.Run(path, false /* error */);
+      watcher->reset();
+    }
+  }
+  base::RunLoop loop_;
+  base::RepeatingClosure barrier_closure_;
+
+  base::RepeatingCallback<void(const base::FilePath& path, bool error)>
+      watch_callback_;
+
+  std::unique_ptr<base::FilePathWatcher> config_watcher_;
+  std::unique_ptr<base::FilePathWatcher> creds_watcher_;
+};
+
 }  // namespace
 
 class ExistingUserControllerTest : public policy::DevicePolicyCrosBrowserTest {
@@ -810,7 +870,8 @@
     existing_user_controller()->CompleteLogin(user_context);
 
     profile_prepared_observer.Wait();
-    WaitForKerberosFilesChanged();
+    KerberosFilesChangeWaiter files_change_waiter(false /* files_must_exist */);
+    files_change_waiter.Wait();
     CheckKerberosFiles(true /* enable_dns_cname_lookup */);
   }
 
@@ -848,19 +909,20 @@
   void CheckKerberosFiles(bool enable_dns_cname_lookup) {
     base::ScopedAllowBlockingForTesting allow_io;
     std::string file_contents;
-    EXPECT_TRUE(
-        base::ReadFileToString(GetKerberosConfigPath(), &file_contents));
+    EXPECT_TRUE(base::ReadFileToString(
+        base::FilePath(GetKerberosConfigFileName()), &file_contents));
     EXPECT_EQ(GetExpectedKerberosConfig(enable_dns_cname_lookup),
               file_contents);
 
-    EXPECT_TRUE(base::ReadFileToString(GetKerberosCredentialsCachePath(),
-                                       &file_contents));
+    EXPECT_TRUE(base::ReadFileToString(
+        base::FilePath(GetKerberosCredentialsCacheFileName()), &file_contents));
     EXPECT_EQ(file_contents,
               FakeAuthPolicyClient::Get()->user_kerberos_creds());
   }
 
   // Applies policy and waits until both config and credentials files changed.
   void ApplyPolicyAndWaitFilesChanged(bool enable_dns_cname_lookup) {
+    KerberosFilesChangeWaiter files_change_waiter(true /* files_must_exist */);
     policy::PolicyMap policies;
     policies.Set(policy::key::kDisableAuthNegotiateCnameLookup,
                  policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
@@ -868,26 +930,7 @@
                  std::make_unique<base::Value>(!enable_dns_cname_lookup),
                  nullptr);
     UpdateProviderPolicy(policies);
-    WaitForKerberosFilesChanged();
-  }
-
-  // Waits until the Kerberos files change on disk.
-  void WaitForKerberosFilesChanged() {
-    auto* auth_policy_credentials_manager =
-        static_cast<AuthPolicyCredentialsManager*>(
-            AuthPolicyCredentialsManagerFactory::GetInstance()
-                ->GetServiceForBrowserContext(
-                    ProfileManager::GetLastUsedProfile(), false /* create */));
-    EXPECT_TRUE(auth_policy_credentials_manager);
-
-    base::RunLoop run_loop;
-    auth_policy_credentials_manager->GetKerberosFilesHandlerForTesting()
-        ->SetFilesChangedForTesting(base::BindOnce(
-            [](base::OnceClosure quit_closure) {
-              std::move(quit_closure).Run();
-            },
-            run_loop.QuitClosure()));
-    run_loop.Run();
+    files_change_waiter.Wait();
   }
 
  private:
@@ -929,8 +972,9 @@
 
 // Tests if DisabledAuthNegotiateCnameLookup changes trigger updating user
 // Kerberos files.
+// Disabled due to flakiness, see https://crbug.com/865206.
 IN_PROC_BROWSER_TEST_F(ExistingUserControllerActiveDirectoryTest,
-                       PolicyChangeTriggersFileUpdate) {
+                       DISABLED_PolicyChangeTriggersFileUpdate) {
   LoginAdOnline();
 
   ApplyPolicyAndWaitFilesChanged(false /* enable_dns_cname_lookup */);
@@ -942,12 +986,15 @@
 
 // Tests if user Kerberos files changed D-Bus signal triggers updating user
 // Kerberos files.
-IN_PROC_BROWSER_TEST_F(ExistingUserControllerActiveDirectoryTest,
-                       UserKerberosFilesChangedSignalTriggersFileUpdate) {
+// Disabled due to flakiness, see https://crbug.com/865206.
+IN_PROC_BROWSER_TEST_F(
+    ExistingUserControllerActiveDirectoryTest,
+    DISABLED_UserKerberosFilesChangedSignalTriggersFileUpdate) {
   LoginAdOnline();
+  KerberosFilesChangeWaiter files_change_waiter(true /* files_must_exist */);
   FakeAuthPolicyClient::Get()->SetUserKerberosFiles("new_kerberos_creds",
                                                     "new_kerberos_config");
-  WaitForKerberosFilesChanged();
+  files_change_waiter.Wait();
   CheckKerberosFiles(true /* enable_dns_cname_lookup */);
 }
 
@@ -968,7 +1015,8 @@
       content::NotificationService::AllSources());
   existing_user_controller()->Login(user_context, SigninSpecifics());
   profile_prepared_observer.Wait();
-  WaitForKerberosFilesChanged();
+  KerberosFilesChangeWaiter files_change_waiter(false /* files_must_exist */);
+  files_change_waiter.Wait();
   CheckKerberosFiles(true /* enable_dns_cname_lookup */);
 }
 
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.cc b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.cc
index 08ca541..3d5c804c 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.cc
@@ -1,37 +1,633 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// 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/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
 
-#include "base/callback.h"
-#include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.h"
+#include "ash/public/interfaces/constants.mojom.h"
+#include "ash/public/interfaces/cros_display_config.mojom.h"
+#include "base/base64url.h"
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/common/service_manager_connection.h"
+#include "extensions/common/api/system_display.h"
+#include "gpu/config/gpu_info.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "third_party/zlib/google/compression_utils.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/events/devices/input_device.h"
+#include "ui/events/devices/input_device_manager.h"
+#include "ui/gfx/extension_set.h"
+#include "ui/gl/gl_version_info.h"
 
 namespace chromeos {
 
 namespace {
 
-// The factory callback that will be used to create RecommendAppsFetcher
-// instances other than default RecommendAppsFetcherImpl.
-// It can be set by SetFactoryCallbackForTesting().
-RecommendAppsFetcher::FactoryCallback* g_factory_callback = nullptr;
+constexpr const char kGetAppListUrl[] =
+    "https://android.clients.google.com/fdfe/chrome/getfastreinstallappslist";
+
+constexpr int kResponseErrorNotEnoughApps = 5;
+
+constexpr int kResponseErrorNotFirstTimeChromebookUser = 6;
+
+constexpr base::TimeDelta kDownloadTimeOut = base::TimeDelta::FromMinutes(1);
+
+constexpr const int64_t kMaxDownloadBytes = 1024 * 1024;  // 1Mb
+
+constexpr const int kMaxAppCount = 21;
+
+enum RecommendAppsResponseParseResult {
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_ERROR = 0,
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON = 1,
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP = 2,
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_OWNS_CHROMEBOOK_ALREADY = 3,
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_UNKNOWN_ERROR_CODE = 4,
+  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE = 5,
+
+  kMaxValue = RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE
+};
+
+bool HasTouchScreen() {
+  return !ui::InputDeviceManager::GetInstance()
+              ->GetTouchscreenDevices()
+              .empty();
+}
+
+bool HasStylusInput() {
+  // Check to see if the hardware reports it is stylus capable.
+  for (const ui::TouchscreenDevice& device :
+       ui::InputDeviceManager::GetInstance()->GetTouchscreenDevices()) {
+    if (device.has_stylus &&
+        device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool HasKeyboard() {
+  return !ui::InputDeviceManager::GetInstance()->GetKeyboardDevices().empty();
+}
+
+bool HasHardKeyboard() {
+  for (const ui::InputDevice& device :
+       ui::InputDeviceManager::GetInstance()->GetKeyboardDevices()) {
+    if (!device.phys.empty())
+      return true;
+  }
+
+  return false;
+}
+
+gfx::Size GetScreenSize() {
+  return display::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
+}
+
+// TODO(rsgingerrs): This function is copied from Play. We need to find a way to
+// keep this synced with the Play side if there are any changes there. Another
+// approach is to let the server do the calculation since we have provided the
+// screen width, height and dpi.
+int CalculateStableScreenLayout(const int screen_width,
+                                const int screen_height,
+                                const float dpi) {
+  const int density_default = 160;
+  const float px_to_dp = density_default / static_cast<float>(dpi);
+  const int short_size_dp = static_cast<int>(screen_width * px_to_dp);
+  const int long_size_dp = static_cast<int>(screen_height * px_to_dp);
+
+  int screen_layout_size;
+  bool screen_layout_long;
+
+  const int screenlayout_size_small = 0x01;
+  const int screenlayout_size_normal = 0x02;
+  const int screenlayout_size_large = 0x03;
+  const int screenlayout_size_xlarge = 0x04;
+  const int screenlayout_long_no = 0x10;
+
+  // These semi-magic numbers define our compatibility modes for
+  // applications with different screens.  These are guarantees to
+  // app developers about the space they can expect for a particular
+  // configuration.  DO NOT CHANGE!
+  if (long_size_dp < 470) {
+    // This is shorter than an HVGA normal density screen (which
+    // is 480 pixels on its long side).
+    screen_layout_size = screenlayout_size_small;
+    screen_layout_long = false;
+  } else {
+    // What size is this screen?
+    if (long_size_dp >= 960 && short_size_dp >= 720) {
+      // 1.5xVGA or larger screens at medium density are the point
+      // at which we consider it to be an extra large screen.
+      screen_layout_size = screenlayout_size_xlarge;
+    } else if (long_size_dp >= 640 && short_size_dp >= 480) {
+      // VGA or larger screens at medium density are the point
+      // at which we consider it to be a large screen.
+      screen_layout_size = screenlayout_size_large;
+    } else {
+      screen_layout_size = screenlayout_size_normal;
+    }
+
+    // Is this a long screen? Anything wider than WVGA (5:3) is considering to
+    // be long.
+    screen_layout_long = ((long_size_dp * 3) / 5) >= (short_size_dp - 1);
+  }
+
+  int screen_layout = screen_layout_size;
+  if (!screen_layout_long) {
+    screen_layout |= screenlayout_long_no;
+  }
+
+  return screen_layout;
+}
+
+device_configuration::DeviceConfigurationProto_ScreenLayout
+GetScreenLayoutSizeId(const int screen_layout_size_value) {
+  const int screenlayout_size_small = 0x01;
+  const int screenlayout_size_normal = 0x02;
+  const int screenlayout_size_large = 0x03;
+  const int screenlayout_size_xlarge = 0x04;
+  const int screenlayout_size_mask = 0x0f;
+  int size_bits = screen_layout_size_value & screenlayout_size_mask;
+
+  switch (size_bits) {
+    case screenlayout_size_small:
+      return device_configuration::DeviceConfigurationProto_ScreenLayout::
+          DeviceConfigurationProto_ScreenLayout_SMALL;
+    case screenlayout_size_normal:
+      return device_configuration::DeviceConfigurationProto_ScreenLayout::
+          DeviceConfigurationProto_ScreenLayout_NORMAL;
+    case screenlayout_size_large:
+      return device_configuration::DeviceConfigurationProto_ScreenLayout::
+          DeviceConfigurationProto_ScreenLayout_LARGE;
+    case screenlayout_size_xlarge:
+      return device_configuration::DeviceConfigurationProto_ScreenLayout::
+          DeviceConfigurationProto_ScreenLayout_EXTRA_LARGE;
+    default:
+      return device_configuration::DeviceConfigurationProto_ScreenLayout::
+          DeviceConfigurationProto_ScreenLayout_UNDEFINED_SCREEN_LAYOUT;
+  }
+}
+
+const gpu::GPUInfo GetGPUInfo() {
+  return content::GpuDataManager::GetInstance()->GetGPUInfo();
+}
+
+// This function converts the major and minor versions to the proto accepted
+// value. For example, if the version is 3.2, the return value is 0x00030002.
+unsigned GetGLVersionInfo() {
+  const gpu::GPUInfo gpu_info = GetGPUInfo();
+  gfx::ExtensionSet extensionSet(gfx::MakeExtensionSet(gpu_info.gl_extensions));
+  gl::GLVersionInfo glVersionInfo(gpu_info.gl_version.c_str(),
+                                  gpu_info.gl_renderer.c_str(), extensionSet);
+
+  unsigned major_version = glVersionInfo.major_version;
+  unsigned minor_version = glVersionInfo.minor_version;
+  unsigned version = 0x0000ffff;
+  version &= minor_version;
+  version |= (major_version << 16) & 0xffff0000;
+
+  return version;
+}
+
+gfx::ExtensionSet GetGLExtensions() {
+  const gpu::GPUInfo gpu_info = GetGPUInfo();
+  gfx::ExtensionSet extensionSet(gfx::MakeExtensionSet(gpu_info.gl_extensions));
+
+  return extensionSet;
+}
+
+const std::string& GetAndroidSdkVersion(const arc::ArcFeatures& arc_features) {
+  return arc_features.build_props.at("ro.build.version.sdk");
+}
+
+std::vector<std::string> GetCpuAbiList(const arc::ArcFeatures& arc_features) {
+  const std::string& abi_list_str =
+      arc_features.build_props.at("ro.product.cpu.abilist");
+  return base::SplitString(abi_list_str, ",", base::TRIM_WHITESPACE,
+                           base::SPLIT_WANT_ALL);
+}
+
+std::string CompressAndEncodeProtoMessageOnBlockingThread(
+    device_configuration::DeviceConfigurationProto device_config) {
+  std::string encoded_device_configuration_proto;
+
+  std::string serialized_proto;
+  device_config.SerializeToString(&serialized_proto);
+  std::string compressed_proto;
+  compression::GzipCompress(serialized_proto, &compressed_proto);
+  base::Base64UrlEncode(compressed_proto,
+                        base::Base64UrlEncodePolicy::OMIT_PADDING,
+                        &encoded_device_configuration_proto);
+
+  return encoded_device_configuration_proto;
+}
+
+void RecordUmaResponseAppCount(int app_count) {
+  UMA_HISTOGRAM_CUSTOM_COUNTS("OOBE.RecommendApps.Fetcher.AppCount", app_count,
+                              0, kMaxAppCount, kMaxAppCount + 1);
+}
+
+void RecordUmaDownloadTime(base::TimeDelta download_time) {
+  UMA_HISTOGRAM_TIMES("OOBE.RecommendApps.Fetcher.DownloadTime", download_time);
+}
+
+void RecordUmaResponseCode(int code) {
+  base::UmaHistogramSparse("OOBE.RecommendApps.Fetcher.ResponseCode", code);
+}
+
+void RecordUmaResponseParseResult(RecommendAppsResponseParseResult result) {
+  UMA_HISTOGRAM_ENUMERATION("OOBE.RecommendApps.Fetcher.ResponseParseResult",
+                            result);
+}
+
+void RecordUmaResponseSize(unsigned long responseSize) {
+  UMA_HISTOGRAM_COUNTS_1M(
+      "OOBE.RecommendApps.Fetcher.ResponseSize",
+      static_cast<base::HistogramBase::Sample>(responseSize));
+}
 
 }  // namespace
 
-// static
-std::unique_ptr<RecommendAppsFetcher> RecommendAppsFetcher::Create(
-    RecommendAppsScreenView* view) {
-  if (g_factory_callback)
-    return g_factory_callback->Run(view);
-  return std::make_unique<RecommendAppsFetcherImpl>(view);
+RecommendAppsFetcher::RecommendAppsFetcher(RecommendAppsScreenView* view)
+    : view_(view), weak_ptr_factory_(this) {
+  service_manager::Connector* connector =
+      content::ServiceManagerConnection::GetForProcess()->GetConnector();
+  DCHECK(connector);
+  connector->BindInterface(ash::mojom::kServiceName, &cros_display_config_);
+
+  PopulateDeviceConfig();
+  StartAshRequest();
+  arc::ArcFeaturesParser::GetArcFeatures(
+      base::BindOnce(&RecommendAppsFetcher::OnArcFeaturesRead,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
-// static
-void RecommendAppsFetcher::SetFactoryCallbackForTesting(
-    FactoryCallback* callback) {
-  DCHECK(!g_factory_callback || !callback);
+RecommendAppsFetcher::~RecommendAppsFetcher() = default;
 
-  g_factory_callback = callback;
+void RecommendAppsFetcher::PopulateDeviceConfig() {
+  if (!HasTouchScreen()) {
+    device_config_.set_touch_screen(
+        device_configuration::DeviceConfigurationProto_TouchScreen::
+            DeviceConfigurationProto_TouchScreen_NOTOUCH);
+  } else if (!HasStylusInput()) {
+    device_config_.set_touch_screen(
+        device_configuration::DeviceConfigurationProto_TouchScreen::
+            DeviceConfigurationProto_TouchScreen_FINGER);
+  } else {
+    device_config_.set_touch_screen(
+        device_configuration::DeviceConfigurationProto_TouchScreen::
+            DeviceConfigurationProto_TouchScreen_STYLUS);
+  }
+
+  if (!HasKeyboard()) {
+    device_config_.set_keyboard(
+        device_configuration::DeviceConfigurationProto_Keyboard::
+            DeviceConfigurationProto_Keyboard_NOKEYS);
+  } else {
+    // TODO(rsgingerrs): Currently there is no straightforward way to determine
+    // whether it is a full keyboard or not. We assume it is safe to set it as
+    // QWERTY keyboard for this feature.
+    device_config_.set_keyboard(
+        device_configuration::DeviceConfigurationProto_Keyboard::
+            DeviceConfigurationProto_Keyboard_QWERTY);
+  }
+  device_config_.set_has_hard_keyboard(HasHardKeyboard());
+
+  // TODO(rsgingerrs): There is no straightforward way to get this info. We
+  // assume it is safe to set it as no navigation.
+  device_config_.set_navigation(
+      device_configuration::DeviceConfigurationProto_Navigation::
+          DeviceConfigurationProto_Navigation_NONAV);
+  device_config_.set_has_five_way_navigation(false);
+
+  device_config_.set_gl_es_version(GetGLVersionInfo());
+
+  for (const base::StringPiece& gl_extension : GetGLExtensions()) {
+    if (!gl_extension.empty())
+      device_config_.add_gl_extension(gl_extension.as_string());
+  }
+}
+
+void RecommendAppsFetcher::StartAshRequest() {
+  cros_display_config_->GetDisplayUnitInfoList(
+      false /* single_unified */,
+      base::BindOnce(&RecommendAppsFetcher::OnAshResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void RecommendAppsFetcher::MaybeStartCompressAndEncodeProtoMessage() {
+  if (!ash_ready_ || !arc_features_ready_ || has_started_proto_processing_)
+    return;
+
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+      base::BindOnce(&CompressAndEncodeProtoMessageOnBlockingThread,
+                     std::move(device_config_)),
+      base::BindOnce(&RecommendAppsFetcher::OnProtoMessageCompressedAndEncoded,
+                     weak_ptr_factory_.GetWeakPtr()));
+  has_started_proto_processing_ = true;
+}
+
+void RecommendAppsFetcher::OnProtoMessageCompressedAndEncoded(
+    std::string encoded_device_configuration_proto) {
+  proto_compressed_and_encoded_ = true;
+  encoded_device_configuration_proto_ = encoded_device_configuration_proto;
+  StartDownload();
+}
+
+void RecommendAppsFetcher::OnAshResponse(
+    std::vector<ash::mojom::DisplayUnitInfoPtr> all_displays_info) {
+  ash_ready_ = true;
+
+  int screen_density = 0;
+  for (const ash::mojom::DisplayUnitInfoPtr& display_info : all_displays_info) {
+    if (base::NumberToString(display::Display::InternalDisplayId()) ==
+        display_info->id) {
+      screen_density = display_info->dpi_x + display_info->dpi_y;
+      break;
+    }
+  }
+  device_config_.set_screen_density(screen_density);
+
+  const int screen_width = GetScreenSize().width();
+  const int screen_height = GetScreenSize().height();
+  device_config_.set_screen_width(screen_width);
+  device_config_.set_screen_height(screen_height);
+
+  const int screen_layout =
+      CalculateStableScreenLayout(screen_width, screen_height, screen_density);
+  device_config_.set_screen_layout(GetScreenLayoutSizeId(screen_layout));
+
+  MaybeStartCompressAndEncodeProtoMessage();
+}
+
+void RecommendAppsFetcher::OnArcFeaturesRead(
+    base::Optional<arc::ArcFeatures> read_result) {
+  arc_features_ready_ = true;
+
+  if (read_result != base::nullopt) {
+    for (const auto& feature : read_result.value().feature_map) {
+      device_config_.add_system_available_feature(feature.first);
+    }
+
+    for (const auto& abi : GetCpuAbiList(read_result.value())) {
+      device_config_.add_native_platform(abi);
+    }
+
+    play_store_version_ = read_result.value().play_store_version;
+
+    android_sdk_version_ = GetAndroidSdkVersion(read_result.value());
+  }
+
+  MaybeStartCompressAndEncodeProtoMessage();
+}
+
+void RecommendAppsFetcher::StartDownload() {
+  if (!proto_compressed_and_encoded_)
+    return;
+
+  net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("play_recommended_apps_download", R"(
+        semantics {
+          sender: "ChromeOS Recommended Apps Screen"
+          description:
+            "Chrome OS downloads the recommended app list from Google Play API."
+          trigger:
+            "When user has accepted the ARC Terms of Service."
+          data:
+            "URL of the Google Play API."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: YES
+          cookie_store: "user"
+          setting:
+            "NA"
+          policy_exception_justification:
+            "Not implemented, considered not necessary."
+        })");
+
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = GURL(kGetAppListUrl);
+  resource_request->method = "GET";
+  resource_request->load_flags =
+      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
+
+  resource_request->headers.SetHeader("X-DFE-Device-Config",
+                                      encoded_device_configuration_proto_);
+  resource_request->headers.SetHeader("X-DFE-Sdk-Version",
+                                      android_sdk_version_);
+  resource_request->headers.SetHeader("X-DFE-Chromesky-Client-Version",
+                                      play_store_version_);
+
+  network::mojom::URLLoaderFactory* loader_factory =
+      content::BrowserContext::GetDefaultStoragePartition(profile)
+          ->GetURLLoaderFactoryForBrowserProcess()
+          .get();
+
+  start_time_ = base::TimeTicks::Now();
+  app_list_loader_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), traffic_annotation);
+  // Retry up to three times if network changes are detected during the
+  // download.
+  app_list_loader_->SetRetryOptions(
+      3, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+  app_list_loader_->DownloadToString(
+      loader_factory,
+      base::BindOnce(&RecommendAppsFetcher::OnDownloaded,
+                     base::Unretained(this)),
+      kMaxDownloadBytes);
+
+  // Abort the download attempt if it takes longer than one minute.
+  download_timer_.Start(FROM_HERE, kDownloadTimeOut, this,
+                        &RecommendAppsFetcher::OnDownloadTimeout);
+}
+
+void RecommendAppsFetcher::OnDownloadTimeout() {
+  // Destroy the fetcher, which will abort the download attempt.
+  app_list_loader_.reset();
+
+  RecordUmaDownloadTime(base::TimeTicks::Now() - start_time_);
+
+  // Show an error message to the user.
+  if (view_)
+    view_->OnLoadError();
+}
+
+void RecommendAppsFetcher::OnDownloaded(
+    std::unique_ptr<std::string> response_body) {
+  download_timer_.Stop();
+
+  RecordUmaDownloadTime(base::TimeTicks::Now() - start_time_);
+
+  std::unique_ptr<network::SimpleURLLoader> loader(std::move(app_list_loader_));
+  if (!view_)
+    return;
+
+  int response_code = 0;
+  if (!loader->ResponseInfo() || !loader->ResponseInfo()->headers) {
+    view_->OnLoadError();
+    return;
+  }
+  response_code = loader->ResponseInfo()->headers->response_code();
+  RecordUmaResponseCode(response_code);
+
+  // If the recommended app list could not be downloaded, show an error message
+  // to the user.
+  if (!response_body || response_body->empty()) {
+    view_->OnLoadError();
+    return;
+  }
+
+  // If the recommended app list were downloaded successfully, show them to
+  // the user.
+  //
+  // The response starts with a prefix ")]}'". This needs to be removed before
+  // further parsing.
+  RecordUmaResponseSize(response_body->size());
+  constexpr base::StringPiece json_xss_prevention_prefix(")]}'");
+  base::StringPiece response_body_json(*response_body);
+  if (response_body_json.starts_with(json_xss_prevention_prefix))
+    response_body_json.remove_prefix(json_xss_prevention_prefix.length());
+  base::Optional<base::Value> output = ParseResponse(response_body_json);
+  if (!output.has_value()) {
+    RecordUmaResponseAppCount(0);
+    view_->OnParseResponseError();
+    return;
+  }
+
+  view_->OnLoadSuccess(std::move(output.value()));
+}
+
+void RecommendAppsFetcher::Retry() {
+  StartDownload();
+}
+
+base::Optional<base::Value> RecommendAppsFetcher::ParseResponse(
+    base::StringPiece response) {
+  base::Value output(base::Value::Type::LIST);
+
+  int error_code;
+  std::string error_msg;
+  std::unique_ptr<base::Value> json_value =
+      base::JSONReader::ReadAndReturnErrorDeprecated(
+          response, base::JSON_PARSE_RFC, &error_code, &error_msg);
+
+  if (!json_value || (!json_value->is_list() && !json_value->is_dict())) {
+    LOG(ERROR) << "Error parsing response JSON: " << error_msg;
+    RecordUmaResponseParseResult(
+        RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON);
+    return base::nullopt;
+  }
+
+  // If the response is a dictionary, it is an error message in the
+  // following format:
+  //   {"Error code":"error code","Error message":"Error message"}
+  if (json_value->is_dict()) {
+    const base::Value* response_error_code_value =
+        json_value->FindKeyOfType("Error code", base::Value::Type::STRING);
+
+    if (!response_error_code_value) {
+      LOG(ERROR) << "Unable to find error code: response="
+                 << response.substr(0, 128);
+      RecordUmaResponseParseResult(
+          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON);
+      return base::nullopt;
+    }
+
+    base::StringPiece response_error_code_str =
+        response_error_code_value->GetString();
+    int response_error_code = 0;
+    if (!base::StringToInt(response_error_code_str, &response_error_code)) {
+      LOG(WARNING) << "Unable to parse error code: " << response_error_code_str;
+      RecordUmaResponseParseResult(
+          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE);
+      return base::nullopt;
+    }
+
+    if (response_error_code == kResponseErrorNotFirstTimeChromebookUser) {
+      RecordUmaResponseParseResult(
+          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_OWNS_CHROMEBOOK_ALREADY);
+    } else if (response_error_code == kResponseErrorNotEnoughApps) {
+      RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP);
+    } else {
+      LOG(WARNING) << "Unknown error code: " << response_error_code_str;
+      RecordUmaResponseParseResult(
+          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_UNKNOWN_ERROR_CODE);
+    }
+
+    return base::nullopt;
+  }
+
+  // Otherwise, the response should return a list of apps.
+  const base::Value::ListStorage& app_list = json_value->GetList();
+  if (app_list.empty()) {
+    DVLOG(1) << "No app in the response.";
+    RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP);
+    return base::nullopt;
+  }
+
+  for (auto& item : app_list) {
+    base::Value output_map(base::Value::Type::DICTIONARY);
+
+    if (!item.is_dict()) {
+      DVLOG(1) << "Cannot parse item.";
+      continue;
+    }
+
+    // Retrieve the app title.
+    const base::Value* title =
+        item.FindPathOfType({"title_", "name_"}, base::Value::Type::STRING);
+    if (title)
+      output_map.SetKey("name", base::Value(title->GetString()));
+
+    // Retrieve the package name.
+    const base::Value* package_name =
+        item.FindPathOfType({"id_", "id_"}, base::Value::Type::STRING);
+    if (package_name)
+      output_map.SetKey("package_name", base::Value(package_name->GetString()));
+
+    // Retrieve the icon URL for the app.
+    //
+    // The name "privateDoNotAccessOrElseSafeUrlWrappedValue_" here is because
+    // it is a direct serialization from the proto message. The value has been
+    // sanitized so it is regarded as a safe URL. In general, if the response is
+    // a protobuf, we should not directly access this field but use the wrapper
+    // method getSafeUrlString() to read it. In our case, we don't have the
+    // option other than access it directly.
+    const base::Value* icon_url = item.FindPathOfType(
+        {"icon_", "url_", "privateDoNotAccessOrElseSafeUrlWrappedValue_"},
+        base::Value::Type::STRING);
+    if (icon_url)
+      output_map.SetKey("icon", base::Value(icon_url->GetString()));
+
+    output.GetList().push_back(std::move(output_map));
+  }
+
+  RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_ERROR);
+  RecordUmaResponseAppCount(static_cast<int>(output.GetList().size()));
+
+  return output;
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h
index f6be97a..357c7a5 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h
+++ b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// 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.
 
@@ -6,27 +6,132 @@
 #define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_RECOMMEND_APPS_RECOMMEND_APPS_FETCHER_H_
 
 #include <memory>
+#include <string>
+#include <vector>
 
-#include "base/callback_forward.h"
+#include "ash/public/interfaces/cros_display_config.mojom.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/chromeos/login/screens/recommend_apps/device_configuration.pb.h"
+#include "chrome/browser/chromeos/login/screens/recommend_apps_screen_view.h"
+#include "components/arc/arc_features_parser.h"
+#include "extensions/browser/api/system_display/display_info_provider.h"
+
+namespace network {
+class SimpleURLLoader;
+}
 
 namespace chromeos {
 
-class RecommendAppsScreenView;
-
+// This class handles the network request for the Recommend Apps screen. It is
+// supposed to run on the UI thread. The request requires the following headers:
+// 1. X-Device-Config
+// 2. X-Sdk-Version
+// Play requires Android device config information to filter apps.
+// device_configuration.proto is used to encode all the info. The following
+// fields will be retrieved and sent:
+// 1. touch_screen
+// 2. keyboard
+// 3. navigation
+// 4. screen_layout
+// 5. has_hard_keyboard
+// 6. has_five_way_navigation
+// 7. screen_density
+// 8. screen_width
+// 9. screen_height
+// 10. gl_es_version
+// 11. system_available_feature
+// 12. native_platform
+// 13. gl_extension
 class RecommendAppsFetcher {
  public:
-  static std::unique_ptr<RecommendAppsFetcher> Create(
-      RecommendAppsScreenView* view);
+  explicit RecommendAppsFetcher(RecommendAppsScreenView* view);
+  ~RecommendAppsFetcher();
 
-  using FactoryCallback =
-      base::RepeatingCallback<std::unique_ptr<RecommendAppsFetcher>(
-          RecommendAppsScreenView* view)>;
-  static void SetFactoryCallbackForTesting(FactoryCallback* callback);
+  // Provide a retry method to download the app list again.
+  void Retry();
 
-  virtual ~RecommendAppsFetcher() = default;
+ private:
+  // Populate the required device config info.
+  void PopulateDeviceConfig();
 
-  virtual void Start() = 0;
-  virtual void Retry() = 0;
+  // Start the connection to ash. Send the request to get display unit info
+  // list.
+  void StartAshRequest();
+
+  // Start to compress and encode the proto message if we finish ash request
+  // and ARC feature is read.
+  void MaybeStartCompressAndEncodeProtoMessage();
+
+  // Callback function called when display unit info list is retrieved from ash.
+  // It will populate the device config info related to the screen density.
+  void OnAshResponse(
+      std::vector<ash::mojom::DisplayUnitInfoPtr> all_displays_info);
+
+  // Callback function called when ARC features are read by the parser.
+  // It will populate the device config info related to ARC features.
+  void OnArcFeaturesRead(base::Optional<arc::ArcFeatures> read_result);
+
+  // Callback function called when the proto message has been compressed and
+  // encoded.
+  void OnProtoMessageCompressedAndEncoded(
+      std::string encoded_device_configuration_proto);
+
+  // Start downloading the recommended app list.
+  void StartDownload();
+
+  // Abort the attempt to download the recommended app list if it takes too
+  // long.
+  void OnDownloadTimeout();
+
+  // Callback function called when SimpleURLLoader completes.
+  void OnDownloaded(std::unique_ptr<std::string> response_body);
+
+  // If the response is not a valid JSON, return base::nullopt.
+  // If the response contains no app, return base::nullopt;
+  // Value output, in true, is a list containing:
+  // 1. name: the title of the app.
+  // 2. package_name
+  // 3. Possibly an Icon URL.
+  // Parses an input string that looks somewhat like this:
+  // [{"title_" : {"name_" : {title of app"}},
+  //   "id_" : {"id_" : {com.package.name"}},
+  //  "icon_": {"url_": {"privateDoNotAccessOrElseSafeUrlWrappedValue_":
+  //  "http://icon_url.com/url"}}},
+  //  {"title_" : "title of second app",
+  //   "packageName_": "second package name.",
+  //  }]
+  base::Optional<base::Value> ParseResponse(base::StringPiece response);
+
+  device_configuration::DeviceConfigurationProto device_config_;
+
+  std::string android_sdk_version_;
+
+  std::string play_store_version_;
+
+  std::string encoded_device_configuration_proto_;
+
+  bool ash_ready_ = false;
+  bool arc_features_ready_ = false;
+  bool has_started_proto_processing_ = false;
+  bool proto_compressed_and_encoded_ = false;
+
+  RecommendAppsScreenView* view_;
+
+  std::unique_ptr<network::SimpleURLLoader> app_list_loader_;
+
+  // Timer that enforces a custom (shorter) timeout on the attempt to download
+  // the recommended app list.
+  base::OneShotTimer download_timer_;
+
+  base::TimeTicks start_time_;
+
+  ash::mojom::CrosDisplayConfigControllerPtr cros_display_config_;
+  base::WeakPtrFactory<RecommendAppsFetcher> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecommendAppsFetcher);
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
deleted file mode 100644
index 3c4cdf9..0000000
--- a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.cc
+++ /dev/null
@@ -1,637 +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 "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.h"
-
-#include "ash/public/interfaces/constants.mojom.h"
-#include "ash/public/interfaces/cros_display_config.mojom.h"
-#include "base/base64url.h"
-#include "base/bind.h"
-#include "base/json/json_reader.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_split.h"
-#include "base/task/post_task.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "content/public/browser/gpu_data_manager.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/common/service_manager_connection.h"
-#include "extensions/common/api/system_display.h"
-#include "gpu/config/gpu_info.h"
-#include "net/base/load_flags.h"
-#include "net/http/http_status_code.h"
-#include "services/network/public/cpp/simple_url_loader.h"
-#include "services/service_manager/public/cpp/connector.h"
-#include "third_party/zlib/google/compression_utils.h"
-#include "ui/display/display.h"
-#include "ui/display/screen.h"
-#include "ui/events/devices/input_device.h"
-#include "ui/events/devices/input_device_manager.h"
-#include "ui/gfx/extension_set.h"
-#include "ui/gl/gl_version_info.h"
-
-namespace chromeos {
-
-namespace {
-
-constexpr const char kGetAppListUrl[] =
-    "https://android.clients.google.com/fdfe/chrome/getfastreinstallappslist";
-
-constexpr int kResponseErrorNotEnoughApps = 5;
-
-constexpr int kResponseErrorNotFirstTimeChromebookUser = 6;
-
-constexpr base::TimeDelta kDownloadTimeOut = base::TimeDelta::FromMinutes(1);
-
-constexpr const int64_t kMaxDownloadBytes = 1024 * 1024;  // 1Mb
-
-constexpr const int kMaxAppCount = 21;
-
-enum RecommendAppsResponseParseResult {
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_ERROR = 0,
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON = 1,
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP = 2,
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_OWNS_CHROMEBOOK_ALREADY = 3,
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_UNKNOWN_ERROR_CODE = 4,
-  RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE = 5,
-
-  kMaxValue = RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE
-};
-
-bool HasTouchScreen() {
-  return !ui::InputDeviceManager::GetInstance()
-              ->GetTouchscreenDevices()
-              .empty();
-}
-
-bool HasStylusInput() {
-  // Check to see if the hardware reports it is stylus capable.
-  for (const ui::TouchscreenDevice& device :
-       ui::InputDeviceManager::GetInstance()->GetTouchscreenDevices()) {
-    if (device.has_stylus &&
-        device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-bool HasKeyboard() {
-  return !ui::InputDeviceManager::GetInstance()->GetKeyboardDevices().empty();
-}
-
-bool HasHardKeyboard() {
-  for (const ui::InputDevice& device :
-       ui::InputDeviceManager::GetInstance()->GetKeyboardDevices()) {
-    if (!device.phys.empty())
-      return true;
-  }
-
-  return false;
-}
-
-gfx::Size GetScreenSize() {
-  return display::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
-}
-
-// TODO(rsgingerrs): This function is copied from Play. We need to find a way to
-// keep this synced with the Play side if there are any changes there. Another
-// approach is to let the server do the calculation since we have provided the
-// screen width, height and dpi.
-int CalculateStableScreenLayout(const int screen_width,
-                                const int screen_height,
-                                const float dpi) {
-  const int density_default = 160;
-  const float px_to_dp = density_default / static_cast<float>(dpi);
-  const int short_size_dp = static_cast<int>(screen_width * px_to_dp);
-  const int long_size_dp = static_cast<int>(screen_height * px_to_dp);
-
-  int screen_layout_size;
-  bool screen_layout_long;
-
-  const int screenlayout_size_small = 0x01;
-  const int screenlayout_size_normal = 0x02;
-  const int screenlayout_size_large = 0x03;
-  const int screenlayout_size_xlarge = 0x04;
-  const int screenlayout_long_no = 0x10;
-
-  // These semi-magic numbers define our compatibility modes for
-  // applications with different screens.  These are guarantees to
-  // app developers about the space they can expect for a particular
-  // configuration.  DO NOT CHANGE!
-  if (long_size_dp < 470) {
-    // This is shorter than an HVGA normal density screen (which
-    // is 480 pixels on its long side).
-    screen_layout_size = screenlayout_size_small;
-    screen_layout_long = false;
-  } else {
-    // What size is this screen?
-    if (long_size_dp >= 960 && short_size_dp >= 720) {
-      // 1.5xVGA or larger screens at medium density are the point
-      // at which we consider it to be an extra large screen.
-      screen_layout_size = screenlayout_size_xlarge;
-    } else if (long_size_dp >= 640 && short_size_dp >= 480) {
-      // VGA or larger screens at medium density are the point
-      // at which we consider it to be a large screen.
-      screen_layout_size = screenlayout_size_large;
-    } else {
-      screen_layout_size = screenlayout_size_normal;
-    }
-
-    // Is this a long screen? Anything wider than WVGA (5:3) is considering to
-    // be long.
-    screen_layout_long = ((long_size_dp * 3) / 5) >= (short_size_dp - 1);
-  }
-
-  int screen_layout = screen_layout_size;
-  if (!screen_layout_long) {
-    screen_layout |= screenlayout_long_no;
-  }
-
-  return screen_layout;
-}
-
-device_configuration::DeviceConfigurationProto_ScreenLayout
-GetScreenLayoutSizeId(const int screen_layout_size_value) {
-  const int screenlayout_size_small = 0x01;
-  const int screenlayout_size_normal = 0x02;
-  const int screenlayout_size_large = 0x03;
-  const int screenlayout_size_xlarge = 0x04;
-  const int screenlayout_size_mask = 0x0f;
-  int size_bits = screen_layout_size_value & screenlayout_size_mask;
-
-  switch (size_bits) {
-    case screenlayout_size_small:
-      return device_configuration::DeviceConfigurationProto_ScreenLayout::
-          DeviceConfigurationProto_ScreenLayout_SMALL;
-    case screenlayout_size_normal:
-      return device_configuration::DeviceConfigurationProto_ScreenLayout::
-          DeviceConfigurationProto_ScreenLayout_NORMAL;
-    case screenlayout_size_large:
-      return device_configuration::DeviceConfigurationProto_ScreenLayout::
-          DeviceConfigurationProto_ScreenLayout_LARGE;
-    case screenlayout_size_xlarge:
-      return device_configuration::DeviceConfigurationProto_ScreenLayout::
-          DeviceConfigurationProto_ScreenLayout_EXTRA_LARGE;
-    default:
-      return device_configuration::DeviceConfigurationProto_ScreenLayout::
-          DeviceConfigurationProto_ScreenLayout_UNDEFINED_SCREEN_LAYOUT;
-  }
-}
-
-const gpu::GPUInfo GetGPUInfo() {
-  return content::GpuDataManager::GetInstance()->GetGPUInfo();
-}
-
-// This function converts the major and minor versions to the proto accepted
-// value. For example, if the version is 3.2, the return value is 0x00030002.
-unsigned GetGLVersionInfo() {
-  const gpu::GPUInfo gpu_info = GetGPUInfo();
-  gfx::ExtensionSet extensionSet(gfx::MakeExtensionSet(gpu_info.gl_extensions));
-  gl::GLVersionInfo glVersionInfo(gpu_info.gl_version.c_str(),
-                                  gpu_info.gl_renderer.c_str(), extensionSet);
-
-  unsigned major_version = glVersionInfo.major_version;
-  unsigned minor_version = glVersionInfo.minor_version;
-  unsigned version = 0x0000ffff;
-  version &= minor_version;
-  version |= (major_version << 16) & 0xffff0000;
-
-  return version;
-}
-
-gfx::ExtensionSet GetGLExtensions() {
-  const gpu::GPUInfo gpu_info = GetGPUInfo();
-  gfx::ExtensionSet extensionSet(gfx::MakeExtensionSet(gpu_info.gl_extensions));
-
-  return extensionSet;
-}
-
-const std::string& GetAndroidSdkVersion(const arc::ArcFeatures& arc_features) {
-  return arc_features.build_props.at("ro.build.version.sdk");
-}
-
-std::vector<std::string> GetCpuAbiList(const arc::ArcFeatures& arc_features) {
-  const std::string& abi_list_str =
-      arc_features.build_props.at("ro.product.cpu.abilist");
-  return base::SplitString(abi_list_str, ",", base::TRIM_WHITESPACE,
-                           base::SPLIT_WANT_ALL);
-}
-
-std::string CompressAndEncodeProtoMessageOnBlockingThread(
-    device_configuration::DeviceConfigurationProto device_config) {
-  std::string encoded_device_configuration_proto;
-
-  std::string serialized_proto;
-  device_config.SerializeToString(&serialized_proto);
-  std::string compressed_proto;
-  compression::GzipCompress(serialized_proto, &compressed_proto);
-  base::Base64UrlEncode(compressed_proto,
-                        base::Base64UrlEncodePolicy::OMIT_PADDING,
-                        &encoded_device_configuration_proto);
-
-  return encoded_device_configuration_proto;
-}
-
-void RecordUmaResponseAppCount(int app_count) {
-  UMA_HISTOGRAM_CUSTOM_COUNTS("OOBE.RecommendApps.Fetcher.AppCount", app_count,
-                              0, kMaxAppCount, kMaxAppCount + 1);
-}
-
-void RecordUmaDownloadTime(base::TimeDelta download_time) {
-  UMA_HISTOGRAM_TIMES("OOBE.RecommendApps.Fetcher.DownloadTime", download_time);
-}
-
-void RecordUmaResponseCode(int code) {
-  base::UmaHistogramSparse("OOBE.RecommendApps.Fetcher.ResponseCode", code);
-}
-
-void RecordUmaResponseParseResult(RecommendAppsResponseParseResult result) {
-  UMA_HISTOGRAM_ENUMERATION("OOBE.RecommendApps.Fetcher.ResponseParseResult",
-                            result);
-}
-
-void RecordUmaResponseSize(unsigned long responseSize) {
-  UMA_HISTOGRAM_COUNTS_1M(
-      "OOBE.RecommendApps.Fetcher.ResponseSize",
-      static_cast<base::HistogramBase::Sample>(responseSize));
-}
-
-}  // namespace
-
-RecommendAppsFetcherImpl::RecommendAppsFetcherImpl(
-    RecommendAppsScreenView* view)
-    : view_(view), weak_ptr_factory_(this) {
-  service_manager::Connector* connector =
-      content::ServiceManagerConnection::GetForProcess()->GetConnector();
-  DCHECK(connector);
-  connector->BindInterface(ash::mojom::kServiceName, &cros_display_config_);
-}
-
-RecommendAppsFetcherImpl::~RecommendAppsFetcherImpl() = default;
-
-void RecommendAppsFetcherImpl::PopulateDeviceConfig() {
-  if (!HasTouchScreen()) {
-    device_config_.set_touch_screen(
-        device_configuration::DeviceConfigurationProto_TouchScreen::
-            DeviceConfigurationProto_TouchScreen_NOTOUCH);
-  } else if (!HasStylusInput()) {
-    device_config_.set_touch_screen(
-        device_configuration::DeviceConfigurationProto_TouchScreen::
-            DeviceConfigurationProto_TouchScreen_FINGER);
-  } else {
-    device_config_.set_touch_screen(
-        device_configuration::DeviceConfigurationProto_TouchScreen::
-            DeviceConfigurationProto_TouchScreen_STYLUS);
-  }
-
-  if (!HasKeyboard()) {
-    device_config_.set_keyboard(
-        device_configuration::DeviceConfigurationProto_Keyboard::
-            DeviceConfigurationProto_Keyboard_NOKEYS);
-  } else {
-    // TODO(rsgingerrs): Currently there is no straightforward way to determine
-    // whether it is a full keyboard or not. We assume it is safe to set it as
-    // QWERTY keyboard for this feature.
-    device_config_.set_keyboard(
-        device_configuration::DeviceConfigurationProto_Keyboard::
-            DeviceConfigurationProto_Keyboard_QWERTY);
-  }
-  device_config_.set_has_hard_keyboard(HasHardKeyboard());
-
-  // TODO(rsgingerrs): There is no straightforward way to get this info. We
-  // assume it is safe to set it as no navigation.
-  device_config_.set_navigation(
-      device_configuration::DeviceConfigurationProto_Navigation::
-          DeviceConfigurationProto_Navigation_NONAV);
-  device_config_.set_has_five_way_navigation(false);
-
-  device_config_.set_gl_es_version(GetGLVersionInfo());
-
-  for (const base::StringPiece& gl_extension : GetGLExtensions()) {
-    if (!gl_extension.empty())
-      device_config_.add_gl_extension(gl_extension.as_string());
-  }
-}
-
-void RecommendAppsFetcherImpl::StartAshRequest() {
-  cros_display_config_->GetDisplayUnitInfoList(
-      false /* single_unified */,
-      base::BindOnce(&RecommendAppsFetcherImpl::OnAshResponse,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void RecommendAppsFetcherImpl::MaybeStartCompressAndEncodeProtoMessage() {
-  if (!ash_ready_ || !arc_features_ready_ || has_started_proto_processing_)
-    return;
-
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::BindOnce(&CompressAndEncodeProtoMessageOnBlockingThread,
-                     std::move(device_config_)),
-      base::BindOnce(
-          &RecommendAppsFetcherImpl::OnProtoMessageCompressedAndEncoded,
-          weak_ptr_factory_.GetWeakPtr()));
-  has_started_proto_processing_ = true;
-}
-
-void RecommendAppsFetcherImpl::OnProtoMessageCompressedAndEncoded(
-    std::string encoded_device_configuration_proto) {
-  proto_compressed_and_encoded_ = true;
-  encoded_device_configuration_proto_ = encoded_device_configuration_proto;
-  StartDownload();
-}
-
-void RecommendAppsFetcherImpl::OnAshResponse(
-    std::vector<ash::mojom::DisplayUnitInfoPtr> all_displays_info) {
-  ash_ready_ = true;
-
-  int screen_density = 0;
-  for (const ash::mojom::DisplayUnitInfoPtr& display_info : all_displays_info) {
-    if (base::NumberToString(display::Display::InternalDisplayId()) ==
-        display_info->id) {
-      screen_density = display_info->dpi_x + display_info->dpi_y;
-      break;
-    }
-  }
-  device_config_.set_screen_density(screen_density);
-
-  const int screen_width = GetScreenSize().width();
-  const int screen_height = GetScreenSize().height();
-  device_config_.set_screen_width(screen_width);
-  device_config_.set_screen_height(screen_height);
-
-  const int screen_layout =
-      CalculateStableScreenLayout(screen_width, screen_height, screen_density);
-  device_config_.set_screen_layout(GetScreenLayoutSizeId(screen_layout));
-
-  MaybeStartCompressAndEncodeProtoMessage();
-}
-
-void RecommendAppsFetcherImpl::OnArcFeaturesRead(
-    base::Optional<arc::ArcFeatures> read_result) {
-  arc_features_ready_ = true;
-
-  if (read_result != base::nullopt) {
-    for (const auto& feature : read_result.value().feature_map) {
-      device_config_.add_system_available_feature(feature.first);
-    }
-
-    for (const auto& abi : GetCpuAbiList(read_result.value())) {
-      device_config_.add_native_platform(abi);
-    }
-
-    play_store_version_ = read_result.value().play_store_version;
-
-    android_sdk_version_ = GetAndroidSdkVersion(read_result.value());
-  }
-
-  MaybeStartCompressAndEncodeProtoMessage();
-}
-
-void RecommendAppsFetcherImpl::StartDownload() {
-  if (!proto_compressed_and_encoded_)
-    return;
-
-  net::NetworkTrafficAnnotationTag traffic_annotation =
-      net::DefineNetworkTrafficAnnotation("play_recommended_apps_download", R"(
-        semantics {
-          sender: "ChromeOS Recommended Apps Screen"
-          description:
-            "Chrome OS downloads the recommended app list from Google Play API."
-          trigger:
-            "When user has accepted the ARC Terms of Service."
-          data:
-            "URL of the Google Play API."
-          destination: GOOGLE_OWNED_SERVICE
-        }
-        policy {
-          cookies_allowed: YES
-          cookie_store: "user"
-          setting:
-            "NA"
-          policy_exception_justification:
-            "Not implemented, considered not necessary."
-        })");
-
-  Profile* profile = ProfileManager::GetActiveUserProfile();
-
-  auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = GURL(kGetAppListUrl);
-  resource_request->method = "GET";
-  resource_request->load_flags =
-      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
-
-  resource_request->headers.SetHeader("X-DFE-Device-Config",
-                                      encoded_device_configuration_proto_);
-  resource_request->headers.SetHeader("X-DFE-Sdk-Version",
-                                      android_sdk_version_);
-  resource_request->headers.SetHeader("X-DFE-Chromesky-Client-Version",
-                                      play_store_version_);
-
-  network::mojom::URLLoaderFactory* loader_factory =
-      content::BrowserContext::GetDefaultStoragePartition(profile)
-          ->GetURLLoaderFactoryForBrowserProcess()
-          .get();
-
-  start_time_ = base::TimeTicks::Now();
-  app_list_loader_ = network::SimpleURLLoader::Create(
-      std::move(resource_request), traffic_annotation);
-  // Retry up to three times if network changes are detected during the
-  // download.
-  app_list_loader_->SetRetryOptions(
-      3, network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
-  app_list_loader_->DownloadToString(
-      loader_factory,
-      base::BindOnce(&RecommendAppsFetcherImpl::OnDownloaded,
-                     base::Unretained(this)),
-      kMaxDownloadBytes);
-
-  // Abort the download attempt if it takes longer than one minute.
-  download_timer_.Start(FROM_HERE, kDownloadTimeOut, this,
-                        &RecommendAppsFetcherImpl::OnDownloadTimeout);
-}
-
-void RecommendAppsFetcherImpl::OnDownloadTimeout() {
-  // Destroy the fetcher, which will abort the download attempt.
-  app_list_loader_.reset();
-
-  RecordUmaDownloadTime(base::TimeTicks::Now() - start_time_);
-
-  // Show an error message to the user.
-  if (view_)
-    view_->OnLoadError();
-}
-
-void RecommendAppsFetcherImpl::OnDownloaded(
-    std::unique_ptr<std::string> response_body) {
-  download_timer_.Stop();
-
-  RecordUmaDownloadTime(base::TimeTicks::Now() - start_time_);
-
-  std::unique_ptr<network::SimpleURLLoader> loader(std::move(app_list_loader_));
-  if (!view_)
-    return;
-
-  int response_code = 0;
-  if (!loader->ResponseInfo() || !loader->ResponseInfo()->headers) {
-    view_->OnLoadError();
-    return;
-  }
-  response_code = loader->ResponseInfo()->headers->response_code();
-  RecordUmaResponseCode(response_code);
-
-  // If the recommended app list could not be downloaded, show an error message
-  // to the user.
-  if (!response_body || response_body->empty()) {
-    view_->OnLoadError();
-    return;
-  }
-
-  // If the recommended app list were downloaded successfully, show them to
-  // the user.
-  //
-  // The response starts with a prefix ")]}'". This needs to be removed before
-  // further parsing.
-  RecordUmaResponseSize(response_body->size());
-  constexpr base::StringPiece json_xss_prevention_prefix(")]}'");
-  base::StringPiece response_body_json(*response_body);
-  if (response_body_json.starts_with(json_xss_prevention_prefix))
-    response_body_json.remove_prefix(json_xss_prevention_prefix.length());
-  base::Optional<base::Value> output = ParseResponse(response_body_json);
-  if (!output.has_value()) {
-    RecordUmaResponseAppCount(0);
-    view_->OnParseResponseError();
-    return;
-  }
-
-  view_->OnLoadSuccess(std::move(output.value()));
-}
-
-void RecommendAppsFetcherImpl::Start() {
-  PopulateDeviceConfig();
-  StartAshRequest();
-  arc::ArcFeaturesParser::GetArcFeatures(
-      base::BindOnce(&RecommendAppsFetcherImpl::OnArcFeaturesRead,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-void RecommendAppsFetcherImpl::Retry() {
-  StartDownload();
-}
-
-base::Optional<base::Value> RecommendAppsFetcherImpl::ParseResponse(
-    base::StringPiece response) {
-  base::Value output(base::Value::Type::LIST);
-
-  int error_code;
-  std::string error_msg;
-  std::unique_ptr<base::Value> json_value =
-      base::JSONReader::ReadAndReturnErrorDeprecated(
-          response, base::JSON_PARSE_RFC, &error_code, &error_msg);
-
-  if (!json_value || (!json_value->is_list() && !json_value->is_dict())) {
-    LOG(ERROR) << "Error parsing response JSON: " << error_msg;
-    RecordUmaResponseParseResult(
-        RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON);
-    return base::nullopt;
-  }
-
-  // If the response is a dictionary, it is an error message in the
-  // following format:
-  //   {"Error code":"error code","Error message":"Error message"}
-  if (json_value->is_dict()) {
-    const base::Value* response_error_code_value =
-        json_value->FindKeyOfType("Error code", base::Value::Type::STRING);
-
-    if (!response_error_code_value) {
-      LOG(ERROR) << "Unable to find error code: response="
-                 << response.substr(0, 128);
-      RecordUmaResponseParseResult(
-          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_JSON);
-      return base::nullopt;
-    }
-
-    base::StringPiece response_error_code_str =
-        response_error_code_value->GetString();
-    int response_error_code = 0;
-    if (!base::StringToInt(response_error_code_str, &response_error_code)) {
-      LOG(WARNING) << "Unable to parse error code: " << response_error_code_str;
-      RecordUmaResponseParseResult(
-          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_INVALID_ERROR_CODE);
-      return base::nullopt;
-    }
-
-    if (response_error_code == kResponseErrorNotFirstTimeChromebookUser) {
-      RecordUmaResponseParseResult(
-          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_OWNS_CHROMEBOOK_ALREADY);
-    } else if (response_error_code == kResponseErrorNotEnoughApps) {
-      RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP);
-    } else {
-      LOG(WARNING) << "Unknown error code: " << response_error_code_str;
-      RecordUmaResponseParseResult(
-          RECOMMEND_APPS_RESPONSE_PARSE_RESULT_UNKNOWN_ERROR_CODE);
-    }
-
-    return base::nullopt;
-  }
-
-  // Otherwise, the response should return a list of apps.
-  const base::Value::ListStorage& app_list = json_value->GetList();
-  if (app_list.empty()) {
-    DVLOG(1) << "No app in the response.";
-    RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_APP);
-    return base::nullopt;
-  }
-
-  for (auto& item : app_list) {
-    base::Value output_map(base::Value::Type::DICTIONARY);
-
-    if (!item.is_dict()) {
-      DVLOG(1) << "Cannot parse item.";
-      continue;
-    }
-
-    // Retrieve the app title.
-    const base::Value* title =
-        item.FindPathOfType({"title_", "name_"}, base::Value::Type::STRING);
-    if (title)
-      output_map.SetKey("name", base::Value(title->GetString()));
-
-    // Retrieve the package name.
-    const base::Value* package_name =
-        item.FindPathOfType({"id_", "id_"}, base::Value::Type::STRING);
-    if (package_name)
-      output_map.SetKey("package_name", base::Value(package_name->GetString()));
-
-    // Retrieve the icon URL for the app.
-    //
-    // The name "privateDoNotAccessOrElseSafeUrlWrappedValue_" here is because
-    // it is a direct serialization from the proto message. The value has been
-    // sanitized so it is regarded as a safe URL. In general, if the response is
-    // a protobuf, we should not directly access this field but use the wrapper
-    // method getSafeUrlString() to read it. In our case, we don't have the
-    // option other than access it directly.
-    const base::Value* icon_url = item.FindPathOfType(
-        {"icon_", "url_", "privateDoNotAccessOrElseSafeUrlWrappedValue_"},
-        base::Value::Type::STRING);
-    if (icon_url)
-      output_map.SetKey("icon", base::Value(icon_url->GetString()));
-
-    output.GetList().push_back(std::move(output_map));
-  }
-
-  RecordUmaResponseParseResult(RECOMMEND_APPS_RESPONSE_PARSE_RESULT_NO_ERROR);
-  RecordUmaResponseAppCount(static_cast<int>(output.GetList().size()));
-
-  return output;
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.h b/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.h
deleted file mode 100644
index 7c51c1f..0000000
--- a/chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher_impl.h
+++ /dev/null
@@ -1,142 +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 CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_RECOMMEND_APPS_RECOMMEND_APPS_FETCHER_IMPL_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_RECOMMEND_APPS_RECOMMEND_APPS_FETCHER_IMPL_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "ash/public/interfaces/cros_display_config.mojom.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/timer/timer.h"
-#include "chrome/browser/chromeos/login/screens/recommend_apps/device_configuration.pb.h"
-#include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
-#include "chrome/browser/chromeos/login/screens/recommend_apps_screen_view.h"
-#include "components/arc/arc_features_parser.h"
-#include "extensions/browser/api/system_display/display_info_provider.h"
-
-namespace network {
-class SimpleURLLoader;
-}
-
-namespace chromeos {
-
-// This class handles the network request for the Recommend Apps screen. It is
-// supposed to run on the UI thread. The request requires the following headers:
-// 1. X-Device-Config
-// 2. X-Sdk-Version
-// Play requires Android device config information to filter apps.
-// device_configuration.proto is used to encode all the info. The following
-// fields will be retrieved and sent:
-// 1. touch_screen
-// 2. keyboard
-// 3. navigation
-// 4. screen_layout
-// 5. has_hard_keyboard
-// 6. has_five_way_navigation
-// 7. screen_density
-// 8. screen_width
-// 9. screen_height
-// 10. gl_es_version
-// 11. system_available_feature
-// 12. native_platform
-// 13. gl_extension
-class RecommendAppsFetcherImpl : public RecommendAppsFetcher {
- public:
-  explicit RecommendAppsFetcherImpl(RecommendAppsScreenView* view);
-  ~RecommendAppsFetcherImpl() override;
-
-  // Provide a retry method to download the app list again.
-  // RecommendAppsFetcher:
-  void Start() override;
-  void Retry() override;
-
- private:
-  // Populate the required device config info.
-  void PopulateDeviceConfig();
-
-  // Start the connection to ash. Send the request to get display unit info
-  // list.
-  void StartAshRequest();
-
-  // Start to compress and encode the proto message if we finish ash request
-  // and ARC feature is read.
-  void MaybeStartCompressAndEncodeProtoMessage();
-
-  // Callback function called when display unit info list is retrieved from ash.
-  // It will populate the device config info related to the screen density.
-  void OnAshResponse(
-      std::vector<ash::mojom::DisplayUnitInfoPtr> all_displays_info);
-
-  // Callback function called when ARC features are read by the parser.
-  // It will populate the device config info related to ARC features.
-  void OnArcFeaturesRead(base::Optional<arc::ArcFeatures> read_result);
-
-  // Callback function called when the proto message has been compressed and
-  // encoded.
-  void OnProtoMessageCompressedAndEncoded(
-      std::string encoded_device_configuration_proto);
-
-  // Start downloading the recommended app list.
-  void StartDownload();
-
-  // Abort the attempt to download the recommended app list if it takes too
-  // long.
-  void OnDownloadTimeout();
-
-  // Callback function called when SimpleURLLoader completes.
-  void OnDownloaded(std::unique_ptr<std::string> response_body);
-
-  // If the response is not a valid JSON, return base::nullopt.
-  // If the response contains no app, return base::nullopt;
-  // Value output, in true, is a list containing:
-  // 1. name: the title of the app.
-  // 2. package_name
-  // 3. Possibly an Icon URL.
-  // Parses an input string that looks somewhat like this:
-  // [{"title_" : {"name_" : {title of app"}},
-  //   "id_" : {"id_" : {com.package.name"}},
-  //  "icon_": {"url_": {"privateDoNotAccessOrElseSafeUrlWrappedValue_":
-  //  "http://icon_url.com/url"}}},
-  //  {"title_" : "title of second app",
-  //   "packageName_": "second package name.",
-  //  }]
-  base::Optional<base::Value> ParseResponse(base::StringPiece response);
-
-  device_configuration::DeviceConfigurationProto device_config_;
-
-  std::string android_sdk_version_;
-
-  std::string play_store_version_;
-
-  std::string encoded_device_configuration_proto_;
-
-  bool ash_ready_ = false;
-  bool arc_features_ready_ = false;
-  bool has_started_proto_processing_ = false;
-  bool proto_compressed_and_encoded_ = false;
-
-  RecommendAppsScreenView* view_;
-
-  std::unique_ptr<network::SimpleURLLoader> app_list_loader_;
-
-  // Timer that enforces a custom (shorter) timeout on the attempt to download
-  // the recommended app list.
-  base::OneShotTimer download_timer_;
-
-  base::TimeTicks start_time_;
-
-  ash::mojom::CrosDisplayConfigControllerPtr cros_display_config_;
-  base::WeakPtrFactory<RecommendAppsFetcherImpl> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(RecommendAppsFetcherImpl);
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_RECOMMEND_APPS_RECOMMEND_APPS_FETCHER_IMPL_H_
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc b/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
index cc71080..026bad3 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
@@ -4,8 +4,6 @@
 
 #include "chrome/browser/chromeos/login/screens/recommend_apps_screen.h"
 
-#include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
-
 namespace chromeos {
 
 RecommendAppsScreen::RecommendAppsScreen(
@@ -30,8 +28,7 @@
 void RecommendAppsScreen::Show() {
   view_->Show();
 
-  recommend_apps_fetcher_ = RecommendAppsFetcher::Create(view_);
-  recommend_apps_fetcher_->Start();
+  recommend_apps_fetcher_ = std::make_unique<RecommendAppsFetcher>(view_);
 }
 
 void RecommendAppsScreen::Hide() {
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen.h b/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
index 252ac1dc3..9d7af0b 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
@@ -11,12 +11,11 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
+#include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps_screen_view.h"
 
 namespace chromeos {
 
-class RecommendAppsFetcher;
-
 // This is Recommend Apps screen that is displayed as a part of user first
 // sign-in flow.
 class RecommendAppsScreen : public BaseScreen,
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
deleted file mode 100644
index a84684a..0000000
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc
+++ /dev/null
@@ -1,703 +0,0 @@
-// Copyright 2019 The Chromium 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/chromeos/login/screens/recommend_apps_screen.h"
-
-#include <memory>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/run_loop.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/stringprintf.h"
-#include "base/values.h"
-#include "chrome/browser/chromeos/login/login_wizard.h"
-#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
-#include "chrome/browser/chromeos/login/oobe_screen.h"
-#include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
-#include "chrome/browser/chromeos/login/test/js_checker.h"
-#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
-#include "chrome/browser/chromeos/login/ui/login_display_host.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
-#include "components/arc/arc_prefs.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/test/browser_test_utils.h"
-
-namespace chromeos {
-
-namespace {
-
-chromeos::OobeUI* GetOobeUI() {
-  auto* host = chromeos::LoginDisplayHost::default_host();
-  return host ? host->GetOobeUI() : nullptr;
-}
-
-struct FakeAppInfo {
- public:
-  FakeAppInfo(const std::string& id,
-              const std::string& package_name,
-              const std::string& name)
-      : id(id), package_name(package_name), name(name) {}
-  ~FakeAppInfo() = default;
-
-  base::Value ToValue() const {
-    base::Value result(base::Value::Type::DICTIONARY);
-    result.SetKey("id", base::Value(id));
-    result.SetKey("package_name", base::Value(package_name));
-    result.SetKey("name", base::Value(name));
-    return result;
-  }
-
-  const std::string id;
-  const std::string package_name;
-  const std::string name;
-};
-
-class FakeRecommendAppsFetcher : public RecommendAppsFetcher {
- public:
-  explicit FakeRecommendAppsFetcher(RecommendAppsScreenView* view)
-      : view_(view) {}
-  ~FakeRecommendAppsFetcher() override = default;
-
-  bool started() const { return started_; }
-  int retries() const { return retries_; }
-
-  void SimulateSuccess(const std::vector<FakeAppInfo>& apps) {
-    EXPECT_TRUE(started_);
-    base::Value app_list(base::Value::Type::LIST);
-    for (const auto& app : apps) {
-      app_list.GetList().emplace_back(app.ToValue());
-    }
-    view_->OnLoadSuccess(app_list);
-  }
-
-  void SimulateParseError() {
-    EXPECT_TRUE(started_);
-    view_->OnParseResponseError();
-  }
-
-  void SimulateLoadError() {
-    EXPECT_TRUE(started_);
-    view_->OnLoadError();
-  }
-
-  // RecommendAppsFetcher:
-  void Start() override {
-    EXPECT_FALSE(started_);
-    started_ = true;
-  }
-  void Retry() override {
-    EXPECT_TRUE(started_);
-    ++retries_;
-  }
-
- private:
-  RecommendAppsScreenView* const view_;
-  bool started_ = false;
-  int retries_ = 0;
-};
-}  // namespace
-
-class RecommendAppsScreenTest : public InProcessBrowserTest {
- public:
-  RecommendAppsScreenTest() = default;
-  ~RecommendAppsScreenTest() override = default;
-
-  void SetUpOnMainThread() override {
-    ShowLoginWizard(OobeScreen::SCREEN_TEST_NO_WINDOW);
-
-    fetcher_factory_callback_ = base::BindRepeating(
-        &RecommendAppsScreenTest::CreateRecommendAppsFetcher,
-        base::Unretained(this));
-    RecommendAppsFetcher::SetFactoryCallbackForTesting(
-        &fetcher_factory_callback_);
-
-    recommend_apps_screen_ = std::make_unique<RecommendAppsScreen>(
-        GetOobeUI()->GetRecommendAppsScreenView(),
-        base::BindRepeating(&RecommendAppsScreenTest::HandleScreenExit,
-                            base::Unretained(this)));
-
-    InProcessBrowserTest::SetUpOnMainThread();
-  }
-  void TearDownOnMainThread() override {
-    RecommendAppsFetcher::SetFactoryCallbackForTesting(nullptr);
-    fetcher_factory_callback_.Reset();
-    recommend_apps_fetcher_ = nullptr;
-
-    InProcessBrowserTest::TearDownOnMainThread();
-  }
-
-  void WaitForScreenExit() {
-    if (screen_result_.has_value())
-      return;
-    base::RunLoop run_loop;
-    screen_exit_callback_ = run_loop.QuitClosure();
-    run_loop.Run();
-  }
-
-  bool WaitForAppListSize(const std::string& webview_path, int app_count) {
-    std::string count_apps_script =
-        "Array.from(document.getElementById('recommend-apps-container')"
-        "     .querySelectorAll('.item'))"
-        "     .map(i => i.getAttribute('data-packagename'));";
-
-    std::string script = base::StringPrintf(
-        "(function() {"
-        "  var getAppCount = function() {"
-        "    %s.executeScript({code: \"%s\"}, r => {"
-        "      if (!r || !r[0] || r[0].length !== %d) {"
-        "        setTimeout(getAppCount, 50);"
-        "        return;"
-        "      }"
-        "      console.error('send DONE');"
-        "      window.domAutomationController.send(true);"
-        "    });"
-        "  };"
-        "  getAppCount();"
-        "})();",
-        webview_path.c_str(), count_apps_script.c_str(), app_count);
-
-    // Wait for some apps to be shown
-    bool result;
-    return content::ExecuteScriptAndExtractBool(
-               LoginDisplayHost::default_host()->GetOobeWebContents(), script,
-               &result) &&
-           result;
-  }
-
-  // Simulates click on the apps in the webview's app list.
-  // The apps are expected to be passed in as a JavaScript array string.
-  // For example ['app_package_name1', 'app_package_name_2']
-  bool ToggleAppsSelection(const std::string& webview_path,
-                           const std::string& package_names) {
-    std::string toggle_apps_script = base::StringPrintf(
-        "Array.from(document.getElementById('recommend-apps-container')"
-        "     .querySelectorAll('.item'))"
-        "     .filter(i => %s.includes(i.getAttribute('data-packagename')))"
-        "     .forEach(i => i.querySelector('.image-picker').click());",
-        package_names.c_str());
-
-    std::string script = base::StringPrintf(
-        "(function() {"
-        "  %s.executeScript({code: \"%s\"},"
-        "                   r => window.domAutomationController.send(true));"
-        "})();",
-        webview_path.c_str(), toggle_apps_script.c_str());
-
-    bool result;
-    return content::ExecuteScriptAndExtractBool(
-               LoginDisplayHost::default_host()->GetOobeWebContents(), script,
-               &result) &&
-           result;
-  }
-
-  std::unique_ptr<RecommendAppsScreen> recommend_apps_screen_;
-  base::Optional<RecommendAppsScreen::Result> screen_result_;
-  FakeRecommendAppsFetcher* recommend_apps_fetcher_ = nullptr;
-
- private:
-  void HandleScreenExit(RecommendAppsScreen::Result result) {
-    ASSERT_FALSE(screen_result_.has_value());
-    screen_result_ = result;
-    if (screen_exit_callback_)
-      std::move(screen_exit_callback_).Run();
-  }
-
-  std::unique_ptr<RecommendAppsFetcher> CreateRecommendAppsFetcher(
-      RecommendAppsScreenView* view) {
-    EXPECT_EQ(view, GetOobeUI()->GetRecommendAppsScreenView());
-    EXPECT_FALSE(recommend_apps_fetcher_);
-
-    auto fetcher = std::make_unique<FakeRecommendAppsFetcher>(view);
-    recommend_apps_fetcher_ = fetcher.get();
-    return fetcher;
-  }
-
-  // The callback passed to
-  // RecommendAppsFetcher::SetFactoryCallbackForTesting(). Bound to
-  // CreateRecommendAppsFetcher().
-  RecommendAppsFetcher::FactoryCallback fetcher_factory_callback_;
-
-  base::OnceClosure screen_exit_callback_;
-};
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, BasicSelection) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1"),
-      FakeAppInfo("test_app_2", "test.app.foo.app2", "Test app 2"),
-      FakeAppInfo("test_app_3", "test.app.foo.app3", "Test app 3")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().ExpectDisabledPath(install_button);
-
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  test::OobeJS().ExpectPathDisplayed(true, install_button);
-  test::OobeJS().ExpectDisabledPath(install_button);
-  test::OobeJS().ExpectPathDisplayed(true, skip_button);
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(
-      webview_path, "['test.app.foo.app1', 'test.app.foo.app2']"));
-
-  test::OobeJS().CreateEnabledWaiter(true, install_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  test::OobeJS().TapOnPath(install_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SELECTED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-
-  base::Value expected_pref_value(base::Value::Type::LIST);
-  expected_pref_value.GetList().emplace_back("test.app.foo.app1");
-  expected_pref_value.GetList().emplace_back("test.app.foo.app2");
-  EXPECT_EQ(expected_pref_value, *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, SelectionChange) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1"),
-      FakeAppInfo("test_app_2", "test.app.foo.app2", "Test app 2"),
-      FakeAppInfo("test_app_3", "test.app.foo.app3", "Test app 3")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().ExpectDisabledPath(install_button);
-
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  test::OobeJS().ExpectPathDisplayed(true, install_button);
-  test::OobeJS().ExpectDisabledPath(install_button);
-  test::OobeJS().ExpectPathDisplayed(true, skip_button);
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(
-      webview_path, "['test.app.foo.app1', 'test.app.foo.app2']"));
-
-  test::OobeJS().CreateEnabledWaiter(true, install_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(webview_path, "['test.app.foo.app1']"));
-
-  test::OobeJS().TapOnPath(install_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SELECTED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-
-  base::Value expected_pref_value(base::Value::Type::LIST);
-  expected_pref_value.GetList().emplace_back("test.app.foo.app2");
-  EXPECT_EQ(expected_pref_value, *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, SkipWithSelectedApps) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1"),
-      FakeAppInfo("test_app_2", "test.app.foo.app2", "Test app 2"),
-      FakeAppInfo("test_app_3", "test.app.foo.app3", "Test app 3")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().ExpectDisabledPath(install_button);
-
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  test::OobeJS().ExpectPathDisplayed(true, install_button);
-  test::OobeJS().ExpectDisabledPath(install_button);
-  test::OobeJS().ExpectPathDisplayed(true, skip_button);
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(webview_path, "['test.app.foo.app2']"));
-
-  test::OobeJS().CreateEnabledWaiter(true, install_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  test::OobeJS().TapOnPath(skip_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, SkipWithNoAppsSelected) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1"),
-      FakeAppInfo("test_app_2", "test.app.foo.app2", "Test app 2"),
-      FakeAppInfo("test_app_3", "test.app.foo.app3", "Test app 3")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().ExpectDisabledPath(install_button);
-
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  test::OobeJS().ExpectPathDisplayed(true, install_button);
-  test::OobeJS().ExpectDisabledPath(install_button);
-  test::OobeJS().ExpectPathDisplayed(true, skip_button);
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(webview_path, "['test.app.foo.app2']"));
-
-  test::OobeJS().CreateEnabledWaiter(true, install_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  ASSERT_TRUE(ToggleAppsSelection(webview_path, "['test.app.foo.app2']"));
-
-  test::OobeJS().CreateEnabledWaiter(false, install_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  test::OobeJS().TapOnPath(skip_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, InstallWithNoAppsSelected) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  // The install button is expected to be disabled at this point. Send empty app
-  // list directly to test handler behavior when install is triggered with no
-  // apps selected.
-  test::OobeJS().Evaluate("chrome.send('recommendAppsInstall', []);");
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, NoRecommendedApps) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  recommend_apps_fetcher_->SimulateSuccess(std::vector<FakeAppInfo>());
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().CreateDisplayedWaiter(true, skip_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, install_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  test::OobeJS().TapOnPath(skip_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, ParseError) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  recommend_apps_fetcher_->SimulateParseError();
-
-  ASSERT_TRUE(screen_result_.has_value());
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, SkipOnLoadError) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  recommend_apps_fetcher_->SimulateLoadError();
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().CreateDisplayedWaiter(true, skip_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().CreateDisplayedWaiter(true, retry_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(retry_button);
-  test::OobeJS().ExpectPathDisplayed(false, install_button);
-
-  test::OobeJS().TapOnPath(skip_button);
-
-  WaitForScreenExit();
-  EXPECT_EQ(RecommendAppsScreen::Result::SKIPPED, screen_result_.value());
-  EXPECT_EQ(0, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-IN_PROC_BROWSER_TEST_F(RecommendAppsScreenTest, RetryOnLoadError) {
-  recommend_apps_screen_->Show();
-
-  OobeScreenWaiter screen_waiter(OobeScreen::SCREEN_RECOMMEND_APPS);
-  screen_waiter.set_assert_next_screen();
-  screen_waiter.Wait();
-
-  // Wait for loading screen.
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-loading"});
-  test::OobeJS().ExpectHidden("recommend-apps-screen");
-
-  recommend_apps_fetcher_->SimulateLoadError();
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::initializer_list<base::StringPiece> install_button = {
-      "recommend-apps-screen", "recommend-apps-install-button"};
-  const std::initializer_list<base::StringPiece> skip_button = {
-      "recommend-apps-screen", "recommend-apps-skip-button"};
-  const std::initializer_list<base::StringPiece> retry_button = {
-      "recommend-apps-screen", "recommend-apps-retry-button"};
-
-  test::OobeJS().CreateDisplayedWaiter(true, skip_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().CreateDisplayedWaiter(true, retry_button)->Wait();
-  test::OobeJS().ExpectEnabledPath(retry_button);
-  test::OobeJS().ExpectPathDisplayed(false, install_button);
-
-  test::OobeJS().TapOnPath(retry_button);
-
-  EXPECT_EQ(1, recommend_apps_fetcher_->retries());
-
-  test::OobeJS().CreateVisibilityWaiter(false, {"recommend-apps-screen"});
-  test::OobeJS().ExpectVisible("recommend-apps-loading");
-
-  std::vector<FakeAppInfo> test_apps = {
-      FakeAppInfo("test_app_1", "test.app.foo.app1", "Test app 1")};
-  recommend_apps_fetcher_->SimulateSuccess(test_apps);
-
-  test::OobeJS().CreateVisibilityWaiter(true, {"recommend-apps-screen"});
-  test::OobeJS().ExpectHidden("recommend-apps-loading");
-
-  const std::string webview_path =
-      test::GetOobeElementPath({"recommend-apps-screen", "app-list-view"});
-  test::OobeJS()
-      .CreateDisplayedWaiter(true, {"recommend-apps-screen", "app-list-view"})
-      ->Wait();
-  ASSERT_TRUE(WaitForAppListSize(webview_path, test_apps.size()));
-
-  test::OobeJS().ExpectPathDisplayed(true, install_button);
-  test::OobeJS().ExpectDisabledPath(install_button);
-  test::OobeJS().ExpectPathDisplayed(true, skip_button);
-  test::OobeJS().ExpectEnabledPath(skip_button);
-  test::OobeJS().ExpectPathDisplayed(false, retry_button);
-
-  EXPECT_FALSE(screen_result_.has_value());
-  EXPECT_EQ(1, recommend_apps_fetcher_->retries());
-
-  const base::Value* fast_reinstall_packages =
-      ProfileManager::GetActiveUserProfile()->GetPrefs()->Get(
-          arc::prefs::kArcFastAppReinstallPackages);
-  ASSERT_TRUE(fast_reinstall_packages);
-  EXPECT_EQ(base::Value(base::Value::Type::LIST), *fast_reinstall_packages);
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/js_checker.cc b/chrome/browser/chromeos/login/test/js_checker.cc
index 8382afa..58f2379c 100644
--- a/chrome/browser/chromeos/login/test/js_checker.cc
+++ b/chrome/browser/chromeos/login/test/js_checker.cc
@@ -136,28 +136,6 @@
   return CreateWaiter(js_condition);
 }
 
-std::unique_ptr<TestConditionWaiter> JSChecker::CreateDisplayedWaiter(
-    bool displayed,
-    std::initializer_list<base::StringPiece> element_ids) {
-  const std::string element_path = GetOobeElementPath(element_ids);
-  std::string js_condition = element_path + ".offsetWidth > 0 && " +
-                             element_path + ".offsetHeight > 0";
-  if (!displayed) {
-    js_condition = "!(" + js_condition + ")";
-  }
-  return CreateWaiter(js_condition);
-}
-
-std::unique_ptr<TestConditionWaiter> JSChecker::CreateEnabledWaiter(
-    bool enabled,
-    std::initializer_list<base::StringPiece> element_ids) {
-  std::string js_condition = GetOobeElementPath(element_ids) + ".disabled";
-  if (enabled) {
-    js_condition = "!(" + js_condition + ")";
-  }
-  return CreateWaiter(js_condition);
-}
-
 void JSChecker::GetBoolImpl(const std::string& expression, bool* result) {
   CHECK(web_contents_);
   ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
@@ -202,28 +180,6 @@
   ExpectHiddenPath({element_id});
 }
 
-void JSChecker::ExpectPathDisplayed(
-    bool displayed,
-    std::initializer_list<base::StringPiece> element_ids) {
-  const std::string element_path = GetOobeElementPath(element_ids);
-  std::string js_condition = element_path + ".offsetWidth > 0 && " +
-                             element_path + ".offsetHeight > 0";
-  if (!displayed) {
-    js_condition = "!(" + js_condition + ")";
-  }
-  ExpectTrue(js_condition);
-}
-
-void JSChecker::ExpectDisabledPath(
-    std::initializer_list<base::StringPiece> element_ids) {
-  ExpectTrue(GetOobeElementPath(element_ids) + ".disabled");
-}
-
-void JSChecker::ExpectEnabledPath(
-    std::initializer_list<base::StringPiece> element_ids) {
-  ExpectFalse(GetOobeElementPath(element_ids) + ".disabled");
-}
-
 void JSChecker::ExpectHasClass(
     const std::string& css_class,
     std::initializer_list<base::StringPiece> element_ids) {
diff --git a/chrome/browser/chromeos/login/test/js_checker.h b/chrome/browser/chromeos/login/test/js_checker.h
index 0db84970..6a289db 100644
--- a/chrome/browser/chromeos/login/test/js_checker.h
+++ b/chrome/browser/chromeos/login/test/js_checker.h
@@ -65,42 +65,14 @@
       bool visibility,
       std::initializer_list<base::StringPiece> element_ids);
 
-  // Waiter that waits until specified element is (not) displayed with non-zero
-  // size.
-  std::unique_ptr<TestConditionWaiter> CreateDisplayedWaiter(
-      bool displayed,
-      std::initializer_list<base::StringPiece> element_ids);
-
-  // Waiter that waits until an element is enabled or disabled.
-  std::unique_ptr<TestConditionWaiter> CreateEnabledWaiter(
-      bool enabled,
-      std::initializer_list<base::StringPiece> element_ids);
-
   // Expects that indicated UI element is not hidden.
-  // NOTE: This only checks hidden property - it might not work for elements
-  // hidden by "display: none" style.
   void ExpectVisiblePath(std::initializer_list<base::StringPiece> element_ids);
   void ExpectVisible(const std::string& element_id);
 
   // Expects that indicated UI element is hidden.
-  // NOTE: This only checks hidden property - it might not work for elements
-  // hidden by "display: none" style.
   void ExpectHiddenPath(std::initializer_list<base::StringPiece> element_ids);
   void ExpectHidden(const std::string& element_id);
 
-  // Expects that the element is displayed on screen - i.e. that it has non-null
-  // size. Unlike ExpectHidden and ExpectVisible methods, this will correctly
-  // elements with "display: none" style, but might not work for polymer module
-  // roots.
-  void ExpectPathDisplayed(bool displayed,
-                           std::initializer_list<base::StringPiece> element_id);
-
-  // Expects that the indicated UI element is disabled.
-  void ExpectDisabledPath(std::initializer_list<base::StringPiece> element_ids);
-
-  // Expects that the indicated UI element is not disabled.
-  void ExpectEnabledPath(std::initializer_list<base::StringPiece> element_ids);
-
   // Expects that indicated UI element has particular class.
   void ExpectHasClass(const std::string& css_class,
                       std::initializer_list<base::StringPiece> element_ids);
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
index 69ef8c0..ca9cf9f 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
@@ -56,38 +56,21 @@
                  ModelConfigLoader* model_config_loader,
                  MetricsReporter* metrics_reporter,
                  chromeos::PowerManagerClient* power_manager_client)
-    : profile_(profile),
-      als_reader_observer_(this),
-      brightness_monitor_observer_(this),
-      modeller_observer_(this),
-      model_config_loader_observer_(this),
-      power_manager_client_observer_(this),
-      metrics_reporter_(metrics_reporter),
-      power_manager_client_(power_manager_client),
-      tick_clock_(base::DefaultTickClock::GetInstance()),
-      weak_ptr_factory_(this) {
-  DCHECK(profile);
-  DCHECK(als_reader);
-  DCHECK(brightness_monitor);
-  DCHECK(modeller);
-  DCHECK(model_config_loader);
-  DCHECK(power_manager_client);
-
-  als_reader_observer_.Add(als_reader);
-  brightness_monitor_observer_.Add(brightness_monitor);
-  modeller_observer_.Add(modeller);
-  model_config_loader_observer_.Add(model_config_loader);
-  power_manager_client_observer_.Add(power_manager_client);
-
-  power_manager_client_->WaitForServiceToBeAvailable(
-      base::BindOnce(&Adapter::OnPowerManagerServiceAvailable,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
+    : Adapter(profile,
+              als_reader,
+              brightness_monitor,
+              modeller,
+              model_config_loader,
+              metrics_reporter,
+              power_manager_client,
+              base::DefaultTickClock::GetInstance()) {}
 
 Adapter::~Adapter() = default;
 
 void Adapter::OnAmbientLightUpdated(int lux) {
   // Ambient light data is only used when adapter is initialized to success.
+  // |ambient_light_values_| may not be available to use when adapter is being
+  // initialized.
   if (adapter_status_ != Status::kSuccess)
     return;
 
@@ -104,6 +87,7 @@
   DCHECK(!als_init_status_);
 
   als_init_status_ = status;
+  als_init_time_ = tick_clock_->NowTicks();
   UpdateStatus();
 }
 
@@ -116,14 +100,26 @@
 
 void Adapter::OnUserBrightnessChanged(double old_brightness_percent,
                                       double new_brightness_percent) {
+  const base::TimeTicks now = tick_clock_->NowTicks();
   // We skip this notification if adapter hasn't been initialised because its
   // |params_| may change. We need to log even if adapter is initialized to
   // disabled.
   if (adapter_status_ == Status::kInitializing)
     return;
 
-  // TODO(jiameng): record current average als and update thresholds.
-  current_brightness_ = new_brightness_percent;
+  // |latest_brightness_change_time_|, |current_brightness_|,
+  // |log_average_ambient_lux_| and thresholds are only needed if adapter is
+  // |kSuccess|.
+  if (adapter_status_ == Status::kSuccess) {
+    DCHECK(ambient_light_values_);
+    const base::Optional<AlsAvgStdDev> als_avg_stddev =
+        ambient_light_values_->AverageAmbientWithStdDev(now);
+
+    OnBrightnessChanged(now, new_brightness_percent,
+                        als_avg_stddev ? base::Optional<double>(
+                                             ConvertToLog(als_avg_stddev->avg))
+                                       : base::nullopt);
+  }
 
   if (!metrics_reporter_)
     return;
@@ -161,9 +157,10 @@
 }
 
 void Adapter::OnUserBrightnessChangeRequested() {
-  // We skip this notification if adapter hasn't been initialised because its
-  // |params_| may change.
-  if (adapter_status_ == Status::kInitializing)
+  // We skip this notification if adapter hasn't been initialised (because its
+  // |params_| may change), or, if adapter is disabled (because adapter won't
+  // change brightness anyway).
+  if (adapter_status_ != Status::kSuccess)
     return;
 
   if (params_.user_adjustment_effect != UserAdjustmentEffect::kContinueAuto) {
@@ -210,19 +207,16 @@
 }
 
 void Adapter::SuspendDone(const base::TimeDelta& /* sleep_duration */) {
-  // We skip this notification if adapter hasn't been initialised because its
-  // |params_| may change.
-  if (adapter_status_ == Status::kInitializing)
+  // We skip this notification if adapter hasn't been initialised (because its
+  // |params_| may change), or, if adapter is disabled (because adapter won't
+  // change brightness anyway).
+  if (adapter_status_ != Status::kSuccess)
     return;
 
   if (params_.user_adjustment_effect == UserAdjustmentEffect::kPauseAuto)
     adapter_disabled_by_user_adjustment_ = false;
 }
 
-void Adapter::SetTickClockForTesting(const base::TickClock* test_tick_clock) {
-  tick_clock_ = test_tick_clock;
-}
-
 Adapter::Status Adapter::GetStatusForTesting() const {
   return adapter_status_;
 }
@@ -255,6 +249,60 @@
   return *darkening_threshold_;
 }
 
+base::Optional<double> Adapter::GetCurrentLogAvgAlsForTesting() const {
+  return log_average_ambient_lux_;
+}
+
+std::unique_ptr<Adapter> Adapter::CreateForTesting(
+    Profile* profile,
+    AlsReader* als_reader,
+    BrightnessMonitor* brightness_monitor,
+    Modeller* modeller,
+    ModelConfigLoader* model_config_loader,
+    MetricsReporter* metrics_reporter,
+    chromeos::PowerManagerClient* power_manager_client,
+    const base::TickClock* tick_clock) {
+  return base::WrapUnique(new Adapter(
+      profile, als_reader, brightness_monitor, modeller, model_config_loader,
+      metrics_reporter, power_manager_client, tick_clock));
+}
+
+Adapter::Adapter(Profile* profile,
+                 AlsReader* als_reader,
+                 BrightnessMonitor* brightness_monitor,
+                 Modeller* modeller,
+                 ModelConfigLoader* model_config_loader,
+                 MetricsReporter* metrics_reporter,
+                 chromeos::PowerManagerClient* power_manager_client,
+                 const base::TickClock* tick_clock)
+    : profile_(profile),
+      als_reader_observer_(this),
+      brightness_monitor_observer_(this),
+      modeller_observer_(this),
+      model_config_loader_observer_(this),
+      power_manager_client_observer_(this),
+      metrics_reporter_(metrics_reporter),
+      power_manager_client_(power_manager_client),
+      tick_clock_(tick_clock),
+      weak_ptr_factory_(this) {
+  DCHECK(profile);
+  DCHECK(als_reader);
+  DCHECK(brightness_monitor);
+  DCHECK(modeller);
+  DCHECK(model_config_loader);
+  DCHECK(power_manager_client);
+
+  als_reader_observer_.Add(als_reader);
+  brightness_monitor_observer_.Add(brightness_monitor);
+  modeller_observer_.Add(modeller);
+  model_config_loader_observer_.Add(model_config_loader);
+  power_manager_client_observer_.Add(power_manager_client);
+
+  power_manager_client_->WaitForServiceToBeAvailable(
+      base::BindOnce(&Adapter::OnPowerManagerServiceAvailable,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
 void Adapter::InitParams(const ModelConfig& model_config) {
   if (!base::FeatureList::IsEnabled(features::kAutoScreenBrightness) &&
       model_config.metrics_key != "atlas") {
@@ -274,6 +322,10 @@
       features::kAutoScreenBrightness, "darkening_log_lux_threshold",
       params_.darkening_log_lux_threshold);
 
+  params_.stabilization_threshold = GetFieldTrialParamByFeatureAsDouble(
+      features::kAutoScreenBrightness, "stabilization_threshold",
+      params_.stabilization_threshold);
+
   const int model_curve = base::GetFieldTrialParamByFeatureAsInt(
       features::kAutoScreenBrightness, "model_curve", 2);
   if (model_curve < 0 || model_curve > 2) {
@@ -284,7 +336,10 @@
   params_.model_curve = static_cast<ModelCurve>(model_curve);
 
   const int auto_brightness_als_horizon_seconds =
-      model_config.auto_brightness_als_horizon_seconds;
+      GetFieldTrialParamByFeatureAsInt(
+          features::kAutoScreenBrightness,
+          "auto_brightness_als_horizon_seconds",
+          model_config.auto_brightness_als_horizon_seconds);
 
   if (auto_brightness_als_horizon_seconds <= 0) {
     adapter_status_ = Status::kDisabled;
@@ -371,10 +426,12 @@
 }
 
 base::Optional<Adapter::BrightnessChangeCause> Adapter::CanAdjustBrightness(
-    double current_log_average_ambient) const {
+    double current_log_average_ambient,
+    double stddev) const {
   if (adapter_status_ != Status::kSuccess ||
-      adapter_disabled_by_user_adjustment_)
+      adapter_disabled_by_user_adjustment_) {
     return base::nullopt;
+  }
 
   // Do not change brightness if it's set by the policy, but do not completely
   // disable the model as the policy could change.
@@ -385,18 +442,29 @@
     return base::nullopt;
   }
 
-  if (latest_brightness_change_time_.is_null()) {
-    // Brightness hasn't been changed before.
+  if (!log_average_ambient_lux_) {
+    // Either
+    // 1. brightness hasn't been changed, or,
+    // 2. brightness was changed by the user but there wasn't any ALS data. This
+    //    case should be rare.
+    // In either case, we change brightness as soon as we have brightness.
     return BrightnessChangeCause::kInitialAlsReceived;
   }
 
   // The following thresholds should have been set last time when brightness was
   // changed.
-  if (current_log_average_ambient > *brightening_threshold_) {
+  DCHECK(brightening_threshold_);
+  DCHECK(darkening_threshold_);
+
+  if (current_log_average_ambient > *brightening_threshold_ &&
+      stddev <= params_.brightening_log_lux_threshold *
+                    params_.stabilization_threshold) {
     return BrightnessChangeCause::kBrightneningThresholdExceeded;
   }
 
-  if (current_log_average_ambient < *darkening_threshold_) {
+  if (current_log_average_ambient < *darkening_threshold_ &&
+      stddev <= params_.darkening_log_lux_threshold *
+                    params_.stabilization_threshold) {
     return BrightnessChangeCause::kDarkeningThresholdExceeded;
   }
 
@@ -406,6 +474,18 @@
 void Adapter::MaybeAdjustBrightness(base::TimeTicks now) {
   DCHECK_EQ(adapter_status_, Status::kSuccess);
   DCHECK(ambient_light_values_);
+  DCHECK(!als_init_time_.is_null());
+  // Wait until we've had enough ALS data to calc avg.
+  if (now - als_init_time_ < params_.auto_brightness_als_horizon)
+    return;
+
+  // Check if we've waited long enough from previous brightness change (either
+  // by user or by model).
+  if (!latest_brightness_change_time_.is_null() &&
+      now - latest_brightness_change_time_ <
+          params_.auto_brightness_als_horizon)
+    return;
+
   const base::Optional<AlsAvgStdDev> als_avg_stddev =
       ambient_light_values_->AverageAmbientWithStdDev(now);
   if (!als_avg_stddev)
@@ -414,7 +494,7 @@
   const double log_average_ambient_lux = ConvertToLog(als_avg_stddev->avg);
 
   const base::Optional<BrightnessChangeCause> brightness_change_cause =
-      CanAdjustBrightness(log_average_ambient_lux);
+      CanAdjustBrightness(log_average_ambient_lux, als_avg_stddev->stddev);
 
   if (!brightness_change_cause.has_value())
     return;
@@ -435,35 +515,22 @@
   power_manager_client_->SetScreenBrightness(request);
 
   const base::TimeTicks brightness_change_time = tick_clock_->NowTicks();
-  if (!latest_brightness_change_time_.is_null()) {
+  if (!latest_model_brightness_change_time_.is_null()) {
     UMA_HISTOGRAM_LONG_TIMES(
         "AutoScreenBrightness.BrightnessChange.ElapsedTime",
-        brightness_change_time - latest_brightness_change_time_);
+        brightness_change_time - latest_model_brightness_change_time_);
   }
-  latest_brightness_change_time_ = brightness_change_time;
+  latest_model_brightness_change_time_ = brightness_change_time;
 
   const BrightnessChangeCause cause = *brightness_change_cause;
   UMA_HISTOGRAM_ENUMERATION("AutoScreenBrightness.BrightnessChange.Cause",
                             cause);
 
   WriteLogMessages(log_average_ambient_lux, *brightness, cause);
+  model_brightness_change_counter_++;
 
-  current_brightness_ = *brightness;
-  brightness_change_counter_++;
-
-  log_average_ambient_lux_ = log_average_ambient_lux;
-
-  UpdateBrightnessChangeThresholds();
-}
-
-void Adapter::UpdateBrightnessChangeThresholds() {
-  DCHECK_NE(adapter_status_, Status::kInitializing);
-  DCHECK(log_average_ambient_lux_);
-
-  brightening_threshold_ =
-      *log_average_ambient_lux_ + params_.brightening_log_lux_threshold;
-  darkening_threshold_ =
-      *log_average_ambient_lux_ - params_.darkening_log_lux_threshold;
+  OnBrightnessChanged(brightness_change_time, *brightness,
+                      log_average_ambient_lux);
 }
 
 base::Optional<double> Adapter::GetBrightnessBasedOnAmbientLogLux(
@@ -484,6 +551,31 @@
   }
 }
 
+void Adapter::OnBrightnessChanged(base::TimeTicks now,
+                                  double new_brightness_percent,
+                                  base::Optional<double> new_log_als) {
+  DCHECK_NE(adapter_status_, Status::kInitializing);
+
+  current_brightness_ = new_brightness_percent;
+  latest_brightness_change_time_ = now;
+
+  UMA_HISTOGRAM_BOOLEAN("AutoScreenBrightness.MissingAlsWhenBrightnessChanged",
+                        !new_log_als);
+
+  if (!new_log_als)
+    return;
+
+  // Update |log_average_ambient_lux_| with the new reference value. Brightness
+  // will be changed by the model if next log-avg ALS value goes outside of the
+  // range
+  // [|darkening_threshold_|, |brightening_threshold_|].
+  // Thresholds in |params_| are absolute values to be added/subtracted from
+  // the reference values. Log-avg can be negative.
+  log_average_ambient_lux_ = new_log_als;
+  brightening_threshold_ = *new_log_als + params_.brightening_log_lux_threshold;
+  darkening_threshold_ = *new_log_als - params_.darkening_log_lux_threshold;
+}
+
 void Adapter::WriteLogMessages(double new_log_als,
                                double new_brightness,
                                BrightnessChangeCause cause) const {
@@ -498,7 +590,8 @@
           ? base::StringPrintf("%.4f", current_brightness_.value()) + "%->"
           : "";
 
-  VLOG(1) << "Screen brightness change #" << brightness_change_counter_ << ": "
+  VLOG(1) << "Screen brightness change #" << model_brightness_change_counter_
+          << ": "
           << "brightness=" << old_brightness
           << base::StringPrintf("%.4f", new_brightness) << "%"
           << " cause=" << BrightnessChangeCauseToString(cause)
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
index 4a75d2f..87591ba 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
@@ -68,14 +68,23 @@
   };
 
   // The values in Params can be overridden by experiment flags.
+  // TODO(jiameng): move them to cros config json file once experiments are
+  // complete.
   struct Params {
     Params();
 
-    // The log of average ambient value has to go up (resp. down) by
-    // |brightening_log_lux_threshold| (resp. |darkening_log_lux_threshold|)
-    // from the current value before brightness could be changed.
-    double brightening_log_lux_threshold = 1.0;
-    double darkening_log_lux_threshold = 1.0;
+    // Brightness is only changed if
+    // 1. the log of average ambient value has gone up (resp. down) by
+    //    |brightening_log_lux_threshold| (resp. |darkening_log_lux_threshold|)
+    //    from the reference value. The reference value is the average ALS when
+    //    brightness was changed last time (by user or model).
+    //   and
+    // 2. the std-dev of ALS within the averaging period is less than
+    //    |stabilization_threshold| multiplied by the brightening/darkening
+    //    thresholds to show the ALS has stabilized.
+    double brightening_log_lux_threshold = 0.6;
+    double darkening_log_lux_threshold = 0.6;
+    double stabilization_threshold = 0.15;
 
     ModelCurve model_curve = ModelCurve::kLatest;
 
@@ -83,7 +92,7 @@
     // |auto_brightness_als_horizon|. This is only used for brightness update,
     // which can be different from the horizon used in model training.
     base::TimeDelta auto_brightness_als_horizon =
-        base::TimeDelta::FromSeconds(5);
+        base::TimeDelta::FromSeconds(4);
 
     UserAdjustmentEffect user_adjustment_effect =
         UserAdjustmentEffect::kDisableAuto;
@@ -144,8 +153,6 @@
   // chromeos::PowerManagerClient::Observer overrides:
   void SuspendDone(const base::TimeDelta& sleep_duration) override;
 
-  void SetTickClockForTesting(const base::TickClock* test_tick_clock);
-
   Status GetStatusForTesting() const;
 
   // Only returns true if Adapter status is success and it's not disabled by
@@ -160,10 +167,32 @@
   double GetBrighteningThresholdForTesting() const;
   double GetDarkeningThresholdForTesting() const;
 
+  // Returns |log_average_ambient_lux_|.
+  base::Optional<double> GetCurrentLogAvgAlsForTesting() const;
+
+  static std::unique_ptr<Adapter> CreateForTesting(
+      Profile* profile,
+      AlsReader* als_reader,
+      BrightnessMonitor* brightness_monitor,
+      Modeller* modeller,
+      ModelConfigLoader* model_config_loader,
+      MetricsReporter* metrics_reporter,
+      chromeos::PowerManagerClient* power_manager_client,
+      const base::TickClock* tick_clock);
+
  private:
+  Adapter(Profile* profile,
+          AlsReader* als_reader,
+          BrightnessMonitor* brightness_monitor,
+          Modeller* modeller,
+          ModelConfigLoader* model_config_loader,
+          MetricsReporter* metrics_reporter,
+          chromeos::PowerManagerClient* power_manager_client,
+          const base::TickClock* tick_clock);
+
   // Called by |OnModelConfigLoaded|. It will initialize all params used by
-  // the modeller from |model_config| and also other experiment flags. If any
-  // param is invalid, it will disable the adapter.
+  // the modeller from |model_config| and also other experiment flags. If
+  // any param is invalid, it will disable the adapter.
   void InitParams(const ModelConfig& model_config);
 
   // Called when powerd becomes available.
@@ -177,22 +206,22 @@
   // Returns a BrightnessChangeCause if the adapter can change the brightness.
   // This is generally the case when the brightness hasn't been manually
   // set, we've received enough initial ambient light readings, and
-  // the ambient light has changed beyond thresholds.
+  // the ambient light has changed beyond thresholds and has stabilized.
   // Returns nullopt if it shouldn't change the brightness.
   base::Optional<BrightnessChangeCause> CanAdjustBrightness(
-      double current_average_ambient) const;
+      double current_log_average_ambient,
+      double stddev) const;
 
   // Called when ambient light changes. It only changes screen brightness if
   // |CanAdjustBrightness| returns true and a required curve is set up:
   // if the required curve is personal but no personal curve is available, then
   // brightness won't be changed.
-  // It will call |UpdateBrightnessChangeThresholds| if brightness is actually
-  // changed.
+  // It will call |OnBrightnessChanged| if brightness is actually changed.
+  // |now| should be the timestamp when ALS reading comes in, i.e. when
+  // |OnAmbientLightUpdated| is called. |OnAmbientLightUpdated| is the event
+  // that triggers the call of |MaybeAdjustBrightness|.
   void MaybeAdjustBrightness(base::TimeTicks now);
 
-  // This is only called when brightness is changed.
-  void UpdateBrightnessChangeThresholds();
-
   // Calculates brightness from given |ambient_log_lux| based on either
   // |global_curve_| or |personal_curve_| (as specified by the experiment
   // params). Returns nullopt if a personal curve should be used but it's not
@@ -200,6 +229,17 @@
   base::Optional<double> GetBrightnessBasedOnAmbientLogLux(
       double ambient_log_lux) const;
 
+  // Called when brightness is changed by the model or user. This function
+  // updates |latest_brightness_change_time_|, |current_brightness_|. If
+  // |new_log_als| is not nullopt, it will also update
+  // |log_average_ambient_lux_| and thresholds. |new_log_als| should be
+  // available when this function is called, but may be nullopt when a user
+  // changes brightness before any ALS reading comes in. We log an error if this
+  // happens.
+  void OnBrightnessChanged(base::TimeTicks now,
+                           double new_brightness_percent,
+                           base::Optional<double> new_log_als);
+
   // Called by |MaybeAdjustBrightness| when brightness should be changed.
   void WriteLogMessages(double new_log_als,
                         double new_brightness,
@@ -229,10 +269,15 @@
   // This will be replaced by a mock tick clock during tests.
   const base::TickClock* tick_clock_;
 
+  // TODO(jiameng): refactor internal states and flags.
+
   // This buffer will be used to store the recent ambient light values.
   std::unique_ptr<AmbientLightSampleBuffer> ambient_light_values_;
 
   base::Optional<AlsReader::AlsInitStatus> als_init_status_;
+  // Time when AlsReader is initialized.
+  base::TimeTicks als_init_time_;
+
   base::Optional<bool> brightness_monitor_success_;
 
   // |model_config_exists_| will remain nullopt until |OnModelConfigLoaded| is
@@ -249,32 +294,34 @@
   // This is set to true whenever a user makes a manual adjustment, and if
   // |params_.user_adjustment_effect| is not |kContinueAuto|. It will be
   // reset to false if |params_.user_adjustment_effect| is |kPauseAuto|.
+  // It won't be set/reset if adapter is disabled because it won't be necessary
+  // to check |adapter_disabled_by_user_adjustment_|.
   bool adapter_disabled_by_user_adjustment_ = false;
 
   // The thresholds are calculated from the |log_average_ambient_lux_|.
-  // They are only updated when brightness should occur (because the log of
-  // average ambient value changed sufficiently).
+  // They are only updated when brightness is changed (either by user or model).
   base::Optional<double> brightening_threshold_;
   base::Optional<double> darkening_threshold_;
 
   base::Optional<MonotoneCubicSpline> global_curve_;
   base::Optional<MonotoneCubicSpline> personal_curve_;
 
-  // Average ambient value is only calculated when |CanAdjustBrightness|
-  // returns true. This is the log of average over all values in
-  // |ambient_light_values_|. The adapter will notify powerd to change
-  // brightness. New thresholds will be calculated from it.
+  // |log_average_ambient_lux_| is only recorded when screen brightness is
+  // changed by either model or user. New thresholds will be calculated from it.
   base::Optional<double> log_average_ambient_lux_;
 
-  // Last time brightness change occurred.
+  // Last time brightness change occurred, either by user or model.
   base::TimeTicks latest_brightness_change_time_;
 
+  // Last time brightness was changed by the model.
+  base::TimeTicks latest_model_brightness_change_time_;
+
   // Current recorded brightness. It can be either the user requested brightness
   // or the model requested brightness.
   base::Optional<double> current_brightness_;
 
   // Used to record number of model-triggered brightness changes.
-  int brightness_change_counter_ = 1;
+  int model_brightness_change_counter_ = 1;
 
   base::WeakPtrFactory<Adapter> weak_ptr_factory_;
 
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc b/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
index febce768a..5334b5f 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/chromeos/power/auto_screen_brightness/adapter.h"
 
 #include <map>
+#include <numeric>
 #include <vector>
 
 #include "ash/public/cpp/ash_pref_names.h"
@@ -40,25 +41,15 @@
 
 namespace {
 
-// Checks |result.avg| and |result.stddev| are the same as that
-// calculated from the |expected_data|.
-void CheckAverageAndStdDev(const AlsAvgStdDev& result,
-                           const std::vector<double>& expected_data) {
+// Checks |actual_log_avg| is equal to the log avg calculated from
+// |expected_data|.
+void CheckLogAvg(const std::vector<double>& expected_data,
+                 double actual_log_avg) {
   const size_t count = expected_data.size();
   CHECK_NE(count, 0u);
-  double expected_avg = 0;
-  double expected_stddev = 0;
-
-  for (const auto& i : expected_data) {
-    expected_avg += i;
-    expected_stddev += i * i;
-  }
-
-  expected_avg = expected_avg / count;
-  expected_stddev =
-      std::sqrt(expected_stddev / count - expected_avg * expected_avg);
-  EXPECT_DOUBLE_EQ(result.avg, expected_avg);
-  EXPECT_DOUBLE_EQ(result.stddev, expected_stddev);
+  const double expected_log_avg = ConvertToLog(
+      std::accumulate(expected_data.begin(), expected_data.end(), 0.0) / count);
+  EXPECT_DOUBLE_EQ(actual_log_avg, expected_log_avg);
 }
 
 // Testing modeller.
@@ -166,8 +157,13 @@
     chromeos::PowerManagerClient::Shutdown();
   }
 
+  // Creates Adapter only, but its input may or may not be ready.
   void SetUpAdapter(const std::map<std::string, std::string>& params,
                     bool brightness_set_by_policy = false) {
+    // Simulate the real clock that will not produce TimeTicks equal to 0.
+    // This is because the Adapter will treat 0 TimeTicks are uninitialized
+    // values.
+    thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
     sync_preferences::PrefServiceMockFactory factory;
     factory.set_user_prefs(base::WrapRefCounted(new TestingPrefStore()));
     scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
@@ -207,13 +203,15 @@
           features::kAutoScreenBrightness, params);
     }
 
-    adapter_ = std::make_unique<Adapter>(
+    adapter_ = Adapter::CreateForTesting(
         profile_.get(), &fake_als_reader_, &fake_brightness_monitor_,
         &fake_modeller_, &fake_model_config_loader_,
-        nullptr /* metrics_reporter */, chromeos::PowerManagerClient::Get());
-    adapter_->SetTickClockForTesting(thread_bundle_.GetMockTickClock());
+        nullptr /* metrics_reporter */, chromeos::PowerManagerClient::Get(),
+        thread_bundle_.GetMockTickClock());
+    thread_bundle_.RunUntilIdle();
   }
 
+  // Sets up all required input for Adapter and then creates Adapter.
   void Init(AlsReader::AlsInitStatus als_reader_status,
             BrightnessMonitor::Status brightness_monitor_status,
             const base::Optional<MonotoneCubicSpline>& global_curve,
@@ -229,7 +227,6 @@
     }
 
     SetUpAdapter(params, brightness_set_by_policy);
-    thread_bundle_.RunUntilIdle();
   }
 
   void ReportSuspendDone() {
@@ -253,6 +250,29 @@
     return model_config;
   }
 
+  void ReportAls(int als_value) {
+    fake_als_reader_.ReportAmbientLightUpdate(als_value);
+    thread_bundle_.RunUntilIdle();
+  }
+
+  void ReportUserBrightnessChangeRequest(double old_brightness_percent,
+                                         double new_brightness_percent) {
+    fake_brightness_monitor_.ReportUserBrightnessChanged(
+        old_brightness_percent, new_brightness_percent);
+    fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
+    thread_bundle_.RunUntilIdle();
+  }
+
+  // Forwards time first and then reports Als.
+  void ForwardTimeAndReportAls(const std::vector<int>& als_values) {
+    for (const int als_value : als_values) {
+      // Forward 1 second to simulate the real AlsReader that samples data at
+      // 1hz.
+      thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+      ReportAls(als_value);
+    }
+  }
+
  protected:
   content::TestBrowserThreadBundle thread_bundle_;
 
@@ -271,10 +291,16 @@
 
   base::HistogramTester histogram_tester_;
 
+  // |brightening_log_lux_threshold| and |darkening_log_lux_threshold| are set
+  // to very small values so a slight change in ALS would trigger brightness
+  // update. |stabilization_threshold| is set to a very high value so that we
+  // don't have to check ALS has stablized.
   const std::map<std::string, std::string> default_params_ = {
-      {"brightening_log_lux_threshold", "0.1"},
-      {"darkening_log_lux_threshold", "0.2"},
+      {"brightening_log_lux_threshold", "0.00001"},
+      {"darkening_log_lux_threshold", "0.00001"},
+      {"stabilization_threshold", "100000000"},
       {"model_curve", "2"},
+      {"auto_brightness_als_horizon_seconds", "5"},
       {"user_adjustment_effect", "0"},
   };
 
@@ -456,6 +482,44 @@
   EXPECT_FALSE(adapter_->GetPersonalCurveForTesting());
 }
 
+// First ALS comes in 1 second after AlsReader is initialized. Hence after
+// |auto_brightness_als_horizon_seconds|, brightness is changed.
+TEST_F(AdapterTest, FirstAlsAfterAlsReaderInitTime) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+
+  // |auto_brightness_als_horizon_seconds| is 5.
+  ForwardTimeAndReportAls({1, 2, 3, 4});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+
+  ForwardTimeAndReportAls({100});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 100},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
+// First ALS comes in at the same time when AlsReader is initialized. Hence
+// after |auto_brightness_als_horizon_seconds| + 1 readings, brightness is
+// changed.
+TEST_F(AdapterTest, FirstAlsAtAlsReaderInitTime) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+
+  // First ALS when AlsReader is initialized.
+  ReportAls(10);
+  ForwardTimeAndReportAls({1, 2, 3, 4});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+
+  ForwardTimeAndReportAls({100});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 100},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
 TEST_F(AdapterTest, SequenceOfBrightnessUpdatesWithDefaultParams) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
        global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
@@ -466,113 +530,227 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ForwardTimeAndReportAls({1, 2, 3, 4});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
 
-  // Brightness is changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // Brightness is changed for the first time after the 5th reading.
+  ForwardTimeAndReportAls({5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {10});
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   ConvertToLog(10.0) + 0.1);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   ConvertToLog(10.0) - 0.2);
+  // Several other ALS readings come in, but need to wait for
+  // |params.auto_brightness_als_horizon_seconds| to pass before having any
+  // effect
+  ForwardTimeAndReportAls({20});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({30});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({40});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({50});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // The next ALS reading triggers brightness change.
+  ForwardTimeAndReportAls({60});
   EXPECT_EQ(test_observer_.num_changes(), 2);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {10, 20});
-
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   ConvertToLog(15.0) + 0.1);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   ConvertToLog(15.0) - 0.2);
+  CheckLogAvg({20, 30, 40, 50, 60},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
   // |params.auto_brightness_als_horizon_seconds| has elapsed since we've made
   // the change, but there's no new ALS value, hence no brightness change is
   // triggered.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(10));
   EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({20, 30, 40, 50, 60},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
   EXPECT_EQ(adapter_->GetAverageAmbientWithStdDevForTesting(
                 thread_bundle_.NowTicks()),
             base::nullopt);
 
   // A new ALS value triggers a brightness change.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(40);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({100});
   EXPECT_EQ(test_observer_.num_changes(), 3);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {40});
-
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   ConvertToLog(40.0) + 0.1);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   ConvertToLog(40.0) - 0.2);
-
-  // Adapter will not be applied after a user manual adjustment.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_FALSE(adapter_->IsAppliedForTesting());
-
-  // SuspendDone does not re-enable Adapter as default for effect is
-  // |kDisableAuto|.
-  ReportSuspendDone();
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_FALSE(adapter_->IsAppliedForTesting());
-
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(100);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 3);
-
-  // Another user manual adjustment came in.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  CheckLogAvg({100}, adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
-TEST_F(AdapterTest, UserBrightnessRequestBeforeAnyModelUpdate) {
+// A user brightness change comes in when ALS readings exist. This also disables
+// the adapter because |user_adjustment_effect| is 0 (disabled).
+TEST_F(AdapterTest, UserBrightnessChangeAlsReadingExists) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
        global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
-  EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ForwardTimeAndReportAls({1, 2, 3, 4});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
 
   // Adapter will not be applied after a user manual adjustment.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
 
-  // Another user manual adjustment came in.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 1);
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  CheckLogAvg({1, 2, 3, 4}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // An als reading comes in but will not change the brightness.
+  ForwardTimeAndReportAls({100});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  CheckLogAvg({1, 2, 3, 4}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Another user manual adjustment comes in.
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ReportUserBrightnessChangeRequest(30.0, 40.0);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 2);
+  CheckLogAvg({2, 3, 4, 100},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
-TEST_F(AdapterTest, BrightnessLuxThresholds) {
+// Same as |UserBrightnessChangeAlsReadingExists| except that user adjustment
+// effect is Continue.
+TEST_F(AdapterTest, UserBrightnessChangeAlsReadingExistsContinue) {
   std::map<std::string, std::string> params = default_params_;
-  params["brightening_log_lux_threshold"] = "1";
-  params["darkening_log_lux_threshold"] = "0.2";
+  // UserAdjustmentEffect::kContinueAuto = 2.
+  params["user_adjustment_effect"] = "2";
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+
+  ForwardTimeAndReportAls({2, 4, 6, 8});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+
+  // User brightness change comes in.
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 1);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->IsAppliedForTesting());
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  CheckLogAvg({2, 4, 6, 8}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Four ALS readings come in, but not enough time has passed since user
+  // brightness change.
+  ForwardTimeAndReportAls({4, 6, 8, 2});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  CheckLogAvg({2, 4, 6, 8}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Another ALS reading is in, but avg is the same as reference ALS (when user
+  // changed brightness). Hence no change to brightness.
+  ForwardTimeAndReportAls({5});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  CheckLogAvg({2, 4, 6, 8}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Another user manual adjustment comes in.
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ReportUserBrightnessChangeRequest(30.0, 40.0);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->IsAppliedForTesting());
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 2);
+  CheckLogAvg({6, 8, 2, 5}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
+// Same as |UserBrightnessChangeAlsReadingExists| except that the 1st user
+// brightness change comes when there is no ALS reading.
+TEST_F(AdapterTest, UserBrightnessChangeAlsReadingAbsent) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+
+  // Adapter will not be applied after a user manual adjustment.
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
+
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", true, 1);
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  EXPECT_FALSE(adapter_->GetCurrentLogAvgAlsForTesting());
+
+  // ALS readings come in but will not change the brightness.
+  ForwardTimeAndReportAls({100, 101, 102, 103, 104});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  EXPECT_FALSE(adapter_->GetCurrentLogAvgAlsForTesting());
+
+  // Another user manual adjustment comes in.
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ReportUserBrightnessChangeRequest(30.0, 40.0);
+  histogram_tester_.ExpectBucketCount(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", true, 1);
+  histogram_tester_.ExpectBucketCount(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 1);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+  CheckLogAvg({101, 102, 103, 104},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
+// Same as |UserBrightnessChangeAlsReadingAbsent| except that user adjustment
+// effect is Continue.
+TEST_F(AdapterTest, UserBrightnessChangeAlsReadingAbsentContinue) {
+  std::map<std::string, std::string> params = default_params_;
+  // UserAdjustmentEffect::kContinueAuto = 2.
+  params["user_adjustment_effect"] = "2";
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
+
+  histogram_tester_.ExpectUniqueSample(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", true, 1);
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->IsAppliedForTesting());
+  EXPECT_FALSE(adapter_->GetCurrentLogAvgAlsForTesting());
+
+  // ALS readings come in, and will trigger a brightness change.
+  ForwardTimeAndReportAls({100});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  ForwardTimeAndReportAls({101, 102, 103, 104});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({100, 101, 102, 103, 104},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Another user manual adjustment comes in.
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+  ReportUserBrightnessChangeRequest(30.0, 40.0);
+  histogram_tester_.ExpectBucketCount(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", true, 1);
+  histogram_tester_.ExpectBucketCount(
+      "AutoScreenBrightness.MissingAlsWhenBrightnessChanged", false, 2);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->IsAppliedForTesting());
+  CheckLogAvg({101, 102, 103, 104},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
+// Set |brightening_log_lux_threshold| to a very high value to effectively make
+// brightening impossible.
+TEST_F(AdapterTest, BrighteningThreshold) {
+  std::map<std::string, std::string> params = default_params_;
+  params["brightening_log_lux_threshold"] = "100";
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
        global_curve_, personal_curve_, GetTestModelConfig(), params);
 
@@ -582,90 +760,130 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness is changed after the 1st ALS value, and the thresholds are
-  // changed.
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({1, 2, 3, 4});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  ForwardTimeAndReportAls({5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20});
-
-  double expected_log_avg = ConvertToLog(20);
-  double expected_brightening_threshold = expected_log_avg + 1;
-  double expected_darkening_threshold = expected_log_avg - 0.2;
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
   EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_brightening_threshold);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 100);
   EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_darkening_threshold);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 0.00001);
 
-  // A 2nd ALS comes in, but average ambient is within the thresholds, hence
-  // brightness isn't changed and thresholds aren't updated.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(21);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(1, test_observer_.num_changes());
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20, 21});
-
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_brightening_threshold);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_darkening_threshold);
-
-  // // A 3rd ALS comes in, but still not enough to trigger brightness change.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(15);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({4, 4, 4, 4, 4});
   EXPECT_EQ(test_observer_.num_changes(), 1);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20, 21, 15});
-
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
   EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_brightening_threshold);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 100);
   EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_darkening_threshold);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 0.00001);
 
-  // A 4th ALS makes average value below the darkening threshold, hence
-  // brightness is changed. Thresholds are also changed.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(5);
-  thread_bundle_.RunUntilIdle();
+  // Darkening is still possible.
+  ForwardTimeAndReportAls({1});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 100);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 0.00001);
+
+  ForwardTimeAndReportAls({1});
   EXPECT_EQ(test_observer_.num_changes(), 2);
-  expected_log_avg = ConvertToLog((20 + 21 + 15 + 5) / 4.0);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20, 21, 15, 5});
-
+  CheckLogAvg({4, 4, 4, 1, 1},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
   EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_log_avg + 1);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 100);
   EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_log_avg - 0.2);
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 0.00001);
+}
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(8);
-  thread_bundle_.RunUntilIdle();
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20, 21, 15, 5, 8});
+// Set |darkening_log_lux_threshold| to a very high value to effectively make
+// darkening impossible.
+TEST_F(AdapterTest, DarkeningThreshold) {
+  std::map<std::string, std::string> params = default_params_;
+  params["darkening_log_lux_threshold"] = "100";
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(9);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({10, 20, 30, 40});
+  EXPECT_EQ(test_observer_.num_changes(), 0);
+  ForwardTimeAndReportAls({50});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({10, 20, 30, 40, 50},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 0.00001);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 100);
 
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {21, 15, 5, 8, 9});
+  ForwardTimeAndReportAls({29, 29, 29, 29, 29});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({10, 20, 30, 40, 50},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 0.00001);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 100);
+
+  ForwardTimeAndReportAls({40});
+  CheckLogAvg({29, 29, 29, 29, 40},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() + 0.00001);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   adapter_->GetCurrentLogAvgAlsForTesting().value() - 100);
+}
+
+// Set |stabilization_threshold| to a very low value so that the average really
+// should have little fluctuations before we change brightness.
+TEST_F(AdapterTest, StablizationThreshold) {
+  std::map<std::string, std::string> params = default_params_;
+  params["stabilization_threshold"] = "0.00001";
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
+
+  ForwardTimeAndReportAls({10, 20, 30, 40, 50});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({10, 20, 30, 40, 50},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // A slight fluctuation means brightness is not changed.
+  ForwardTimeAndReportAls({29, 29, 29, 29, 28});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({10, 20, 30, 40, 50},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({28, 28, 28, 28});
+  EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({28, 28, 28, 28, 28},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
+// Shorten |auto_brightness_als_horizon| to 1 second. Averaging period is
+// shorter and |stabilization_threshold| is ineffective in regularizing
+// stabilization.
+TEST_F(AdapterTest, AlsHorizon) {
+  std::map<std::string, std::string> params = default_params_;
+  params["auto_brightness_als_horizon_seconds"] = "1";
+  // Small |stabilization_threshold|.
+  params["stabilization_threshold"] = "0.00001";
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
+
+  ForwardTimeAndReportAls({10});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({10}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({100});
+  EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({100}, adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({2});
+  EXPECT_EQ(test_observer_.num_changes(), 3);
+  CheckLogAvg({2}, adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
 TEST_F(AdapterTest, UsePersonalCurve) {
@@ -679,33 +897,32 @@
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
 
-  // ALS comes in but no brightness change is triggered because there is no
-  // personal curve.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // Sufficient ALS data has come in but no brightness change is triggered
+  // because there is no personal curve.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5, 6, 7, 8});
   EXPECT_EQ(test_observer_.num_changes(), 0);
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
 
-  // Personal curve is received.
+  // Personal curve is received, it does not lead to any immediate brightness
+  // change.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_modeller_.ReportModelTrained(*personal_curve_);
   EXPECT_EQ(test_observer_.num_changes(), 0);
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
+
+  // Another ALS comes in, which triggers a brightness change.
+  ReportAls(20);
   EXPECT_EQ(test_observer_.num_changes(), 1);
   EXPECT_EQ(test_observer_.GetCause(),
             power_manager::BacklightBrightnessChange_Cause_MODEL);
 
-  const double expected_log_avg = ConvertToLog((10 + 20) / 2.0);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {10, 20});
+  CheckLogAvg({5, 6, 7, 8, 20},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  const double expected_brightness_percent =
-      personal_curve_->Interpolate(expected_log_avg);
+  // Brightness is changed according to the personal curve.
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
-                   expected_brightness_percent);
+                   personal_curve_->Interpolate(
+                       adapter_->GetCurrentLogAvgAlsForTesting().value()));
 }
 
 TEST_F(AdapterTest, UseGlobalCurve) {
@@ -717,39 +934,28 @@
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
-  const double expected_log_avg1 = ConvertToLog(10);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {10});
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  const double expected_brightness_percent1 =
-      global_curve_->Interpolate(expected_log_avg1);
+  // Brightness is changed according to the global curve.
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
-                   expected_brightness_percent1);
+                   global_curve_->Interpolate(
+                       adapter_->GetCurrentLogAvgAlsForTesting().value()));
 
   // A new personal curve is received but adapter still uses the global curve.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(20));
   fake_modeller_.ReportModelTrained(*personal_curve_);
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
+  ReportAls(20);
   EXPECT_EQ(test_observer_.num_changes(), 2);
   EXPECT_EQ(test_observer_.GetCause(),
             power_manager::BacklightBrightnessChange_Cause_MODEL);
 
-  const double expected_log_avg2 = ConvertToLog(20);
-  CheckAverageAndStdDev(
-      adapter_->GetAverageAmbientWithStdDevForTesting(thread_bundle_.NowTicks())
-          .value(),
-      {20});
-  const double expected_brightness_percent2 =
-      global_curve_->Interpolate(expected_log_avg2);
+  // Brightness is changed according to the global curve.
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
-                   expected_brightness_percent2);
+                   global_curve_->Interpolate(
+                       adapter_->GetCurrentLogAvgAlsForTesting().value()));
 }
 
 TEST_F(AdapterTest, BrightnessSetByPolicy) {
@@ -759,10 +965,9 @@
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5, 6, 7, 8});
   EXPECT_EQ(test_observer_.num_changes(), 0);
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
 }
 
 TEST_F(AdapterTest, FeatureDisabled) {
@@ -773,17 +978,16 @@
        global_curve_, personal_curve_, GetTestModelConfig(), empty_params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
+
   // Global and personal curves are received, but they won't be used to change
   // brightness.
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness not changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // No brightness is changed.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5, 6, 7, 8});
   EXPECT_EQ(test_observer_.num_changes(), 0);
+  EXPECT_EQ(adapter_->GetCurrentLogAvgAlsForTesting(), base::nullopt);
 }
 
 TEST_F(AdapterTest, FeatureEnabledForAtlas) {
@@ -799,11 +1003,10 @@
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
 TEST_F(AdapterTest, ValidParameters) {
@@ -825,8 +1028,47 @@
       static_cast<int>(ParameterError::kAdapterError), 1);
 }
 
+TEST_F(AdapterTest, UserAdjustmentEffectDisable) {
+  // |default_params_| sets the effect to disable.
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
+  EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
+  EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
+  EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
+
+  // Brightness is changed for the 1st time.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // Adapter will not be applied after a user manual adjustment.
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+
+  ForwardTimeAndReportAls({6, 7, 8, 9, 10, 11});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // SuspendDone is received, which does not enable Adapter.
+  ReportSuspendDone();
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_FALSE(adapter_->IsAppliedForTesting());
+
+  ForwardTimeAndReportAls({11, 12, 13, 14, 15, 16});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+}
+
 TEST_F(AdapterTest, UserAdjustmentEffectPause) {
   std::map<std::string, std::string> params = default_params_;
+  // UserAdjustmentEffect::kPauseAuto = 1.
   params["user_adjustment_effect"] = "1";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
@@ -838,44 +1080,67 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness is changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // Brightness is changed for the 1st time.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  // Adapter will not be applied after a user manual adjustment.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
+  // User manually changes brightness so that adapter will not be applied.
+  ReportUserBrightnessChangeRequest(20.0, 30.0);
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_FALSE(adapter_->IsAppliedForTesting());
 
-  // SuspendDone is received, which reenables Adapter.
+  // New ALS data will not trigger brightness update.
+  ForwardTimeAndReportAls({101, 102, 103, 104, 105, 106, 107, 108});
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // // SuspendDone is received, which reenables adapter.
   ReportSuspendDone();
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->IsAppliedForTesting());
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(30);
-  thread_bundle_.RunUntilIdle();
+  // Another ALS results in a brightness change.
+  ForwardTimeAndReportAls({109});
   EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({105, 106, 107, 108, 109},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  // Another user manual adjustment that stops Adapter from being applied.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
+  // Another user brightness change.
+  ReportUserBrightnessChangeRequest(40.0, 50.0);
+  CheckLogAvg({105, 106, 107, 108, 109},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_FALSE(adapter_->IsAppliedForTesting());
 
-  // Brightness is not changed after another ALS reading comes in.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(60);
-  thread_bundle_.RunUntilIdle();
+  // New ALS data will not trigger brightness update.
+  ForwardTimeAndReportAls({200});
   EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({105, 106, 107, 108, 109},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  // SuspendDone is received, which reenables adapter.
+  ReportSuspendDone();
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->IsAppliedForTesting());
+
+  // Als readings come in but not sufficient time since user changed brightness.
+  ForwardTimeAndReportAls({201, 202, 203});
+  EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({105, 106, 107, 108, 109},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({204});
+  EXPECT_EQ(test_observer_.num_changes(), 3);
+  CheckLogAvg({200, 201, 202, 203, 204},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
 TEST_F(AdapterTest, UserAdjustmentEffectContinue) {
   std::map<std::string, std::string> params = default_params_;
+  // UserAdjustmentEffect::kContinueAuto = 2.
   params["user_adjustment_effect"] = "2";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
@@ -887,33 +1152,37 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness is changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // Brightness is changed for the 1st time.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  // User manual adjustment doesn't disable Adapter.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({10});
+  // User manual adjustment doesn't disable adapter.
+  ReportUserBrightnessChangeRequest(40.0, 50.0);
+  CheckLogAvg({2, 3, 4, 5, 10},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->IsAppliedForTesting());
 
-  // Brightness is changed again after another ALS reading comes in.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(30);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({100, 101, 102, 103});
+  CheckLogAvg({2, 3, 4, 5, 10},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({104});
   EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({100, 101, 102, 103, 104},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
 // Default user adjustment effect for atlas is Continue.
 TEST_F(AdapterTest, UserAdjustmentEffectContinueDefaultForAtlas) {
-  const std::map<std::string, std::string> params = {
-      {"brightening_log_lux_threshold", "0.1"},
-      {"darkening_log_lux_threshold", "0.2"},
-      {"model_curve", "2"},
-  };
+  std::map<std::string, std::string> params = default_params_;
+  // User adjustment effect for Atlas is only Continue when it's not explicitly
+  // set by the finch params.
+  params.erase("user_adjustment_effect");
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
        global_curve_, personal_curve_, GetTestModelConfig("atlas"), params);
@@ -924,24 +1193,29 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness is changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
+  // Brightness is changed for the 1st time.
+  ForwardTimeAndReportAls({1, 2, 3, 4, 5});
   EXPECT_EQ(test_observer_.num_changes(), 1);
+  CheckLogAvg({1, 2, 3, 4, 5},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 
-  // User manual adjustment doesn't disable Adapter.
-  fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({10});
+  // User manual adjustment doesn't disable adapter.
+  ReportUserBrightnessChangeRequest(40.0, 50.0);
+  CheckLogAvg({2, 3, 4, 5, 10},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->IsAppliedForTesting());
 
-  // Brightness is changed again after another ALS reading comes in.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(30);
-  thread_bundle_.RunUntilIdle();
+  ForwardTimeAndReportAls({100, 101, 102, 103});
+  CheckLogAvg({2, 3, 4, 5, 10},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
+
+  ForwardTimeAndReportAls({104});
   EXPECT_EQ(test_observer_.num_changes(), 2);
+  CheckLogAvg({100, 101, 102, 103, 104},
+              adapter_->GetCurrentLogAvgAlsForTesting().value());
 }
 
 }  // namespace auto_screen_brightness
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
index cca5246..260f582 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
@@ -776,6 +776,30 @@
                                       BYPASS_EVENT_TYPE_MALFORMED_407, 1);
 }
 
+IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest,
+                       ProxyBypassedForCurrentRequestOn502Error) {
+  base::HistogramTester histogram_tester;
+  net::EmbeddedTestServer test_server;
+  test_server.RegisterRequestHandler(
+      base::BindRepeating(&BasicResponse, kDummyBody));
+  ASSERT_TRUE(test_server.Start());
+
+  SetStatusCode(net::HTTP_BAD_GATEWAY);
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GetURLWithMockHost(test_server, "/echo"));
+  EXPECT_THAT(GetBody(), kDummyBody);
+  histogram_tester.ExpectUniqueSample(
+      "DataReductionProxy.BlockTypePrimary",
+      BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY, 1);
+
+  // Proxy should no longer be blocked, and use first proxy.
+  SetStatusCode(net::HTTP_OK);
+  ui_test_utils::NavigateToURL(browser(),
+                               GetURLWithMockHost(test_server, "/echo"));
+  EXPECT_EQ(GetBody(), kPrimaryResponse);
+}
+
 // Tests that if using data reduction proxy results in redirect loop, then
 // the proxy is bypassed, and the request is fetched directly.
 IN_PROC_BROWSER_TEST_F(DataReductionProxyFallbackBrowsertest, RedirectCycle) {
diff --git a/chrome/browser/engagement/important_sites_util.cc b/chrome/browser/engagement/important_sites_util.cc
index 366314d2..900f794 100644
--- a/chrome/browser/engagement/important_sites_util.cc
+++ b/chrome/browser/engagement/important_sites_util.cc
@@ -15,6 +15,7 @@
 #include "base/stl_util.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "build/build_config.h"
 #include "chrome/browser/banners/app_banner_settings_helper.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -32,8 +33,13 @@
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "third_party/blink/public/mojom/site_engagement/site_engagement.mojom.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 #include "url/url_util.h"
 
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/search_permissions/search_permissions_service.h"
+#endif
+
 namespace {
 using bookmarks::BookmarkModel;
 using bookmarks::UrlAndTitle;
@@ -274,14 +280,28 @@
   HostContentSettingsMapFactory::GetForProfile(profile)->GetSettingsForOneType(
       content_type, content_settings::ResourceIdentifier(),
       &content_settings_list);
+
   // Extract a set of urls, using the primary pattern. We don't handle
   // wildcard patterns.
   std::set<GURL> content_origins;
   for (const ContentSettingPatternSource& site : content_settings_list) {
     if (site.GetContentSetting() != CONTENT_SETTING_ALLOW)
       continue;
-    MaybePopulateImportantInfoForReason(GURL(site.primary_pattern.ToString()),
-                                        &content_origins, reason, output);
+    GURL url(site.primary_pattern.ToString());
+
+#if defined(OS_ANDROID)
+    SearchPermissionsService* search_permissions_service =
+        SearchPermissionsService::Factory::GetInstance()->GetForBrowserContext(
+            profile);
+    // If the permission is controlled by the Default Search Engine then don't
+    // consider it important. The DSE gets these permissions by default.
+    if (search_permissions_service->IsPermissionControlledByDSE(
+            content_type, url::Origin::Create(url))) {
+      continue;
+    }
+#endif
+
+    MaybePopulateImportantInfoForReason(url, &content_origins, reason, output);
   }
 }
 
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.cc b/chrome/browser/extensions/bookmark_app_extension_util.cc
index 5675c05..5131b1d9 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.cc
+++ b/chrome/browser/extensions/bookmark_app_extension_util.cc
@@ -30,6 +30,20 @@
 
 namespace extensions {
 
+namespace {
+
+#if !defined(OS_CHROMEOS)
+bool CanOsAddDesktopShortcuts() {
+#if defined(OS_LINUX) || defined(OS_WIN)
+  return true;
+#else
+  return false;
+#endif
+}
+#endif  // !defined(OS_CHROMEOS)
+
+}  // namespace
+
 bool CanBookmarkAppCreateOsShortcuts() {
 #if defined(OS_CHROMEOS)
   return false;
@@ -41,19 +55,18 @@
 void BookmarkAppCreateOsShortcuts(
     Profile* profile,
     const Extension* extension,
+    bool add_to_desktop,
     base::OnceCallback<void(bool created_shortcuts)> callback) {
   DCHECK(CanBookmarkAppCreateOsShortcuts());
 #if !defined(OS_CHROMEOS)
   web_app::ShortcutLocations creation_locations;
-#if defined(OS_LINUX) || defined(OS_WIN)
-  creation_locations.on_desktop = true;
-#else
-  creation_locations.on_desktop = false;
-#endif
   creation_locations.applications_menu_location =
       web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS;
   creation_locations.in_quick_launch_bar = false;
 
+  if (CanOsAddDesktopShortcuts())
+    creation_locations.on_desktop = add_to_desktop;
+
   Profile* current_profile = profile->GetOriginalProfile();
   web_app::CreateShortcuts(web_app::SHORTCUT_CREATION_BY_USER,
                            creation_locations, current_profile, extension,
diff --git a/chrome/browser/extensions/bookmark_app_extension_util.h b/chrome/browser/extensions/bookmark_app_extension_util.h
index fcde5da8..56ce30b 100644
--- a/chrome/browser/extensions/bookmark_app_extension_util.h
+++ b/chrome/browser/extensions/bookmark_app_extension_util.h
@@ -21,6 +21,7 @@
 void BookmarkAppCreateOsShortcuts(
     Profile* profile,
     const Extension* extension,
+    bool add_to_desktop,
     base::OnceCallback<void(bool created_shortcuts)> callback);
 
 bool CanBookmarkAppBePinnedToShelf();
diff --git a/chrome/browser/extensions/bookmark_app_helper.cc b/chrome/browser/extensions/bookmark_app_helper.cc
index 8266f29..2521a96a 100644
--- a/chrome/browser/extensions/bookmark_app_helper.cc
+++ b/chrome/browser/extensions/bookmark_app_helper.cc
@@ -463,9 +463,11 @@
 
   web_app::RecordAppBanner(contents_, web_app_info_.app_url);
 
-  if (create_shortcuts_ && CanBookmarkAppCreateOsShortcuts()) {
+  // TODO(ortuno): Make adding a shortcut to the applications menu independent
+  // from adding a shortcut to desktop.
+  if (add_to_applications_menu_ && CanBookmarkAppCreateOsShortcuts()) {
     BookmarkAppCreateOsShortcuts(
-        profile_, extension,
+        profile_, extension, add_to_desktop_,
         base::BindOnce(&BookmarkAppHelper::OnShortcutCreationCompleted,
                        weak_factory_.GetWeakPtr(), extension->id()));
   } else {
@@ -486,7 +488,7 @@
     return;
   }
 
-  if (create_shortcuts_ && CanBookmarkAppBePinnedToShelf())
+  if (add_to_quick_launch_bar_ && CanBookmarkAppBePinnedToShelf())
     BookmarkAppPinToShelf(extension);
 
   // If there is a browser, it means that the app is being installed in the
diff --git a/chrome/browser/extensions/bookmark_app_helper.h b/chrome/browser/extensions/bookmark_app_helper.h
index 2805806..84abe2c5 100644
--- a/chrome/browser/extensions/bookmark_app_helper.h
+++ b/chrome/browser/extensions/bookmark_app_helper.h
@@ -98,10 +98,25 @@
 
   bool is_no_network_install() { return is_no_network_install_; }
 
-  // If called, desktop shortcuts will not be created.
-  void set_skip_shortcut_creation() { create_shortcuts_ = false; }
+  void set_skip_adding_to_applications_menu() {
+    add_to_applications_menu_ = false;
+  }
 
-  bool create_shortcuts() const { return create_shortcuts_; }
+  bool add_to_applications_menu() { return add_to_applications_menu_; }
+
+  // If called, desktop shortcuts will not be created. Has no effect on
+  // platforms other than Linux and Windows.
+  void set_skip_adding_to_desktop() { add_to_desktop_ = false; }
+
+  bool add_to_desktop() const { return add_to_desktop_; }
+
+  // If called, the app will not be pinned to the shelf. Has no effect on
+  // platforms other than Chrome OS.
+  void set_skip_adding_to_quick_launch_bar() {
+    add_to_quick_launch_bar_ = false;
+  }
+
+  bool add_to_quick_launch_bar() { return add_to_quick_launch_bar_; }
 
   // If called, the installability check won't test for a service worker.
   void set_bypass_service_worker_check() {
@@ -198,7 +213,11 @@
   // installation and we should not try to fetch a manifest.
   bool is_no_network_install_ = false;
 
-  bool create_shortcuts_ = true;
+  bool add_to_applications_menu_ = true;
+
+  bool add_to_desktop_ = true;
+
+  bool add_to_quick_launch_bar_ = true;
 
   bool bypass_service_worker_check_ = false;
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index f73303f1..1fcdaa8 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1867,6 +1867,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "enable-web-authentication-pin-support",
+    "owners": [ "webauthn-team@google.com" ],
+    "expiry_milestone": 77
+  },
+  {
     "name": "enable-webassembly",
     "owners": [ "titzer", "wasm-team@google.com" ],
     "expiry_milestone": 72
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index dc23d11..518c3ae 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -979,6 +979,12 @@
     "Enable the cloud-assisted pairingless BLE protocol for use with "
     "the Web Authentication API.";
 
+const char kEnableWebAuthenticationPINSupportName[] =
+    "Web Authentication PIN support";
+const char kEnableWebAuthenticationPINSupportDescription[] =
+    "Enable the use of PINs with the Web Authentication API and compatible "
+    "security keys.";
+
 const char kEnableIncognitoWindowCounterName[] = "Incognito Window Counter";
 const char kEnableIncognitoWindowCounterDescription[] =
     "Shows the count of Incognito windows next to the Incognito icon on the "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 487b1f5..a7be4ca09 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -589,6 +589,9 @@
 extern const char kEnableWebAuthenticationCableSupportName[];
 extern const char kEnableWebAuthenticationCableSupportDescription[];
 
+extern const char kEnableWebAuthenticationPINSupportName[];
+extern const char kEnableWebAuthenticationPINSupportDescription[];
+
 extern const char kEnableWebUsbName[];
 extern const char kEnableWebUsbDescription[];
 
diff --git a/chrome/browser/resources/chromeos/login/screen_recommend_apps.js b/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
index 50cd1ed..9e1f9a2 100644
--- a/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
+++ b/chrome/browser/resources/chromeos/login/screen_recommend_apps.js
@@ -92,12 +92,6 @@
       // Hide the loading throbber and show the recommend app list.
       this.setThrobberVisible(false);
 
-      // Disable install button until the webview reports that some apps are
-      // selected.
-      $('recommend-apps-screen')
-          .getElement('recommend-apps-install-button')
-          .disabled = true;
-
       const appListView = this.getElement_('app-list-view');
       const subtitle = this.getElement_('subtitle');
       subtitle.innerText = loadTimeData.getStringF(
@@ -188,4 +182,4 @@
       $('recommend-apps-screen').hidden = visible;
     },
   };
-});
+});
\ No newline at end of file
diff --git a/chrome/browser/ui/page_info/OWNERS b/chrome/browser/ui/page_info/OWNERS
index 1df299f..d0ebc2a7 100644
--- a/chrome/browser/ui/page_info/OWNERS
+++ b/chrome/browser/ui/page_info/OWNERS
@@ -1,10 +1,10 @@
 # Please use for OWNERS code reviews
 benwells@chromium.org
+engedy@chromium.org
 estark@chromium.org
 felt@chromium.org
 meacer@chromium.org
 patricialor@chromium.org
-raymes@chromium.org
 
 # COMPONENT: UI>Browser>Bubbles>PageInfo
 # TEAM: security-enamel@chromium.org
diff --git a/chrome/browser/ui/views/page_info/OWNERS b/chrome/browser/ui/views/page_info/OWNERS
index 39f0eac..72c5daf 100644
--- a/chrome/browser/ui/views/page_info/OWNERS
+++ b/chrome/browser/ui/views/page_info/OWNERS
@@ -1,9 +1,4 @@
-benwells@chromium.org
-estark@chromium.org
-felt@chromium.org
-meacer@chromium.org
-patricialor@chromium.org
-raymes@chromium.org
+file://chrome/browser/ui/page_info/OWNERS
 
 # COMPONENT: UI>Browser>Bubbles>PageInfo
 # TEAM: security-enamel@chromium.org
diff --git a/chrome/browser/ui/web_applications/bookmark_app_browsertest.cc b/chrome/browser/ui/web_applications/bookmark_app_browsertest.cc
index e821af2b..76399f9 100644
--- a/chrome/browser/ui/web_applications/bookmark_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/bookmark_app_browsertest.cc
@@ -45,7 +45,9 @@
                                           web_app::LaunchContainer::kWindow,
                                           web_app::InstallSource::kInternal);
   // Avoid creating real shortcuts in tests.
-  install_options.create_shortcuts = false;
+  install_options.add_to_applications_menu = false;
+  install_options.add_to_desktop = false;
+  install_options.add_to_quick_launch_bar = false;
   return install_options;
 }
 
diff --git a/chrome/browser/vr/model/capturing_state_model.h b/chrome/browser/vr/model/capturing_state_model.h
index 367c960..6e5f0d25 100644
--- a/chrome/browser/vr/model/capturing_state_model.h
+++ b/chrome/browser/vr/model/capturing_state_model.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_VR_MODEL_CAPTURING_STATE_MODEL_H_
 #define CHROME_BROWSER_VR_MODEL_CAPTURING_STATE_MODEL_H_
 
+#include <string>
+
 #include "chrome/browser/vr/vr_base_export.h"
 
 namespace vr {
@@ -15,6 +17,24 @@
   bool screen_capture_enabled = false;
   bool location_access_enabled = false;
   bool bluetooth_connected = false;
+
+  bool operator==(const CapturingStateModel& rhs) const {
+    return audio_capture_enabled == rhs.audio_capture_enabled &&
+           video_capture_enabled == rhs.video_capture_enabled &&
+           screen_capture_enabled == rhs.screen_capture_enabled &&
+           location_access_enabled == rhs.location_access_enabled &&
+           bluetooth_connected == rhs.bluetooth_connected;
+  }
+
+  bool operator!=(const CapturingStateModel& rhs) const {
+    return !(*this == rhs);
+  }
+
+  bool IsAnyCapturingEnabled() const {
+    return audio_capture_enabled || video_capture_enabled ||
+           screen_capture_enabled || location_access_enabled ||
+           bluetooth_connected;
+  }
 };
 
 typedef bool CapturingStateModel::*CapturingStateModelMemberPtr;
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
index 73ed60b..0febaa4 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
@@ -7,18 +7,21 @@
 #include <memory>
 
 #include "base/task/post_task.h"
+#include "chrome/browser/content_settings/tab_specific_content_settings.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+#include "chrome/browser/permissions/permission_manager.h"
+#include "chrome/browser/permissions/permission_result.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/vr/metrics/session_metrics_helper.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
 #include "chrome/browser/vr/service/xr_runtime_manager.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/browser/vr/win/vr_browser_renderer_thread_win.h"
-#include "components/strings/grit/components_strings.h"
-#include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/render_view_host.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace vr {
@@ -26,11 +29,18 @@
 namespace {
 static constexpr base::TimeDelta kPermissionPromptTimeout =
     base::TimeDelta::FromSeconds(5);
+
+static constexpr base::TimeDelta kPollCapturingStateInterval =
+    base::TimeDelta::FromSecondsD(0.2);
+
+const CapturingStateModel g_default_capturing_state;
 }  // namespace
 
 VRUiHostImpl::VRUiHostImpl(device::mojom::XRDeviceId device_id,
                            device::mojom::XRCompositorHostPtr compositor)
-    : compositor_(std::move(compositor)), weak_ptr_factory_(this) {
+    : compositor_(std::move(compositor)),
+      main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      weak_ptr_factory_(this) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DVLOG(1) << __func__;
 
@@ -39,6 +49,10 @@
   if (runtime) {
     runtime->AddObserver(this);
   }
+
+  auto* connector =
+      content::ServiceManagerConnection::GetForProcess()->GetConnector();
+  connector->BindInterface(device::mojom::kServiceName, &geolocation_config_);
 }
 
 VRUiHostImpl::~VRUiHostImpl() {
@@ -109,8 +123,11 @@
   web_contents_ = contents;
   if (contents) {
     StartUiRendering();
+    InitCapturingStates();
     ui_rendering_thread_->SetWebXrPresenting(true);
 
+    PollCapturingState();
+
     PermissionRequestManager::CreateForWebContents(contents);
     permission_request_manager_ =
         PermissionRequestManager::FromWebContents(contents);
@@ -129,6 +146,8 @@
       DVLOG(1) << __func__ << ": No PermissionRequestManager";
     }
   } else {
+    poll_capturing_state_task_.Cancel();
+
     if (ui_rendering_thread_)
       ui_rendering_thread_->SetWebXrPresenting(false);
     StopUiRendering();
@@ -159,6 +178,7 @@
 void VRUiHostImpl::StopUiRendering() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DVLOG(1) << __func__;
+
   ui_rendering_thread_ = nullptr;
 }
 
@@ -185,30 +205,130 @@
 
   SetLocationInfoOnUi();
 
+  if (indicators_visible_) {
+    indicators_visible_ = false;
+    ui_rendering_thread_->SetIndicatorsVisible(false);
+  }
+
   ui_rendering_thread_->SetVisibleExternalPromptNotification(
       ExternalPromptNotificationType::kPromptGenericPermission);
 
-  is_prompt_showing_in_headset_ = true;
-  current_prompt_sequence_num_++;
-  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&VRUiHostImpl::RemoveHeadsetNotificationPrompt,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     current_prompt_sequence_num_),
+  is_external_prompt_showing_in_headset_ = true;
+  external_prompt_timeout_task_.Reset(
+      base::BindRepeating(&VRUiHostImpl::RemoveHeadsetNotificationPrompt,
+                          weak_ptr_factory_.GetWeakPtr()));
+  main_thread_task_runner_->PostDelayedTask(
+      FROM_HERE, external_prompt_timeout_task_.callback(),
       kPermissionPromptTimeout);
 }
 
 void VRUiHostImpl::OnBubbleRemoved() {
-  RemoveHeadsetNotificationPrompt(current_prompt_sequence_num_);
+  external_prompt_timeout_task_.Cancel();
+  RemoveHeadsetNotificationPrompt();
 }
 
-void VRUiHostImpl::RemoveHeadsetNotificationPrompt(int prompt_sequence_num) {
-  if (!is_prompt_showing_in_headset_)
+void VRUiHostImpl::RemoveHeadsetNotificationPrompt() {
+  if (!is_external_prompt_showing_in_headset_)
     return;
-  if (prompt_sequence_num != current_prompt_sequence_num_)
-    return;
-  is_prompt_showing_in_headset_ = false;
+  is_external_prompt_showing_in_headset_ = false;
   ui_rendering_thread_->SetVisibleExternalPromptNotification(
       ExternalPromptNotificationType::kPromptNone);
+  indicators_shown_start_time_ = base::Time::Now();
 }
+
+void VRUiHostImpl::InitCapturingStates() {
+  active_capturing_ = g_default_capturing_state;
+  potential_capturing_ = g_default_capturing_state;
+
+  DCHECK(web_contents_);
+  PermissionManager* permission_manager = PermissionManager::Get(
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
+  const GURL& origin = web_contents_->GetLastCommittedURL();
+  content::RenderFrameHost* rfh = web_contents_->GetMainFrame();
+  potential_capturing_.audio_capture_enabled =
+      permission_manager
+          ->GetPermissionStatusForFrame(
+              ContentSettingsType::CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, rfh,
+              origin)
+          .content_setting == CONTENT_SETTING_ALLOW;
+  potential_capturing_.video_capture_enabled =
+      permission_manager
+          ->GetPermissionStatusForFrame(
+              ContentSettingsType::CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+              rfh, origin)
+          .content_setting == CONTENT_SETTING_ALLOW;
+  potential_capturing_.location_access_enabled =
+      permission_manager
+          ->GetPermissionStatusForFrame(
+              ContentSettingsType::CONTENT_SETTINGS_TYPE_GEOLOCATION, rfh,
+              origin)
+          .content_setting == CONTENT_SETTING_ALLOW;
+
+  indicators_shown_start_time_ = base::Time::Now();
+  indicators_visible_ = false;
+}
+
+void VRUiHostImpl::PollCapturingState() {
+  poll_capturing_state_task_.Reset(base::BindRepeating(
+      &VRUiHostImpl::PollCapturingState, base::Unretained(this)));
+  main_thread_task_runner_->PostDelayedTask(
+      FROM_HERE, poll_capturing_state_task_.callback(),
+      kPollCapturingStateInterval);
+
+  // Microphone, Camera, location.
+  CapturingStateModel active_capturing = active_capturing_;
+  TabSpecificContentSettings* settings =
+      TabSpecificContentSettings::FromWebContents(web_contents_);
+  if (settings) {
+    const ContentSettingsUsagesState& usages_state =
+        settings->geolocation_usages_state();
+    if (!usages_state.state_map().empty()) {
+      unsigned int state_flags = 0;
+      usages_state.GetDetailedInfo(nullptr, &state_flags);
+      active_capturing.location_access_enabled = !!(
+          state_flags & ContentSettingsUsagesState::TABSTATE_HAS_ANY_ALLOWED);
+    }
+    active_capturing.audio_capture_enabled =
+        (settings->GetMicrophoneCameraState() &
+         TabSpecificContentSettings::MICROPHONE_ACCESSED) &&
+        !(settings->GetMicrophoneCameraState() &
+          TabSpecificContentSettings::MICROPHONE_BLOCKED);
+    active_capturing.video_capture_enabled =
+        (settings->GetMicrophoneCameraState() &
+         TabSpecificContentSettings::CAMERA_ACCESSED) &
+        !(settings->GetMicrophoneCameraState() &
+          TabSpecificContentSettings::CAMERA_BLOCKED);
+  }
+
+  // Screen capture, bluetooth.
+  scoped_refptr<MediaStreamCaptureIndicator> indicator =
+      MediaCaptureDevicesDispatcher::GetInstance()
+          ->GetMediaStreamCaptureIndicator();
+  active_capturing.screen_capture_enabled =
+      indicator->IsBeingMirrored(web_contents_);
+  active_capturing.bluetooth_connected =
+      web_contents_->IsConnectedToBluetoothDevice();
+
+  if (active_capturing_ != active_capturing) {
+    indicators_shown_start_time_ = base::Time::Now();
+  }
+
+  active_capturing_ = active_capturing;
+  ui_rendering_thread_->SetCapturingState(
+      active_capturing_, g_default_capturing_state, potential_capturing_);
+
+  if (indicators_shown_start_time_ + kPermissionPromptTimeout >
+      base::Time::Now()) {
+    if (!indicators_visible_ && !is_external_prompt_showing_in_headset_) {
+      indicators_visible_ = true;
+      ui_rendering_thread_->SetIndicatorsVisible(true);
+    }
+  } else {
+    if (indicators_visible_) {
+      indicators_visible_ = false;
+      ui_rendering_thread_->SetIndicatorsVisible(false);
+    }
+  }
+}
+
 }  // namespace vr
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.h b/chrome/browser/vr/ui_host/vr_ui_host_impl.h
index 03f3bd8..07d12bb 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.h
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.h
@@ -5,13 +5,17 @@
 #ifndef CHROME_BROWSER_VR_UI_HOST_VR_UI_HOST_IMPL_H_
 #define CHROME_BROWSER_VR_UI_HOST_VR_UI_HOST_IMPL_H_
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/permissions/permission_request_manager.h"
+#include "chrome/browser/vr/model/capturing_state_model.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
 #include "chrome/browser/vr/service/vr_ui_host.h"
 #include "content/public/browser/web_contents.h"
+#include "services/device/public/mojom/geolocation_config.mojom.h"
 
 namespace vr {
 
@@ -46,17 +50,28 @@
   void OnBubbleAdded() override;
   void OnBubbleRemoved() override;
 
-  void RemoveHeadsetNotificationPrompt(int prompt_sequence_num);
+  void RemoveHeadsetNotificationPrompt();
   void SetLocationInfoOnUi();
 
+  void InitCapturingStates();
+  void PollCapturingState();
+
   device::mojom::XRCompositorHostPtr compositor_;
   std::unique_ptr<VRBrowserRendererThreadWin> ui_rendering_thread_;
   device::mojom::VRDisplayInfoPtr info_;
   content::WebContents* web_contents_ = nullptr;
   PermissionRequestManager* permission_request_manager_ = nullptr;
+  scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
 
-  bool is_prompt_showing_in_headset_ = false;
-  int current_prompt_sequence_num_ = 0;
+  base::CancelableClosure external_prompt_timeout_task_;
+  bool is_external_prompt_showing_in_headset_ = false;
+
+  CapturingStateModel active_capturing_;
+  CapturingStateModel potential_capturing_;
+  device::mojom::GeolocationConfigPtr geolocation_config_;
+  base::CancelableClosure poll_capturing_state_task_;
+  base::Time indicators_shown_start_time_;
+  bool indicators_visible_ = false;
 
   THREAD_CHECKER(thread_checker_);
 
diff --git a/chrome/browser/vr/ui_scene_creator.cc b/chrome/browser/vr/ui_scene_creator.cc
index 984ddb62..58329c02c 100644
--- a/chrome/browser/vr/ui_scene_creator.cc
+++ b/chrome/browser/vr/ui_scene_creator.cc
@@ -685,8 +685,9 @@
 
 std::unique_ptr<UiElement> CreateWebVrIndicator(Model* model,
                                                 UiBrowserInterface* browser,
-                                                IndicatorSpec spec) {
-  auto container = Create<Rect>(spec.webvr_name, kPhaseOverlayForeground);
+                                                IndicatorSpec spec,
+                                                DrawPhase phase) {
+  auto container = Create<Rect>(spec.webvr_name, phase);
   VR_BIND_COLOR(model, container.get(),
                 &ColorScheme::webvr_permission_background, &Rect::SetColor);
   container->set_corner_radius(kWebVrPermissionCornerRadius);
@@ -699,7 +700,7 @@
   auto layout = Create<LinearLayout>(kNone, kPhaseNone, LinearLayout::kRight);
   layout->set_margin(kWebVrPermissionMargin);
 
-  auto icon_element = Create<VectorIcon>(kNone, kPhaseOverlayForeground, 128);
+  auto icon_element = Create<VectorIcon>(kNone, phase, 128);
   VR_BIND_COLOR(model, icon_element.get(),
                 &ColorScheme::webvr_permission_foreground,
                 &VectorIcon::SetColor);
@@ -717,12 +718,12 @@
   std::unique_ptr<UiElement> description_element;
   if (spec.is_url) {
     auto url_text = Create<UrlText>(
-        kNone, kPhaseOverlayForeground, kWebVrPermissionFontHeight,
+        kNone, phase, kWebVrPermissionFontHeight,
         base::BindRepeating(&UiBrowserInterface::OnUnsupportedMode,
                             base::Unretained(browser),
                             UiUnsupportedMode::kUnhandledCodePoint)
 
-            );
+    );
     url_text->SetFieldWidth(kWebVrPermissionTextWidth);
     url_text->AddBinding(VR_BIND_FUNC(GURL, Model, model,
                                       model->location_bar_state.gurl, UrlText,
@@ -736,8 +737,7 @@
     description_element = std::move(url_text);
 
   } else {
-    auto text_element = Create<Text>(kNone, kPhaseOverlayForeground,
-                                     kWebVrPermissionFontHeight);
+    auto text_element = Create<Text>(kNone, phase, kWebVrPermissionFontHeight);
     text_element->SetLayoutMode(kMultiLineFixedWidth);
     text_element->SetAlignment(kTextAlignmentLeft);
     text_element->SetColor(SK_ColorWHITE);
@@ -758,12 +758,11 @@
   return container;
 }
 
-std::unique_ptr<UiElement> CreateHostedUi(
-    Model* model,
-    UiBrowserInterface* browser,
-    UiElementName name,
-    UiElementName element_name,
-    float distance) {
+std::unique_ptr<UiElement> CreateHostedUi(Model* model,
+                                          UiBrowserInterface* browser,
+                                          UiElementName name,
+                                          UiElementName element_name,
+                                          float distance) {
   auto hosted_ui = Create<PlatformUiElement>(element_name, kPhaseForeground);
   hosted_ui->SetSize(kContentWidth * kHostedUiWidthRatio,
                      kContentHeight * kHostedUiHeightRatio);
@@ -942,6 +941,178 @@
   return parent;
 }
 
+#if defined(OS_WIN)
+void BindIndicatorTranscienceForWin(
+    TransientElement* e,
+    Model* model,
+    UiScene* scene,
+    const base::Optional<
+        std::tuple<bool, CapturingStateModel, CapturingStateModel>>& last_value,
+    const std::tuple<bool, CapturingStateModel, CapturingStateModel>& value) {
+  const bool in_web_vr_presentation = model->web_vr_enabled() &&
+                                      model->web_vr.IsImmersiveWebXrVisible() &&
+                                      model->web_vr.has_received_permissions;
+
+  const CapturingStateModel active_capture = std::get<1>(value);
+  const CapturingStateModel potential_capture = std::get<2>(value);
+  const CapturingStateModel last_active_capture =
+      last_value ? std::get<1>(last_value.value()) : CapturingStateModel();
+  const CapturingStateModel last_potential_capture =
+      last_value ? std::get<2>(last_value.value()) : CapturingStateModel();
+
+  // Update the visibility state of the indicators based on the capturing state
+  // diff. potential_capture represents the permissions granted to the site
+  // before the presentation. active_capture members are set when a relevant
+  // device starts to get used.
+  // When the session starts, indicators display which permissions are granted
+  // upfront (struct potential_capture). Then, when a device gets accessed,
+  // an indicator notifies the user about its usage.
+  // The below logic tries to capture this logic.
+  bool initial_toasts = !active_capture.IsAnyCapturingEnabled();
+  if (active_capture != last_active_capture ||
+      potential_capture != last_potential_capture) {
+    auto specs = GetIndicatorSpecs();
+    for (const auto& spec : specs) {
+      bool allowed = potential_capture.*spec.signal;
+      bool triggered =
+          !(last_active_capture.*spec.signal) && (active_capture.*spec.signal);
+      bool show_ui = initial_toasts ? allowed : triggered;
+      SetVisibleInLayout(scene->GetUiElementByName(spec.webvr_name), show_ui);
+    }
+  }
+
+  if (!in_web_vr_presentation) {
+    e->SetVisibleImmediately(false);
+    return;
+  }
+
+  e->SetVisible(true);
+  e->RefreshVisible();
+  SetVisibleInLayout(scene->GetUiElementByName(kWebVrExclusiveScreenToast),
+                     !model->browsing_disabled);
+
+  e->RemoveKeyframeModels(TRANSFORM);
+
+  e->SetTranslate(0, kWebVrPermissionOffsetStart, 0);
+
+  // Build up a keyframe model for the initial transition.
+  std::unique_ptr<cc::KeyframedTransformAnimationCurve> curve(
+      cc::KeyframedTransformAnimationCurve::Create());
+
+  cc::TransformOperations value_1;
+  value_1.AppendTranslate(0, kWebVrPermissionOffsetStart, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta(), value_1,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  cc::TransformOperations value_2;
+  value_2.AppendTranslate(0, kWebVrPermissionOffsetOvershoot, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta::FromMilliseconds(kWebVrPermissionOffsetMs), value_2,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  cc::TransformOperations value_3;
+  value_3.AppendTranslate(0, kWebVrPermissionOffsetFinal, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta::FromMilliseconds(kWebVrPermissionAnimationDurationMs),
+      value_3,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  e->AddKeyframeModel(cc::KeyframeModel::Create(
+      std::move(curve), Animation::GetNextKeyframeModelId(),
+      Animation::GetNextGroupId(), TRANSFORM));
+}
+
+#else
+
+void BindIndicatorTranscience(
+    TransientElement* e,
+    Model* model,
+    UiScene* scene,
+    const base::Optional<std::tuple<bool, bool, bool>>& last_value,
+    const std::tuple<bool, bool, bool>& value) {
+  const bool in_web_vr_presentation = std::get<0>(value);
+  const bool in_long_press = std::get<1>(value);
+  const bool showing_hosted_ui = std::get<2>(value);
+  const bool was_in_long_press = last_value && std::get<1>(last_value.value());
+  const bool was_showing_hosted_ui =
+      last_value && std::get<2>(last_value.value());
+
+  if (!in_web_vr_presentation) {
+    e->SetVisibleImmediately(false);
+    return;
+  }
+
+  // The reason we need the previous state is to disguish the
+  // situation where the app button has been released after a long
+  // press, and the situation when we want to initially show the
+  // indicators.
+  if (was_in_long_press && !in_long_press)
+    return;
+
+  // Similarly, we need to know when we've finished presenting hosted
+  // ui because we should not show indicators then.
+  if (was_showing_hosted_ui && !showing_hosted_ui)
+    return;
+
+  e->SetVisible(true);
+  e->RefreshVisible();
+  SetVisibleInLayout(scene->GetUiElementByName(kWebVrExclusiveScreenToast),
+                     !model->browsing_disabled && !in_long_press);
+
+  auto specs = GetIndicatorSpecs();
+  for (const auto& spec : specs) {
+    SetVisibleInLayout(scene->GetUiElementByName(spec.webvr_name),
+                       model->active_capturing.*spec.signal ||
+                           model->potential_capturing.*spec.signal ||
+                           model->background_capturing.*spec.signal);
+  }
+
+  e->RemoveKeyframeModels(TRANSFORM);
+  if (in_long_press) {
+    // We do not do a translation animation for long press.
+    e->SetTranslate(0, 0, 0);
+    return;
+  }
+
+  e->SetTranslate(0, kWebVrPermissionOffsetStart, 0);
+
+  // Build up a keyframe model for the initial transition.
+  std::unique_ptr<cc::KeyframedTransformAnimationCurve> curve(
+      cc::KeyframedTransformAnimationCurve::Create());
+
+  cc::TransformOperations value_1;
+  value_1.AppendTranslate(0, kWebVrPermissionOffsetStart, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta(), value_1,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  cc::TransformOperations value_2;
+  value_2.AppendTranslate(0, kWebVrPermissionOffsetOvershoot, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta::FromMilliseconds(kWebVrPermissionOffsetMs), value_2,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  cc::TransformOperations value_3;
+  value_3.AppendTranslate(0, kWebVrPermissionOffsetFinal, 0);
+  curve->AddKeyframe(cc::TransformKeyframe::Create(
+      base::TimeDelta::FromMilliseconds(kWebVrPermissionAnimationDurationMs),
+      value_3,
+      cc::CubicBezierTimingFunction::CreatePreset(
+          cc::CubicBezierTimingFunction::EaseType::EASE)));
+
+  e->AddKeyframeModel(cc::KeyframeModel::Create(
+      std::move(curve), Animation::GetNextKeyframeModelId(),
+      Animation::GetNextGroupId(), TRANSFORM));
+}
+
+#endif
+
 }  // namespace
 
 UiSceneCreator::UiSceneCreator(UiBrowserInterface* browser,
@@ -1845,8 +2016,7 @@
       float, Model, model_,
       model->reposition_window_enabled() ? kRepositionContentOpacity : 1.0f,
       UiElement, content_toggle.get(), SetOpacity));
-  scene_->AddParentUiElement(k2dBrowsingForeground,
-                             std::move(content_toggle));
+  scene_->AddParentUiElement(k2dBrowsingForeground, std::move(content_toggle));
 
   auto hit_plane =
       Create<InvisibleHitTarget>(kContentRepositionHitPlane, kPhaseForeground);
@@ -2311,7 +2481,8 @@
   };
   std::vector<MenuItem> menu_items = {
       {
-          kOverflowMenuNewIncognitoTabItem, new_incognito_tab_res_id,
+          kOverflowMenuNewIncognitoTabItem,
+          new_incognito_tab_res_id,
           base::BindRepeating(
               [](UiBrowserInterface* browser) { browser->OpenNewTab(true); }),
           base::BindRepeating([](Model* m) { return !m->incognito; }),
@@ -2803,6 +2974,8 @@
   indicators->SetTranslate(0, 0, kWebVrPermissionDepth);
   indicators->set_margin(kWebVrPermissionOuterMargin);
 
+  DrawPhase phase = kPhaseOverlayForeground;
+
   IndicatorSpec app_button_spec = {kNone,
                                    kWebVrExclusiveScreenToast,
                                    GetVrIcon(kVrRemoveCircleOutlineIcon),
@@ -2811,15 +2984,34 @@
                                    0,
                                    nullptr,
                                    false};
-  indicators->AddChild(CreateWebVrIndicator(model_, browser_, app_button_spec));
+  indicators->AddChild(
+      CreateWebVrIndicator(model_, browser_, app_button_spec, phase));
 
   auto specs = GetIndicatorSpecs();
   for (const auto& spec : specs) {
-    indicators->AddChild(CreateWebVrIndicator(model_, browser_, spec));
+    indicators->AddChild(CreateWebVrIndicator(model_, browser_, spec, phase));
   }
 
   auto parent = CreateTransientParent(kWebVrIndicatorTransience,
                                       kToastTimeoutSeconds, true);
+#if defined(OS_WIN)
+  parent->AddBinding(
+      std::make_unique<
+          Binding<std::tuple<bool, CapturingStateModel, CapturingStateModel>>>(
+          VR_BIND_LAMBDA(
+              [](Model* model) {
+                return std::tuple<bool, CapturingStateModel,
+                                  CapturingStateModel>(
+                    model->web_vr_enabled() &&
+                        model->web_vr.IsImmersiveWebXrVisible() &&
+                        model->web_vr.has_received_permissions,
+                    model->active_capturing, model->potential_capturing);
+              },
+              base::Unretained(model_)),
+          VR_BIND_LAMBDA(BindIndicatorTranscienceForWin,
+                         base::Unretained(parent.get()),
+                         base::Unretained(model_), base::Unretained(scene_))));
+#else
   parent->AddBinding(std::make_unique<Binding<std::tuple<bool, bool, bool>>>(
       VR_BIND_LAMBDA(
           [](Model* model) {
@@ -2831,93 +3023,9 @@
                 model->web_vr.showing_hosted_ui);
           },
           base::Unretained(model_)),
-      VR_BIND_LAMBDA(
-          [](TransientElement* e, Model* model, UiScene* scene,
-             const base::Optional<std::tuple<bool, bool, bool>>& last_value,
-             const std::tuple<bool, bool, bool>& value) {
-            const bool in_web_vr_presentation = std::get<0>(value);
-            const bool in_long_press = std::get<1>(value);
-            const bool showing_hosted_ui = std::get<2>(value);
-            const bool was_in_long_press =
-                last_value && std::get<1>(last_value.value());
-            const bool was_showing_hosted_ui =
-                last_value && std::get<2>(last_value.value());
-
-            if (!in_web_vr_presentation) {
-              e->SetVisibleImmediately(false);
-              return;
-            }
-
-            // The reason we need the previous state is to disguish the
-            // situation where the app button has been released after a long
-            // press, and the situation when we want to initially show the
-            // indicators.
-            if (was_in_long_press && !in_long_press)
-              return;
-
-            // Similarly, we need to know when we've finished presenting hosted
-            // ui because we should not show indicators then.
-            if (was_showing_hosted_ui && !showing_hosted_ui)
-              return;
-
-            e->SetVisible(true);
-            e->RefreshVisible();
-            SetVisibleInLayout(
-                scene->GetUiElementByName(kWebVrExclusiveScreenToast),
-                !model->browsing_disabled && !in_long_press);
-
-            auto specs = GetIndicatorSpecs();
-            for (const auto& spec : specs) {
-              SetVisibleInLayout(
-                  scene->GetUiElementByName(spec.webvr_name),
-                  model->active_capturing.*spec.signal ||
-                      model->potential_capturing.*spec.signal ||
-                      model->background_capturing.*spec.signal);
-            }
-
-            e->RemoveKeyframeModels(TRANSFORM);
-            if (in_long_press) {
-              // We do not do a translation animation for long press.
-              e->SetTranslate(0, 0, 0);
-              return;
-            }
-
-            e->SetTranslate(0, kWebVrPermissionOffsetStart, 0);
-
-            // Build up a keyframe model for the initial transition.
-            std::unique_ptr<cc::KeyframedTransformAnimationCurve> curve(
-                cc::KeyframedTransformAnimationCurve::Create());
-
-            cc::TransformOperations value_1;
-            value_1.AppendTranslate(0, kWebVrPermissionOffsetStart, 0);
-            curve->AddKeyframe(cc::TransformKeyframe::Create(
-                base::TimeDelta(), value_1,
-                cc::CubicBezierTimingFunction::CreatePreset(
-                    cc::CubicBezierTimingFunction::EaseType::EASE)));
-
-            cc::TransformOperations value_2;
-            value_2.AppendTranslate(0, kWebVrPermissionOffsetOvershoot, 0);
-            curve->AddKeyframe(cc::TransformKeyframe::Create(
-                base::TimeDelta::FromMilliseconds(kWebVrPermissionOffsetMs),
-                value_2,
-                cc::CubicBezierTimingFunction::CreatePreset(
-                    cc::CubicBezierTimingFunction::EaseType::EASE)));
-
-            cc::TransformOperations value_3;
-            value_3.AppendTranslate(0, kWebVrPermissionOffsetFinal, 0);
-            curve->AddKeyframe(cc::TransformKeyframe::Create(
-                base::TimeDelta::FromMilliseconds(
-                    kWebVrPermissionAnimationDurationMs),
-                value_3,
-                cc::CubicBezierTimingFunction::CreatePreset(
-                    cc::CubicBezierTimingFunction::EaseType::EASE)));
-
-            e->AddKeyframeModel(cc::KeyframeModel::Create(
-                std::move(curve), Animation::GetNextKeyframeModelId(),
-                Animation::GetNextGroupId(), TRANSFORM));
-          },
-          base::Unretained(parent.get()), base::Unretained(model_),
-          base::Unretained(scene_))));
+      VR_BIND_LAMBDA(BindIndicatorTranscience, base::Unretained(parent.get()),
+                     base::Unretained(model_), base::Unretained(scene_))));
+#endif
 
   auto scaler = std::make_unique<ScaledDepthAdjuster>(kWebVrToastDistance);
   scaler->AddChild(std::move(indicators));
diff --git a/chrome/browser/vr/ui_unittest.cc b/chrome/browser/vr/ui_unittest.cc
index e494271..070be975 100644
--- a/chrome/browser/vr/ui_unittest.cc
+++ b/chrome/browser/vr/ui_unittest.cc
@@ -1307,6 +1307,7 @@
 
 // Ensures that permissions do not appear after showing hosted UI.
 TEST_F(UiTest, DoNotShowIndicatorsAfterHostedUi) {
+#if !defined(OS_WIN)
   CreateScene(kInWebVr);
   auto browser_ui = ui_->GetBrowserUiWeakPtr();
   browser_ui->SetWebVrMode(true);
@@ -1323,12 +1324,14 @@
   model_->web_vr.showing_hosted_ui = false;
   OnBeginFrame();
   EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
+#endif
 }
 
 // Ensures that permissions appear on long press, and that when the menu button
 // is released that we do not show the exclusive screen toast. Distinguishing
 // these cases requires knowledge of the previous state.
 TEST_F(UiTest, LongPressMenuButtonInWebVrMode) {
+#if !defined(OS_WIN)
   CreateScene(kInWebVr);
   auto browser_ui = ui_->GetBrowserUiWeakPtr();
   browser_ui->SetWebVrMode(true);
@@ -1360,6 +1363,7 @@
       std::make_unique<InputEvent>(InputEvent::kMenuButtonLongPressEnd));
   ui_->HandleMenuButtonEvents(&events);
   EXPECT_FALSE(model_->menu_button_long_pressed);
+#endif
 }
 
 TEST_F(UiTest, MenuItems) {
diff --git a/chrome/browser/vr/win/graphics_delegate_win.cc b/chrome/browser/vr/win/graphics_delegate_win.cc
index e156569..2a6627e 100644
--- a/chrome/browser/vr/win/graphics_delegate_win.cc
+++ b/chrome/browser/vr/win/graphics_delegate_win.cc
@@ -304,7 +304,7 @@
 }
 
 void GraphicsDelegateWin::PrepareBufferForBrowserUi() {
-  gl_->ClearColor(0, 1, 0, 1);
+  gl_->ClearColor(0, 0, 0, 0);
   gl_->Clear(GL_COLOR_BUFFER_BIT);
 
   DCHECK(prepared_drawing_buffer_ == DrawingBufferMode::kNone);
diff --git a/chrome/browser/vr/win/scheduler_delegate_win.cc b/chrome/browser/vr/win/scheduler_delegate_win.cc
index 271224d..3d171cc8 100644
--- a/chrome/browser/vr/win/scheduler_delegate_win.cc
+++ b/chrome/browser/vr/win/scheduler_delegate_win.cc
@@ -13,13 +13,14 @@
 
 void SchedulerDelegateWin::OnPose(base::OnceCallback<void()> on_frame_ended,
                                   gfx::Transform head_pose,
+                                  bool draw_overlay,
                                   bool draw_ui) {
   on_frame_ended_ = std::move(on_frame_ended);
   base::TimeTicks now = base::TimeTicks::Now();
-  if (draw_ui)
-    browser_renderer_->DrawBrowserFrame(now);
-  else
+  if (draw_overlay)
     browser_renderer_->DrawWebXrFrame(now, head_pose);
+  else if (draw_ui)
+    browser_renderer_->DrawBrowserFrame(now);
 }
 
 void SchedulerDelegateWin::OnPause() {
diff --git a/chrome/browser/vr/win/scheduler_delegate_win.h b/chrome/browser/vr/win/scheduler_delegate_win.h
index 1c0c120..f2e0945 100644
--- a/chrome/browser/vr/win/scheduler_delegate_win.h
+++ b/chrome/browser/vr/win/scheduler_delegate_win.h
@@ -17,6 +17,7 @@
   // Tell browser when poses available, when we rendered, etc.
   void OnPose(base::OnceCallback<void()> on_frame_ended,
               gfx::Transform head_pose,
+              bool draw_overlay,
               bool draw_ui);
 
  private:
diff --git a/chrome/browser/vr/win/vr_browser_renderer_thread_win.cc b/chrome/browser/vr/win/vr_browser_renderer_thread_win.cc
index 16d329e..1501421e 100644
--- a/chrome/browser/vr/win/vr_browser_renderer_thread_win.cc
+++ b/chrome/browser/vr/win/vr_browser_renderer_thread_win.cc
@@ -57,6 +57,8 @@
   started_ = false;
   graphics_ = nullptr;
   scheduler_ = nullptr;
+  ui_ = nullptr;
+  scheduler_ui_ = nullptr;
 }
 
 void VRBrowserRendererThreadWin::SetVRDisplayInfo(
@@ -115,6 +117,13 @@
   OnSpinnerVisibilityChanged(false);
 }
 
+int VRBrowserRendererThreadWin::GetNextRequestId() {
+  current_request_id_++;
+  if (current_request_id_ >= 0x10000)
+    current_request_id_ = 0;
+  return current_request_id_;
+}
+
 void VRBrowserRendererThreadWin::OnWebXrTimeoutImminent() {
   OnSpinnerVisibilityChanged(true);
   scheduler_ui_->OnWebXrTimeoutImminent();
@@ -135,16 +144,48 @@
 
   ui_->SetVisibleExternalPromptNotification(prompt);
 
-  overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
-                                         draw_state_.ShouldDrawWebXR());
+  if (overlay_)
+    overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
+                                           draw_state_.ShouldDrawWebXR());
   if (draw_state_.ShouldDrawUI()) {
-    overlay_->RequestNextOverlayPose(base::BindOnce(
-        &VRBrowserRendererThreadWin::OnPose, base::Unretained(this)));
+    if (overlay_)  // False only while testing
+      overlay_->RequestNextOverlayPose(
+          base::BindOnce(&VRBrowserRendererThreadWin::OnPose,
+                         base::Unretained(this), GetNextRequestId()));
   } else {
     StopOverlay();
   }
 }
 
+void VRBrowserRendererThreadWin::SetIndicatorsVisible(bool visible) {
+  if (!draw_state_.SetIndicatorsVisible(visible))
+    return;
+
+  if (draw_state_.ShouldDrawUI())
+    StartOverlay();
+
+  if (overlay_)
+    overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
+                                           draw_state_.ShouldDrawWebXR());
+  if (draw_state_.ShouldDrawUI()) {
+    if (overlay_)  // False only while testing
+      overlay_->RequestNextOverlayPose(
+          base::BindOnce(&VRBrowserRendererThreadWin::OnPose,
+                         base::Unretained(this), GetNextRequestId()));
+  } else {
+    StopOverlay();
+  }
+}
+
+void VRBrowserRendererThreadWin::SetCapturingState(
+    const CapturingStateModel& active_capturing,
+    const CapturingStateModel& background_capturing,
+    const CapturingStateModel& potential_capturing) {
+  if (ui_)
+    ui_->SetCapturingState(active_capturing, background_capturing,
+                           potential_capturing);
+}
+
 VRBrowserRendererThreadWin*
 VRBrowserRendererThreadWin::GetInstanceForTesting() {
   return instance_for_testing_;
@@ -263,22 +304,24 @@
 }
 
 void VRBrowserRendererThreadWin::OnSpinnerVisibilityChanged(bool visible) {
-  if (draw_state_.SetSpinnerVisible(visible)) {
-    if (draw_state_.ShouldDrawUI()) {
-      StartOverlay();
-    }
+  if (!draw_state_.SetSpinnerVisible(visible))
+    return;
+  if (draw_state_.ShouldDrawUI()) {
+    StartOverlay();
+  }
 
-    if (overlay_) {
-      overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
-                                             draw_state_.ShouldDrawWebXR());
-    }
+  if (overlay_) {
+    overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
+                                           draw_state_.ShouldDrawWebXR());
+  }
 
-    if (draw_state_.ShouldDrawUI()) {
-      overlay_->RequestNextOverlayPose(base::BindOnce(
-          &VRBrowserRendererThreadWin::OnPose, base::Unretained(this)));
-    } else {
-      StopOverlay();
-    }
+  if (draw_state_.ShouldDrawUI()) {
+    if (overlay_)  // False only while testing.
+      overlay_->RequestNextOverlayPose(
+          base::BindOnce(&VRBrowserRendererThreadWin::OnPose,
+                         base::Unretained(this), GetNextRequestId()));
+  } else {
+    StopOverlay();
   }
 }
 
@@ -288,12 +331,16 @@
   StopWebXrTimeout();
 }
 
-void VRBrowserRendererThreadWin::OnPose(device::mojom::XRFrameDataPtr data) {
+void VRBrowserRendererThreadWin::OnPose(int request_id,
+                                        device::mojom::XRFrameDataPtr data) {
+  if (request_id != current_request_id_) {
+    // Old request. Do nothing.
+    return;
+  }
   if (!draw_state_.ShouldDrawUI()) {
     // We shouldn't be showing UI.
     overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
                                            draw_state_.ShouldDrawWebXR());
-
     if (graphics_)
       graphics_->ResetMemoryBuffer();
     return;
@@ -330,7 +377,8 @@
   // calling the callback if we are destroyed.
   scheduler_->OnPose(base::BindOnce(&VRBrowserRendererThreadWin::SubmitFrame,
                                     base::Unretained(this), std::move(data)),
-                     head_from_world, draw_state_.ShouldDrawUI());
+                     head_from_world, draw_state_.ShouldDrawWebXR(),
+                     draw_state_.ShouldDrawUI());
 }
 
 void VRBrowserRendererThreadWin::SubmitFrame(
@@ -345,24 +393,28 @@
 }
 
 void VRBrowserRendererThreadWin::SubmitResult(bool success) {
-  if (!success) {
+  if (!success && graphics_) {
     graphics_->ResetMemoryBuffer();
   }
+  if (scheduler_ui_ && success)
+    scheduler_ui_->OnWebXrFrameAvailable();
   if (draw_state_.ShouldDrawUI() && started_) {
-    overlay_->RequestNextOverlayPose(base::BindOnce(
-        &VRBrowserRendererThreadWin::OnPose, base::Unretained(this)));
+    overlay_->RequestNextOverlayPose(
+        base::BindOnce(&VRBrowserRendererThreadWin::OnPose,
+                       base::Unretained(this), GetNextRequestId()));
   }
 }
 
 // VRBrowserRendererThreadWin::DrawContentType functions.
 bool VRBrowserRendererThreadWin::DrawState::ShouldDrawUI() {
   return prompt_ != ExternalPromptNotificationType::kPromptNone ||
-         spinner_visible_;
+         spinner_visible_ || indicators_visible_;
 }
 
 bool VRBrowserRendererThreadWin::DrawState::ShouldDrawWebXR() {
-  return prompt_ == ExternalPromptNotificationType::kPromptNone &&
-         !spinner_visible_;
+  return (prompt_ == ExternalPromptNotificationType::kPromptNone &&
+          !spinner_visible_) ||
+         indicators_visible_;
 }
 
 bool VRBrowserRendererThreadWin::DrawState::SetPrompt(
@@ -380,4 +432,11 @@
   return old_ui != ShouldDrawUI() || old_webxr != ShouldDrawWebXR();
 }
 
+bool VRBrowserRendererThreadWin::DrawState::SetIndicatorsVisible(bool visible) {
+  bool old_ui = ShouldDrawUI();
+  bool old_webxr = ShouldDrawWebXR();
+  indicators_visible_ = visible;
+  return old_ui != ShouldDrawUI() || old_webxr != ShouldDrawWebXR();
+}
+
 }  // namespace vr
diff --git a/chrome/browser/vr/win/vr_browser_renderer_thread_win.h b/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
index 8204a03..a8675ee 100644
--- a/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
+++ b/chrome/browser/vr/win/vr_browser_renderer_thread_win.h
@@ -9,6 +9,7 @@
 
 #include "base/threading/thread.h"
 #include "chrome/browser/vr/browser_renderer.h"
+#include "chrome/browser/vr/model/capturing_state_model.h"
 #include "chrome/browser/vr/model/web_vr_model.h"
 #include "chrome/browser/vr/service/browser_xr_runtime.h"
 #include "chrome/browser/vr/vr_export.h"
@@ -37,6 +38,10 @@
   // The below function(s) affect(s) whether UI is drawn or not.
   void SetVisibleExternalPromptNotification(
       ExternalPromptNotificationType prompt);
+  void SetIndicatorsVisible(bool visible);
+  void SetCapturingState(const CapturingStateModel& active_capturing,
+                         const CapturingStateModel& background_capturing,
+                         const CapturingStateModel& potential_capturing);
 
   static VRBrowserRendererThreadWin* GetInstanceForTesting();
   BrowserRenderer* GetBrowserRendererForTesting();
@@ -48,6 +53,7 @@
     // State changing methods.
     bool SetPrompt(ExternalPromptNotificationType prompt);
     bool SetSpinnerVisible(bool visible);
+    bool SetIndicatorsVisible(bool visible);
 
     // State querying methods.
     bool ShouldDrawUI();
@@ -58,9 +64,10 @@
         ExternalPromptNotificationType::kPromptNone;
 
     bool spinner_visible_ = false;
+    bool indicators_visible_ = false;
   };
 
-  void OnPose(device::mojom::XRFrameDataPtr data);
+  void OnPose(int request_id, device::mojom::XRFrameDataPtr data);
   void SubmitResult(bool success);
   void SubmitFrame(device::mojom::XRFrameDataPtr data);
   void StartOverlay();
@@ -71,6 +78,7 @@
   void OnWebXrTimedOut();
   void StartWebXrTimeout();
   void StopWebXrTimeout();
+  int GetNextRequestId();
 
   // We need to do some initialization of GraphicsDelegateWin before
   // browser_renderer_, so we first store it in a unique_ptr, then transition
@@ -94,6 +102,7 @@
   DrawState draw_state_;
   bool started_ = false;
   bool webxr_presenting_ = false;
+  int current_request_id_ = 0;
 
   device::mojom::ImmersiveOverlayPtr overlay_;
   device::mojom::VRDisplayInfoPtr display_info_;
diff --git a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager.cc b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager.cc
index f0eb05d..c51d98c 100644
--- a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager.cc
+++ b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager.cc
@@ -195,8 +195,14 @@
       break;
   }
 
-  if (!options.create_shortcuts)
-    helper->set_skip_shortcut_creation();
+  if (!options.add_to_applications_menu)
+    helper->set_skip_adding_to_applications_menu();
+
+  if (!options.add_to_desktop)
+    helper->set_skip_adding_to_desktop();
+
+  if (!options.add_to_quick_launch_bar)
+    helper->set_skip_adding_to_quick_launch_bar();
 
   if (options.bypass_service_worker_check)
     helper->set_bypass_service_worker_check();
diff --git a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
index 7f27258..3643787 100644
--- a/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/bookmark_app_install_manager_unittest.cc
@@ -11,11 +11,14 @@
 #include "base/files/file_path.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
+#include "chrome/browser/extensions/bookmark_app_helper.h"
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/installable/installable_manager.h"
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
+#include "chrome/browser/web_applications/components/install_options.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/test/test_data_retriever.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"
@@ -115,10 +118,195 @@
         run_loop.Quit();
       }));
 
-  // Destroy InstallManager.
+  // Destroy InstallManager: Call Reset as if Profile gets destroyed.
   install_manager_->Reset();
   run_loop.Run();
 
+  // Delete InstallManager object.
+  install_manager_.reset();
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(BookmarkAppInstallManagerTest, WithOptions_WebContentsDestroyed) {
+  const GURL app_url("https://example.com/path");
+  NavigateAndCommit(app_url);
+
+  web_app::InstallOptions install_options(
+      app_url, web_app::LaunchContainer::kWindow,
+      web_app::InstallSource::kExternalPolicy);
+
+  base::RunLoop run_loop;
+  bool callback_called = false;
+
+  install_manager_->InstallWebAppWithOptions(
+      web_contents(), install_options,
+      base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                     web_app::InstallResultCode code) {
+        EXPECT_EQ(web_app::InstallResultCode::kWebContentsDestroyed, code);
+        EXPECT_EQ(web_app::AppId(), installed_app_id);
+        callback_called = true;
+        run_loop.Quit();
+      }));
+  EXPECT_FALSE(callback_called);
+
+  // Destroy WebContents.
+  DeleteContents();
+  EXPECT_EQ(nullptr, web_contents());
+
+  run_loop.Run();
+
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(BookmarkAppInstallManagerTest,
+       WithOptions_WebContentsDestroyedAfterDataRetrieval) {
+  const GURL app_url("https://example.com/path");
+  NavigateAndCommit(app_url);
+
+  web_app::InstallOptions install_options(
+      app_url, web_app::LaunchContainer::kWindow,
+      web_app::InstallSource::kExternalPolicy);
+
+  base::RunLoop retrieval_run_loop;
+  bool data_retrieval_passed = false;
+
+  install_manager_->SetDataRetrieverFactoryForTesting(
+      base::BindLambdaForTesting([&]() {
+        WebApplicationInfo info;
+        info.app_url = app_url;
+        auto data_retriever = std::make_unique<web_app::TestDataRetriever>(
+            std::make_unique<WebApplicationInfo>(std::move(info)));
+
+        return std::unique_ptr<web_app::WebAppDataRetriever>(
+            std::move(data_retriever));
+      }));
+
+  install_manager_->SetBookmarkAppHelperFactoryForTesting(
+      base::BindLambdaForTesting([&](Profile* profile,
+                                     const WebApplicationInfo& web_app_info,
+                                     content::WebContents* web_contents,
+                                     WebappInstallSource install_source) {
+        data_retrieval_passed = true;
+        retrieval_run_loop.Quit();
+        return std::make_unique<BookmarkAppHelper>(
+            profile, web_app_info, web_contents, install_source);
+      }));
+
+  base::RunLoop install_run_loop;
+  bool callback_called = false;
+
+  install_manager_->InstallWebAppWithOptions(
+      web_contents(), install_options,
+      base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                     web_app::InstallResultCode code) {
+        EXPECT_EQ(web_app::InstallResultCode::kWebContentsDestroyed, code);
+        EXPECT_EQ(web_app::AppId(), installed_app_id);
+        callback_called = true;
+        install_run_loop.Quit();
+      }));
+  EXPECT_FALSE(callback_called);
+
+  retrieval_run_loop.Run();
+  EXPECT_TRUE(data_retrieval_passed);
+  EXPECT_FALSE(callback_called);
+
+  // Destroy WebContents.
+  DeleteContents();
+  EXPECT_EQ(nullptr, web_contents());
+
+  install_run_loop.Run();
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(BookmarkAppInstallManagerTest, WithOptions_InstallManagerDestroyed) {
+  const GURL app_url("https://example.com/path");
+  NavigateAndCommit(app_url);
+
+  web_app::InstallOptions install_options(
+      app_url, web_app::LaunchContainer::kWindow,
+      web_app::InstallSource::kExternalPolicy);
+
+  base::RunLoop run_loop;
+  bool callback_called = false;
+
+  install_manager_->InstallWebAppWithOptions(
+      web_contents(), install_options,
+      base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                     web_app::InstallResultCode code) {
+        EXPECT_EQ(web_app::InstallResultCode::kInstallManagerDestroyed, code);
+        EXPECT_EQ(web_app::AppId(), installed_app_id);
+        callback_called = true;
+        run_loop.Quit();
+      }));
+  EXPECT_FALSE(callback_called);
+
+  // Destroy InstallManager: Call Reset as if Profile gets destroyed.
+  install_manager_->Reset();
+  run_loop.Run();
+
+  // Delete InstallManager object.
+  install_manager_.reset();
+
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(BookmarkAppInstallManagerTest,
+       WithOptions_InstallManagerDestroyedAfterDataRetrieval) {
+  const GURL app_url("https://example.com/path");
+  NavigateAndCommit(app_url);
+
+  web_app::InstallOptions install_options(
+      app_url, web_app::LaunchContainer::kWindow,
+      web_app::InstallSource::kExternalPolicy);
+
+  base::RunLoop retrieval_run_loop;
+  bool data_retrieval_passed = false;
+
+  install_manager_->SetDataRetrieverFactoryForTesting(
+      base::BindLambdaForTesting([&]() {
+        WebApplicationInfo info;
+        info.app_url = app_url;
+        auto data_retriever = std::make_unique<web_app::TestDataRetriever>(
+            std::make_unique<WebApplicationInfo>(std::move(info)));
+
+        return std::unique_ptr<web_app::WebAppDataRetriever>(
+            std::move(data_retriever));
+      }));
+
+  install_manager_->SetBookmarkAppHelperFactoryForTesting(
+      base::BindLambdaForTesting([&](Profile* profile,
+                                     const WebApplicationInfo& web_app_info,
+                                     content::WebContents* web_contents,
+                                     WebappInstallSource install_source) {
+        data_retrieval_passed = true;
+        retrieval_run_loop.Quit();
+        return std::make_unique<BookmarkAppHelper>(
+            profile, web_app_info, web_contents, install_source);
+      }));
+
+  base::RunLoop install_run_loop;
+  bool callback_called = false;
+
+  install_manager_->InstallWebAppWithOptions(
+      web_contents(), install_options,
+      base::BindLambdaForTesting([&](const web_app::AppId& installed_app_id,
+                                     web_app::InstallResultCode code) {
+        EXPECT_EQ(web_app::InstallResultCode::kInstallManagerDestroyed, code);
+        EXPECT_EQ(web_app::AppId(), installed_app_id);
+        callback_called = true;
+        install_run_loop.Quit();
+      }));
+  EXPECT_FALSE(callback_called);
+
+  retrieval_run_loop.Run();
+  EXPECT_TRUE(data_retrieval_passed);
+  EXPECT_FALSE(callback_called);
+
+  // Destroy InstallManager: Call Reset as if Profile gets destroyed.
+  install_manager_->Reset();
+  install_run_loop.Run();
+
+  // Delete InstallManager object.
   install_manager_.reset();
   EXPECT_TRUE(callback_called);
 }
diff --git a/chrome/browser/web_applications/bookmark_apps/external_web_apps_unittest.cc b/chrome/browser/web_applications/bookmark_apps/external_web_apps_unittest.cc
index 07ef8765..04889c4f 100644
--- a/chrome/browser/web_applications/bookmark_apps/external_web_apps_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/external_web_apps_unittest.cc
@@ -188,7 +188,9 @@
         GURL("https://www.chromestatus.com/features"),
         web_app::LaunchContainer::kTab,
         web_app::InstallSource::kExternalDefault);
-    install_options.create_shortcuts = true;
+    install_options.add_to_applications_menu = true;
+    install_options.add_to_desktop = true;
+    install_options.add_to_quick_launch_bar = true;
     install_options.require_manifest = true;
     test_install_options_list.push_back(std::move(install_options));
   }
@@ -197,7 +199,9 @@
         GURL("https://events.google.com/io2016/?utm_source=web_app_manifest"),
         web_app::LaunchContainer::kWindow,
         web_app::InstallSource::kExternalDefault);
-    install_options.create_shortcuts = false;
+    install_options.add_to_applications_menu = false;
+    install_options.add_to_desktop = false;
+    install_options.add_to_quick_launch_bar = false;
     install_options.require_manifest = true;
     test_install_options_list.push_back(std::move(install_options));
   }
diff --git a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
index 0ede645..92a894f2 100644
--- a/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/policy/web_app_policy_manager_unittest.cc
@@ -50,7 +50,9 @@
 InstallOptions GetWindowedInstallOptions() {
   InstallOptions options(GURL(kWindowedUrl), LaunchContainer::kWindow,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
@@ -65,7 +67,9 @@
 InstallOptions GetTabbedInstallOptions() {
   InstallOptions options(GURL(kTabbedUrl), LaunchContainer::kTab,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
@@ -78,7 +82,9 @@
 InstallOptions GetNoContainerInstallOptions() {
   InstallOptions options(GURL(kNoContainerUrl), LaunchContainer::kTab,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
@@ -91,7 +97,9 @@
 InstallOptions GetCreateDesktopShorcutDefaultInstallOptions() {
   InstallOptions options(GURL(kNoContainerUrl), LaunchContainer::kTab,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
@@ -105,7 +113,9 @@
 InstallOptions GetCreateDesktopShorcutFalseInstallOptions() {
   InstallOptions options(GURL(kNoContainerUrl), LaunchContainer::kTab,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
@@ -119,7 +129,9 @@
 InstallOptions GetCreateDesktopShorcutTrueInstallOptions() {
   InstallOptions options(GURL(kNoContainerUrl), LaunchContainer::kTab,
                          InstallSource::kExternalPolicy);
-  options.create_shortcuts = true;
+  options.add_to_applications_menu = true;
+  options.add_to_desktop = true;
+  options.add_to_quick_launch_bar = false;
   return options;
 }
 
diff --git a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_unittest.cc b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_unittest.cc
index 6b715d2..1a875f6 100644
--- a/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/bookmark_apps/system_web_app_manager_unittest.cc
@@ -41,7 +41,9 @@
 InstallOptions GetWindowedInstallOptions() {
   InstallOptions options(GURL(kAppUrl1), LaunchContainer::kWindow,
                          InstallSource::kSystemInstalled);
-  options.create_shortcuts = false;
+  options.add_to_applications_menu = false;
+  options.add_to_desktop = false;
+  options.add_to_quick_launch_bar = false;
   options.bypass_service_worker_check = true;
   options.always_update = true;
   return options;
diff --git a/chrome/browser/web_applications/components/install_options.cc b/chrome/browser/web_applications/components/install_options.cc
index 611db56..9f6aab82 100644
--- a/chrome/browser/web_applications/components/install_options.cc
+++ b/chrome/browser/web_applications/components/install_options.cc
@@ -26,11 +26,14 @@
     default;
 
 bool InstallOptions::operator==(const InstallOptions& other) const {
-  return std::tie(url, launch_container, install_source, create_shortcuts,
-                  override_previous_user_uninstall, bypass_service_worker_check,
-                  require_manifest, always_update) ==
+  return std::tie(url, launch_container, install_source,
+                  add_to_applications_menu, add_to_desktop,
+                  add_to_quick_launch_bar, override_previous_user_uninstall,
+                  bypass_service_worker_check, require_manifest,
+                  always_update) ==
          std::tie(other.url, other.launch_container, other.install_source,
-                  other.create_shortcuts,
+                  other.add_to_applications_menu, other.add_to_desktop,
+                  other.add_to_quick_launch_bar,
                   other.override_previous_user_uninstall,
                   other.bypass_service_worker_check, other.require_manifest,
                   other.always_update);
@@ -42,7 +45,11 @@
              << static_cast<int32_t>(install_options.launch_container)
              << "\n install_source: "
              << static_cast<int32_t>(install_options.install_source)
-             << "\n create_shortcuts: " << install_options.create_shortcuts
+             << "\n add_to_applications_menu: "
+             << install_options.add_to_applications_menu
+             << "\n add_to_desktop: " << install_options.add_to_desktop
+             << "\n add_to_quick_launch_bar: "
+             << install_options.add_to_quick_launch_bar
              << "\n override_previous_user_uninstall: "
              << install_options.override_previous_user_uninstall
              << "\n bypass_service_worker_check: "
diff --git a/chrome/browser/web_applications/components/install_options.h b/chrome/browser/web_applications/components/install_options.h
index bf368132..bcb3a5a 100644
--- a/chrome/browser/web_applications/components/install_options.h
+++ b/chrome/browser/web_applications/components/install_options.h
@@ -29,7 +29,23 @@
   LaunchContainer launch_container;
   InstallSource install_source;
 
-  bool create_shortcuts = true;
+  // If true, a shortcut is added to the Applications folder on macOS, and Start
+  // Menu on Linux and Windows. On Chrome OS, all installed apps show up in the
+  // app list, so there is no need to do anything there. If false, we skip
+  // adding a shortcut to desktop as well, regardless of the value of
+  // |add_to_desktop|.
+  // TODO(ortuno): Make adding a shortcut to the applications menu independent
+  // from adding a shortcut to desktop.
+  bool add_to_applications_menu = true;
+
+  // If true, a shortcut is added to the desktop on Linux and Windows. Has no
+  // effect on macOS and Chrome OS.
+  bool add_to_desktop = true;
+
+  // If true, a shortcut is added to the "quick launch bar" of the OS: the Shelf
+  // for Chrome OS, the Dock for macOS, and the Quick Launch Bar or Taskbar on
+  // Windows. Currently this only works on Chrome OS.
+  bool add_to_quick_launch_bar = true;
 
   // Whether the app should be reinstalled even if the user has previously
   // uninstalled it.
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
index a2fc2a72..83bdb2d 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_install_finalizer.cc
@@ -104,7 +104,8 @@
     const web_app::AppId& app_id,
     CreateOsShortcutsCallback callback) {
   const Extension* app = GetExtensionById(profile_, app_id);
-  BookmarkAppCreateOsShortcuts(profile_, app, std::move(callback));
+  BookmarkAppCreateOsShortcuts(profile_, app, true /* add_to_desktop */,
+                               std::move(callback));
 }
 
 bool BookmarkAppInstallFinalizer::CanPinAppToShelf() const {
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 d7e59943..edfa360 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
@@ -183,7 +183,9 @@
 
             EXPECT_EQ(result.app_id.value(), id.value());
 
-            EXPECT_TRUE(test_helper().create_shortcuts());
+            EXPECT_TRUE(test_helper().add_to_quick_launch_bar());
+            EXPECT_TRUE(test_helper().add_to_desktop());
+            EXPECT_TRUE(test_helper().add_to_quick_launch_bar());
             EXPECT_FALSE(test_helper().forced_launch_type().has_value());
             EXPECT_TRUE(test_helper().is_default_app());
             EXPECT_FALSE(test_helper().is_policy_installed_app());
@@ -236,11 +238,11 @@
 }
 
 TEST_F(BookmarkAppInstallationTaskTest,
-       WebAppOrShortcutFromContents_NoShortcuts) {
+       WebAppOrShortcutFromContents_NoDesktopShortcut) {
   web_app::InstallOptions install_options(app_url(),
                                           web_app::LaunchContainer::kWindow,
                                           web_app::InstallSource::kInternal);
-  install_options.create_shortcuts = false;
+  install_options.add_to_desktop = false;
   auto task = std::make_unique<BookmarkAppInstallationTask>(
       profile(), std::move(install_options));
 
@@ -252,7 +254,71 @@
                                 result.code);
                       EXPECT_TRUE(result.app_id.has_value());
 
-                      EXPECT_FALSE(test_helper().create_shortcuts());
+                      EXPECT_TRUE(test_helper().add_to_applications_menu());
+                      EXPECT_TRUE(test_helper().add_to_quick_launch_bar());
+                      EXPECT_FALSE(test_helper().add_to_desktop());
+
+                      callback_called = true;
+                    }));
+  content::RunAllTasksUntilIdle();
+
+  test_helper().CompleteInstallation();
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(BookmarkAppInstallationTaskTest,
+       WebAppOrShortcutFromContents_NoQuickLaunchBarShortcut) {
+  web_app::InstallOptions install_options(app_url(),
+                                          web_app::LaunchContainer::kWindow,
+                                          web_app::InstallSource::kInternal);
+  install_options.add_to_quick_launch_bar = false;
+  auto task = std::make_unique<BookmarkAppInstallationTask>(
+      profile(), std::move(install_options));
+
+  bool callback_called = false;
+  task->Install(web_contents(),
+                base::BindLambdaForTesting(
+                    [&](BookmarkAppInstallationTask::Result result) {
+                      EXPECT_EQ(web_app::InstallResultCode::kSuccess,
+                                result.code);
+                      EXPECT_TRUE(result.app_id.has_value());
+
+                      EXPECT_TRUE(test_helper().add_to_applications_menu());
+                      EXPECT_FALSE(test_helper().add_to_quick_launch_bar());
+                      EXPECT_TRUE(test_helper().add_to_desktop());
+
+                      callback_called = true;
+                    }));
+
+  content::RunAllTasksUntilIdle();
+
+  test_helper().CompleteInstallation();
+  EXPECT_TRUE(callback_called);
+}
+
+TEST_F(
+    BookmarkAppInstallationTaskTest,
+    WebAppOrShortcutFromContents_NoDesktopShortcutAndNoQuickLaunchBarShortcut) {
+  web_app::InstallOptions install_options(app_url(),
+                                          web_app::LaunchContainer::kWindow,
+                                          web_app::InstallSource::kInternal);
+  install_options.add_to_desktop = false;
+  install_options.add_to_quick_launch_bar = false;
+  auto task = std::make_unique<BookmarkAppInstallationTask>(
+      profile(), std::move(install_options));
+
+  bool callback_called = false;
+  task->Install(web_contents(),
+                base::BindLambdaForTesting(
+                    [&](BookmarkAppInstallationTask::Result result) {
+                      EXPECT_EQ(web_app::InstallResultCode::kSuccess,
+                                result.code);
+                      EXPECT_TRUE(result.app_id.has_value());
+
+                      EXPECT_TRUE(test_helper().add_to_applications_menu());
+                      EXPECT_FALSE(test_helper().add_to_quick_launch_bar());
+                      EXPECT_FALSE(test_helper().add_to_desktop());
+
                       callback_called = true;
                     }));
 
diff --git a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_browsertest.cc b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_browsertest.cc
index 34f43ab..16cfb86 100644
--- a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_browsertest.cc
@@ -32,7 +32,10 @@
                                           web_app::LaunchContainer::kWindow,
                                           web_app::InstallSource::kInternal);
   // Avoid creating real shortcuts in tests.
-  install_options.create_shortcuts = false;
+  install_options.add_to_applications_menu = false;
+  install_options.add_to_desktop = false;
+  install_options.add_to_quick_launch_bar = false;
+
   return install_options;
 }
 
diff --git a/chrome/browser/web_applications/external_web_apps.cc b/chrome/browser/web_applications/external_web_apps.cc
index 5d4b7a5..f4d07de 100644
--- a/chrome/browser/web_applications/external_web_apps.cc
+++ b/chrome/browser/web_applications/external_web_apps.cc
@@ -174,7 +174,9 @@
     web_app::InstallOptions install_options(
         std::move(app_url), launch_container,
         web_app::InstallSource::kExternalDefault);
-    install_options.create_shortcuts = create_shortcuts;
+    install_options.add_to_applications_menu = create_shortcuts;
+    install_options.add_to_desktop = create_shortcuts;
+    install_options.add_to_quick_launch_bar = create_shortcuts;
     install_options.require_manifest = true;
 
     install_options_list.push_back(std::move(install_options));
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 79cbdb5..914f9b2 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -92,11 +92,12 @@
     if (create_desktop_shortcut)
       create_shortcut = create_desktop_shortcut->GetBool();
 
-    // This currently pins the app to the shelf on Chrome OS, which we don't
-    // want to do because there is a separate policy for that.
-    // TODO(ortuno): Introduce an option to specifically create desktop
-    // shortcuts and not pin the app to the shelf.
-    install_options.create_shortcuts = create_shortcut;
+    install_options.add_to_applications_menu = create_shortcut;
+    install_options.add_to_desktop = create_shortcut;
+
+    // It's not yet clear how pinning to shelf will work for policy installed
+    // Web Apps, but for now never pin them. See crbug.com/880125.
+    install_options.add_to_quick_launch_bar = false;
 
     install_options_list.push_back(std::move(install_options));
   }
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index d0d4dee..7b5a4fa0 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -37,7 +37,9 @@
 
   web_app::InstallOptions install_options(url, LaunchContainer::kWindow,
                                           InstallSource::kSystemInstalled);
-  install_options.create_shortcuts = false;
+  install_options.add_to_applications_menu = false;
+  install_options.add_to_desktop = false;
+  install_options.add_to_quick_launch_bar = false;
   install_options.bypass_service_worker_check = true;
   install_options.always_update = true;
   return install_options;
diff --git a/chrome/common/conflicts/module_watcher_win.cc b/chrome/common/conflicts/module_watcher_win.cc
index 77048fb3..bdcfb79 100644
--- a/chrome/common/conflicts/module_watcher_win.cc
+++ b/chrome/common/conflicts/module_watcher_win.cc
@@ -12,18 +12,15 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
-#include "base/rand_util.h"
 #include "base/sequenced_task_runner.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/lock.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
-#include "base/threading/platform_thread.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/win/scoped_handle.h"
 
@@ -114,11 +111,6 @@
 constexpr char kLdrRegisterDllNotification[] = "LdrRegisterDllNotification";
 constexpr char kLdrUnregisterDllNotification[] = "LdrUnregisterDllNotification";
 
-// It is currently estimated that around 10 DLLs are loaded in a background
-// sequence in Chrome. This number was chosen so that on average 1% of launches
-// will cause a process dump.
-constexpr int kMaxBackgroundLoadedDllCount = 1000;
-
 // Helper function for converting a UNICODE_STRING to a FilePath.
 base::FilePath ToFilePath(const UNICODE_STRING* str) {
   return base::FilePath(
@@ -139,15 +131,13 @@
 
 // static
 std::unique_ptr<ModuleWatcher> ModuleWatcher::Create(
-    OnModuleEventCallback callback,
-    bool report_background_loaded_modules) {
+    OnModuleEventCallback callback) {
   {
     base::AutoLock lock(g_module_watcher_lock.Get());
     // If a ModuleWatcher already exists then bail out.
     if (g_module_watcher_instance)
       return nullptr;
-    g_module_watcher_instance =
-        new ModuleWatcher(report_background_loaded_modules);
+    g_module_watcher_instance = new ModuleWatcher();
   }
 
   // Initialization mustn't occur while holding |g_module_watcher_lock|.
@@ -156,8 +146,6 @@
 }
 
 ModuleWatcher::~ModuleWatcher() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
   // Done before acquiring |g_module_watcher_lock|.
   UnregisterDllNotificationCallback();
 
@@ -168,17 +156,10 @@
   g_module_watcher_instance = nullptr;
 }
 
-ModuleWatcher::ModuleWatcher(bool report_background_loaded_modules)
-    : report_background_loaded_modules_(report_background_loaded_modules),
-      background_loaded_dll_count_(0),
-      num_background_loaded_dll_report_(
-          base::RandInt(0, kMaxBackgroundLoadedDllCount)),
-      weak_ptr_factory_(this) {}
+ModuleWatcher::ModuleWatcher() : weak_ptr_factory_(this) {}
 
 // Initializes the ModuleWatcher instance.
 void ModuleWatcher::Initialize(OnModuleEventCallback callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
   callback_ = std::move(callback);
   RegisterDllNotificationCallback();
 
@@ -245,18 +226,13 @@
   }
 }
 
-void ModuleWatcher::DumpOnBackgroundLoadedModule() {
-  if (!report_background_loaded_modules_ ||
-      base::PlatformThread::GetCurrentThreadPriority() !=
-          base::ThreadPriority::BACKGROUND) {
-    return;
-  }
-
-  if (background_loaded_dll_count_++ == num_background_loaded_dll_report_) {
-    // DLL loaded on a thread with background priority. This can cause jank on
-    // the UI thread if it tries to acquire the loader lock.
-    base::debug::DumpWithoutCrashing();
-  }
+// static
+ModuleWatcher::OnModuleEventCallback ModuleWatcher::GetCallbackForContext(
+    void* context) {
+  base::AutoLock lock(g_module_watcher_lock.Get());
+  if (context != g_module_watcher_instance)
+    return OnModuleEventCallback();
+  return g_module_watcher_instance->callback_;
 }
 
 // static
@@ -264,16 +240,7 @@
     unsigned long notification_reason,
     const LDR_DLL_NOTIFICATION_DATA* notification_data,
     void* context) {
-  OnModuleEventCallback callback;
-  {
-    base::AutoLock lock(g_module_watcher_lock.Get());
-    if (context == g_module_watcher_instance) {
-      callback = g_module_watcher_instance->callback_;
-
-      g_module_watcher_instance->DumpOnBackgroundLoadedModule();
-    }
-  }
-
+  auto callback = GetCallbackForContext(context);
   if (!callback)
     return;
 
@@ -295,7 +262,5 @@
 }
 
 void ModuleWatcher::RunCallback(const ModuleEvent& event) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
   callback_.Run(event);
 }
diff --git a/chrome/common/conflicts/module_watcher_win.h b/chrome/common/conflicts/module_watcher_win.h
index fe681833..a4af43b 100644
--- a/chrome/common/conflicts/module_watcher_win.h
+++ b/chrome/common/conflicts/module_watcher_win.h
@@ -11,7 +11,6 @@
 #include "base/files/file_path.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
-#include "base/sequence_checker.h"
 
 class ModuleWatcherTest;
 
@@ -90,9 +89,7 @@
   //
   // Only a single instance of a watcher may exist at any moment. This will
   // return nullptr when trying to create a second watcher.
-  static std::unique_ptr<ModuleWatcher> Create(
-      OnModuleEventCallback callback,
-      bool report_background_loaded_modules);
+  static std::unique_ptr<ModuleWatcher> Create(OnModuleEventCallback callback);
 
   // This can be called on any thread. After destruction the |callback|
   // provided to the constructor will no longer be invoked with module events.
@@ -103,7 +100,7 @@
   friend class ModuleWatcherTest;
 
   // Private to enforce Singleton semantics. See Create above.
-  explicit ModuleWatcher(bool report_background_loaded_modules);
+  ModuleWatcher();
 
   // Initializes the ModuleWatcher instance.
   void Initialize(OnModuleEventCallback callback);
@@ -122,9 +119,9 @@
       scoped_refptr<base::SequencedTaskRunner> task_runner,
       OnModuleEventCallback callback);
 
-  // Dumps the process if executed in a background sequence and
-  // |report_background_loaded_modules_| is true.
-  void DumpOnBackgroundLoadedModule();
+  // Helper function for retrieving the callback associated with a given
+  // LdrNotification context.
+  static OnModuleEventCallback GetCallbackForContext(void* context);
 
   // The loader notification callback. This is actually
   // void CALLBACK LoaderNotificationCallback(
@@ -144,17 +141,6 @@
   // Used by the DllNotification mechanism.
   void* dll_notification_cookie_ = nullptr;
 
-  // Indicates if modules loaded in a background sequence should be reported.
-  const bool report_background_loaded_modules_;
-
-  // The count of DLL that were loaded in a background sequence.
-  int background_loaded_dll_count_;
-
-  // The number of background loaded DLL that will cause a process dump.
-  const int num_background_loaded_dll_report_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-
   base::WeakPtrFactory<ModuleWatcher> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ModuleWatcher);
diff --git a/chrome/common/conflicts/module_watcher_win_unittest.cc b/chrome/common/conflicts/module_watcher_win_unittest.cc
index 98073e2..9efcb783 100644
--- a/chrome/common/conflicts/module_watcher_win_unittest.cc
+++ b/chrome/common/conflicts/module_watcher_win_unittest.cc
@@ -59,8 +59,7 @@
 
   std::unique_ptr<ModuleWatcher> Create() {
     return ModuleWatcher::Create(
-        base::Bind(&ModuleWatcherTest::OnModuleEvent, base::Unretained(this)),
-        /* report_background_loaded_modules = */ false);
+        base::Bind(&ModuleWatcherTest::OnModuleEvent, base::Unretained(this)));
   }
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
diff --git a/chrome/common/conflicts/remote_module_watcher_win.cc b/chrome/common/conflicts/remote_module_watcher_win.cc
index ca22d93..50cb691 100644
--- a/chrome/common/conflicts/remote_module_watcher_win.cc
+++ b/chrome/common/conflicts/remote_module_watcher_win.cc
@@ -63,12 +63,10 @@
   connector->BindInterface(content::mojom::kBrowserServiceName,
                            &module_event_sink_);
 
-  module_watcher_ = ModuleWatcher::Create(
-      base::BindRepeating(
-          &OnModuleEvent, task_runner_,
-          base::BindRepeating(&RemoteModuleWatcher::HandleModuleEvent,
-                              weak_ptr_factory_.GetWeakPtr())),
-      /* report_background_loaded_modules = */ false);
+  module_watcher_ = ModuleWatcher::Create(base::BindRepeating(
+      &OnModuleEvent, task_runner_,
+      base::BindRepeating(&RemoteModuleWatcher::HandleModuleEvent,
+                          weak_ptr_factory_.GetWeakPtr())));
 }
 
 void RemoteModuleWatcher::HandleModuleEvent(
diff --git a/chrome/credential_provider/gaiacp/gaia_resources.grd b/chrome/credential_provider/gaiacp/gaia_resources.grd
index e365506..55ec8341 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources.grd
+++ b/chrome/credential_provider/gaiacp/gaia_resources.grd
@@ -88,11 +88,14 @@
       <message name="IDS_AUTH_FID_DESCRIPTION" desc="">
         Sign in using your work account.
       </message>
+      <message name="IDS_REAUTH_FID_DESCRIPTION" desc="">
+        Your session has expired. Sign in using your work account.
+      </message>
       <message name="IDS_AUTH_FID_PROVIDER_LABEL" desc="">
-        Add person
+        Add work account
       </message>
       <message name="IDS_EXISTING_AUTH_FID_PROVIDER_LABEL" desc="">
-        Sign in using your work account.
+        Sign in using your work account
       </message>
       <message name="IDS_USER_ACCOUNT_COMMENT" desc="">
         User created from a work account
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_PROVIDER_LABEL.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_PROVIDER_LABEL.png.sha1
index b0b9fc5..be20487 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_PROVIDER_LABEL.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_PROVIDER_LABEL.png.sha1
@@ -1 +1 @@
-19a49972aaaec7dd9126cff9eb619384ca97a9bb
\ No newline at end of file
+5882568cc1329600b141a486c6a69f0cd7385945
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
index e0fd529..9e9df61f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
@@ -1 +1 @@
-0290a1c7d0e931d0cc66411b35ccafdddb2a028e
\ No newline at end of file
+2918e662e203c8dd44e70bd311f5677500c0ccf2
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FID_DESCRIPTION.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FID_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..32dd291
--- /dev/null
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FID_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+e04970e79ced97e4e2e96ff0db4bbc0ae86bb34b
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/reauth_credential_base.cc b/chrome/credential_provider/gaiacp/reauth_credential_base.cc
index dcbb34d..6c1ee262 100644
--- a/chrome/credential_provider/gaiacp/reauth_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/reauth_credential_base.cc
@@ -69,6 +69,9 @@
     base::string16 label(
         GetStringResource(IDS_EXISTING_AUTH_FID_PROVIDER_LABEL_BASE));
     return ::SHStrDupW(label.c_str(), value);
+  } else if (field_id == FID_DESCRIPTION) {
+    base::string16 label(GetStringResource(IDS_REAUTH_FID_DESCRIPTION_BASE));
+    return ::SHStrDupW(label.c_str(), value);
   }
 
   return CGaiaCredentialBase::GetStringValueImpl(field_id, value);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 15a67a3..2c707c5 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1871,7 +1871,6 @@
         "../browser/chromeos/login/screens/mock_wrong_hwid_screen.cc",
         "../browser/chromeos/login/screens/mock_wrong_hwid_screen.h",
         "../browser/chromeos/login/screens/network_screen_browsertest.cc",
-        "../browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc",
         "../browser/chromeos/login/screens/update_screen_browsertest.cc",
         "../browser/chromeos/login/screens/user_selection_screen_browsertest.cc",
         "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric.h",
diff --git a/chrome/test/base/v8_unit_test.cc b/chrome/test/base/v8_unit_test.cc
index 18626ac4..cae4e69 100644
--- a/chrome/test/base/v8_unit_test.cc
+++ b/chrome/test/base/v8_unit_test.cc
@@ -38,12 +38,41 @@
 // testDone results.
 bool g_test_result_ok = false;
 
+// Location of src root.
+base::FilePath g_src_root;
+
 // Location of test data (currently test/data/webui).
 base::FilePath g_test_data_directory;
 
 // Location of generated test data (<(PROGRAM_DIR)/test_data).
 base::FilePath g_gen_test_data_directory;
 
+// Finds the file that is indicated by |library_path|, updates |library_path|
+// to be an absolute path to that file, and returns true.
+// If no file is found, returns false.
+bool FindLibraryFile(base::FilePath* library_path) {
+  if (library_path->IsAbsolute()) {
+    // Absolute file. Only one place to look.
+    return base::PathExists(*library_path);
+  }
+
+  // Look for relative file.
+  base::FilePath possible_path = g_src_root.Append(*library_path);
+  if (!base::PathExists(possible_path)) {
+    possible_path = g_gen_test_data_directory.Append(*library_path);
+    if (!base::PathExists(possible_path)) {
+      possible_path = g_test_data_directory.Append(*library_path);
+      if (!base::PathExists(possible_path)) {
+        return false;  // Couldn't find relative file anywhere.
+      }
+    }
+  }
+
+  *library_path = base::MakeAbsoluteFilePath(possible_path);
+  return true;
+}
+
+
 }  // namespace
 
 V8UnitTest::V8UnitTest() : handle_scope_(blink::MainThreadIsolate()) {
@@ -64,14 +93,14 @@
        ++user_libraries_iterator) {
     std::string library_content;
     base::FilePath library_file(*user_libraries_iterator);
-    if (!user_libraries_iterator->IsAbsolute()) {
-      base::FilePath gen_file = g_gen_test_data_directory.Append(library_file);
-      library_file = base::PathExists(gen_file) ?
-          gen_file : g_test_data_directory.Append(*user_libraries_iterator);
+
+    if (!FindLibraryFile(&library_file)) {
+      ADD_FAILURE() << "Couldn't find " << library_file.value();
+      return false;
     }
-    library_file = base::MakeAbsoluteFilePath(library_file);
+
     if (!base::ReadFileToString(library_file, &library_content)) {
-      ADD_FAILURE() << library_file.value();
+      ADD_FAILURE() << "Error reading " << library_file.value();
       return false;
     }
     ExecuteScriptInContext(library_content, library_file.MaybeAsASCII());
@@ -156,21 +185,20 @@
   ASSERT_TRUE(base::PathService::Get(chrome::DIR_GEN_TEST_DATA,
                                      &g_gen_test_data_directory));
 
-  base::FilePath src_root;
-  ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
+  ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &g_src_root));
 
-  AddLibrary(src_root.AppendASCII("chrome")
-                     .AppendASCII("third_party")
-                     .AppendASCII("mock4js")
-                     .AppendASCII("mock4js.js"));
+  AddLibrary(g_src_root.AppendASCII("chrome")
+                       .AppendASCII("third_party")
+                       .AppendASCII("mock4js")
+                       .AppendASCII("mock4js.js"));
 
-  AddLibrary(src_root.AppendASCII("third_party")
-                     .AppendASCII("chaijs")
-                     .AppendASCII("chai.js"));
+  AddLibrary(g_src_root.AppendASCII("third_party")
+                       .AppendASCII("chaijs")
+                       .AppendASCII("chai.js"));
 
-  AddLibrary(src_root.AppendASCII("third_party")
-                     .AppendASCII("accessibility-audit")
-                     .AppendASCII("axs_testing.js"));
+  AddLibrary(g_src_root.AppendASCII("third_party")
+                       .AppendASCII("accessibility-audit")
+                       .AppendASCII("axs_testing.js"));
 
   AddLibrary(g_test_data_directory.AppendASCII("test_api.js"));
 }
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
index cd6eade..0000693 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol_unittest.cc
@@ -643,382 +643,227 @@
     int expected_duration;
     DataReductionProxyBypassType expected_bypass_type;
   } tests[] = {
-    // Valid data reduction proxy response with no bypass message.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      false,
-      false,
-      0u,
-      true,
-      -1,
-      BYPASS_EVENT_TYPE_MAX,
-    },
-    // Response error does not result in bypass.
-    { "GET",
-      "Not an HTTP response",
-      false,
-      true,
-      0u,
-      true,
-      -1,
-      BYPASS_EVENT_TYPE_MAX,
-    },
-    // Valid data reduction proxy response with chained via header,
-    // no bypass message.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n",
-      false,
-      false,
-      0u,
-      true,
-      -1,
-      BYPASS_EVENT_TYPE_MAX
-    },
-    // Valid data reduction proxy response with a bypass message.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Valid data reduction proxy response with a bypass message.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=1\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      1,
-      BYPASS_EVENT_TYPE_SHORT
-    },
-    // Same as above with the OPTIONS method, which is idempotent.
-    { "OPTIONS",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Same as above with the HEAD method, which is idempotent.
-    { "HEAD",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      false,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Same as above with the PUT method, which is idempotent.
-    { "PUT",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Same as above with the DELETE method, which is idempotent.
-    { "DELETE",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Same as above with the TRACE method, which is idempotent.
-    { "TRACE",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // 500 responses should be bypassed.
-    { "GET",
-      "HTTP/1.1 500 Internal Server Error\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR
-    },
-    // 502 responses should be bypassed.
-    { "GET",
-      "HTTP/1.1 502 Internal Server Error\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY
-    },
-    // 503 responses should be bypassed.
-    { "GET",
-      "HTTP/1.1 503 Internal Server Error\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE
-    },
-    // Invalid data reduction proxy 4xx response. Missing Via header.
-    { "GET",
-      "HTTP/1.1 404 Not Found\r\n"
-      "Server: proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX
-    },
-    // Invalid data reduction proxy response. Missing Via header.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER
-    },
-    // Invalid data reduction proxy response. Wrong Via header.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Via: 1.0 some-other-proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER
-    },
-    // Valid data reduction proxy response. 304 missing Via header.
-    { "GET",
-      "HTTP/1.1 304 Not Modified\r\n"
-      "Server: proxy\r\n\r\n",
-      false,
-      false,
-      0u,
-      false,
-      0,
-      BYPASS_EVENT_TYPE_MAX
-    },
-    // Valid data reduction proxy response with a bypass message. It will
-    // not be retried because the request is non-idempotent.
-    { "POST",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=0\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      false,
-      false,
-      1u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_MEDIUM
-    },
-    // Valid data reduction proxy response with block message. Both proxies
-    // should be on the retry list when it completes.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block=1\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      2u,
-      true,
-      1,
-      BYPASS_EVENT_TYPE_SHORT
-    },
-    // Valid data reduction proxy response with a block-once message. It will be
-    // retried, and there will be no proxies on the retry list since block-once
-    // only affects the current request.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Same as above with the OPTIONS method, which is idempotent.
-    { "OPTIONS",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Same as above with the HEAD method, which is idempotent.
-    { "HEAD",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      false,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Same as above with the PUT method, which is idempotent.
-    { "PUT",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Same as above with the DELETE method, which is idempotent.
-    { "DELETE",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Same as above with the TRACE method, which is idempotent.
-    { "TRACE",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Valid Data Reduction Proxy response with a block-once message. It will
-    // be retried because block-once indicates that request did not reach the
-    // origin and client should retry. Only current request is retried direct,
-    // so there should be no proxies on the retry list.
-    { "POST",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      0u,
-      true,
-      0,
-      BYPASS_EVENT_TYPE_CURRENT
-    },
-    // Valid Data Reduction Proxy response with a bypass message. It will
-    // not be retried because the request is non-idempotent. Both proxies
-    // should be on the retry list for 1 second.
-    { "POST",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block=1\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      false,
-      false,
-      2u,
-      true,
-      1,
-      BYPASS_EVENT_TYPE_SHORT
-    },
-    // Valid data reduction proxy response with block and block-once messages.
-    // The block message will override the block-once message, so both proxies
-    // should be on the retry list when it completes.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: block=1, block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      2u,
-      true,
-      1,
-      BYPASS_EVENT_TYPE_SHORT
-    },
-    // Valid data reduction proxy response with bypass and block-once messages.
-    // The bypass message will override the block-once message, so one proxy
-    // should be on the retry list when it completes.
-    { "GET",
-      "HTTP/1.1 200 OK\r\n"
-      "Server: proxy\r\n"
-      "Chrome-Proxy: bypass=1, block-once\r\n"
-      "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-      true,
-      false,
-      1u,
-      true,
-      1,
-      BYPASS_EVENT_TYPE_SHORT
-    },
+      // Valid data reduction proxy response with no bypass message.
+      {
+          "GET",
+          "HTTP/1.1 200 OK\r\n"
+          "Server: proxy\r\n"
+          "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+          false,
+          false,
+          0u,
+          true,
+          -1,
+          BYPASS_EVENT_TYPE_MAX,
+      },
+      // Response error does not result in bypass.
+      {
+          "GET",
+          "Not an HTTP response",
+          false,
+          true,
+          0u,
+          true,
+          -1,
+          BYPASS_EVENT_TYPE_MAX,
+      },
+      // Valid data reduction proxy response with chained via header,
+      // no bypass message.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy, 1.0 some-other-proxy\r\n\r\n",
+       false, false, 0u, true, -1, BYPASS_EVENT_TYPE_MAX},
+      // Valid data reduction proxy response with a bypass message.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Valid data reduction proxy response with a bypass message.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=1\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 1, BYPASS_EVENT_TYPE_SHORT},
+      // Same as above with the OPTIONS method, which is idempotent.
+      {"OPTIONS",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Same as above with the HEAD method, which is idempotent.
+      {"HEAD",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, false, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Same as above with the PUT method, which is idempotent.
+      {"PUT",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Same as above with the DELETE method, which is idempotent.
+      {"DELETE",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Same as above with the TRACE method, which is idempotent.
+      {"TRACE",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // 500 responses should be bypassed.
+      {"GET",
+       "HTTP/1.1 500 Internal Server Error\r\n"
+       "Server: proxy\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0,
+       BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR},
+      // 502 responses should be bypassed.
+      {"GET",
+       "HTTP/1.1 502 Internal Server Error\r\n"
+       "Server: proxy\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
+      // 503 responses should be bypassed.
+      {"GET",
+       "HTTP/1.1 503 Internal Server Error\r\n"
+       "Server: proxy\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 0,
+       BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE},
+      // Invalid data reduction proxy 4xx response. Missing Via header.
+      {"GET",
+       "HTTP/1.1 404 Not Found\r\n"
+       "Server: proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_4XX},
+      // Invalid data reduction proxy response. Missing Via header.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER},
+      // Invalid data reduction proxy response. Wrong Via header.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Via: 1.0 some-other-proxy\r\n\r\n",
+       true, false, 1u, true, 0, BYPASS_EVENT_TYPE_MISSING_VIA_HEADER_OTHER},
+      // Valid data reduction proxy response. 304 missing Via header.
+      {"GET",
+       "HTTP/1.1 304 Not Modified\r\n"
+       "Server: proxy\r\n\r\n",
+       false, false, 0u, false, 0, BYPASS_EVENT_TYPE_MAX},
+      // Valid data reduction proxy response with a bypass message. It will
+      // not be retried because the request is non-idempotent.
+      {"POST",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=0\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       false, false, 1u, true, 0, BYPASS_EVENT_TYPE_MEDIUM},
+      // Valid data reduction proxy response with block message. Both proxies
+      // should be on the retry list when it completes.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block=1\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
+      // Valid data reduction proxy response with a block-once message. It will
+      // be
+      // retried, and there will be no proxies on the retry list since
+      // block-once
+      // only affects the current request.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Same as above with the OPTIONS method, which is idempotent.
+      {"OPTIONS",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Same as above with the HEAD method, which is idempotent.
+      {"HEAD",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, false, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Same as above with the PUT method, which is idempotent.
+      {"PUT",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Same as above with the DELETE method, which is idempotent.
+      {"DELETE",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Same as above with the TRACE method, which is idempotent.
+      {"TRACE",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Valid Data Reduction Proxy response with a block-once message. It will
+      // be retried because block-once indicates that request did not reach the
+      // origin and client should retry. Only current request is retried direct,
+      // so there should be no proxies on the retry list.
+      {"POST",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 0u, true, 0, BYPASS_EVENT_TYPE_CURRENT},
+      // Valid Data Reduction Proxy response with a bypass message. It will
+      // not be retried because the request is non-idempotent. Both proxies
+      // should be on the retry list for 1 second.
+      {"POST",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block=1\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       false, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
+      // Valid data reduction proxy response with block and block-once messages.
+      // The block message will override the block-once message, so both proxies
+      // should be on the retry list when it completes.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: block=1, block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 2u, true, 1, BYPASS_EVENT_TYPE_SHORT},
+      // Valid data reduction proxy response with bypass and block-once
+      // messages.
+      // The bypass message will override the block-once message, so one proxy
+      // should be on the retry list when it completes.
+      {"GET",
+       "HTTP/1.1 200 OK\r\n"
+       "Server: proxy\r\n"
+       "Chrome-Proxy: bypass=1, block-once\r\n"
+       "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
+       true, false, 1u, true, 1, BYPASS_EVENT_TYPE_SHORT},
   };
   test_context_->config()->test_params()->UseNonSecureProxiesForHttp();
   std::string primary = test_context_->config()
@@ -1172,14 +1017,14 @@
       {"HTTP/1.1 502 Bad Gateway\r\n"
        "Server: proxy\r\n"
        "Via: 1.1 Chrome-Compression-Proxy\r\n\r\n",
-       true, true, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
+       true, false, BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY},
       {"HTTP/1.1 200 OK\r\n"
        "Server: proxy\r\n"
        "Chrome-Proxy: block=0\r\n\r\n",
        false, false, BYPASS_EVENT_TYPE_MAX},
       {"HTTP/1.1 502 Bad Gateway\r\n"
        "Server: proxy\r\n\r\n",
-       false, false, BYPASS_EVENT_TYPE_MAX},
+       true, false, BYPASS_EVENT_TYPE_MAX},
   };
 
   for (const auto& test : test_cases) {
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
index bdb54b35..4f929ba 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats_unittest.cc
@@ -473,6 +473,22 @@
       histogram_tester, "DataReductionProxy.BypassedBytes.ProxyOverridden");
 }
 
+TEST_F(DataReductionProxyBypassStatsEndToEndTest, NoChromeProxy502AreBypassed) {
+  InitializeContext();
+  base::HistogramTester histogram_tester;
+  CreateAndExecuteRequest(GURL("http://foo.com"), net::LOAD_NORMAL, net::OK,
+                          "HTTP/1.1 502 Bad Gateway\r\n"
+                          "Via: 1.1 Chrome-Compression-Proxy\r\n"
+                          "Chrome-Proxy: block-once\r\n\r\n",
+                          kErrorBody.c_str(), "HTTP/1.1 200 OK\r\n\r\n",
+                          kBody.c_str());
+
+  histogram_tester.ExpectUniqueSample(
+      "DataReductionProxy.BypassedBytes.Current", kBody.size(), 1);
+  ExpectOtherBypassedBytesHistogramsEmpty(
+      histogram_tester, "DataReductionProxy.BypassedBytes.Current");
+}
+
 TEST_F(DataReductionProxyBypassStatsEndToEndTest, BypassedBytesCurrent) {
   InitializeContext();
   struct TestCase {
@@ -610,9 +626,6 @@
     { "DataReductionProxy.BypassedBytes.Status500HttpInternalServerError",
       "HTTP/1.1 500 Internal Server Error\r\n\r\n",
     },
-    { "DataReductionProxy.BypassedBytes.Status502HttpBadGateway",
-      "HTTP/1.1 502 Bad Gateway\r\n\r\n",
-    },
     { "DataReductionProxy.BypassedBytes.Status503HttpServiceUnavailable",
       "HTTP/1.1 503 Service Unavailable\r\n\r\n",
     },
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
index 1834dee..3c234c3 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.cc
@@ -46,5 +46,10 @@
     "DataReductionProxyEnabledWithNetworkService",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables block-once action when 502 is received with no Chrome-Proxy header.
+const base::Feature kDataReductionProxyBlockOnceOnBadGatewayResponse{
+    "DataReductionProxyBlockOnceOnBadGatewayResponse",
+    base::FEATURE_ENABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
index 3347846..65802a2 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_features.h
@@ -17,6 +17,7 @@
 extern const base::Feature kDataSaverSiteBreakdownUsingPageLoadMetrics;
 extern const base::Feature kDataReductionProxyEnabledWithNetworkService;
 extern const base::Feature kDataSaverUseOnDeviceSafeBrowsing;
+extern const base::Feature kDataReductionProxyBlockOnceOnBadGatewayResponse;
 
 }  // namespace features
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
index 2764f52..305286e 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_headers.cc
@@ -418,8 +418,16 @@
   // Fall back if a 500, 502 or 503 is returned.
   if (headers.response_code() == net::HTTP_INTERNAL_SERVER_ERROR)
     return BYPASS_EVENT_TYPE_STATUS_500_HTTP_INTERNAL_SERVER_ERROR;
-  if (headers.response_code() == net::HTTP_BAD_GATEWAY)
+  if (headers.response_code() == net::HTTP_BAD_GATEWAY) {
+    if (base::FeatureList::IsEnabled(
+            features::kDataReductionProxyBlockOnceOnBadGatewayResponse)) {
+      data_reduction_proxy_info->bypass_all = true;
+      data_reduction_proxy_info->mark_proxies_as_bad = false;
+      data_reduction_proxy_info->bypass_duration = base::TimeDelta();
+      data_reduction_proxy_info->bypass_action = BYPASS_ACTION_TYPE_BLOCK_ONCE;
+    }
     return BYPASS_EVENT_TYPE_STATUS_502_HTTP_BAD_GATEWAY;
+  }
   if (headers.response_code() == net::HTTP_SERVICE_UNAVAILABLE)
     return BYPASS_EVENT_TYPE_STATUS_503_HTTP_SERVICE_UNAVAILABLE;
   // TODO(kundaji): Bypass if Proxy-Authenticate header value cannot be
diff --git a/components/sync_sessions/favicon_cache.cc b/components/sync_sessions/favicon_cache.cc
index 2fed6405..ca58d61f 100644
--- a/components/sync_sessions/favicon_cache.cc
+++ b/components/sync_sessions/favicon_cache.cc
@@ -226,7 +226,6 @@
                            history::HistoryService* history_service,
                            int max_sync_favicon_limit)
     : favicon_service_(favicon_service),
-      history_service_(history_service),
       max_sync_favicon_limit_(max_sync_favicon_limit),
       history_service_observer_(this),
       weak_ptr_factory_(this) {
@@ -237,16 +236,6 @@
 
 FaviconCache::~FaviconCache() {}
 
-void FaviconCache::WaitUntilReadyToSync(base::OnceClosure done) {
-  if (history_service_->backend_loaded()) {
-    std::move(done).Run();
-  } else {
-    // Wait until HistoryService's backend loads, reported via
-    // OnHistoryServiceLoaded().
-    wait_until_ready_to_sync_cb_ = std::move(done);
-  }
-}
-
 syncer::SyncMergeResult FaviconCache::MergeDataAndStartSyncing(
     syncer::ModelType type,
     const syncer::SyncDataList& initial_sync_data,
@@ -1027,10 +1016,4 @@
   }
 }
 
-void FaviconCache::OnHistoryServiceLoaded(
-    history::HistoryService* history_service) {
-  if (wait_until_ready_to_sync_cb_)
-    std::move(wait_until_ready_to_sync_cb_).Run();
-}
-
 }  // namespace sync_sessions
diff --git a/components/sync_sessions/favicon_cache.h b/components/sync_sessions/favicon_cache.h
index 03aca1b0..08946fb 100644
--- a/components/sync_sessions/favicon_cache.h
+++ b/components/sync_sessions/favicon_cache.h
@@ -66,7 +66,6 @@
   ~FaviconCache() override;
 
   // SyncableService implementation.
-  void WaitUntilReadyToSync(base::OnceClosure done) override;
   syncer::SyncMergeResult MergeDataAndStartSyncing(
       syncer::ModelType type,
       const syncer::SyncDataList& initial_sync_data,
@@ -199,11 +198,8 @@
   // history::HistoryServiceObserver:
   void OnURLsDeleted(history::HistoryService* history_service,
                      const history::DeletionInfo& deletion_info) override;
-  void OnHistoryServiceLoaded(
-      history::HistoryService* history_service) override;
 
-  favicon::FaviconService* const favicon_service_;
-  history::HistoryService* const history_service_;
+  favicon::FaviconService* favicon_service_;
 
   // Task tracker for loading favicons.
   base::CancelableTaskTracker cancelable_task_tracker_;
@@ -230,8 +226,6 @@
   // Maximum number of favicons to sync. 0 means no limit.
   const size_t max_sync_favicon_limit_;
 
-  base::OnceClosure wait_until_ready_to_sync_cb_;
-
   ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
       history_service_observer_;
 
diff --git a/components/ui_devtools/ui_element.cc b/components/ui_devtools/ui_element.cc
index a760ee02..20a3489 100644
--- a/components/ui_devtools/ui_element.cc
+++ b/components/ui_devtools/ui_element.cc
@@ -76,19 +76,23 @@
   children_.erase(iter);
 }
 
-void UIElement::ReorderChild(UIElement* child, int new_index) {
-  auto iter = std::find(children_.begin(), children_.end(), child);
-  DCHECK(iter != children_.end());
+void UIElement::ReorderChild(UIElement* child, int index) {
+  auto i = std::find(children_.begin(), children_.end(), child);
+  DCHECK(i != children_.end());
+  DCHECK_GE(index, 0);
+  DCHECK_LT(static_cast<size_t>(index), children_.size());
 
-  // Don't re-order if the new position is the same as the old position.
-  if (std::distance(children_.begin(), iter) == new_index)
+  // If |child| is already at the desired position, there's nothing to do.
+  const auto pos = std::next(children_.begin(), index);
+  if (i == pos)
     return;
-  children_.erase(iter);
 
-  // Move child to new position |new_index| in vector |children_|.
-  new_index = std::min(static_cast<int>(children_.size()) - 1, new_index);
-  iter = children_.begin() + new_index;
-  children_.insert(iter, child);
+  // Rotate |child| to be at the desired position.
+  if (pos < i)
+    std::rotate(pos, i, std::next(i));
+  else
+    std::rotate(i, std::next(i), std::next(pos));
+
   delegate()->OnUIElementReordered(child->parent(), child);
 }
 
diff --git a/components/ui_devtools/ui_element.h b/components/ui_devtools/ui_element.h
index fb3737d5..217e406 100644
--- a/components/ui_devtools/ui_element.h
+++ b/components/ui_devtools/ui_element.h
@@ -65,8 +65,8 @@
   // OnUIElementRemoved(), which destroys the DOM node for |child|.
   void RemoveChild(UIElement* child, bool notify_delegate = true);
 
-  // Move |child| to position new_index in |children_|.
-  void ReorderChild(UIElement* child, int new_index);
+  // Moves |child| to position |index| in |children_|.
+  void ReorderChild(UIElement* child, int index);
 
   template <class T>
   int FindUIElementIdForBackendElement(T* element) const;
diff --git a/components/viz/common/frame_sinks/begin_frame_source.cc b/components/viz/common/frame_sinks/begin_frame_source.cc
index 65b081cb..769cd717 100644
--- a/components/viz/common/frame_sinks/begin_frame_source.cc
+++ b/components/viz/common/frame_sinks/begin_frame_source.cc
@@ -41,8 +41,29 @@
   observer->OnBeginFrame(args);
 }
 
+// Checks |args| for continuity with our last args.  It is possible that the
+// source in which |args| originate changes, or that our hookup to this source
+// changes, so we have to check for continuity.  See also
+// https://crbug.com/690127 for what may happen without this check.
+bool CheckBeginFrameContinuity(BeginFrameObserver* observer,
+                               const BeginFrameArgs& args) {
+  const BeginFrameArgs& last_args = observer->LastUsedBeginFrameArgs();
+  if (!last_args.IsValid() || (args.frame_time > last_args.frame_time)) {
+    DCHECK((args.source_id != last_args.source_id) ||
+           (args.sequence_number > last_args.sequence_number))
+        << "current " << args.AsValue()->ToString() << ", last "
+        << last_args.AsValue()->ToString();
+    return true;
+  }
+  return false;
+}
 }  // namespace
 
+// BeginFrameObserver -----------------------------------------------------
+bool BeginFrameObserver::IsRoot() const {
+  return false;
+}
+
 // BeginFrameObserverBase -------------------------------------------------
 BeginFrameObserverBase::BeginFrameObserverBase() = default;
 
@@ -385,18 +406,24 @@
 
   last_begin_frame_args_ = args;
   base::flat_set<BeginFrameObserver*> observers(observers_);
+
+  // Process non-root observers.
+  // TODO(ericrk): Remove root/non-root handling once a better workaround
+  // exists. https://crbug.com/947717
   for (auto* obs : observers) {
-    // It is possible that the source in which |args| originate changes, or that
-    // our hookup to this source changes, so we have to check for continuity.
-    // See also https://crbug.com/690127 for what may happen without this check.
-    const BeginFrameArgs& last_args = obs->LastUsedBeginFrameArgs();
-    if (!last_args.IsValid() || (args.frame_time > last_args.frame_time)) {
-      DCHECK((args.source_id != last_args.source_id) ||
-             (args.sequence_number > last_args.sequence_number))
-          << "current " << args.AsValue()->ToString() << ", last "
-          << last_args.AsValue()->ToString();
-      FilterAndIssueBeginFrame(obs, args);
-    }
+    if (obs->IsRoot())
+      continue;
+    if (!CheckBeginFrameContinuity(obs, args))
+      continue;
+    FilterAndIssueBeginFrame(obs, args);
+  }
+  // Process root observers.
+  for (auto* obs : observers) {
+    if (!obs->IsRoot())
+      continue;
+    if (!CheckBeginFrameContinuity(obs, args))
+      continue;
+    FilterAndIssueBeginFrame(obs, args);
   }
 }
 
@@ -404,17 +431,9 @@
     BeginFrameObserver* obs) {
   if (!last_begin_frame_args_.IsValid())
     return BeginFrameArgs();
-
-  const BeginFrameArgs& last_args = obs->LastUsedBeginFrameArgs();
-  if (last_args.IsValid() &&
-      last_begin_frame_args_.frame_time <= last_args.frame_time) {
+  if (!CheckBeginFrameContinuity(obs, last_begin_frame_args_))
     return BeginFrameArgs();
-  }
 
-  DCHECK((last_begin_frame_args_.source_id != last_args.source_id) ||
-         (last_begin_frame_args_.sequence_number > last_args.sequence_number))
-      << "current " << last_begin_frame_args_.AsValue()->ToString() << ", last "
-      << last_args.AsValue()->ToString();
   BeginFrameArgs missed_args = last_begin_frame_args_;
   missed_args.type = BeginFrameArgs::MISSED;
   return missed_args;
diff --git a/components/viz/common/frame_sinks/begin_frame_source.h b/components/viz/common/frame_sinks/begin_frame_source.h
index ec70441..a718369 100644
--- a/components/viz/common/frame_sinks/begin_frame_source.h
+++ b/components/viz/common/frame_sinks/begin_frame_source.h
@@ -62,6 +62,13 @@
 
   // Whether the observer also wants to receive animate_only BeginFrames.
   virtual bool WantsAnimateOnlyBeginFrames() const = 0;
+
+  // Indicates whether this observer is the root frame sink. This helps in
+  // a workaround for input jank, allowing us to deliver BeginFrames to the
+  // root last, avoiding a race.
+  // TODO(ericrk): Remove this once we have a longer-term fix.
+  // https://crbug.com/947717
+  virtual bool IsRoot() const;
 };
 
 // Simple base class which implements a BeginFrameObserver which checks the
diff --git a/components/viz/common/frame_sinks/begin_frame_source_unittest.cc b/components/viz/common/frame_sinks/begin_frame_source_unittest.cc
index 6d5633a..65fc6fd 100644
--- a/components/viz/common/frame_sinks/begin_frame_source_unittest.cc
+++ b/components/viz/common/frame_sinks/begin_frame_source_unittest.cc
@@ -618,5 +618,52 @@
   source_->AddObserver(obs_.get());
 }
 
+// Tests that an observer which returns true from IsRoot is notified after
+// observers which return false.
+TEST_F(ExternalBeginFrameSourceTest, RootsNotifiedLast) {
+  using ::testing::InSequence;
+
+  NiceMock<MockBeginFrameObserver> obs1, obs2;
+  source_->AddObserver(&obs1);
+  source_->AddObserver(&obs2);
+
+  {
+    BeginFrameArgs args = CreateBeginFrameArgsForTesting(
+        BEGINFRAME_FROM_HERE, 0, 1, 10000, 10100, 100);
+    // Set obs1 to root, obs2 to child.
+    EXPECT_CALL(obs1, IsRoot()).WillRepeatedly(::testing::Return(true));
+    EXPECT_CALL(obs2, IsRoot()).WillRepeatedly(::testing::Return(false));
+    {
+      // Ensure that OnBeginFrame delivers the calls in the right order.
+      InSequence s;
+      EXPECT_CALL(obs2, OnBeginFrame(args))
+          .WillOnce(::testing::SaveArg<0>(&(obs2.last_begin_frame_args)));
+      EXPECT_CALL(obs1, OnBeginFrame(args))
+          .WillOnce(::testing::SaveArg<0>(&(obs1.last_begin_frame_args)));
+      source_->OnBeginFrame(args);
+    }
+  }
+
+  {
+    BeginFrameArgs args = CreateBeginFrameArgsForTesting(
+        BEGINFRAME_FROM_HERE, 0, 2, 10001, 10101, 100);
+    // Set obs2 to root, obs1 to child.
+    EXPECT_CALL(obs1, IsRoot()).WillRepeatedly(::testing::Return(false));
+    EXPECT_CALL(obs2, IsRoot()).WillRepeatedly(::testing::Return(true));
+    {
+      // Ensure that OnBeginFrame delivers the calls in the right order.
+      InSequence s;
+      EXPECT_CALL(obs1, OnBeginFrame(args))
+          .WillOnce(::testing::SaveArg<0>(&(obs1.last_begin_frame_args)));
+      EXPECT_CALL(obs2, OnBeginFrame(args))
+          .WillOnce(::testing::SaveArg<0>(&(obs2.last_begin_frame_args)));
+      source_->OnBeginFrame(args);
+    }
+  }
+
+  source_->RemoveObserver(&obs1);
+  source_->RemoveObserver(&obs2);
+}
+
 }  // namespace
 }  // namespace viz
diff --git a/components/viz/common/quads/shared_quad_state.h b/components/viz/common/quads/shared_quad_state.h
index c9043f50..c339d46c 100644
--- a/components/viz/common/quads/shared_quad_state.h
+++ b/components/viz/common/quads/shared_quad_state.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/optional.h"
 #include "components/viz/common/viz_common_export.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
 #include "ui/gfx/geometry/rect.h"
@@ -76,6 +77,11 @@
   // merge quads for a surface into their target render pass. It is a
   // performance optimization by avoiding render passes as much as possible.
   bool is_fast_rounded_corner = false;
+  // This is for underlay optimization and used only in the SurfaceAggregator
+  // and the OverlayProcessor. This damage rect contains union of damage from
+  // occluding surfaces and is only for quads that are the only quad in
+  // their surface. SetAll() doesn't update this data.
+  base::Optional<gfx::Rect> occluding_damage_rect;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display/dc_layer_overlay.cc b/components/viz/service/display/dc_layer_overlay.cc
index 9fe6799b..429590b 100644
--- a/components/viz/service/display/dc_layer_overlay.cc
+++ b/components/viz/service/display/dc_layer_overlay.cc
@@ -330,8 +330,8 @@
         gfx::ToEnclosingRect(ClippedQuadRectangle(solid_quad));
     // Propagate punch through rect as damage up the stack of render passes.
     // TODO(sunnyps): We should avoid this extra damage if we knew that the
-    // video (in child render surface) was the only thing damaging this render
-    // surface.
+    // video (in child render surface) was the only thing damaging this
+    // render surface.
     damage_rect->Union(clipped_quad_rect);
 
     // Add transformed info to list in case this renderpass is included in
@@ -438,8 +438,7 @@
     } else {
       ProcessForUnderlay(display_rect, render_pass,
                          quad_rectangle_in_target_space, occlusion_bounding_box,
-                         it, is_root, has_occluding_surface_damage, damage_rect,
-                         &this_frame_underlay_rect,
+                         it, is_root, damage_rect, &this_frame_underlay_rect,
                          &this_frame_underlay_occlusion, &dc_layer);
     }
 
@@ -510,7 +509,6 @@
     const gfx::RectF& occlusion_bounding_box,
     const QuadList::Iterator& it,
     bool is_root,
-    bool has_occluding_surface_damage,
     gfx::Rect* damage_rect,
     gfx::Rect* this_frame_underlay_rect,
     gfx::Rect* this_frame_underlay_occlusion,
@@ -564,12 +562,12 @@
 
   if (is_root && current_frame_processed_overlay_count_ == 0 &&
       is_axis_aligned && is_opaque && !underlay_rect_changed &&
-      !display_rect_changed) {
+      !display_rect_changed &&
+      shared_quad_state->occluding_damage_rect.has_value()) {
     // If this underlay rect is the same as for last frame, subtract its area
     // from the damage of the main surface, as the cleared area was already
     // cleared last frame. Add back the damage from the occluded area for this
-    // and last frame, as that may have changed.
-    gfx::Rect occluding_damage_rect = *damage_rect;
+    // frame.
     damage_rect->Subtract(quad_rectangle);
 
     // If none of the quads on top give any damage, we can skip compositing
@@ -578,19 +576,9 @@
     // compositor will be empty. If the incoming damage rect is bigger than the
     // video quad, we don't have an oppertunity for power optimization even if
     // no damage on top. The output damage rect will not be empty in this case.
-    if (has_occluding_surface_damage) {
-      gfx::Rect occlusion = gfx::ToEnclosingRect(occlusion_bounding_box);
-      occlusion.Union(previous_frame_underlay_occlusion_);
-
-      occluding_damage_rect.Intersect(quad_rectangle);
-      occluding_damage_rect.Intersect(occlusion);
-
-      damage_rect->Union(occluding_damage_rect);
-    }
+    damage_rect->Union(shared_quad_state->occluding_damage_rect.value());
   } else {
     // Entire replacement quad must be redrawn.
-    // TODO(sunnyps): We should avoid this extra damage if we knew that the
-    // video was the only thing damaging this render surface.
     damage_rect->Union(quad_rectangle);
   }
 
diff --git a/components/viz/service/display/dc_layer_overlay.h b/components/viz/service/display/dc_layer_overlay.h
index 0fe4e8d..919f112 100644
--- a/components/viz/service/display/dc_layer_overlay.h
+++ b/components/viz/service/display/dc_layer_overlay.h
@@ -108,7 +108,6 @@
                           const gfx::RectF& occlusion_bounding_box,
                           const QuadList::Iterator& it,
                           bool is_root,
-                          bool has_occluding_surface_damage,
                           gfx::Rect* damage_rect,
                           gfx::Rect* this_frame_underlay_rect,
                           gfx::Rect* this_frame_underlay_occlusion,
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 02fe84d..9d50f66c 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -340,10 +340,15 @@
 
   // TODO(jbauman): Outputting an incomplete quad list doesn't work when using
   // overlays.
-  bool output_partial_list = renderer_->use_partial_swap() &&
-                             !output_surface_->GetOverlayCandidateValidator();
+  OverlayCandidateValidator* overlay_validator =
+      output_surface_->GetOverlayCandidateValidator();
+  bool output_partial_list =
+      renderer_->use_partial_swap() && !overlay_validator;
+  bool needs_surface_occluding_damage_rect =
+      overlay_validator && overlay_validator->AllowDCLayerOverlays();
   aggregator_.reset(new SurfaceAggregator(
-      surface_manager_, resource_provider_.get(), output_partial_list));
+      surface_manager_, resource_provider_.get(), output_partial_list,
+      needs_surface_occluding_damage_rect));
   aggregator_->set_output_is_secure(output_is_secure_);
   aggregator_->SetOutputColorSpace(blending_color_space_, device_color_space_);
 }
diff --git a/components/viz/service/display/overlay_unittest.cc b/components/viz/service/display/overlay_unittest.cc
index 17fcaf2..c54847f 100644
--- a/components/viz/service/display/overlay_unittest.cc
+++ b/components/viz/service/display/overlay_unittest.cc
@@ -2485,6 +2485,8 @@
   feature_list.InitAndEnableFeature(features::kDirectCompositionUnderlays);
   {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
+    SharedQuadState* first_shared_state = pass->shared_quad_state_list.back();
+    first_shared_state->occluding_damage_rect = gfx::Rect(1, 1, 10, 10);
     CreateOpaqueQuadAt(resource_provider_.get(),
                        pass->shared_quad_state_list.back(), pass.get(),
                        gfx::Rect(0, 3, 100, 100), SK_ColorWHITE);
@@ -2492,6 +2494,9 @@
         resource_provider_.get(), child_resource_provider_.get(),
         child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
 
+    SharedQuadState* second_shared_state =
+        pass->CreateAndAppendSharedQuadState();
+    second_shared_state->occluding_damage_rect = gfx::Rect(1, 1, 10, 10);
     auto* second_video_quad = CreateFullscreenCandidateYUVVideoQuad(
         resource_provider_.get(), child_resource_provider_.get(),
         child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
@@ -2522,6 +2527,8 @@
   }
   {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
+    SharedQuadState* first_shared_state = pass->shared_quad_state_list.back();
+    first_shared_state->occluding_damage_rect = gfx::Rect(1, 1, 10, 10);
     CreateOpaqueQuadAt(resource_provider_.get(),
                        pass->shared_quad_state_list.back(), pass.get(),
                        gfx::Rect(3, 3, 100, 100), SK_ColorWHITE);
@@ -2529,6 +2536,9 @@
         resource_provider_.get(), child_resource_provider_.get(),
         child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
 
+    SharedQuadState* second_shared_state =
+        pass->CreateAndAppendSharedQuadState();
+    second_shared_state->occluding_damage_rect = gfx::Rect(1, 1, 10, 10);
     auto* second_video_quad = CreateFullscreenCandidateYUVVideoQuad(
         resource_provider_.get(), child_resource_provider_.get(),
         child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
@@ -2570,6 +2580,8 @@
   feature_list.InitAndEnableFeature(features::kDirectCompositionUnderlays);
   {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
+    SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
+    shared_quad_state->occluding_damage_rect = gfx::Rect(210, 210, 20, 20);
     // Occluding quad fully contained in video rect.
     CreateOpaqueQuadAt(resource_provider_.get(),
                        pass->shared_quad_state_list.back(), pass.get(),
@@ -2606,6 +2618,8 @@
   }
   {
     std::unique_ptr<RenderPass> pass = CreateRenderPass();
+    SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
+    shared_quad_state->occluding_damage_rect = gfx::Rect(210, 210, 20, 20);
     // Occluding quad fully contained in video rect.
     CreateOpaqueQuadAt(resource_provider_.get(),
                        pass->shared_quad_state_list.back(), pass.get(),
@@ -3005,9 +3019,9 @@
 
     // The quad on top does not give damage on the third frame
     if (i == 2)
-      shared_state_on_top->has_surface_damage = false;
+      shared_state->occluding_damage_rect = gfx::Rect();
     else
-      shared_state_on_top->has_surface_damage = true;
+      shared_state->occluding_damage_rect = kOverlayBottomRightRect;
 
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 32821fdd..0237d951 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -78,11 +78,13 @@
 
 SurfaceAggregator::SurfaceAggregator(SurfaceManager* manager,
                                      DisplayResourceProvider* provider,
-                                     bool aggregate_only_damaged)
+                                     bool aggregate_only_damaged,
+                                     bool needs_surface_occluding_damage_rect)
     : manager_(manager),
       provider_(provider),
       next_render_pass_id_(1),
       aggregate_only_damaged_(aggregate_only_damaged),
+      needs_surface_occluding_damage_rect_(needs_surface_occluding_damage_rect),
       weak_factory_(this) {
   DCHECK(manager_);
 }
@@ -185,6 +187,102 @@
   return full_rect;
 }
 
+gfx::Rect SurfaceAggregator::CalculateOccludingSurfaceDamageRect(
+    const DrawQuad* quad,
+    const gfx::Transform& parent_quad_to_root_target_transform) {
+  if (damage_rects_union_of_surfaces_on_top_.IsEmpty())
+    return gfx::Rect();
+
+  // Transform the quad to the parent root target space
+  // Note: this quad is on the child root render pass.
+  gfx::Transform transform(parent_quad_to_root_target_transform,
+                           quad->shared_quad_state->quad_to_target_transform);
+  gfx::Rect surface_in_root_target_space =
+      cc::MathUtil::MapEnclosingClippedRect(transform, quad->visible_rect);
+
+  // damage_rects_union_of_surfaces_on_top_ is already in the parent root target
+  // space.
+  gfx::Rect occluding_damage_rect = damage_rects_union_of_surfaces_on_top_;
+  occluding_damage_rect.Intersect(surface_in_root_target_space);
+
+  return occluding_damage_rect;
+}
+
+// In CopyPasses(), surfaces are processed from top to bottom. Therefore, all
+// surfaces on top has been added to damage_rects_union_of_surfaces_on_top_
+// before this.
+void SurfaceAggregator::UnionSurfaceDamageRectsOnTop(
+    const gfx::Rect& surface_rect,
+    const gfx::Transform& quad_to_root_target_transform,
+    const RenderPass* render_pass) {
+  DCHECK(!surface_rect.IsEmpty());
+
+  gfx::Rect damage_rect_in_root_target_space =
+      cc::MathUtil::MapEnclosingClippedRect(quad_to_root_target_transform,
+                                            surface_rect);
+  damage_rects_union_of_surfaces_on_top_.Union(
+      damage_rect_in_root_target_space);
+}
+
+// This is for underlay video power optimization.
+// The purpose of this function is to calculate the occluding damage rect if
+// there are elements on top of underlay. This damage rect is later saved in
+// shared_quad_state->occluding_damage_rect and used by the overlay
+// processor for damage rect optimization. This function is called once
+// for each surface. It adds the damage rects of all surfaces to
+// damage_rects_union_of_surfaces_on_top_. The occluding damage rect
+// is then calculated based on this rect.
+bool SurfaceAggregator::ProcessSurfaceOccludingDamage(
+    const Surface* surface,
+    const RenderPassList& render_pass_list,
+    const gfx::Transform& parent_target_transform,
+    const RenderPass* dest_pass,
+    gfx::Rect* occluding_damage_rect) {
+  if (!needs_surface_occluding_damage_rect_)
+    return false;
+
+  bool occluding_damage_rect_valid = false;
+  RenderPass* last_render_pass = render_pass_list.back().get();
+  gfx::Transform quad_to_root_target_transform = gfx::Transform(
+      dest_pass->transform_to_root_target, parent_target_transform);
+
+  // This occluding damage detection only works when there is only one quad
+  // in the current surface.
+  if (render_pass_list.size() == 1 && last_render_pass->quad_list.size() == 1) {
+    auto* quad = last_render_pass->quad_list.back();
+    *occluding_damage_rect = CalculateOccludingSurfaceDamageRect(
+        quad, quad_to_root_target_transform);
+    occluding_damage_rect_valid = true;
+  }
+
+  gfx::Rect surface_damage_rect;
+  if (RenderPassNeedsFullDamage(dest_pass)) {
+    surface_damage_rect = last_render_pass->output_rect;
+  } else {
+    surface_damage_rect = DamageRectForSurface(surface, *last_render_pass,
+                                               last_render_pass->output_rect);
+  }
+
+  // Add the current surface to the damage rect union if there is any damage.
+  // This should be done AFTER checking the occluding damage because the surface
+  // on top should not include its own surface.
+  if (!surface_damage_rect.IsEmpty()) {
+    UnionSurfaceDamageRectsOnTop(
+        surface_damage_rect, quad_to_root_target_transform, last_render_pass);
+  }
+  return occluding_damage_rect_valid;
+}
+
+bool SurfaceAggregator::RenderPassNeedsFullDamage(
+    const RenderPass* pass) const {
+  if (copy_request_passes_.count(pass->id) || pass->cache_render_pass ||
+      moved_pixel_passes_.count(pass->id)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
 // static
 void SurfaceAggregator::UnrefResources(
     base::WeakPtr<SurfaceClient> surface_client,
@@ -330,6 +428,11 @@
       copy_requests.empty() && combined_transform.Preserves2dAxisAlignment() &&
       CanMergeRoundedCorner(rounded_corner_info, *render_pass_list.back());
 
+  gfx::Rect occluding_damage_rect;
+  bool occluding_damage_rect_valid = ProcessSurfaceOccludingDamage(
+      surface, render_pass_list, combined_transform, dest_pass,
+      &occluding_damage_rect);
+
   const RenderPassList& referenced_passes = render_pass_list;
   // TODO(fsamuel): Move this to a separate helper function.
   size_t passes_to_copy =
@@ -367,16 +470,15 @@
                     surface->GetActiveFrame().device_scale_factor(),
                     child_to_parent_map, gfx::Transform(), ClipData(),
                     copy_pass.get(), surface_id, has_surface_damage,
-                    RoundedCornerInfo());
+                    RoundedCornerInfo(), occluding_damage_rect,
+                    occluding_damage_rect_valid);
 
     // If the render pass has copy requests, or should be cached, or has
     // moving-pixel filters, or in a moving-pixel surface, we should damage the
     // whole output rect so that we always drawn the full content. Otherwise, we
     // might have incompleted copy request, or cached patially drawn render
     // pass.
-    if (!copy_request_passes_.count(remapped_pass_id) &&
-        !copy_pass->cache_render_pass &&
-        !moved_pixel_passes_.count(remapped_pass_id)) {
+    if (!RenderPassNeedsFullDamage(copy_pass.get())) {
       gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
       if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) {
         gfx::Rect damage_rect_in_render_pass_space =
@@ -422,7 +524,8 @@
                     surface->GetActiveFrame().device_scale_factor(),
                     child_to_parent_map, surface_transform, quads_clip,
                     dest_pass, surface_id, has_surface_damage,
-                    rounded_corner_info);
+                    rounded_corner_info, occluding_damage_rect,
+                    occluding_damage_rect_valid);
   } else {
     auto* shared_quad_state = CopyAndScaleSharedQuadState(
         source_sqs, scaled_quad_to_target_transform, target_transform,
@@ -432,7 +535,8 @@
         gfx::ScaleToEnclosingRect(source_sqs->visible_quad_layer_rect,
                                   layer_to_content_scale_x,
                                   layer_to_content_scale_y),
-        clip_rect, dest_pass, has_surface_damage, rounded_corner_info);
+        clip_rect, dest_pass, has_surface_damage, rounded_corner_info,
+        occluding_damage_rect, occluding_damage_rect_valid);
 
     gfx::Rect scaled_rect(gfx::ScaleToEnclosingRect(
         source_rect, layer_to_content_scale_x, layer_to_content_scale_y));
@@ -466,11 +570,22 @@
   SkColor background_color = surface_quad->default_background_color;
   auto* shared_quad_state = CopySharedQuadState(
       surface_quad->shared_quad_state, target_transform, clip_rect, dest_pass,
-      /*has_surface_damage*/ true, rounded_corner_info);
+      /*has_surface_damage*/ true, rounded_corner_info,
+      /*occluding_damage_rect*/ gfx::Rect(),
+      /*occluding_damage_rect_valid*/ false);
+
   auto* solid_color_quad =
       dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
   solid_color_quad->SetNew(shared_quad_state, surface_quad->rect,
                            surface_quad->visible_rect, background_color, false);
+
+  if (needs_surface_occluding_damage_rect_) {
+    gfx::Transform transform(
+        target_transform,
+        surface_quad->shared_quad_state->quad_to_target_transform);
+    transform.ConcatTransform(dest_pass->transform_to_root_target);
+    UnionSurfaceDamageRectsOnTop(surface_quad->rect, transform, dest_pass);
+  }
 }
 
 void SurfaceAggregator::EmitGutterQuadsIfNecessary(
@@ -499,7 +614,9 @@
         primary_shared_quad_state,
         primary_shared_quad_state->quad_to_target_transform, target_transform,
         right_gutter_rect, right_gutter_rect, clip_rect, dest_pass,
-        /*has_surface_damage*/ true, rounded_corner_info);
+        /*has_surface_damage*/ true, rounded_corner_info,
+        /*occluding_damage_rect*/ gfx::Rect(),
+        /*occluding_damage_rect_valid*/ false);
 
     auto* right_gutter =
         dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
@@ -516,7 +633,9 @@
         primary_shared_quad_state,
         primary_shared_quad_state->quad_to_target_transform, target_transform,
         bottom_gutter_rect, bottom_gutter_rect, clip_rect, dest_pass,
-        /*has_surface_damage*/ true, rounded_corner_info);
+        /*has_surface_damage*/ true, rounded_corner_info,
+        /*occluding_damage_rect*/ gfx::Rect(),
+        /*occluding_damage_rect_valid*/ false);
 
     auto* bottom_gutter =
         dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
@@ -572,11 +691,14 @@
     const ClipData& clip_rect,
     RenderPass* dest_render_pass,
     bool has_surface_damage,
-    const RoundedCornerInfo& rounded_corner_info) {
+    const RoundedCornerInfo& rounded_corner_info,
+    const gfx::Rect& occluding_damage_rect,
+    bool occluding_damage_rect_valid) {
   return CopyAndScaleSharedQuadState(
       source_sqs, source_sqs->quad_to_target_transform, target_transform,
       source_sqs->quad_layer_rect, source_sqs->visible_quad_layer_rect,
-      clip_rect, dest_render_pass, has_surface_damage, rounded_corner_info);
+      clip_rect, dest_render_pass, has_surface_damage, rounded_corner_info,
+      occluding_damage_rect, occluding_damage_rect_valid);
 }
 
 SharedQuadState* SurfaceAggregator::CopyAndScaleSharedQuadState(
@@ -588,7 +710,9 @@
     const ClipData& clip_rect,
     RenderPass* dest_render_pass,
     bool has_surface_damage,
-    const RoundedCornerInfo& rounded_corner_info) {
+    const RoundedCornerInfo& rounded_corner_info,
+    const gfx::Rect& occluding_damage_rect,
+    bool occluding_damage_rect_valid) {
   auto* shared_quad_state = dest_render_pass->CreateAndAppendSharedQuadState();
   ClipData new_clip_rect = CalculateClipRect(
       clip_rect, ClipData(source_sqs->is_clipped, source_sqs->clip_rect),
@@ -612,6 +736,9 @@
   shared_quad_state->has_surface_damage = has_surface_damage;
   shared_quad_state->is_fast_rounded_corner =
       rounded_corner_info.is_fast_rounded_corner;
+  if (occluding_damage_rect_valid) {
+    shared_quad_state->occluding_damage_rect = occluding_damage_rect;
+  }
 
   return shared_quad_state;
 }
@@ -626,7 +753,9 @@
     RenderPass* dest_pass,
     const SurfaceId& surface_id,
     bool has_surface_damage,
-    const RoundedCornerInfo& parent_rounded_corner_info) {
+    const RoundedCornerInfo& parent_rounded_corner_info,
+    const gfx::Rect& occluding_damage_rect,
+    bool occluding_damage_rect_valid) {
   const SharedQuadState* last_copied_source_shared_quad_state = nullptr;
   // If the current frame has copy requests or cached render passes, then
   // aggregate the entire thing, as otherwise parts of the copy requests may be
@@ -688,7 +817,9 @@
         }
         const SharedQuadState* dest_shared_quad_state = CopySharedQuadState(
             quad->shared_quad_state, target_transform, clip_rect, dest_pass,
-            has_surface_damage, new_rounded_corner_info);
+            has_surface_damage, new_rounded_corner_info, occluding_damage_rect,
+            occluding_damage_rect_valid);
+
         last_copied_source_shared_quad_state = quad->shared_quad_state;
         if (ignore_undamaged) {
           damage_rect_in_quad_space_valid = CalculateQuadSpaceDamageRect(
@@ -771,6 +902,12 @@
   const auto& child_to_parent_map =
       provider_ ? provider_->GetChildToParentMap(ChildIdForSurface(surface))
                 : empty_map;
+
+  gfx::Rect occluding_damage_rect;
+  bool occluding_damage_rect_valid = ProcessSurfaceOccludingDamage(
+      surface, source_pass_list, gfx::Transform(),
+      source_pass_list.back().get(), &occluding_damage_rect);
+
   for (size_t i = 0; i < source_pass_list.size(); ++i) {
     const auto& source = *source_pass_list[i];
 
@@ -795,16 +932,15 @@
                     frame.device_scale_factor(), child_to_parent_map,
                     gfx::Transform(), ClipData(), copy_pass.get(),
                     surface->surface_id(), has_surface_damage,
-                    RoundedCornerInfo());
+                    RoundedCornerInfo(), occluding_damage_rect,
+                    occluding_damage_rect_valid);
 
     // If the render pass has copy requests, or should be cached, or has
     // moving-pixel filters, or in a moving-pixel surface, we should damage the
     // whole output rect so that we always drawn the full content. Otherwise, we
     // might have incompleted copy request, or cached patially drawn render
     // pass.
-    if (!copy_request_passes_.count(remapped_pass_id) &&
-        !copy_pass->cache_render_pass &&
-        !moved_pixel_passes_.count(remapped_pass_id)) {
+    if (!RenderPassNeedsFullDamage(copy_pass.get())) {
       gfx::Transform inverse_transform(gfx::Transform::kSkipInitialization);
       if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) {
         gfx::Rect damage_rect_in_render_pass_space =
@@ -1221,6 +1357,7 @@
   valid_surfaces_.clear();
   has_cached_render_passes_ = false;
   damage_ranges_.clear();
+  damage_rects_union_of_surfaces_on_top_ = gfx::Rect();
   DCHECK(referenced_surfaces_.empty());
   PrewalkResult prewalk_result;
   root_damage_rect_ =
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 6ff8f31f..ffacddf 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -36,7 +36,8 @@
 
   SurfaceAggregator(SurfaceManager* manager,
                     DisplayResourceProvider* provider,
-                    bool aggregate_only_damaged);
+                    bool aggregate_only_damaged,
+                    bool needs_surface_occluding_damage_rect);
   ~SurfaceAggregator();
 
   CompositorFrame Aggregate(const SurfaceId& surface_id,
@@ -151,7 +152,9 @@
       const ClipData& clip_rect,
       RenderPass* dest_render_pass,
       bool has_surface_damage,
-      const RoundedCornerInfo& rounded_corner_info);
+      const RoundedCornerInfo& rounded_corner_info,
+      const gfx::Rect& occluding_damage_rect,
+      bool occluding_damage_rect_valid);
 
   SharedQuadState* CopyAndScaleSharedQuadState(
       const SharedQuadState* source_sqs,
@@ -162,7 +165,9 @@
       const ClipData& clip_rect,
       RenderPass* dest_render_pass,
       bool has_surface_damage,
-      const RoundedCornerInfo& rounded_corner_info);
+      const RoundedCornerInfo& rounded_corner_info,
+      const gfx::Rect& occluding_damage_rect,
+      bool occluding_damage_rect_valid);
 
   void CopyQuadsToPass(
       const QuadList& source_quad_list,
@@ -174,7 +179,10 @@
       RenderPass* dest_pass,
       const SurfaceId& surface_id,
       bool has_surface_damage,
-      const RoundedCornerInfo& rounded_corner_info);
+      const RoundedCornerInfo& rounded_corner_info,
+      const gfx::Rect& occluding_damage_rect,
+      bool occluding_damage_rect_valid);
+
   gfx::Rect PrewalkTree(Surface* surface,
                         bool in_moved_pixel_surface,
                         int parent_pass,
@@ -202,6 +210,18 @@
   gfx::Rect DamageRectForSurface(const Surface* surface,
                                  const RenderPass& source,
                                  const gfx::Rect& full_rect) const;
+  gfx::Rect CalculateOccludingSurfaceDamageRect(
+      const DrawQuad* quad,
+      const gfx::Transform& parent_quad_to_root_target_transform);
+  void UnionSurfaceDamageRectsOnTop(const gfx::Rect& surface_rect,
+                                    const gfx::Transform& target_transform,
+                                    const RenderPass* pass);
+  bool ProcessSurfaceOccludingDamage(const Surface* surface,
+                                     const RenderPassList& render_pass_list,
+                                     const gfx::Transform& target_transform,
+                                     const RenderPass* dest_pass,
+                                     gfx::Rect* occluding_damage_rect);
+  bool RenderPassNeedsFullDamage(const RenderPass* pass) const;
 
   static void UnrefResources(base::WeakPtr<SurfaceClient> surface_client,
                              const std::vector<ReturnedResource>& resources);
@@ -275,6 +295,13 @@
   // The root damage rect of the currently-aggregating frame.
   gfx::Rect root_damage_rect_;
 
+  // Occluding damage rect will be calculated for qualified candidates
+  const bool needs_surface_occluding_damage_rect_;
+
+  // This is the union of the damage rects of all surface on top
+  // of the current surface.
+  gfx::Rect damage_rects_union_of_surfaces_on_top_;
+
   // True if the frame that's currently being aggregated has copy requests.
   // This is valid during Aggregate after PrewalkTree is called.
   bool has_copy_requests_;
diff --git a/components/viz/service/display/surface_aggregator_perftest.cc b/components/viz/service/display/surface_aggregator_perftest.cc
index 64768baf..a6ea493 100644
--- a/components/viz/service/display/surface_aggregator_perftest.cc
+++ b/components/viz/service/display/surface_aggregator_perftest.cc
@@ -53,7 +53,8 @@
       child_tokens[i] = base::UnguessableToken::Create();
     }
     aggregator_ = std::make_unique<SurfaceAggregator>(
-        manager_.surface_manager(), resource_provider_.get(), optimize_damage);
+        manager_.surface_manager(), resource_provider_.get(), optimize_damage,
+        true);
     for (int i = 0; i < num_surfaces; i++) {
       LocalSurfaceId local_surface_id(i + 1, child_tokens[i]);
 
@@ -168,6 +169,22 @@
   RunTest(20, 100, 1.f, false, true, "many_surfaces_opaque");
 }
 
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaque_100) {
+  RunTest(100, 1, 1.f, true, false, "(100 Surfaces, 1 quad each)");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaque_300) {
+  RunTest(300, 1, 1.f, true, false, "(300 Surfaces, 1 quad each)");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesManyQuadsOpaque_100) {
+  RunTest(100, 100, 1.f, true, false, "(100 Surfaces, 100 quads each)");
+}
+
+TEST_F(SurfaceAggregatorPerfTest, ManySurfacesManyQuadsOpaque_300) {
+  RunTest(300, 100, 1.f, true, false, "(300 Surfaces, 100 quads each)");
+}
+
 TEST_F(SurfaceAggregatorPerfTest, ManySurfacesTransparent) {
   RunTest(20, 100, .5f, false, true, "many_surfaces_transparent");
 }
diff --git a/components/viz/service/display/surface_aggregator_pixeltest.cc b/components/viz/service/display/surface_aggregator_pixeltest.cc
index 3b8ea88..e53d0a2 100644
--- a/components/viz/service/display/surface_aggregator_pixeltest.cc
+++ b/components/viz/service/display/surface_aggregator_pixeltest.cc
@@ -113,7 +113,7 @@
       std::move(root_frame));
 
   SurfaceAggregator aggregator(this->manager_.surface_manager(),
-                               this->resource_provider_.get(), true);
+                               this->resource_provider_.get(), true, false);
   CompositorFrame aggregated_frame =
       aggregator.Aggregate(root_surface_id, this->GetNextDisplayTime());
 
@@ -195,7 +195,7 @@
   }
 
   SurfaceAggregator aggregator(this->manager_.surface_manager(),
-                               this->resource_provider_.get(), true);
+                               this->resource_provider_.get(), true, false);
   CompositorFrame aggregated_frame =
       aggregator.Aggregate(root_surface_id, this->GetNextDisplayTime());
 
@@ -337,7 +337,7 @@
   }
 
   SurfaceAggregator aggregator(this->manager_.surface_manager(),
-                               this->resource_provider_.get(), true);
+                               this->resource_provider_.get(), true, false);
   CompositorFrame aggregated_frame =
       aggregator.Aggregate(root_surface_id, this->GetNextDisplayTime());
 
diff --git a/components/viz/service/display/surface_aggregator_unittest.cc b/components/viz/service/display/surface_aggregator_unittest.cc
index a9c8b51..599a005a 100644
--- a/components/viz/service/display/surface_aggregator_unittest.cc
+++ b/components/viz/service/display/surface_aggregator_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/surface_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
+#include "components/viz/common/quads/yuv_video_draw_quad.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #include "components/viz/service/display/display_resource_provider.h"
 #include "components/viz/service/display_embedder/server_shared_bitmap_manager.h"
@@ -119,7 +120,10 @@
             kArbitraryRootFrameSinkId,
             kRootIsRoot,
             kNeedsSyncPoints)),
-        aggregator_(manager_.surface_manager(), nullptr, use_damage_rect) {
+        aggregator_(manager_.surface_manager(),
+                    nullptr,
+                    use_damage_rect,
+                    true) {
     manager_.surface_manager()->AddObserver(&observer_);
   }
 
@@ -139,6 +143,13 @@
       return quad;
     }
 
+    static Quad YUVVideoQuad(const gfx::Rect& rect) {
+      Quad quad;
+      quad.material = DrawQuad::YUV_VIDEO_CONTENT;
+      quad.rect = rect;
+      return quad;
+    }
+
     // If |fallback_surface_id| is a valid surface Id then this will generate
     // two SurfaceDrawQuads.
     static Quad SurfaceQuad(const SurfaceRange& surface_range,
@@ -237,6 +248,9 @@
       case DrawQuad::RENDER_PASS:
         AddRenderPassQuad(pass, desc.render_pass_id);
         break;
+      case DrawQuad::YUV_VIDEO_CONTENT:
+        AddYUVVideoQuad(pass, desc.rect);
+        break;
       default:
         NOTREACHED();
     }
@@ -348,6 +362,18 @@
                  gfx::RectF(), false, 1.0f);
   }
 
+  static void AddYUVVideoQuad(RenderPass* pass, const gfx::Rect& output_rect) {
+    auto* shared_state = pass->CreateAndAppendSharedQuadState();
+    shared_state->SetAll(gfx::Transform(), output_rect, output_rect,
+                         gfx::RRectF(), output_rect, false, false, 1,
+                         SkBlendMode::kSrcOver, 0);
+    auto* quad = pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>();
+    quad->SetNew(shared_state, output_rect, output_rect, false,
+                 gfx::RectF(output_rect), gfx::RectF(), output_rect.size(),
+                 gfx::Size(), 0, 0, 0, 0, gfx::ColorSpace::CreateREC709(), 0,
+                 1.0, 8);
+  }
+
  protected:
   ServerSharedBitmapManager shared_bitmap_manager_;
   FrameSinkManagerImpl manager_;
@@ -858,16 +884,14 @@
 };
 
 TEST_F(SurfaceAggregatorValidSurfaceTest, UndrawnSurfaces) {
-  TestVizClient child(
-      this, &manager_, kArbitraryFrameSinkId1,
-      gfx::Rect(10, 10));
+  TestVizClient child(this, &manager_, kArbitraryFrameSinkId1,
+                      gfx::Rect(10, 10));
   child.SubmitCompositorFrame(SK_ColorBLUE);
 
   // Parent first submits a CompositorFrame that renfereces |child|, but does
   // not provide a DrawQuad that embeds it.
-  TestVizClient parent(
-      this, &manager_, kArbitraryFrameSinkId2,
-      gfx::Rect(15, 15));
+  TestVizClient parent(this, &manager_, kArbitraryFrameSinkId2,
+                       gfx::Rect(15, 15));
   parent.SetEmbeddedClient(&child, false);
   parent.SubmitCompositorFrame(SK_ColorGREEN);
 
@@ -913,17 +937,15 @@
 }
 
 TEST_F(SurfaceAggregatorValidSurfaceTest, UndrawnSurfacesWithCopyRequests) {
-  TestVizClient child(
-      this, &manager_, kArbitraryFrameSinkId1,
-      gfx::Rect(10, 10));
+  TestVizClient child(this, &manager_, kArbitraryFrameSinkId1,
+                      gfx::Rect(10, 10));
   child.SubmitCompositorFrame(SK_ColorBLUE);
   child.RequestCopyOfOutput();
 
   // Parent first submits a CompositorFrame that renfereces |child|, but does
   // not provide a DrawQuad that embeds it.
-  TestVizClient parent(
-      this, &manager_, kArbitraryFrameSinkId2,
-      gfx::Rect(15, 15));
+  TestVizClient parent(this, &manager_, kArbitraryFrameSinkId2,
+                       gfx::Rect(15, 15));
   parent.SetEmbeddedClient(&child, false);
   parent.SubmitCompositorFrame(SK_ColorGREEN);
 
@@ -958,24 +980,21 @@
 
 TEST_F(SurfaceAggregatorValidSurfaceTest,
        SurfacesWithMultipleEmbeddersBothVisibleAndInvisible) {
-  TestVizClient child(
-      this, &manager_, kArbitraryFrameSinkId1,
-      gfx::Rect(10, 10));
+  TestVizClient child(this, &manager_, kArbitraryFrameSinkId1,
+                      gfx::Rect(10, 10));
   child.SubmitCompositorFrame(SK_ColorBLUE);
 
   // First parent submits a CompositorFrame that renfereces |child|, but does
   // not provide a DrawQuad that embeds it.
-  TestVizClient first_parent(
-      this, &manager_, kArbitraryFrameSinkId2,
-      gfx::Rect(15, 15));
+  TestVizClient first_parent(this, &manager_, kArbitraryFrameSinkId2,
+                             gfx::Rect(15, 15));
   first_parent.SetEmbeddedClient(&child, false);
   first_parent.SubmitCompositorFrame(SK_ColorGREEN);
 
   // Second parent submits a CompositorFrame referencing |child|, and also
   // includes a draw-quad for it.
-  TestVizClient second_parent(
-      this, &manager_, kArbitraryMiddleFrameSinkId,
-      gfx::Rect(25, 25));
+  TestVizClient second_parent(this, &manager_, kArbitraryMiddleFrameSinkId,
+                              gfx::Rect(25, 25));
   second_parent.SetEmbeddedClient(&child, true);
   second_parent.SubmitCompositorFrame(SK_ColorYELLOW);
 
@@ -3875,7 +3894,7 @@
         DisplayResourceProvider::kSoftware, nullptr, &shared_bitmap_manager_);
 
     aggregator_ = std::make_unique<SurfaceAggregator>(
-        manager_.surface_manager(), resource_provider_.get(), false);
+        manager_.surface_manager(), resource_provider_.get(), false, false);
     aggregator_->set_output_is_secure(true);
   }
 
@@ -4842,6 +4861,169 @@
   }
 }
 
+// Tests the overlay occluding damage rect
+TEST_F(SurfaceAggregatorValidSurfaceTest, OverlayOccludingDamageRect) {
+  // Video quad
+  std::vector<Quad> child_surface_quads = {
+      Quad::YUVVideoQuad(gfx::Rect(0, 0, 100, 100))};
+
+  std::vector<Pass> child_surface_passes = {
+      Pass(child_surface_quads, /*size*/ gfx::Size(100, 100),
+           /*damage_rect*/ gfx::Rect(0, 0, 100, 100))};
+
+  CompositorFrame child_surface_frame = MakeEmptyCompositorFrame();
+  AddPasses(&child_surface_frame.render_pass_list, child_surface_passes,
+            &child_surface_frame.metadata.referenced_surfaces);
+
+  ParentLocalSurfaceIdAllocator allocator;
+  allocator.GenerateId();
+  LocalSurfaceId child_local_surface_id =
+      allocator.GetCurrentLocalSurfaceIdAllocation().local_surface_id();
+  SurfaceId child_surface_id(child_sink_->frame_sink_id(),
+                             child_local_surface_id);
+  child_sink_->SubmitCompositorFrame(child_local_surface_id,
+                                     std::move(child_surface_frame));
+
+  // Original video quad (0, 0, 100, 100) x this video_transform matrix ==
+  // (10, 0, 80, 80)
+  gfx::Transform video_transform(0.8f, 0, 0, 0.8f, 10.0f, 0);
+
+  // root surface quads
+  std::vector<Quad> root_surface_quads = {
+      Quad::SolidColorQuad(SK_ColorRED, gfx::Rect(60, 0, 40, 40)),
+      Quad::SurfaceQuad(SurfaceRange(base::nullopt, child_surface_id),
+                        SK_ColorWHITE,
+                        /*primary_surface_rect*/ gfx::Rect(0, 0, 100, 100),
+                        /*opacity*/ 1.f, video_transform,
+                        /*stretch_content_to_fill_bounds=*/false,
+                        /*ignores_input_event=*/false)};
+
+  std::vector<Pass> root_passes = {
+      Pass(root_surface_quads,
+           /*size*/ gfx::Size(200, 200),
+           /*damage_rect*/ gfx::Rect(60, 0, 40, 40))};
+
+  CompositorFrame root_frame = MakeEmptyCompositorFrame();
+  AddPasses(&root_frame.render_pass_list, root_passes,
+            &root_frame.metadata.referenced_surfaces);
+
+  SurfaceId root_surface_id(root_sink_->frame_sink_id(),
+                            root_local_surface_id_);
+  root_sink_->SubmitCompositorFrame(root_local_surface_id_,
+                                    std::move(root_frame));
+
+  CompositorFrame aggregated_frame =
+      aggregator_.Aggregate(root_surface_id, GetNextDisplayTimeAndIncrement());
+
+  // Frame # 0 - Full occluding damage rect
+  // The damage rect of the very first frame is always the full rect
+  auto* output_root_pass = aggregated_frame.render_pass_list.back().get();
+  EXPECT_EQ(gfx::Rect(0, 0, 200, 200), output_root_pass->damage_rect);
+
+  const SharedQuadState* video_sqs =
+      output_root_pass->quad_list.back()->shared_quad_state;
+  // Occluding damage of the first frame = the whole surface rect on top
+  // intersects the video quad.
+  // (0, 0, 200, 200) intersect with video quad (10, 0, 80, 80) == (10, 0, 80,
+  // 80).
+  EXPECT_EQ(gfx::Rect(10, 0, 80, 80), video_sqs->occluding_damage_rect.value());
+
+  // Frame #1 - Has occluding damage
+  {
+    CompositorFrame child_surface_frame = MakeEmptyCompositorFrame();
+    AddPasses(&child_surface_frame.render_pass_list, child_surface_passes,
+              &child_surface_frame.metadata.referenced_surfaces);
+    child_sink_->SubmitCompositorFrame(child_local_surface_id,
+                                       std::move(child_surface_frame));
+
+    CompositorFrame root_frame = MakeEmptyCompositorFrame();
+    AddPasses(&root_frame.render_pass_list, root_passes,
+              &root_frame.metadata.referenced_surfaces);
+
+    root_sink_->SubmitCompositorFrame(root_local_surface_id_,
+                                      std::move(root_frame));
+
+    CompositorFrame aggregated_frame = aggregator_.Aggregate(
+        root_surface_id, GetNextDisplayTimeAndIncrement());
+
+    auto* output_root_pass = aggregated_frame.render_pass_list.back().get();
+    // The video quad (10, 0, 80, 80) unions the solid quad on top (60, 0, 40,
+    // 40)
+    EXPECT_EQ(gfx::Rect(10, 0, 90, 80), output_root_pass->damage_rect);
+
+    const SharedQuadState* video_sqs =
+        output_root_pass->quad_list.back()->shared_quad_state;
+    // The solid quad on top (60, 0, 40, 40) intersects the video quad (10, 0,
+    // 80, 80)
+    EXPECT_EQ(gfx::Rect(60, 0, 30, 40),
+              video_sqs->occluding_damage_rect.value());
+  }
+  // Frame #2 - No occluding damage, the quad on top doesn't change
+  {
+    CompositorFrame child_surface_frame = MakeEmptyCompositorFrame();
+    AddPasses(&child_surface_frame.render_pass_list, child_surface_passes,
+              &child_surface_frame.metadata.referenced_surfaces);
+    child_sink_->SubmitCompositorFrame(child_local_surface_id,
+                                       std::move(child_surface_frame));
+
+    // No change in root frame
+    CompositorFrame aggregated_frame = aggregator_.Aggregate(
+        root_surface_id, GetNextDisplayTimeAndIncrement());
+
+    auto* output_root_pass = aggregated_frame.render_pass_list.back().get();
+    // Only the video quad (10, 0, 80, 80) is damaged
+    EXPECT_EQ(gfx::Rect(10, 0, 80, 80), output_root_pass->damage_rect);
+
+    const SharedQuadState* video_sqs =
+        output_root_pass->quad_list.back()->shared_quad_state;
+    // No occluding damage
+    EXPECT_EQ(gfx::Rect(), video_sqs->occluding_damage_rect.value());
+  }
+  // Frame #3 - The only quad on top is removed
+  {
+    CompositorFrame child_surface_frame = MakeEmptyCompositorFrame();
+    AddPasses(&child_surface_frame.render_pass_list, child_surface_passes,
+              &child_surface_frame.metadata.referenced_surfaces);
+    child_sink_->SubmitCompositorFrame(child_local_surface_id,
+                                       std::move(child_surface_frame));
+
+    // root surface quads, the solid quad (60, 0, 40, 40) is removed
+    std::vector<Quad> root_surface_quads = {Quad::SurfaceQuad(
+        SurfaceRange(base::nullopt, child_surface_id), SK_ColorWHITE,
+        /*primary_surface_rect*/ gfx::Rect(0, 0, 100, 100),
+        /*opacity*/ 1.f, video_transform,
+        /*stretch_content_to_fill_bounds=*/false,
+        /*ignores_input_event=*/false)};
+
+    std::vector<Pass> root_passes = {
+        Pass(root_surface_quads,
+             /*size*/ gfx::Size(200, 200),
+             /*damage_rect*/ gfx::Rect(60, 0, 40, 40))};
+
+    CompositorFrame root_frame = MakeEmptyCompositorFrame();
+    AddPasses(&root_frame.render_pass_list, root_passes,
+              &root_frame.metadata.referenced_surfaces);
+
+    root_sink_->SubmitCompositorFrame(root_local_surface_id_,
+                                      std::move(root_frame));
+
+    CompositorFrame aggregated_frame = aggregator_.Aggregate(
+        root_surface_id, GetNextDisplayTimeAndIncrement());
+
+    auto* output_root_pass = aggregated_frame.render_pass_list.back().get();
+    // The video quad (10, 0, 80, 80) unions the expose damage from removing
+    // the solid quad on top (60, 0, 40, 40)
+    EXPECT_EQ(gfx::Rect(10, 0, 90, 80), output_root_pass->damage_rect);
+
+    const SharedQuadState* video_sqs =
+        output_root_pass->quad_list.back()->shared_quad_state;
+    // The expose damage (60, 0, 40, 40) intersects the video quad (10, 0,
+    // 80, 80)
+    EXPECT_EQ(gfx::Rect(60, 0, 30, 40),
+              video_sqs->occluding_damage_rect.value());
+  }
+}
+
 // Tests that quads outside the damage rect are not ignored for cached render
 // pass.
 TEST_F(SurfaceAggregatorPartialSwapTest, NotIgnoreOutsideForCachedRenderPass) {
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 80872fb..4a729685a 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -287,6 +287,10 @@
   return wants_animate_only_begin_frames_;
 }
 
+bool CompositorFrameSinkSupport::IsRoot() const {
+  return is_root_;
+}
+
 void CompositorFrameSinkSupport::DidNotProduceFrame(const BeginFrameAck& ack) {
   TRACE_EVENT2("viz", "CompositorFrameSinkSupport::DidNotProduceFrame",
                "ack.source_id", ack.source_id, "ack.sequence_number",
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 e3a85a8a..521cb34 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -215,6 +215,7 @@
   const BeginFrameArgs& LastUsedBeginFrameArgs() const override;
   void OnBeginFrameSourcePausedChanged(bool paused) override;
   bool WantsAnimateOnlyBeginFrames() const override;
+  bool IsRoot() const override;
 
   void UpdateNeedsBeginFramesInternal();
   Surface* CreateSurface(const SurfaceInfo& surface_info,
diff --git a/components/viz/service/frame_sinks/video_detector_unittest.cc b/components/viz/service/frame_sinks/video_detector_unittest.cc
index 484cac1..dd5b361 100644
--- a/components/viz/service/frame_sinks/video_detector_unittest.cc
+++ b/components/viz/service/frame_sinks/video_detector_unittest.cc
@@ -79,6 +79,7 @@
       : frame_sink_manager_(&shared_bitmap_manager_),
         surface_aggregator_(frame_sink_manager_.surface_manager(),
                             nullptr,
+                            false,
                             false) {}
 
   ~VideoDetectorTest() override {}
diff --git a/components/viz/test/begin_frame_source_test.h b/components/viz/test/begin_frame_source_test.h
index 086f1c53..1ad005a 100644
--- a/components/viz/test/begin_frame_source_test.h
+++ b/components/viz/test/begin_frame_source_test.h
@@ -84,6 +84,7 @@
   MOCK_CONST_METHOD0(LastUsedBeginFrameArgs, const BeginFrameArgs&());
   MOCK_METHOD1(OnBeginFrameSourcePausedChanged, void(bool));
   MOCK_CONST_METHOD0(WantsAnimateOnlyBeginFrames, bool());
+  MOCK_CONST_METHOD0(IsRoot, bool());
 
   virtual void AsValueInto(base::trace_event::TracedValue* dict) const;
 
diff --git a/content/browser/DEPS b/content/browser/DEPS
index 94dcee5..2293035 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -103,7 +103,6 @@
   "+third_party/blink/public/platform/web_touch_event.h",
   "+third_party/blink/public/platform/web_text_input_type.h",
   "+third_party/blink/public/platform/mac/web_scrollbar_theme.h",
-  "+third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom.h",
   "+third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h",
   "+third_party/blink/public/platform/modules/notifications/web_notification_constants.h",
   "+third_party/blink/public/platform/modules/service_worker/web_service_worker_error.h",
@@ -128,7 +127,6 @@
   # don't use WTF types.
   "+third_party/blink/public/platform/modules/bluetooth/web_bluetooth.mojom.h",
   "+third_party/blink/public/platform/modules/cookie_store/cookie_store.mojom.h",
-  "+third_party/blink/public/platform/modules/mediasession/media_session.mojom.h",
   "+third_party/blink/public/platform/modules/mediastream/media_devices.mojom.h",
   "+third_party/blink/public/platform/modules/websockets/websocket.mojom.h",
 
diff --git a/content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h b/content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h
index 01aac22..f60ca495 100644
--- a/content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h
+++ b/content/browser/font_unique_name_lookup/font_unique_name_lookup_service.h
@@ -8,7 +8,7 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
-#include "third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom.h"
+#include "third_party/blink/public/mojom/font_unique_name_lookup/font_unique_name_lookup.mojom.h"
 
 namespace content {
 
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 277edfd2..1f4476e2 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -210,7 +210,7 @@
   // Blink and //content.
   if (IsSecMetadataEnabled() && IsOriginSecure(url)) {
     std::string site_value = "cross-site";
-    std::string user_value = has_user_gesture ? "?T" : "?F";
+    std::string user_value = has_user_gesture ? "?T" : std::string();
 
     // Navigations that aren't triggerable from the web (e.g. typing in the
     // address bar, or clicking a bookmark) are labeled as 'none'. Webby
@@ -255,7 +255,8 @@
     headers->SetHeaderIfMissing("Sec-Fetch-Dest", destination.c_str());
     headers->SetHeaderIfMissing("Sec-Fetch-Mode", mode.c_str());
     headers->SetHeaderIfMissing("Sec-Fetch-Site", site_value.c_str());
-    headers->SetHeaderIfMissing("Sec-Fetch-User", user_value.c_str());
+    if (!user_value.empty())
+      headers->SetHeaderIfMissing("Sec-Fetch-User", user_value.c_str());
   }
 
   // Ask whether we should request a policy.
diff --git a/content/browser/media/session/media_metadata_sanitizer.h b/content/browser/media/session/media_metadata_sanitizer.h
index 0b21ced5..5f1b7b6 100644
--- a/content/browser/media/session/media_metadata_sanitizer.h
+++ b/content/browser/media/session/media_metadata_sanitizer.h
@@ -5,7 +5,7 @@
 #ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_METADATA_SANITIZER_H_
 #define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_METADATA_SANITIZER_H_
 
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
 namespace content {
 
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 3f8cff547..bd2c598 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -26,7 +26,7 @@
 #include "media/base/media_content_type.h"
 #include "services/media_session/public/cpp/media_image_manager.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
 #if defined(OS_ANDROID)
 #include "content/browser/media/session/media_session_android.h"
diff --git a/content/browser/media/session/media_session_impl_service_routing_unittest.cc b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
index fcccdf2..9d366798 100644
--- a/content/browser/media/session/media_session_impl_service_routing_unittest.cc
+++ b/content/browser/media/session/media_session_impl_service_routing_unittest.cc
@@ -19,7 +19,7 @@
 #include "services/media_session/public/cpp/media_metadata.h"
 #include "services/media_session/public/cpp/test/mock_media_session.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
 using ::testing::_;
 using ::testing::AnyNumber;
diff --git a/content/browser/media/session/media_session_service_impl.h b/content/browser/media/session/media_session_service_impl.h
index 06a0d36..22eb0e1 100644
--- a/content/browser/media/session/media_session_service_impl.h
+++ b/content/browser/media/session/media_session_service_impl.h
@@ -5,8 +5,9 @@
 #ifndef CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_SERVICE_IMPL_H_
 
+#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding.h"
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
 namespace content {
 
diff --git a/content/browser/media/session/mock_media_session_service_impl.h b/content/browser/media/session/mock_media_session_service_impl.h
index 8253287..d10b936 100644
--- a/content/browser/media/session/mock_media_session_service_impl.h
+++ b/content/browser/media/session/mock_media_session_service_impl.h
@@ -7,7 +7,7 @@
 
 #include "content/browser/media/session/media_session_service_impl.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
 namespace content {
 
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index f7e9fac3..0c78440 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -520,29 +520,6 @@
   EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
 }
 
-// Test that service returns NOT_ALLOWED_ERROR if user verification is REQUIRED
-// for get().
-TEST_F(AuthenticatorImplTest, GetAssertionUserVerification) {
-  SimulateNavigation(GURL(kTestOrigin1));
-  device::test::ScopedVirtualFidoDevice scoped_virtual_device;
-  auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
-      base::Time::Now(), base::TimeTicks::Now());
-  auto authenticator = ConstructAuthenticatorWithTimer(task_runner);
-
-  PublicKeyCredentialRequestOptionsPtr options =
-      GetTestPublicKeyCredentialRequestOptions();
-  options->user_verification =
-      blink::mojom::UserVerificationRequirement::REQUIRED;
-  TestGetAssertionCallback callback_receiver;
-  authenticator->GetAssertion(std::move(options), callback_receiver.callback());
-
-  // Trigger timer.
-  base::RunLoop().RunUntilIdle();
-  task_runner->FastForwardBy(base::TimeDelta::FromMinutes(1));
-  callback_receiver.WaitForCallback();
-  EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
-}
-
 // Test that MakeCredential request times out with NOT_ALLOWED_ERROR if user
 // verification is required for U2F devices.
 TEST_F(AuthenticatorImplTest, MakeCredentialUserVerification) {
@@ -3102,21 +3079,22 @@
       SCOPED_TRACE(UVToString(uv));
 
       auto options = get_credential_options(uv);
-      // UV cannot be satisfied without fingerprints.
-      const bool should_timeout =
+      // Without a fingerprint enrolled we assume that a UV=required request
+      // cannot be satisfied by an authenticator that cannot do UV. It is
+      // possible for a credential to be created without UV and then later
+      // asserted with UV=required, but that would be bizarre behaviour from
+      // an RP and we currently don't worry about it.
+      const bool should_be_unrecognized =
           !fingerprints_enrolled &&
           uv == blink::mojom::UserVerificationRequirement::REQUIRED;
-      if (should_timeout) {
-        options->adjusted_timeout = base::TimeDelta::FromMilliseconds(100);
-      }
 
       TestGetAssertionCallback callback_receiver;
       authenticator->GetAssertion(std::move(options),
                                   callback_receiver.callback());
       callback_receiver.WaitForCallback();
 
-      if (should_timeout) {
-        EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR,
+      if (should_be_unrecognized) {
+        EXPECT_EQ(AuthenticatorStatus::CREDENTIAL_NOT_RECOGNIZED,
                   callback_receiver.status());
       } else {
         EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index 8011dc8..ac765d0 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -88,6 +88,10 @@
 constexpr char kRelyingPartyRpIconUrlSecurityErrorMessage[] =
     "webauth: SecurityError: 'rp.icon' should be a secure URL";
 
+constexpr char kInvalidStateError[] =
+    "webauth: InvalidStateError: The user attempted to use an authenticator "
+    "that recognized none of the provided credentials.";
+
 // Templates to be used with base::ReplaceStringPlaceholders. Can be
 // modified to include up to 9 replacements. The default values for
 // any additional replacements added should also be added to the
@@ -815,7 +819,10 @@
 }
 
 // Tests that when navigator.credentials.get() is called with user verification
-// required, we get a NotSupportedError.
+// required, we get an InvalidStateError because the virtual device isn't
+// configured with UV and GetAssertionRequestHandler will return
+// |kUserConsentButCredentialNotRecognized| when such an authenticator is
+// touched in that case.
 IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest,
                        GetPublicKeyCredentialUserVerification) {
   for (const auto protocol : kAllProtocols) {
@@ -828,7 +835,7 @@
     ASSERT_TRUE(content::ExecuteScriptAndExtractString(
         shell()->web_contents()->GetMainFrame(),
         BuildGetCallWithParameters(parameters), &result));
-    ASSERT_EQ(kTimeoutErrorMessage, result);
+    ASSERT_EQ(kInvalidStateError, result);
   }
 }
 
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.cc b/content/browser/worker_host/worker_script_fetch_initiator.cc
index 81453c2..b5dc343 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.cc
+++ b/content/browser/worker_host/worker_script_fetch_initiator.cc
@@ -232,7 +232,7 @@
                                                  site_value.c_str());
     resource_request->headers.SetHeaderIfMissing("Sec-Fetch-Mode",
                                                  "same-origin");
-    resource_request->headers.SetHeaderIfMissing("Sec-Fetch-User", "?F");
+    // We don't set `Sec-Fetch-User` for subresource requests.
   }
 }
 
diff --git a/content/common/DEPS b/content/common/DEPS
index 2407b45c..b820802c 100644
--- a/content/common/DEPS
+++ b/content/common/DEPS
@@ -52,7 +52,6 @@
   "+third_party/blink/public/platform/modules/bluetooth/web_bluetooth.mojom.h",
   "+third_party/blink/public/platform/modules/device_orientation/WebDeviceMotionData.h",
   "+third_party/blink/public/platform/modules/device_orientation/WebDeviceOrientationData.h",
-  "+third_party/blink/public/platform/modules/mediasession/media_session.mojom.h",
   "+third_party/blink/public/platform/modules/mediastream/media_devices.mojom.h",
   "+third_party/blink/public/platform/modules/push_messaging/web_push_error.h",
   "+third_party/blink/public/platform/modules/remoteplayback/web_remote_playback_availability.h",
diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
index 1ec506c..650f5a7 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
@@ -426,6 +426,7 @@
             }
         };
 
+        ResourceExtractor.get().setResultTraits(UiThreadTaskTraits.BOOTSTRAP);
         if (completionCallback == null) {
             // If no continuation callback is specified, then force the resource extraction
             // to complete.
@@ -450,6 +451,7 @@
     @Override
     public void initChromiumBrowserProcessForTests() {
         ResourceExtractor resourceExtractor = ResourceExtractor.get();
+        resourceExtractor.setResultTraits(UiThreadTaskTraits.BOOTSTRAP);
         resourceExtractor.startExtractingResources("en");
         resourceExtractor.waitForCompletion();
         nativeSetCommandLineFlags(false);
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestRule.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestRule.java
index 4252934..50434b7 100644
--- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestRule.java
+++ b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestRule.java
@@ -15,6 +15,7 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.resources.ResourceExtractor;
 
 /**
@@ -56,6 +57,7 @@
             try {
                 // Extract compressed resource paks.
                 ResourceExtractor resourceExtractor = ResourceExtractor.get();
+                resourceExtractor.setResultTraits(UiThreadTaskTraits.BOOTSTRAP);
                 resourceExtractor.startExtractingResources("en");
                 resourceExtractor.waitForCompletion();
 
diff --git a/content/renderer/media/stream/media_stream_center.cc b/content/renderer/media/stream/media_stream_center.cc
index 18bd3994..8622723b 100644
--- a/content/renderer/media/stream/media_stream_center.cc
+++ b/content/renderer/media/stream/media_stream_center.cc
@@ -180,7 +180,8 @@
 
 blink::WebAudioSourceProvider*
 MediaStreamCenter::CreateWebAudioSourceFromMediaStreamTrack(
-    const blink::WebMediaStreamTrack& track) {
+    const blink::WebMediaStreamTrack& track,
+    int context_sample_rate) {
   DVLOG(1) << "MediaStreamCenter::createWebAudioSourceFromMediaStreamTrack";
   blink::WebPlatformMediaStreamTrack* media_stream_track =
       track.GetPlatformTrack();
@@ -195,7 +196,7 @@
   // TODO(tommi): Rename WebRtcLocalAudioSourceProvider to
   // WebAudioMediaStreamSink since it's not specific to any particular source.
   // https://crbug.com/577874
-  return new WebRtcLocalAudioSourceProvider(track);
+  return new WebRtcLocalAudioSourceProvider(track, context_sample_rate);
 }
 
 void MediaStreamCenter::DidStopMediaStreamSource(
diff --git a/content/renderer/media/stream/media_stream_center.h b/content/renderer/media/stream/media_stream_center.h
index 55fbd4f5..c83a5cd 100644
--- a/content/renderer/media/stream/media_stream_center.h
+++ b/content/renderer/media/stream/media_stream_center.h
@@ -40,7 +40,8 @@
       const blink::WebMediaStreamTrack& track) override;
 
   blink::WebAudioSourceProvider* CreateWebAudioSourceFromMediaStreamTrack(
-      const blink::WebMediaStreamTrack& track) override;
+      const blink::WebMediaStreamTrack& track,
+      int context_sample_rate) override;
 
   void DidStopMediaStreamSource(
       const blink::WebMediaStreamSource& web_source) override;
diff --git a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
index de47e79..76c29a8 100644
--- a/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
+++ b/content/renderer/media/webrtc/transmission_encoding_info_handler.cc
@@ -144,8 +144,7 @@
 
 void TransmissionEncodingInfoHandler::EncodingInfo(
     const blink::WebMediaConfiguration& configuration,
-    std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks> callbacks)
-    const {
+    OnMediaCapabilitiesEncodingInfoCallback callback) const {
   DCHECK(configuration.video_configuration ||
          configuration.audio_configuration);
 
@@ -153,7 +152,7 @@
   if (!configuration.video_configuration &&
       !configuration.audio_configuration) {
     DVLOG(2) << "Neither video nor audio configuration specified.";
-    callbacks->OnSuccess(std::move(info));
+    std::move(callback).Run(std::move(info));
     return;
   }
 
@@ -191,7 +190,7 @@
     DVLOG(2) << "Audio MIME type:" << mime_type
              << " capabilities:" << ToString(*info);
   }
-  callbacks->OnSuccess(std::move(info));
+  std::move(callback).Run(std::move(info));
 }
 
 }  // namespace content
diff --git a/content/renderer/media/webrtc/transmission_encoding_info_handler.h b/content/renderer/media/webrtc/transmission_encoding_info_handler.h
index f56a9cde..b5aca6a 100644
--- a/content/renderer/media/webrtc/transmission_encoding_info_handler.h
+++ b/content/renderer/media/webrtc/transmission_encoding_info_handler.h
@@ -36,10 +36,8 @@
   ~TransmissionEncodingInfoHandler() override;
 
   // blink::WebTransmissionEncodingInfoHandler implementation.
-  void EncodingInfo(
-      const blink::WebMediaConfiguration& configuration,
-      std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks> cb)
-      const override;
+  void EncodingInfo(const blink::WebMediaConfiguration& configuration,
+                    OnMediaCapabilitiesEncodingInfoCallback cb) const override;
 
  private:
   // Extracts supported video/audio codec name from |mime_type|. Returns "" if
diff --git a/content/renderer/media/webrtc/transmission_encoding_info_handler_unittest.cc b/content/renderer/media/webrtc/transmission_encoding_info_handler_unittest.cc
index ed9910b..2e30db0 100644
--- a/content/renderer/media/webrtc/transmission_encoding_info_handler_unittest.cc
+++ b/content/renderer/media/webrtc/transmission_encoding_info_handler_unittest.cc
@@ -7,6 +7,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/bind.h"
 #include "media/base/video_codecs.h"
 #include "media/video/video_encode_accelerator.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -93,22 +94,20 @@
 // OnSuccess() received argument. So it moves OnSuccess()'s received argument,
 // WebMediaCapabilitiesInfo instance, to EncodingInfoObserver instance for
 // inspection.
-class WebMediaCapabilitiesEncodingInfoCallbacksForTest
-    : public blink::WebMediaCapabilitiesEncodingInfoCallbacks {
+class WebMediaCapabilitiesEncodingInfoCallbacksForTest {
  public:
   WebMediaCapabilitiesEncodingInfoCallbacksForTest(
       EncodingInfoObserver* observer)
       : observer_(observer) {
     DCHECK(observer_);
   }
-  ~WebMediaCapabilitiesEncodingInfoCallbacksForTest() override = default;
+  virtual ~WebMediaCapabilitiesEncodingInfoCallbacksForTest() = default;
 
-  void OnSuccess(
-      std::unique_ptr<blink::WebMediaCapabilitiesInfo> info) override {
+  void OnSuccess(std::unique_ptr<blink::WebMediaCapabilitiesInfo> info) {
     observer_->OnSuccess(std::move(info));
   }
 
-  void OnError() override { observer_->OnError(); }
+  void OnError() { observer_->OnError(); }
 
  private:
   EncodingInfoObserver* observer_;
@@ -165,7 +164,11 @@
     auto callbacks =
         std::make_unique<WebMediaCapabilitiesEncodingInfoCallbacksForTest>(
             &observer);
-    handler.EncodingInfo(configuration, std::move(callbacks));
+    handler.EncodingInfo(
+        configuration,
+        base::BindOnce(
+            &WebMediaCapabilitiesEncodingInfoCallbacksForTest::OnSuccess,
+            base::Unretained(callbacks.get())));
 
     EXPECT_TRUE(observer.IsCalled());
     EXPECT_TRUE(observer.is_success());
diff --git a/content/renderer/media/webrtc_local_audio_source_provider.cc b/content/renderer/media/webrtc_local_audio_source_provider.cc
index 078585f3..21412ec 100644
--- a/content/renderer/media/webrtc_local_audio_source_provider.cc
+++ b/content/renderer/media/webrtc_local_audio_source_provider.cc
@@ -28,10 +28,9 @@
 const size_t WebRtcLocalAudioSourceProvider::kWebAudioRenderBufferSize = 128;
 
 WebRtcLocalAudioSourceProvider::WebRtcLocalAudioSourceProvider(
-    const blink::WebMediaStreamTrack& track)
-    : is_enabled_(false),
-      track_(track),
-      track_stopped_(false) {
+    const blink::WebMediaStreamTrack& track,
+    int context_sample_rate)
+    : is_enabled_(false), track_(track), track_stopped_(false) {
   // Get the native audio output hardware sample-rate for the sink.
   // We need to check if there is a valid frame since the unittests
   // do not have one and they will inject their own |sink_params_| for testing.
@@ -39,13 +38,8 @@
       blink::WebLocalFrame::FrameForCurrentContext();
   RenderFrame* const render_frame = RenderFrame::FromWebFrame(web_frame);
   if (render_frame) {
-    int sample_rate =
-        AudioDeviceFactory::GetOutputDeviceInfo(render_frame->GetRoutingID(),
-                                                media::AudioSinkParameters())
-            .output_params()
-            .sample_rate();
     sink_params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-                       media::CHANNEL_LAYOUT_STEREO, sample_rate,
+                       media::CHANNEL_LAYOUT_STEREO, context_sample_rate,
                        kWebAudioRenderBufferSize);
   }
   // Connect the source provider to the track as a sink.
diff --git a/content/renderer/media/webrtc_local_audio_source_provider.h b/content/renderer/media/webrtc_local_audio_source_provider.h
index 83624c2..930c02f 100644
--- a/content/renderer/media/webrtc_local_audio_source_provider.h
+++ b/content/renderer/media/webrtc_local_audio_source_provider.h
@@ -57,7 +57,8 @@
   static const size_t kWebAudioRenderBufferSize;
 
   explicit WebRtcLocalAudioSourceProvider(
-      const blink::WebMediaStreamTrack& track);
+      const blink::WebMediaStreamTrack& track,
+      int context_sample_rate);
   ~WebRtcLocalAudioSourceProvider() override;
 
   // blink::WebMediaStreamAudioSink implementation.
diff --git a/content/renderer/media/webrtc_local_audio_source_provider_unittest.cc b/content/renderer/media/webrtc_local_audio_source_provider_unittest.cc
index e611d93..e833984 100644
--- a/content/renderer/media/webrtc_local_audio_source_provider_unittest.cc
+++ b/content/renderer/media/webrtc_local_audio_source_provider_unittest.cc
@@ -21,9 +21,10 @@
   void SetUp() override {
     source_params_.Reset(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                          media::CHANNEL_LAYOUT_MONO, 48000, 480);
+    const int context_sample_rate = 44100;
     sink_params_.Reset(
         media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-        media::CHANNEL_LAYOUT_STEREO, 44100,
+        media::CHANNEL_LAYOUT_STEREO, context_sample_rate,
         WebRtcLocalAudioSourceProvider::kWebAudioRenderBufferSize);
     sink_bus_ = media::AudioBus::Create(sink_params_);
     blink::WebMediaStreamSource audio_source;
@@ -35,7 +36,8 @@
                             audio_source);
     blink_track_.SetPlatformTrack(
         std::make_unique<blink::MediaStreamAudioTrack>(true));
-    source_provider_.reset(new WebRtcLocalAudioSourceProvider(blink_track_));
+    source_provider_.reset(
+        new WebRtcLocalAudioSourceProvider(blink_track_, context_sample_rate));
     source_provider_->SetSinkParamsForTesting(sink_params_);
     source_provider_->OnSetFormat(source_params_);
   }
diff --git a/content/renderer/media_recorder/media_recorder_handler.cc b/content/renderer/media_recorder/media_recorder_handler.cc
index 00b3f1d3..7d5c614 100644
--- a/content/renderer/media_recorder/media_recorder_handler.cc
+++ b/content/renderer/media_recorder/media_recorder_handler.cc
@@ -22,6 +22,7 @@
 #include "media/base/video_codecs.h"
 #include "media/base/video_frame.h"
 #include "media/muxers/webm_muxer.h"
+#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h"
 #include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_track.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_track.h"
@@ -36,8 +37,6 @@
 
 namespace content {
 
-using blink::WebMediaCapabilitiesEncodingInfoCallbacks;
-
 namespace {
 
 // Encoding smoothness depends on a number of parameters, namely: frame rate,
@@ -348,8 +347,7 @@
 
 void MediaRecorderHandler::EncodingInfo(
     const blink::WebMediaConfiguration& configuration,
-    std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks>
-        callbacks) {
+    OnMediaCapabilitiesEncodingInfoCallback callback) {
   DCHECK(main_render_thread_checker_.CalledOnValidThread());
   DCHECK(configuration.video_configuration ||
          configuration.audio_configuration);
@@ -398,7 +396,7 @@
            << " is" << (info->supported ? " supported" : " NOT supported")
            << " and" << (info->smooth ? " smooth" : " NOT smooth");
 
-  callbacks->OnSuccess(std::move(info));
+  std::move(callback).Run(std::move(info));
 }
 
 blink::WebString MediaRecorderHandler::ActualMimeType() {
diff --git a/content/renderer/media_recorder/media_recorder_handler.h b/content/renderer/media_recorder/media_recorder_handler.h
index 29377153..f34a4c8 100644
--- a/content/renderer/media_recorder/media_recorder_handler.h
+++ b/content/renderer/media_recorder/media_recorder_handler.h
@@ -64,10 +64,8 @@
   void Stop() override;
   void Pause() override;
   void Resume() override;
-  void EncodingInfo(
-      const blink::WebMediaConfiguration& configuration,
-      std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks> cb)
-      override;
+  void EncodingInfo(const blink::WebMediaConfiguration& configuration,
+                    OnMediaCapabilitiesEncodingInfoCallback cb) override;
   blink::WebString ActualMimeType() override;
 
  private:
diff --git a/device/fido/features.cc b/device/fido/features.cc
index 16ed5cc..48dfe89f 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -19,8 +19,8 @@
 extern const base::Feature kWebAuthProxyCryptotoken{
     "WebAuthenticationProxyCryptotoken", base::FEATURE_ENABLED_BY_DEFAULT};
 
-extern const base::Feature kWebAuthPINSupport{
-    "WebAuthenticationPINSupport", base::FEATURE_DISABLED_BY_DEFAULT};
+extern const base::Feature kWebAuthPINSupport{"WebAuthenticationPINSupport",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
 extern const base::Feature kWebAuthResidentKeys{
     "WebAuthenticationResidentKeys", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/device/fido/fido_test_data.h b/device/fido/fido_test_data.h
index 3419817..8a82db7 100644
--- a/device/fido/fido_test_data.h
+++ b/device/fido/fido_test_data.h
@@ -87,6 +87,29 @@
     0x00, 0x00,
 };
 
+// kU2fBogusRegisterCommandApdu is the U2F register command generated by
+// |ConstructBogusU2fRegistrationCommand|.
+constexpr uint8_t kU2fBogusRegisterCommandApdu[] = {
+    // clang-format off
+    // CLA, INS, P1, P2 APDU instructions
+    0x00, 0x01, 0x03, 0x00,
+    // Data length in 3 bytes in big endian order.
+    0x00, 0x00, 0x40,
+    // Challenge parameter -- see kClientDataHash
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+    // Application parameter
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
+    // Max response length
+    0x00, 0x00,
+    // clang-format on
+};
+
 // Sample U2F sign request parameters used in example 7 of the CTAP spec.
 // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#using-the-ctap2-authenticatormakecredential-command-with-ctap1-u2f-authenticators
 constexpr uint8_t kU2fSignKeyHandle[] = {
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index ed42af0..e30688d 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -23,8 +23,10 @@
 #include "device/fido/fido_transport_protocol.h"
 #include "device/fido/get_assertion_request_handler.h"
 #include "device/fido/hid/fake_hid_impl_for_testing.h"
+#include "device/fido/make_credential_task.h"
 #include "device/fido/mock_fido_device.h"
 #include "device/fido/test_callback_receiver.h"
+#include "device/fido/u2f_command_constructor.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -236,11 +238,15 @@
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutUvSupport);
+  device->ExpectRequestAndRespondWith(
+      MakeCredentialTask::GetTouchRequest(device.get()).EncodeAsCBOR(),
+      test_data::kTestMakeCredentialResponse);
 
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(get_assertion_callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kUserConsentButCredentialNotRecognized,
+            get_assertion_callback().status());
 }
 
 TEST_F(FidoGetAssertionHandlerTest,
@@ -256,10 +262,14 @@
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
+  device->ExpectRequestAndRespondWith(
+      ConstructBogusU2fRegistrationCommand(),
+      test_data::kApduEncodedNoErrorRegisterResponse);
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(get_assertion_callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kUserConsentButCredentialNotRecognized,
+            get_assertion_callback().status());
 }
 
 TEST_F(FidoGetAssertionHandlerTest, IncorrectRpIdHash) {
diff --git a/device/fido/make_credential_handler_unittest.cc b/device/fido/make_credential_handler_unittest.cc
index dee7783..c5c9afca 100644
--- a/device/fido/make_credential_handler_unittest.cc
+++ b/device/fido/make_credential_handler_unittest.cc
@@ -22,6 +22,7 @@
 #include "device/fido/fido_test_data.h"
 #include "device/fido/fido_transport_protocol.h"
 #include "device/fido/make_credential_request_handler.h"
+#include "device/fido/make_credential_task.h"
 #include "device/fido/mock_fido_device.h"
 #include "device/fido/test_callback_receiver.h"
 #include "device/fido/virtual_ctap2_device.h"
@@ -189,46 +190,58 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, false /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
               UserVerificationRequirement::kRequired));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
+  device->ExpectRequestAndRespondWith(
+      test_data::kU2fBogusRegisterCommandApdu,
+      test_data::kApduEncodedNoErrorRegisterResponse);
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kAuthenticatorMissingUserVerification,
+            callback().status());
 }
 
 TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) {
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, true /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
               UserVerificationRequirement::kPreferred));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
   auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
+  device->ExpectRequestAndRespondWith(
+      test_data::kU2fBogusRegisterCommandApdu,
+      test_data::kApduEncodedNoErrorRegisterResponse);
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kAuthenticatorMissingResidentKeys,
+            callback().status());
 }
 
 TEST_F(FidoMakeCredentialHandlerTest, UserVerificationRequirementNotMet) {
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, false /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
               UserVerificationRequirement::kRequired));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutUvSupport);
+  device->ExpectRequestAndRespondWith(
+      MakeCredentialTask::GetTouchRequest(device.get()).EncodeAsCBOR(),
+      test_data::kTestMakeCredentialResponse);
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kAuthenticatorMissingUserVerification,
+            callback().status());
 }
 
 // TODO(crbug.com/873710): Platform authenticators are temporarily disabled if
@@ -242,7 +255,7 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, false /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
 
   // MakeCredentialHandler will not dispatch the kAny request to the platform
@@ -265,7 +278,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kCrossPlatform,
-              false /* require_resident_key */,
+              /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
 
   // kCloudAssistedBluetoothLowEnergy not yet supported for MakeCredential.
@@ -290,7 +303,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kPlatform,
-              false /* require_resident_key */,
+              /*require_resident_key=*/false,
               UserVerificationRequirement::kRequired));
 
   ExpectAllowedTransportsForRequestAre(request_handler.get(),
@@ -301,16 +314,21 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, true /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
               UserVerificationRequirement::kPreferred));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
   auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
       test_data::kTestGetInfoResponseWithoutResidentKeySupport);
+  device->ExpectRequestAndRespondWith(
+      MakeCredentialTask::GetTouchRequest(device.get()).EncodeAsCBOR(),
+      test_data::kTestMakeCredentialResponse);
+
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kAuthenticatorMissingResidentKeys,
+            callback().status());
 }
 
 TEST_F(FidoMakeCredentialHandlerTest,
@@ -320,7 +338,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kCrossPlatform,
-              true /* require_resident_key */,
+              /*require_resident_key=*/true,
               UserVerificationRequirement::kRequired));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
@@ -359,7 +377,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kPlatform,
-              true /* require_resident_key */,
+              /*require_resident_key=*/true,
               UserVerificationRequirement::kRequired));
 
   callback().WaitForCallback();
@@ -378,7 +396,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kCrossPlatform,
-              false /* require_resident_key */,
+              /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
@@ -408,7 +426,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kPlatform,
-              true /* require_resident_key */,
+              /*require_resident_key=*/true,
               UserVerificationRequirement::kRequired));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
@@ -427,7 +445,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kCrossPlatform,
-              false /* require_resident_key */,
+              /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
 
   ExpectAllowedTransportsForRequestAre(request_handler.get(), kBleAndNfc);
@@ -437,7 +455,7 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, false /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
   discovery()->WaitForCallToStartAndSimulateSuccess();
 
@@ -466,7 +484,7 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, true /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
               UserVerificationRequirement::kPreferred));
 
   discovery()->WaitForCallToStartAndSimulateSuccess();
@@ -485,14 +503,15 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, true /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
               UserVerificationRequirement::kPreferred));
 
   discovery()->WaitForCallToStartAndSimulateSuccess();
   discovery()->AddDevice(std::move(device));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
-  EXPECT_FALSE(callback().was_called());
+  EXPECT_EQ(FidoReturnCode::kAuthenticatorMissingResidentKeys,
+            callback().status());
 }
 
 // If a device with transport type kInternal returns a
@@ -512,7 +531,7 @@
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
               AuthenticatorAttachment::kPlatform,
-              false /* require_resident_key */,
+              /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
@@ -532,7 +551,7 @@
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
           AuthenticatorSelectionCriteria(
-              AuthenticatorAttachment::kAny, false /* require_resident_key */,
+              AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
               UserVerificationRequirement::kPreferred));
 
   discovery()->WaitForCallToStartAndSimulateSuccess();
diff --git a/device/fido/mock_fido_device.cc b/device/fido/mock_fido_device.cc
index 27030a5..02282bf 100644
--- a/device/fido/mock_fido_device.cc
+++ b/device/fido/mock_fido_device.cc
@@ -52,11 +52,14 @@
 // static
 std::unique_ptr<MockFidoDevice> MockFidoDevice::MakeCtapWithGetInfoExpectation(
     base::Optional<base::span<const uint8_t>> get_info_response) {
-  auto device = std::make_unique<MockFidoDevice>();
-  device->StubGetId();
   if (!get_info_response) {
     get_info_response = test_data::kTestAuthenticatorGetInfoResponse;
   }
+
+  auto get_info = ReadCTAPGetInfoResponse(*get_info_response);
+  CHECK(get_info);
+  auto device = MockFidoDevice::MakeCtap(std::move(*get_info));
+  device->StubGetId();
   device->ExpectCtap2CommandAndRespondWith(
       CtapRequestCommand::kAuthenticatorGetInfo, std::move(get_info_response));
   return device;
diff --git a/device/vr/windows/compositor_base.cc b/device/vr/windows/compositor_base.cc
index 1a714ab0..d4e0807 100644
--- a/device/vr/windows/compositor_base.cc
+++ b/device/vr/windows/compositor_base.cc
@@ -64,10 +64,6 @@
     // frame, we allow the renderer to receive poses.
     std::move(delayed_get_frame_data_callback_).Run();
   }
-
-  if (delayed_overlay_get_frame_data_callback_ && overlay_visible_) {
-    std::move(delayed_overlay_get_frame_data_callback_).Run();
-  }
 }
 
 void XRCompositorCommon::SubmitFrameMissing(int16_t frame_index,
@@ -250,7 +246,6 @@
 
   // Kill outstanding overlays:
   overlay_visible_ = false;
-  delayed_overlay_get_frame_data_callback_.Reset();
   overlay_binding_.Close();
 
   texture_helper_.SetSourceAndOverlayVisible(false, false);
@@ -402,16 +397,6 @@
   DCHECK(overlay_visible_);
   TRACE_EVENT_INSTANT0("xr", "RequestOverlayPose", TRACE_EVENT_SCOPE_THREAD);
 
-  // If we've already given out a pose for the current frame delay giving out a
-  // pose until the next frame we are visible.
-  if (pending_frame_ && pending_frame_->overlay_has_pose_) {
-    DCHECK(!delayed_overlay_get_frame_data_callback_);
-    delayed_overlay_get_frame_data_callback_ =
-        base::BindOnce(&XRCompositorCommon::RequestNextOverlayPose,
-                       base::Unretained(this), std::move(callback));
-    return;
-  }
-
   // Ensure we have a pending frame.
   StartPendingFrame();
   pending_frame_->overlay_has_pose_ = true;
diff --git a/device/vr/windows/compositor_base.h b/device/vr/windows/compositor_base.h
index 49fa920..e19a071b 100644
--- a/device/vr/windows/compositor_base.h
+++ b/device/vr/windows/compositor_base.h
@@ -150,7 +150,6 @@
   bool webxr_visible_ = true;   // The browser may hide a presenting session.
   bool overlay_visible_ = false;
   base::OnceCallback<void()> delayed_get_frame_data_callback_;
-  base::OnceCallback<void()> delayed_overlay_get_frame_data_callback_;
 
   gfx::RectF left_webxr_bounds_;
   gfx::RectF right_webxr_bounds_;
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index f4f76e4..1c6b8a32 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -670,7 +670,6 @@
     "platform/modules/bluetooth/web_bluetooth.mojom",
   ]
   public_deps = [
-    ":android_mojo_bindings",
     "//components/services/font/public/interfaces",
     "//device/bluetooth/public/mojom",
     "//mojo/public/mojom/base",
@@ -703,32 +702,6 @@
   export_header_blink = "third_party/blink/public/platform/web_common.h"
 }
 
-mojom("android_mojo_bindings") {
-  visibility = [ ":mojo_bindings" ]
-  visibility_blink = [ ":mojo_bindings_blink" ]
-  sources = [
-    "platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom",
-    "platform/modules/installation/installation.mojom",
-    "platform/modules/mediasession/media_session.mojom",
-  ]
-  public_deps = [
-    "//mojo/public/mojom/base",
-    "//services/media_session/public/mojom",
-    "//ui/gfx/geometry/mojo",
-    "//url/mojom:url_mojom_gurl",
-  ]
-
-  component_output_prefix = "blink_android_mojo_bindings"
-
-  # See comment above.
-  export_class_attribute = "CONTENT_EXPORT"
-  export_define = "CONTENT_IMPLEMENTATION=1"
-  export_header = "content/common/content_export.h"
-  export_class_attribute_blink = "BLINK_PLATFORM_EXPORT"
-  export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
-  export_header_blink = "third_party/blink/public/platform/web_common.h"
-}
-
 # Note that this intentionally depends on the generator target of the mojom
 # target instead of the mojom target itself directly. This is to ensure that the
 # dependencies are header-only and don't link against any bindings code.
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index b953e9b1..343679a 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -183,10 +183,13 @@
 mojom("android_mojo_bindings") {
   sources = [
     "document_metadata/copyless_paste.mojom",
+    "font_unique_name_lookup/font_unique_name_lookup.mojom",
     "input/input_host.mojom",
     "input/input_messages.mojom",
+    "installation/installation.mojom",
     "installedapp/installed_app_provider.mojom",
     "installedapp/related_application.mojom",
+    "mediasession/media_session.mojom",
     "payments/payment_request.mojom",
     "remote_objects/remote_objects.mojom",
     "webauthn/authenticator.mojom",
@@ -198,6 +201,7 @@
   public_deps = [
     "//components/payments/mojom",
     "//mojo/public/mojom/base",
+    "//services/media_session/public/mojom",
     "//url/mojom:url_mojom_gurl",
     "//url/mojom:url_mojom_origin",
   ]
diff --git a/third_party/blink/public/platform/modules/font_unique_name_lookup/OWNERS b/third_party/blink/public/mojom/font_unique_name_lookup/OWNERS
similarity index 100%
rename from third_party/blink/public/platform/modules/font_unique_name_lookup/OWNERS
rename to third_party/blink/public/mojom/font_unique_name_lookup/OWNERS
diff --git a/third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom b/third_party/blink/public/mojom/font_unique_name_lookup/font_unique_name_lookup.mojom
similarity index 100%
rename from third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom
rename to third_party/blink/public/mojom/font_unique_name_lookup/font_unique_name_lookup.mojom
diff --git a/third_party/blink/public/platform/modules/installation/OWNERS b/third_party/blink/public/mojom/installation/OWNERS
similarity index 100%
rename from third_party/blink/public/platform/modules/installation/OWNERS
rename to third_party/blink/public/mojom/installation/OWNERS
diff --git a/third_party/blink/public/platform/modules/installation/installation.mojom b/third_party/blink/public/mojom/installation/installation.mojom
similarity index 100%
rename from third_party/blink/public/platform/modules/installation/installation.mojom
rename to third_party/blink/public/mojom/installation/installation.mojom
diff --git a/third_party/blink/public/platform/modules/mediasession/OWNERS b/third_party/blink/public/mojom/mediasession/OWNERS
similarity index 100%
rename from third_party/blink/public/platform/modules/mediasession/OWNERS
rename to third_party/blink/public/mojom/mediasession/OWNERS
diff --git a/third_party/blink/public/platform/modules/mediasession/media_session.mojom b/third_party/blink/public/mojom/mediasession/media_session.mojom
similarity index 100%
rename from third_party/blink/public/platform/modules/mediasession/media_session.mojom
rename to third_party/blink/public/mojom/mediasession/media_session.mojom
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h b/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h
index e7b9b57..21446a9 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h
+++ b/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h
@@ -16,9 +16,6 @@
 using WebMediaCapabilitiesDecodingInfoCallbacks =
     WebCallbacks<std::unique_ptr<WebMediaCapabilitiesDecodingInfo>, void>;
 
-using WebMediaCapabilitiesEncodingInfoCallbacks =
-    WebCallbacks<std::unique_ptr<WebMediaCapabilitiesInfo>, void>;
-
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_CALLBACKS_H_
diff --git a/third_party/blink/public/platform/web_media_recorder_handler.h b/third_party/blink/public/platform/web_media_recorder_handler.h
index a918cc3..a7f597e 100644
--- a/third_party/blink/public/platform/web_media_recorder_handler.h
+++ b/third_party/blink/public/platform/web_media_recorder_handler.h
@@ -9,7 +9,7 @@
 
 #include "third_party/blink/public/platform/web_common.h"
 
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h"
+#include "base/callback.h"
 #include "third_party/blink/public/platform/web_string.h"
 
 namespace blink {
@@ -17,6 +17,7 @@
 class WebMediaRecorderHandlerClient;
 struct WebMediaConfiguration;
 class WebMediaStream;
+struct WebMediaCapabilitiesInfo;
 
 // Platform interface of a MediaRecorder.
 class BLINK_PLATFORM_EXPORT WebMediaRecorderHandler {
@@ -52,9 +53,10 @@
 
   // Implements WICG Media Capabilities encodingInfo() call for local encoding.
   // https://wicg.github.io/media-capabilities/#media-capabilities-interface
-  virtual void EncodingInfo(
-      const WebMediaConfiguration&,
-      std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks>) {}
+  using OnMediaCapabilitiesEncodingInfoCallback =
+      base::OnceCallback<void(std::unique_ptr<WebMediaCapabilitiesInfo>)>;
+  virtual void EncodingInfo(const WebMediaConfiguration&,
+                            OnMediaCapabilitiesEncodingInfoCallback) {}
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/platform/web_media_stream_center.h b/third_party/blink/public/platform/web_media_stream_center.h
index a8cb5d1..571d245 100644
--- a/third_party/blink/public/platform/web_media_stream_center.h
+++ b/third_party/blink/public/platform/web_media_stream_center.h
@@ -59,7 +59,8 @@
   // Caller must take the ownership of the returned |WebAudioSourceProvider|
   // object.
   virtual WebAudioSourceProvider* CreateWebAudioSourceFromMediaStreamTrack(
-      const WebMediaStreamTrack&) {
+      const WebMediaStreamTrack&,
+      int context_sample_rate) {
     return nullptr;
   }
 };
diff --git a/third_party/blink/public/platform/web_transmission_encoding_info_handler.h b/third_party/blink/public/platform/web_transmission_encoding_info_handler.h
index 7818d081..bdae8b2 100644
--- a/third_party/blink/public/platform/web_transmission_encoding_info_handler.h
+++ b/third_party/blink/public/platform/web_transmission_encoding_info_handler.h
@@ -26,10 +26,10 @@
   // It implements WICG Media Capabilities encodingInfo() call for transmission
   // encoding.
   // https://wicg.github.io/media-capabilities/#media-capabilities-interface
-  virtual void EncodingInfo(
-      const WebMediaConfiguration&,
-      std::unique_ptr<blink::WebMediaCapabilitiesEncodingInfoCallbacks>)
-      const = 0;
+  using OnMediaCapabilitiesEncodingInfoCallback =
+      base::OnceCallback<void(std::unique_ptr<WebMediaCapabilitiesInfo>)>;
+  virtual void EncodingInfo(const WebMediaConfiguration&,
+                            OnMediaCapabilitiesEncodingInfoCallback) const = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index 75c79e9e..ccc863c 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -2668,7 +2668,8 @@
     return;
   const AtomicString& event_type = event.type();
   if (event_type == event_type_names::kKeydown ||
-      event_type == event_type_names::kKeypress) {
+      event_type == event_type_names::kKeypress ||
+      event_type == event_type_names::kKeyup) {
     if (event.IsKeyboardEvent()) {
       if (LocalFrame* frame = GetDocument().GetFrame()) {
         frame->GetEventHandler().DefaultKeyboardEventHandler(
diff --git a/third_party/blink/renderer/core/editing/editing_style.cc b/third_party/blink/renderer/core/editing/editing_style.cc
index 117d671..9fda59c70 100644
--- a/third_party/blink/renderer/core/editing/editing_style.cc
+++ b/third_party/blink/renderer/core/editing/editing_style.cc
@@ -61,7 +61,6 @@
 #include "third_party/blink/renderer/core/html/html_font_element.h"
 #include "third_party/blink/renderer/core/html/html_span_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
-#include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
diff --git a/third_party/blink/renderer/core/editing/editor.cc b/third_party/blink/renderer/core/editing/editor.cc
index 52b36532..21d575b 100644
--- a/third_party/blink/renderer/core/editing/editor.cc
+++ b/third_party/blink/renderer/core/editing/editor.cc
@@ -80,7 +80,6 @@
 #include "third_party/blink/renderer/core/input/event_handler.h"
 #include "third_party/blink/renderer/core/input_type_names.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
-#include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/loader/empty_clients.h"
 #include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
 #include "third_party/blink/renderer/core/page/drag_data.h"
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.cc b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
index 4587109..8ace57e 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
@@ -17,6 +17,7 @@
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
+#include "third_party/blink/renderer/core/layout/ng/inline/ng_offset_mapping.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/text/unicode_utilities.h"
 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.h b/third_party/blink/renderer/core/editing/finder/find_buffer.h
index be236144..35a882f 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.h
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.h
@@ -8,11 +8,12 @@
 #include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
 #include "third_party/blink/renderer/core/editing/finder/find_options.h"
 #include "third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h"
-#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
+#include "third_party/blink/renderer/core/editing/position.h"
 
 namespace blink {
 
 class LayoutBlockFlow;
+class NGOffsetMapping;
 class Node;
 class WebString;
 
@@ -195,12 +196,7 @@
   Vector<BufferNodeMapping> buffer_node_mappings_;
   Vector<DisplayLockContext::ScopedForcedUpdate> scoped_forced_update_list_;
 
-  // For legacy layout, we need to save a unique_ptr of the NGOffsetMapping
-  // because nobody owns it. In LayoutNG, the NGOffsetMapping is owned by
-  // the corresponding LayoutBlockFlow, so we don't need to save it.
-  std::unique_ptr<NGOffsetMapping> offset_mapping_storage_;
   const NGOffsetMapping* offset_mapping_ = nullptr;
-
   bool mapping_needs_recalc_ = false;
 };
 
diff --git a/third_party/blink/renderer/core/editing/visible_units_line.cc b/third_party/blink/renderer/core/editing/visible_units_line.cc
index e6e4be8b..cbb84df 100644
--- a/third_party/blink/renderer/core/editing/visible_units_line.cc
+++ b/third_party/blink/renderer/core/editing/visible_units_line.cc
@@ -187,6 +187,12 @@
         << "Logical line boundary for BidiCaretAffinity is not implemented yet";
 
     const NGCaretPosition caret_position = ComputeNGCaretPosition(adjusted);
+    if (caret_position.IsNull()) {
+      // TODO(crbug.com/947593): Support |ComputeNGCaretPosition()| on content
+      // hidden by 'text-overflow:ellipsis' so that we always have a non-null
+      // |caret_position| here.
+      return PositionWithAffinityTemplate<Strategy>();
+    }
     DCHECK(caret_position.fragment);
     DCHECK(caret_position.fragment->ContainerLineBox());
     const NGPaintFragment* line_box_paint =
@@ -465,6 +471,12 @@
         << "Logical line boundary for BidiCaretAffinity is not implemented yet";
 
     const NGCaretPosition caret_position = ComputeNGCaretPosition(adjusted);
+    if (caret_position.IsNull()) {
+      // TODO(crbug.com/947593): Support |ComputeNGCaretPosition()| on content
+      // hidden by 'text-overflow:ellipsis' so that we always have a non-null
+      // |caret_position| here.
+      return PositionWithAffinityTemplate<Strategy>();
+    }
     DCHECK(caret_position.fragment);
     DCHECK(caret_position.fragment->ContainerLineBox());
     const NGPaintFragment* line_box_paint =
diff --git a/third_party/blink/renderer/core/editing/visible_units_line_test.cc b/third_party/blink/renderer/core/editing/visible_units_line_test.cc
index 3300eea..8fdb727 100644
--- a/third_party/blink/renderer/core/editing/visible_units_line_test.cc
+++ b/third_party/blink/renderer/core/editing/visible_units_line_test.cc
@@ -699,4 +699,27 @@
   EXPECT_FALSE(InSameLine(position1, position2));
 }
 
+// https://crbug.com/947462
+TEST_F(VisibleUnitsLineTest, TextOverflowEllipsis) {
+  LoadAhem();
+  InsertStyleElement(R"HTML(
+    div {
+      width: 40px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font: 10px/10px Ahem;
+    })HTML");
+  SetBodyContent("<div>foo foo</div>");
+  Element* div = GetDocument().QuerySelector("div");
+  Node* text = div->firstChild();
+  // TODO(crbug.com/947593): Support Start/EndOfLine with ellipsis on LayoutNG
+  EXPECT_EQ(
+      LayoutNGEnabled() ? Position() : Position(text, 0),
+      StartOfLine(CreateVisiblePositionInDOMTree(*text, 6)).DeepEquivalent());
+  EXPECT_EQ(
+      LayoutNGEnabled() ? Position() : Position(text, 7),
+      EndOfLine(CreateVisiblePositionInDOMTree(*text, 6)).DeepEquivalent());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_frame_content_dumper.cc b/third_party/blink/renderer/core/exported/web_frame_content_dumper.cc
index 8dc229b..76b81a95 100644
--- a/third_party/blink/renderer/core/exported/web_frame_content_dumper.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_content_dumper.cc
@@ -16,9 +16,6 @@
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/html_element_type_helpers.h"
 #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
-#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
-#include "third_party/blink/renderer/core/layout/layout_table_row.h"
-#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
 #include "third_party/blink/renderer/core/layout/layout_tree_as_text.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/core/exported/web_layer_test.cc b/third_party/blink/renderer/core/exported/web_layer_test.cc
index 4efef7e..d7c9ee5 100644
--- a/third_party/blink/renderer/core/exported/web_layer_test.cc
+++ b/third_party/blink/renderer/core/exported/web_layer_test.cc
@@ -4,6 +4,7 @@
 
 #include "build/build_config.h"
 #include "cc/layers/picture_layer.h"
+#include "cc/trees/effect_node.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
 #include "third_party/blink/public/web/web_script_source.h"
@@ -741,4 +742,46 @@
               (squashed_bg_color == SK_ColorCYAN));
 }
 
+TEST_P(WebLayerListSimTest, NonDrawableLayersIgnoredForRenderSurfaces) {
+  // TODO(crbug.com/765003): CAP may make different layerization decisions. When
+  // CAP gets closer to launch, this test should be updated to pass.
+  if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
+    return;
+
+  InitializeWithHTML(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        #outer {
+          width: 100px;
+          height: 100px;
+          opacity: 0.5;
+          background: blue;
+        }
+        #inner {
+          width: 10px;
+          height: 10px;
+          will-change: transform;
+        }
+      </style>
+      <div id='outer'>
+        <div id='inner'></div>
+      </div>
+  )HTML");
+
+  Compositor().BeginFrame();
+
+  ASSERT_GE(ContentLayerCount(), 2u);
+  auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
+  EXPECT_FALSE(inner_element_layer->DrawsContent());
+  auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
+  EXPECT_TRUE(outer_element_layer->DrawsContent());
+
+  // The inner element layer is only needed for hit testing and does not draw
+  // content, so it should not cause a render surface.
+  auto effect_tree_index = outer_element_layer->effect_tree_index();
+  auto* effect_node = GetPropertyTrees()->effect_tree.Node(effect_tree_index);
+  EXPECT_EQ(effect_node->opacity, 0.5f);
+  EXPECT_FALSE(effect_node->has_render_surface);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/text_control_element.cc b/third_party/blink/renderer/core/html/forms/text_control_element.cc
index 448af62..ea641f8b 100644
--- a/third_party/blink/renderer/core/html/forms/text_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/text_control_element.cc
@@ -52,9 +52,7 @@
 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
 #include "third_party/blink/renderer/core/html_names.h"
-#include "third_party/blink/renderer/core/layout/layout_block.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
-#include "third_party/blink/renderer/core/layout/layout_theme.h"
 #include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index 5099439..0c4abb2a 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -329,13 +329,15 @@
       // events that aren't necessarily arrow keys.
       DefaultArrowEventHandler(event, possible_focused_node);
     }
-  }
-  if (event->type() == event_type_names::kKeypress) {
+  } else if (event->type() == event_type_names::kKeypress) {
     frame_->GetEditor().HandleKeyboardEvent(event);
     if (event->DefaultHandled())
       return;
     if (event->charCode() == ' ')
       DefaultSpaceEventHandler(event, possible_focused_node);
+  } else if (event->type() == event_type_names::kKeyup) {
+    if (event->key() == "Enter")
+      DefaultEnterEventHandler(event);
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc
index de9b41aa..cdac8554 100644
--- a/third_party/blink/renderer/core/layout/layout_block.cc
+++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -1464,9 +1464,14 @@
 
   // Size-contained elements don't consider their contents for preferred sizing.
   if (ShouldApplySizeContainment()) {
-    max_logical_width = LayoutUnit(scrollbar_width);
-    min_logical_width = LayoutUnit(scrollbar_width);
-    return;
+    // For multicol containers we need the column gaps. So allow descending into
+    // the flow thread, which will take care of that.
+    const auto* block_flow = ToLayoutBlockFlowOrNull(this);
+    if (!block_flow || !block_flow->MultiColumnFlowThread()) {
+      max_logical_width = LayoutUnit(scrollbar_width);
+      min_logical_width = LayoutUnit(scrollbar_width);
+      return;
+    }
   }
 
   if (ChildrenInline()) {
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index 5ab78594..9abe2a2 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -332,13 +332,17 @@
     }
   }
 
+  bool old_style_is_containing_block =
+      old_style && (old_style->CanContainAbsolutePositionObjects() ||
+                    old_style->HasFilter());
+  bool new_style_is_containing_block =
+      old_style &&
+      (new_style.CanContainAbsolutePositionObjects() || new_style.HasFilter());
   // If we are changing to/from static, we need to reposition
   // out-of-flow positioned descendants.
-  if (old_style && old_style->GetPosition() != new_style.GetPosition() &&
-      (new_style.GetPosition() == EPosition::kStatic ||
-       old_style->GetPosition() == EPosition::kStatic)) {
+  if (old_style_is_containing_block != new_style_is_containing_block) {
     LayoutBlock* abs_containing_block = nullptr;
-    if (old_style->GetPosition() == EPosition::kStatic) {
+    if (!old_style_is_containing_block) {
       abs_containing_block = ContainingBlockForAbsolutePosition();
     } else {
       // When position was not static, containingBlockForAbsolutePosition
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc b/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc
index e1e76b9..31e226b 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.cc
@@ -1332,8 +1332,6 @@
 }
 
 void LayoutMultiColumnFlowThread::ComputePreferredLogicalWidths() {
-  LayoutFlowThread::ComputePreferredLogicalWidths();
-
   // The min/max intrinsic widths calculated really tell how much space elements
   // need when laid out inside the columns. In order to eventually end up with
   // the desired column width, we need to convert them to values pertaining to
@@ -1341,9 +1339,20 @@
   const ComputedStyle* multicol_style = MultiColumnBlockFlow()->Style();
   LayoutUnit column_count(
       multicol_style->HasAutoColumnCount() ? 1 : multicol_style->ColumnCount());
-  LayoutUnit column_width;
   LayoutUnit gap_extra((column_count - 1) *
                        ColumnGap(*multicol_style, LayoutUnit()));
+
+  if (MultiColumnBlockFlow()->ShouldApplySizeContainment()) {
+    LayoutUnit size = gap_extra;
+    if (!multicol_style->HasAutoColumnWidth())
+      size += LayoutUnit(multicol_style->ColumnWidth()) * column_count;
+    max_preferred_logical_width_ = min_preferred_logical_width_ = size;
+    ClearPreferredLogicalWidthsDirty();
+    return;
+  }
+
+  LayoutFlowThread::ComputePreferredLogicalWidths();
+  LayoutUnit column_width;
   if (multicol_style->HasAutoColumnWidth()) {
     min_preferred_logical_width_ =
         min_preferred_logical_width_ * column_count + gap_extra;
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.cc b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.cc
index b5bd25c..965bced 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.cc
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.cc
@@ -103,11 +103,22 @@
 
 LayoutUnit LayoutMultiColumnSpannerPlaceholder::MinPreferredLogicalWidth()
     const {
+  // There should be no contribution from a spanner if the multicol container is
+  // size-contained. Normally we'd stop at the object that has contain:size
+  // applied, but for multicol, we descend into the children, in order to get
+  // the flow thread to calculate the correct preferred width (to honor
+  // column-count, column-width and column-gap). Since spanner placeholders are
+  // siblings of the flow thread, we need this check.
+  if (MultiColumnBlockFlow()->ShouldApplySizeContainment())
+    return LayoutUnit();
   return layout_object_in_flow_thread_->MinPreferredLogicalWidth();
 }
 
 LayoutUnit LayoutMultiColumnSpannerPlaceholder::MaxPreferredLogicalWidth()
     const {
+  // See above.
+  if (MultiColumnBlockFlow()->ShouldApplySizeContainment())
+    return LayoutUnit();
   return layout_object_in_flow_thread_->MaxPreferredLogicalWidth();
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
index 888a6bc2..eb832797 100644
--- a/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
+++ b/third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h
@@ -26,6 +26,10 @@
       const ComputedStyle& parent_style,
       LayoutBox&);
 
+  LayoutBlockFlow* MultiColumnBlockFlow() const {
+    return ToLayoutBlockFlow(Parent());
+  }
+
   LayoutMultiColumnFlowThread* FlowThread() const {
     return ToLayoutBlockFlow(Parent())->MultiColumnFlowThread();
   }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index cce0291..345cd3a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -150,24 +150,43 @@
   LayoutDescendantCandidates(&descendant_candidates, only_layout,
                              &placed_objects);
 
-  // Gather candidates that weren't present in the OOF candidates list.
-  // This occurs when a candidate is separated from container by a legacy node.
-  // E.g.
-  // <div style="position: relative;">
-  //   <div style="display: flex;">
-  //     <div style="position: absolute;"></div>
-  //   </div>
-  // </div>
+  if (only_layout)
+    return;
+
+  while (SweepLegacyDescendants(&placed_objects)) {
+    container_builder_->GetAndClearOutOfFlowDescendantCandidates(
+        &descendant_candidates, current_container);
+
+    // We must have at least one new candidate, otherwise we shouldn't have
+    // entered this branch.
+    DCHECK_GT(descendant_candidates.size(), 0u);
+
+    LayoutDescendantCandidates(&descendant_candidates, only_layout,
+                               &placed_objects);
+  }
+}
+
+// Gather candidates that weren't present in the OOF candidates list.
+// This occurs when a candidate is separated from container by a legacy node.
+// E.g.
+// <div style="position: relative;">
+//   <div style="display: flex;">
+//     <div style="position: absolute;"></div>
+//   </div>
+// </div>
+// Returns false if no new candidates were found.
+bool NGOutOfFlowLayoutPart::SweepLegacyDescendants(
+    HashSet<const LayoutObject*>* placed_objects) {
   const LayoutBlock* container_block =
       ToLayoutBlockOrNull(container_builder_->GetLayoutObject());
-  if (!container_block || only_layout)
-    return;
+  if (!container_block)
+    return false;
   TrackedLayoutBoxListHashSet* legacy_objects =
       container_block->PositionedObjects();
-  if (!legacy_objects || legacy_objects->size() == placed_objects.size())
-    return;
+  if (!legacy_objects || legacy_objects->size() == placed_objects->size())
+    return false;
   for (LayoutObject* legacy_object : *legacy_objects) {
-    if (placed_objects.Contains(legacy_object))
+    if (placed_objects->Contains(legacy_object))
       continue;
 
     // Flex OOF children may have center alignment or similar, and in order
@@ -203,16 +222,7 @@
         NGBlockNode(layout_box), static_position,
         css_container->IsBox() ? nullptr : css_container);
   }
-
-  container_builder_->GetAndClearOutOfFlowDescendantCandidates(
-      &descendant_candidates, current_container);
-
-  // We must have at least one new candidate, otherwise we shouldn't have
-  // entered this branch.
-  DCHECK_GT(descendant_candidates.size(), 0u);
-
-  LayoutDescendantCandidates(&descendant_candidates, only_layout,
-                             &placed_objects);
+  return true;
 }
 
 NGOutOfFlowLayoutPart::ContainingBlockInfo
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 388b6e9..1e9648b 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
@@ -90,6 +90,8 @@
     }
   };
 
+  bool SweepLegacyDescendants(HashSet<const LayoutObject*>* placed_objects);
+
   ContainingBlockInfo GetContainingBlockInfo(
       const NGOutOfFlowPositionedDescendant&) const;
 
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index dac2dc4..87c488a 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -178,7 +178,7 @@
           "Sec-Fetch-Mode",
           FetchRequestModeToString(request.GetFetchRequestMode()));
       request.AddHttpHeaderField("Sec-Fetch-Site", site_value);
-      request.AddHttpHeaderField("Sec-Fetch-User", "?F");
+      // We don't set `Sec-Fetch-User` for subresource requests.
     }
   }
 }
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
index 44160e0..48b6136 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_finder.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/range.h"
 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
index da2655d..3d9364f2 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
+++ b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
@@ -168,10 +168,20 @@
     KeyboardEvent* event) {
   DCHECK(page_->GetSettings().GetSpatialNavigationEnabled());
 
-  if (interest_element_) {
-    interest_element_->focus(FocusParams(SelectionBehaviorOnFocus::kReset,
-                                         kWebFocusTypeSpatialNavigation,
-                                         nullptr));
+  Element* interest_element = GetInterestedElement();
+
+  if (!interest_element)
+    return false;
+
+  if (event->type() == event_type_names::kKeydown) {
+    if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled()) {
+      interest_element->focus(FocusParams(SelectionBehaviorOnFocus::kReset,
+                                          kWebFocusTypeSpatialNavigation,
+                                          nullptr));
+    }
+    interest_element->SetActive(true);
+  } else if (event->type() == event_type_names::kKeyup) {
+    interest_element->SetActive(false);
   }
 
   return true;
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_test.cc b/third_party/blink/renderer/core/page/spatial_navigation_test.cc
index 09114a8..37b916c 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_test.cc
+++ b/third_party/blink/renderer/core/page/spatial_navigation_test.cc
@@ -5,12 +5,17 @@
 #include "third_party/blink/renderer/core/page/spatial_navigation.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_keyboard_event.h"
 #include "third_party/blink/renderer/core/exported/web_remote_frame_impl.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
+#include "ui/events/keycodes/dom/dom_key.h"
 
 namespace blink {
 
@@ -653,4 +658,54 @@
   EXPECT_TRUE(HasRemoteFrame(iframe));
 }
 
+class SpatialNavigationWithFocuslessModeTest
+    : public SpatialNavigationTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  SpatialNavigationWithFocuslessModeTest() : use_focusless_mode_(GetParam()) {}
+
+  void SetUp() override {
+    SpatialNavigationTest::SetUp();
+    GetDocument().GetSettings()->SetSpatialNavigationEnabled(true);
+  }
+
+ private:
+  ScopedFocuslessSpatialNavigationForTest use_focusless_mode_;
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         SpatialNavigationWithFocuslessModeTest,
+                         ::testing::Bool());
+
+TEST_P(SpatialNavigationWithFocuslessModeTest, PressEnterKeyActiveElement) {
+  SetBodyInnerHTML("<button id='b'>hello</button>");
+
+  Element* b = GetDocument().getElementById("b");
+
+  // Move interest to button.
+  WebKeyboardEvent arrow_down{WebInputEvent::kRawKeyDown,
+                              WebInputEvent::kNoModifiers,
+                              WebInputEvent::GetStaticTimeStampForTests()};
+  arrow_down.dom_key = ui::DomKey::ARROW_DOWN;
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(arrow_down);
+
+  arrow_down.SetType(WebInputEvent::kKeyUp);
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(arrow_down);
+
+  EXPECT_FALSE(b->IsActive());
+
+  // Enter key down add :active state to element.
+  WebKeyboardEvent enter{WebInputEvent::kRawKeyDown,
+                         WebInputEvent::kNoModifiers,
+                         WebInputEvent::GetStaticTimeStampForTests()};
+  enter.dom_key = ui::DomKey::ENTER;
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(enter);
+  EXPECT_TRUE(b->IsActive());
+
+  // Enter key up remove :active state to element.
+  enter.SetType(WebInputEvent::kKeyUp);
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(enter);
+  EXPECT_FALSE(b->IsActive());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/accessibility/ax_inline_text_box.cc b/third_party/blink/renderer/modules/accessibility/ax_inline_text_box.cc
index a9c06509..02a7e39b 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_inline_text_box.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_inline_text_box.cc
@@ -140,8 +140,9 @@
   DCHECK(!IsDetached());
   if (!inline_text_box_ || !ax_object_cache_)
     return nullptr;
-
   LineLayoutText line_layout_text = inline_text_box_->GetLineLayoutItem();
+  if (!line_layout_text)
+    return nullptr;
   return ax_object_cache_->GetOrCreate(
       LineLayoutAPIShim::LayoutObjectFrom(line_layout_text));
 }
diff --git a/third_party/blink/renderer/modules/installation/installation_service_impl.h b/third_party/blink/renderer/modules/installation/installation_service_impl.h
index 34cae46..37f87780 100644
--- a/third_party/blink/renderer/modules/installation/installation_service_impl.h
+++ b/third_party/blink/renderer/modules/installation/installation_service_impl.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_INSTALLATION_INSTALLATION_SERVICE_IMPL_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_INSTALLATION_INSTALLATION_SERVICE_IMPL_H_
 
-#include "third_party/blink/public/platform/modules/installation/installation.mojom-blink.h"
+#include "third_party/blink/public/mojom/installation/installation.mojom-blink.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 
diff --git a/third_party/blink/renderer/modules/media_capabilities/BUILD.gn b/third_party/blink/renderer/modules/media_capabilities/BUILD.gn
index d37c6e8e..bad5d87 100644
--- a/third_party/blink/renderer/modules/media_capabilities/BUILD.gn
+++ b/third_party/blink/renderer/modules/media_capabilities/BUILD.gn
@@ -10,8 +10,6 @@
     "media_capabilities.h",
     "media_capabilities_decoding_info_callbacks.cc",
     "media_capabilities_decoding_info_callbacks.h",
-    "media_capabilities_encoding_info_callbacks.cc",
-    "media_capabilities_encoding_info_callbacks.h",
     "navigator_media_capabilities.cc",
     "navigator_media_capabilities.h",
   ]
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
index 230dd86..9265e0582 100644
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
+++ b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
@@ -24,7 +24,6 @@
 #include "third_party/blink/renderer/modules/encryptedmedia/encrypted_media_utils.h"
 #include "third_party/blink/renderer/modules/media_capabilities/media_capabilities_decoding_info.h"
 #include "third_party/blink/renderer/modules/media_capabilities/media_capabilities_decoding_info_callbacks.h"
-#include "third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.h"
 #include "third_party/blink/renderer/modules/media_capabilities/media_capabilities_info.h"
 #include "third_party/blink/renderer/modules/media_capabilities/media_configuration.h"
 #include "third_party/blink/renderer/modules/media_capabilities/media_decoding_configuration.h"
@@ -32,6 +31,7 @@
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
 #include "third_party/blink/renderer/platform/network/parsed_content_type.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 
 namespace blink {
 
@@ -397,6 +397,22 @@
   return media::IsSupportedAudioType({audio_codec});
 }
 
+void OnMediaCapabilitiesEncodingInfo(
+    ScriptPromiseResolver* resolver,
+    std::unique_ptr<WebMediaCapabilitiesInfo> result) {
+  if (!resolver->GetExecutionContext() ||
+      resolver->GetExecutionContext()->IsContextDestroyed()) {
+    return;
+  }
+
+  Persistent<MediaCapabilitiesInfo> info(MediaCapabilitiesInfo::Create());
+  info->setSupported(result->supported);
+  info->setSmooth(result->smooth);
+  info->setPowerEfficient(result->power_efficient);
+
+  resolver->Resolve(std::move(info));
+}
+
 }  // anonymous namespace
 
 MediaCapabilities::MediaCapabilities() = default;
@@ -526,9 +542,9 @@
   if (configuration->type() == "transmission") {
     if (auto* handler =
             Platform::Current()->TransmissionEncodingInfoHandler()) {
-      handler->EncodingInfo(
-          ToWebMediaConfiguration(configuration),
-          std::make_unique<MediaCapabilitiesEncodingInfoCallbacks>(resolver));
+      handler->EncodingInfo(ToWebMediaConfiguration(configuration),
+                            WTF::Bind(&OnMediaCapabilitiesEncodingInfo,
+                                      WrapPersistent(resolver)));
       return promise;
     }
     resolver->Reject(DOMException::Create(
@@ -541,9 +557,9 @@
     if (auto handler = Platform::Current()->CreateMediaRecorderHandler(
             ExecutionContext::From(script_state)
                 ->GetTaskRunner(TaskType::kInternalMediaRealTime))) {
-      handler->EncodingInfo(
-          ToWebMediaConfiguration(configuration),
-          std::make_unique<MediaCapabilitiesEncodingInfoCallbacks>(resolver));
+      handler->EncodingInfo(ToWebMediaConfiguration(configuration),
+                            WTF::Bind(&OnMediaCapabilitiesEncodingInfo,
+                                      WrapPersistent(resolver)));
       return promise;
     }
     resolver->Reject(DOMException::Create(
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.cc
deleted file mode 100644
index 044b9c8..0000000
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.cc
+++ /dev/null
@@ -1,37 +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 "third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.h"
-
-#include "third_party/blink/renderer/modules/media_capabilities/media_capabilities_info.h"
-
-namespace blink {
-
-MediaCapabilitiesEncodingInfoCallbacks::MediaCapabilitiesEncodingInfoCallbacks(
-    ScriptPromiseResolver* resolver)
-    : resolver_(resolver) {}
-
-MediaCapabilitiesEncodingInfoCallbacks::
-    ~MediaCapabilitiesEncodingInfoCallbacks() = default;
-
-void MediaCapabilitiesEncodingInfoCallbacks::OnSuccess(
-    std::unique_ptr<WebMediaCapabilitiesInfo> result) {
-  if (!resolver_->GetExecutionContext() ||
-      resolver_->GetExecutionContext()->IsContextDestroyed()) {
-    return;
-  }
-
-  Persistent<MediaCapabilitiesInfo> info(MediaCapabilitiesInfo::Create());
-  info->setSupported(result->supported);
-  info->setSmooth(result->smooth);
-  info->setPowerEfficient(result->power_efficient);
-
-  resolver_->Resolve(std::move(info));
-}
-
-void MediaCapabilitiesEncodingInfoCallbacks::OnError() {
-  NOTREACHED();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.h b/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.h
deleted file mode 100644
index 6f847b7..0000000
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities_encoding_info_callbacks.h
+++ /dev/null
@@ -1,31 +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 THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CAPABILITIES_MEDIA_CAPABILITIES_ENCODING_INFO_CALLBACKS_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CAPABILITIES_MEDIA_CAPABILITIES_ENCODING_INFO_CALLBACKS_H_
-
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_callbacks.h"
-#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
-#include "third_party/blink/renderer/platform/heap/persistent.h"
-
-namespace blink {
-
-class MediaCapabilitiesEncodingInfoCallbacks
-    : public WebMediaCapabilitiesEncodingInfoCallbacks {
- public:
-  explicit MediaCapabilitiesEncodingInfoCallbacks(
-      ScriptPromiseResolver* resolver);
-
-  ~MediaCapabilitiesEncodingInfoCallbacks() override;
-
-  void OnSuccess(std::unique_ptr<WebMediaCapabilitiesInfo>) override;
-  void OnError() override;
-
- private:
-  Persistent<ScriptPromiseResolver> resolver_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIA_CAPABILITIES_MEDIA_CAPABILITIES_ENCODING_INFO_CALLBACKS_H_
diff --git a/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.h b/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.h
index 16b53eb..afaf74f3 100644
--- a/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.h
+++ b/third_party/blink/renderer/modules/mediasession/media_metadata_sanitizer.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASESSION_MEDIA_METADATA_SANITIZER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASESSION_MEDIA_METADATA_SANITIZER_H_
 
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom-blink.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom-blink.h"
 #include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/modules/mediasession/media_session.h b/third_party/blink/renderer/modules/mediasession/media_session.h
index 28972573..1f2a3b54 100644
--- a/third_party/blink/renderer/modules/mediasession/media_session.h
+++ b/third_party/blink/renderer/modules/mediasession/media_session.h
@@ -7,7 +7,7 @@
 
 #include <memory>
 #include "mojo/public/cpp/bindings/binding.h"
-#include "third_party/blink/public/platform/modules/mediasession/media_session.mojom-blink.h"
+#include "third_party/blink/public/mojom/mediasession/media_session.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
index cc69226..313e3eb 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.cc
@@ -696,9 +696,10 @@
   return !Ended() && HasEventListeners(event_type_names::kEnded);
 }
 
-std::unique_ptr<AudioSourceProvider> MediaStreamTrack::CreateWebAudioSource() {
+std::unique_ptr<AudioSourceProvider> MediaStreamTrack::CreateWebAudioSource(
+    int context_sample_rate) {
   return MediaStreamCenter::Instance().CreateWebAudioSourceFromMediaStreamTrack(
-      Component());
+      Component(), context_sample_rate);
 }
 
 void MediaStreamTrack::RegisterMediaStream(MediaStream* media_stream) {
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track.h b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
index ba90ba9..dbf1979 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track.h
@@ -111,7 +111,8 @@
   // ContextLifecycleObserver
   void ContextDestroyed(ExecutionContext*) override;
 
-  std::unique_ptr<AudioSourceProvider> CreateWebAudioSource();
+  std::unique_ptr<AudioSourceProvider> CreateWebAudioSource(
+      int context_sample_rate);
 
   void Trace(blink::Visitor*) override;
 
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_container.cc b/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
index 1bb7881..8424a91 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
@@ -460,7 +460,7 @@
     if (provider_) {
       provider_->GetRegistrationForReady(
           WTF::Bind(&ServiceWorkerContainer::OnGetRegistrationForReady,
-                    WrapWeakPersistent(this)));
+                    WrapPersistent(this)));
     }
   }
 
diff --git a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
index 15dbcc9..b22626c 100644
--- a/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_stream_audio_source_node.cc
@@ -146,7 +146,7 @@
   // Use the first audio track in the media stream.
   MediaStreamTrack* audio_track = audio_tracks[0];
   std::unique_ptr<AudioSourceProvider> provider =
-      audio_track->CreateWebAudioSource();
+      audio_track->CreateWebAudioSource(context.sampleRate());
 
   MediaStreamAudioSourceNode* node =
       MakeGarbageCollected<MediaStreamAudioSourceNode>(
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index fbb363a..ed38474 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -696,7 +696,9 @@
     "fonts/shaping/glyph_bounds_accumulator.h",
     "fonts/shaping/harfbuzz_face.cc",
     "fonts/shaping/harfbuzz_face.h",
+    "fonts/shaping/harfbuzz_font_cache.cc",
     "fonts/shaping/harfbuzz_font_cache.h",
+    "fonts/shaping/harfbuzz_font_data.h",
     "fonts/shaping/harfbuzz_shaper.cc",
     "fonts/shaping/harfbuzz_shaper.h",
     "fonts/shaping/run_segmenter.cc",
diff --git a/third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.cc b/third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.cc
index be4552b..b7b6d66 100644
--- a/third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.cc
+++ b/third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.cc
@@ -4,7 +4,7 @@
 
 #include "third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.h"
 #include "mojo/public/mojom/base/shared_memory.mojom-blink.h"
-#include "third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom-blink.h"
+#include "third_party/blink/public/mojom/font_unique_name_lookup/font_unique_name_lookup.mojom-blink.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/platform/fonts/font_global_context.cc b/third_party/blink/renderer/platform/fonts/font_global_context.cc
index 20f6dd7..ff2fd24 100644
--- a/third_party/blink/renderer/platform/fonts/font_global_context.cc
+++ b/third_party/blink/renderer/platform/fonts/font_global_context.cc
@@ -6,6 +6,7 @@
 
 #include "third_party/blink/renderer/platform/fonts/font_cache.h"
 #include "third_party/blink/renderer/platform/fonts/font_unique_name_lookup.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h"
 #include "third_party/blink/renderer/platform/wtf/thread_specific.h"
 
 namespace blink {
diff --git a/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.cc b/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.cc
index ef45e99..1b5e433 100644
--- a/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.cc
+++ b/third_party/blink/renderer/platform/fonts/font_unique_name_lookup.cc
@@ -8,7 +8,7 @@
 #include "third_party/blink/public/platform/platform.h"
 
 #if defined(OS_ANDROID)
-#include "third_party/blink/public/platform/modules/font_unique_name_lookup/font_unique_name_lookup.mojom-blink.h"
+#include "third_party/blink/public/mojom/font_unique_name_lookup/font_unique_name_lookup.mojom-blink.h"
 #include "third_party/blink/renderer/platform/fonts/android/font_unique_name_lookup_android.h"
 #elif defined(OS_LINUX)
 #include "third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.h"
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
index d42cbd0..046fd9e 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/platform/fonts/font_global_context.h"
 #include "third_party/blink/renderer/platform/fonts/font_platform_data.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
 #include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
 #include "third_party/blink/renderer/platform/fonts/skia/skia_text_metrics.h"
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.cc
new file mode 100644
index 0000000..02690dd
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.cc
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium 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/fonts/shaping/harfbuzz_font_cache.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h"
+
+namespace blink {
+
+HbFontCacheEntry::HbFontCacheEntry(hb_font_t* font)
+    : hb_font_(HbFontUniquePtr(font)),
+      hb_font_data_(std::make_unique<HarfBuzzFontData>()) {}
+
+HbFontCacheEntry::~HbFontCacheEntry() = default;
+
+scoped_refptr<HbFontCacheEntry> HbFontCacheEntry::Create(hb_font_t* hb_font) {
+  DCHECK(hb_font);
+  return base::AdoptRef(new HbFontCacheEntry(hb_font));
+}
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h
index 4ecf1f2..d3ad8a1 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_cache.h
@@ -8,16 +8,15 @@
 #include <memory>
 
 #include "third_party/blink/renderer/platform/fonts/font_metrics.h"
-#include "third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h"
-#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
 #include "third_party/blink/renderer/platform/fonts/unicode_range_set.h"
-#include "third_party/blink/renderer/platform/wtf/assertions.h"
 
 struct hb_font_t;
 struct hb_face_t;
 
 namespace blink {
 
+struct HarfBuzzFontData;
+
 struct HbFontDeleter {
   void operator()(hb_font_t* font);
 };
@@ -30,101 +29,6 @@
 
 using HbFaceUniquePtr = std::unique_ptr<hb_face_t, HbFaceDeleter>;
 
-const unsigned kInvalidFallbackMetricsValue = static_cast<unsigned>(-1);
-
-// struct to carry user-pointer data for hb_font_t callback
-// functions/operations, that require information related to a font scaled to a
-// particular size.
-struct HarfBuzzFontData {
-  USING_FAST_MALLOC(HarfBuzzFontData);
-
- public:
-  HarfBuzzFontData()
-      : font_(),
-        space_in_gpos_(SpaceGlyphInOpenTypeTables::Unknown),
-        space_in_gsub_(SpaceGlyphInOpenTypeTables::Unknown),
-        vertical_data_(nullptr),
-        range_set_(nullptr) {}
-
-  // The vertical origin and vertical advance functions in HarfBuzzFace require
-  // the ascent and height metrics as fallback in case no specific vertical
-  // layout information is found from the font.
-  void UpdateFallbackMetricsAndScale(
-      const FontPlatformData& platform_data,
-      HarfBuzzFace::VerticalLayoutCallbacks vertical_layout) {
-    float ascent = 0;
-    float descent = 0;
-    unsigned dummy_ascent_inflation = 0;
-    unsigned dummy_descent_inflation = 0;
-
-    font_ = SkFont();
-    platform_data.SetupSkFont(&font_);
-
-    if (UNLIKELY(vertical_layout == HarfBuzzFace::PrepareForVerticalLayout)) {
-      FontMetrics::AscentDescentWithHacks(
-          ascent, descent, dummy_ascent_inflation, dummy_descent_inflation,
-          platform_data, font_);
-      ascent_fallback_ = ascent;
-      // Simulate the rounding that FontMetrics does so far for returning the
-      // integer Height()
-      height_fallback_ = lroundf(ascent) + lroundf(descent);
-
-      int units_per_em =
-          platform_data.GetHarfBuzzFace()->UnitsPerEmFromHeadTable();
-      if (!units_per_em) {
-        DLOG(ERROR)
-            << "Units per EM is 0 for font used in vertical writing mode.";
-      }
-      size_per_unit_ = platform_data.size() / (units_per_em ? units_per_em : 1);
-    } else {
-      ascent_fallback_ = kInvalidFallbackMetricsValue;
-      height_fallback_ = kInvalidFallbackMetricsValue;
-      size_per_unit_ = kInvalidFallbackMetricsValue;
-    }
-  }
-
-  float SizePerUnit(const SkTypeface& typeface) const {
-    if (size_per_unit_ != kInvalidFallbackMetricsValue)
-      return size_per_unit_;
-    int units_per_em = typeface.getUnitsPerEm();
-    size_per_unit_ = font_.getSize() / units_per_em;
-    return size_per_unit_;
-  }
-
-  scoped_refptr<OpenTypeVerticalData> VerticalData() {
-    if (!vertical_data_) {
-      DCHECK_NE(ascent_fallback_, kInvalidFallbackMetricsValue);
-      DCHECK_NE(height_fallback_, kInvalidFallbackMetricsValue);
-      DCHECK_NE(size_per_unit_, kInvalidFallbackMetricsValue);
-
-      vertical_data_ =
-          OpenTypeVerticalData::CreateUnscaled(font_.refTypeface());
-    }
-    vertical_data_->SetScaleAndFallbackMetrics(size_per_unit_, ascent_fallback_,
-                                               height_fallback_);
-    return vertical_data_;
-  }
-
-  SkFont font_;
-
-  // Capture these scaled fallback metrics from FontPlatformData so that a
-  // OpenTypeVerticalData object can be constructed from them when needed.
-  mutable float size_per_unit_;
-  float ascent_fallback_;
-  float height_fallback_;
-
-  enum class SpaceGlyphInOpenTypeTables { Unknown, Present, NotPresent };
-
-  SpaceGlyphInOpenTypeTables space_in_gpos_;
-  SpaceGlyphInOpenTypeTables space_in_gsub_;
-
-  scoped_refptr<OpenTypeVerticalData> vertical_data_;
-  scoped_refptr<UnicodeRangeSet> range_set_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(HarfBuzzFontData);
-};
-
 // Though we have FontCache class, which provides the cache mechanism for
 // WebKit's font objects, we also need additional caching layer for HarfBuzz to
 // reduce the number of hb_font_t objects created. Without it, we would create
@@ -134,18 +38,15 @@
 // FontPlatformData object independent of size, then consider using this here.
 class HbFontCacheEntry : public RefCounted<HbFontCacheEntry> {
  public:
-  static scoped_refptr<HbFontCacheEntry> Create(hb_font_t* hb_font) {
-    DCHECK(hb_font);
-    return base::AdoptRef(new HbFontCacheEntry(hb_font));
-  }
+  static scoped_refptr<HbFontCacheEntry> Create(hb_font_t* hb_font);
 
   hb_font_t* HbFont() { return hb_font_.get(); }
   HarfBuzzFontData* HbFontData() { return hb_font_data_.get(); }
 
+  ~HbFontCacheEntry();
+
  private:
-  explicit HbFontCacheEntry(hb_font_t* font)
-      : hb_font_(HbFontUniquePtr(font)),
-        hb_font_data_(std::make_unique<HarfBuzzFontData>()) {}
+  explicit HbFontCacheEntry(hb_font_t* font);
 
   HbFontUniquePtr hb_font_;
   std::unique_ptr<HarfBuzzFontData> hb_font_data_;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h
new file mode 100644
index 0000000..b1e5bb0
--- /dev/null
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_font_data.h
@@ -0,0 +1,115 @@
+// Copyright 2019 The Chromium 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_FONTS_SHAPING_HARFBUZZ_FONT_DATA_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARFBUZZ_FONT_DATA_H_
+
+#include "third_party/blink/renderer/platform/fonts/font_platform_data.h"
+#include "third_party/blink/renderer/platform/fonts/opentype/open_type_vertical_data.h"
+#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
+#include "third_party/blink/renderer/platform/wtf/assertions.h"
+#include "third_party/skia/include/core/SkFont.h"
+
+struct hb_font_t;
+
+namespace blink {
+
+const unsigned kInvalidFallbackMetricsValue = static_cast<unsigned>(-1);
+
+// The HarfBuzzFontData struct carries user-pointer data for hb_font_t callback
+// functions/operations. It contains metrics and OpenType layout information
+// related to a font scaled to a particular size.
+struct HarfBuzzFontData {
+  USING_FAST_MALLOC(HarfBuzzFontData);
+
+ public:
+  HarfBuzzFontData()
+      : font_(),
+        space_in_gpos_(SpaceGlyphInOpenTypeTables::Unknown),
+        space_in_gsub_(SpaceGlyphInOpenTypeTables::Unknown),
+        vertical_data_(nullptr),
+        range_set_(nullptr) {}
+
+  // The vertical origin and vertical advance functions in HarfBuzzFace require
+  // the ascent and height metrics as fallback in case no specific vertical
+  // layout information is found from the font.
+  void UpdateFallbackMetricsAndScale(
+      const FontPlatformData& platform_data,
+      HarfBuzzFace::VerticalLayoutCallbacks vertical_layout) {
+    float ascent = 0;
+    float descent = 0;
+    unsigned dummy_ascent_inflation = 0;
+    unsigned dummy_descent_inflation = 0;
+
+    font_ = SkFont();
+    platform_data.SetupSkFont(&font_);
+
+    if (UNLIKELY(vertical_layout == HarfBuzzFace::PrepareForVerticalLayout)) {
+      FontMetrics::AscentDescentWithHacks(
+          ascent, descent, dummy_ascent_inflation, dummy_descent_inflation,
+          platform_data, font_);
+      ascent_fallback_ = ascent;
+      // Simulate the rounding that FontMetrics does so far for returning the
+      // integer Height()
+      height_fallback_ = lroundf(ascent) + lroundf(descent);
+
+      int units_per_em =
+          platform_data.GetHarfBuzzFace()->UnitsPerEmFromHeadTable();
+      if (!units_per_em) {
+        DLOG(ERROR)
+            << "Units per EM is 0 for font used in vertical writing mode.";
+      }
+      size_per_unit_ = platform_data.size() / (units_per_em ? units_per_em : 1);
+    } else {
+      ascent_fallback_ = kInvalidFallbackMetricsValue;
+      height_fallback_ = kInvalidFallbackMetricsValue;
+      size_per_unit_ = kInvalidFallbackMetricsValue;
+    }
+  }
+
+  float SizePerUnit(const SkTypeface& typeface) const {
+    if (size_per_unit_ != kInvalidFallbackMetricsValue)
+      return size_per_unit_;
+    int units_per_em = typeface.getUnitsPerEm();
+    size_per_unit_ = font_.getSize() / units_per_em;
+    return size_per_unit_;
+  }
+
+  scoped_refptr<OpenTypeVerticalData> VerticalData() {
+    if (!vertical_data_) {
+      DCHECK_NE(ascent_fallback_, kInvalidFallbackMetricsValue);
+      DCHECK_NE(height_fallback_, kInvalidFallbackMetricsValue);
+      DCHECK_NE(size_per_unit_, kInvalidFallbackMetricsValue);
+
+      vertical_data_ =
+          OpenTypeVerticalData::CreateUnscaled(font_.refTypeface());
+    }
+    vertical_data_->SetScaleAndFallbackMetrics(size_per_unit_, ascent_fallback_,
+                                               height_fallback_);
+    return vertical_data_;
+  }
+
+  SkFont font_;
+
+  // Capture these scaled fallback metrics from FontPlatformData so that a
+  // OpenTypeVerticalData object can be constructed from them when needed.
+  mutable float size_per_unit_;
+  float ascent_fallback_;
+  float height_fallback_;
+
+  enum class SpaceGlyphInOpenTypeTables { Unknown, Present, NotPresent };
+
+  SpaceGlyphInOpenTypeTables space_in_gpos_;
+  SpaceGlyphInOpenTypeTables space_in_gsub_;
+
+  scoped_refptr<OpenTypeVerticalData> vertical_data_;
+  scoped_refptr<UnicodeRangeSet> range_set_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HarfBuzzFontData);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_HARFBUZZ_FONT_DATA_H_
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 5fefc23..40fcb7d0 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -1048,6 +1048,8 @@
   Vector<bool> pending_render_surfaces;
   pending_render_surfaces.resize(effect_tree.size());
   for (const auto& layer : layers) {
+    if (!layer->DrawsContent())
+      continue;
     bool descendant_may_have_backdrop_filter = false;
     auto* effect = effect_tree.Node(layer->effect_tree_index());
     bool may_have_backdrop_filter =
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_center.cc b/third_party/blink/renderer/platform/mediastream/media_stream_center.cc
index b357fe4a..94546a10 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_center.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_center.cc
@@ -97,11 +97,13 @@
 
 std::unique_ptr<AudioSourceProvider>
 MediaStreamCenter::CreateWebAudioSourceFromMediaStreamTrack(
-    MediaStreamComponent* track) {
+    MediaStreamComponent* track,
+    int context_sample_rate) {
   DCHECK(track);
   if (private_) {
-    return std::make_unique<MediaStreamWebAudioSource>(base::WrapUnique(
-        private_->CreateWebAudioSourceFromMediaStreamTrack(track)));
+    return std::make_unique<MediaStreamWebAudioSource>(
+        base::WrapUnique(private_->CreateWebAudioSourceFromMediaStreamTrack(
+            track, context_sample_rate)));
   }
 
   return nullptr;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_center.h b/third_party/blink/renderer/platform/mediastream/media_stream_center.h
index 48fb098e..1b4ea83 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_center.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_center.h
@@ -63,7 +63,8 @@
   void DidSetMediaStreamTrackEnabled(MediaStreamComponent*);
   void DidSetContentHint(MediaStreamComponent*);
   std::unique_ptr<AudioSourceProvider> CreateWebAudioSourceFromMediaStreamTrack(
-      MediaStreamComponent*);
+      MediaStreamComponent*,
+      int context_sample_rate);
 
   void DidCreateMediaStreamAndTracks(MediaStreamDescriptor*);
 
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
index 71425315..307bb5e 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
@@ -118,7 +118,6 @@
 crbug.com/591099 external/wpt/css/css-animations/Element-getAnimations-dynamic-changes.tentative.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-animations/Element-getAnimations.tentative.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-contain/contain-size-grid-002.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-contain/contain-size-multicol-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-contain/contain-style-counters-004.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 ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 4f08349..2aaa945a 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -545,7 +545,6 @@
 crbug.com/882367 external/wpt/css/css-contain/contain-paint-clip-015.html [ Failure ]
 crbug.com/882367 external/wpt/css/css-contain/contain-paint-clip-016.html [ Failure ]
 crbug.com/855261 external/wpt/css/css-contain/contain-size-grid-002.html [ Failure ]
-crbug.com/863454 external/wpt/css/css-contain/contain-size-multicol-001.html [ Failure ]
 crbug.com/869296 external/wpt/css/css-contain/contain-style-counters-004.html [ Failure ]
 crbug.com/882383 external/wpt/css/css-contain/counter-scoping-001.html [ Failure ]
 crbug.com/882383 external/wpt/css/css-contain/counter-scoping-002.html [ Failure ]
@@ -2980,6 +2979,8 @@
 crbug.com/939181 virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html [ Failure Timeout ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-lists/list-item-definition.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-contain/contain-content-011.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-contain/contain-strict-011.html [ Failure ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-textarea-element/multiline-placeholder-cr.html [ Failure ]
@@ -3235,7 +3236,6 @@
 crbug.com/626703 external/wpt/css/css-text/writing-system/writing-system-line-break-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-text/writing-system/writing-system-line-break-002.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-text/writing-system/writing-system-text-transform-001.html [ Failure ]
-crbug.com/903383 external/wpt/css/filter-effects/filter-cb-abspos-inline-003.html [ Failure ]
 crbug.com/903383 external/wpt/css/filter-effects/css-filters-animation-combined-001.html [ Failure ]
 crbug.com/903383 external/wpt/css/filter-effects/css-filters-animation-blur.html [ Failure ]
 crbug.com/903383 external/wpt/css/filter-effects/filters-test-brightness-003.html [ Failure ]
@@ -3660,8 +3660,6 @@
 crbug.com/626703 external/wpt/css/cssom-view/scroll-behavior-smooth.html [ Timeout ]
 crbug.com/626703 external/wpt/websockets/Create-Secure-extensions-empty.any.html [ Timeout ]
 crbug.com/626703 external/wpt/websockets/Create-Secure-extensions-empty.any.worker.html [ Timeout ]
-crbug.com/626703 external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html [ Skip ]
-crbug.com/626703 external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html [ Skip ]
 crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html [ Skip ]
 crbug.com/626703 virtual/streaming-preload/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html [ Skip ]
 crbug.com/626703 external/wpt/html/browsers/history/joint-session-history/joint-session-history-remove-iframe.html [ Timeout ]
diff --git a/third_party/blink/web_tests/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/compositing/lots-of-img-layers-with-opacity-expected.png
new file mode 100644
index 0000000..201b2cc
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/lots-of-img-layers-with-opacity-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index b208671c..fe1da414 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -51227,6 +51227,18 @@
      {}
     ]
    ],
+   "css/css-lists/list-item-definition.html": [
+    [
+     "/css/css-lists/list-item-definition.html",
+     [
+      [
+       "/css/css-lists/list-item-definition-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-lists/list-marker-with-lineheight-and-overflow-hidden-001.html": [
     [
      "/css/css-lists/list-marker-with-lineheight-and-overflow-hidden-001.html",
@@ -97897,6 +97909,42 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html": [
     [
      "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html",
@@ -97969,6 +98017,42 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001.html": [
     [
      "/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001.html",
@@ -99541,6 +99625,18 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html": [
+    [
+     "/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html",
+     [
+      [
+       "/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-dyn-resize-001.html": [
     [
      "/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-dyn-resize-001.html",
@@ -141575,6 +141671,11 @@
      {}
     ]
    ],
+   "css/css-lists/list-item-definition-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-lists/list-marker-with-lineheight-and-overflow-hidden-001-ref.html": [
     [
      {}
@@ -157195,6 +157296,21 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001-ref.html": [
     [
      {}
@@ -157225,6 +157341,21 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001-ref.html": [
     [
      {}
@@ -157775,6 +157906,11 @@
      {}
     ]
    ],
+   "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-dyn-resize-001-ref.html": [
     [
      {}
@@ -223537,6 +223673,12 @@
      {}
     ]
    ],
+   "css/css-position/position-absolute-crash-chrome-004.html": [
+    [
+     "/css/css-position/position-absolute-crash-chrome-004.html",
+     {}
+    ]
+   ],
    "css/css-position/position-absolute-dynamic-containing-block.html": [
     [
      "/css/css-position/position-absolute-dynamic-containing-block.html",
@@ -247272,7 +247414,9 @@
    "fetch/sec-metadata/iframe.tentative.https.sub.html": [
     [
      "/fetch/sec-metadata/iframe.tentative.https.sub.html",
-     {}
+     {
+      "testdriver": true
+     }
     ]
    ],
    "fetch/sec-metadata/iframe.tentative.sub.html": [
@@ -257309,18 +257453,6 @@
      {}
     ]
    ],
-   "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html": [
-    [
-     "/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html",
-     {}
-    ]
-   ],
-   "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html": [
-    [
-     "/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html",
-     {}
-    ]
-   ],
    "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html": [
     [
      "/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html",
@@ -365166,6 +365298,14 @@
    "df54e8fb0df8146f896c2abd136d63d930d92d1c",
    "testharness"
   ],
+  "css/css-lists/list-item-definition-ref.html": [
+   "72a4c90f4ada6a889fa71a27aea20146ea07bc9b",
+   "support"
+  ],
+  "css/css-lists/list-item-definition.html": [
+   "edae4b96bd98876170145cf9bc9c834d188b5640",
+   "reftest"
+  ],
   "css/css-lists/list-marker-with-lineheight-and-overflow-hidden-001-ref.html": [
    "ae6486147e28502db80f6b887b1a6b16c30184f2",
    "support"
@@ -369022,6 +369162,10 @@
    "c443e836e57a361c7de9bc5f9a6debe7ff832fe0",
    "testharness"
   ],
+  "css/css-position/position-absolute-crash-chrome-004.html": [
+   "cc6de63e0011a3588592bd7bf27d5230ea8d2a48",
+   "testharness"
+  ],
   "css/css-position/position-absolute-dynamic-containing-block.html": [
    "3968f685849663574ca213fcb90dc5fb3eaffaa3",
    "testharness"
@@ -402442,6 +402586,30 @@
    "0ed82ff8fc5b8adaeef2bfd973cd79973b8f4fa6",
    "reftest"
   ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html": [
+   "1f41028faac4dfb444745567e4ac9087fefbea37",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html": [
+   "4dc92103eabc6231b4d8260cbb35ac7337b2362b",
+   "reftest"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html": [
+   "2f6ff8322d13942bfff3c54b6a3f132a05cdfb5a",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html": [
+   "cf7e2310169c57658ebc688db3a927152868d97a",
+   "reftest"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html": [
+   "c8647b19dd6c5b64a0aab86ff2f394f93ac7ff3d",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html": [
+   "b6cca54a8c65ca798b836aa7b6ea92627482dc76",
+   "reftest"
+  ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001-ref.html": [
    "efadf9b5df49ca45f829208c03dc0feb875c74f1",
    "support"
@@ -402490,6 +402658,30 @@
    "41458550272f896d47e15210c82f4c22d386d9e0",
    "reftest"
   ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html": [
+   "3d0f38f7c07c4b73cc41418c87e5bc902e7ed862",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html": [
+   "d3a69e5f357bef37bcd2dbb3a0877ea4d8bdc977",
+   "reftest"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html": [
+   "6079764591ff8806c88bc2644c4248e12ea0ca0a",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html": [
+   "7ea22635d35b4471362545d0abcf7f417511323d",
+   "reftest"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html": [
+   "e7f03ff2d31e30b837b654ba41de220daef1c753",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html": [
+   "1de39348b718cd90530be9a0089b4075f3190a8f",
+   "reftest"
+  ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001-ref.html": [
    "93a263b5b2ffc131bdfe4989c3d90c312cadf6e4",
    "support"
@@ -403454,6 +403646,14 @@
    "cf54aabe9936b3963e7343d566d957633fc26c69",
    "reftest"
   ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html": [
+   "fca146671cfb80cd3ae0264a8466be0b9823b3ae",
+   "support"
+  ],
+  "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html": [
+   "76beb2a41db70d2f00de159d1285fbc5310a3d40",
+   "reftest"
+  ],
   "css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-dyn-resize-001-ref.html": [
    "5e5561cadf2ddbd6dfd2c7b4819f146f61be006b",
    "support"
@@ -414463,19 +414663,19 @@
    "support"
   ],
   "fetch/sec-metadata/embed.tentative.https.sub.html": [
-   "b97de9ef2fd4c0c41a5671ad7cd20c5814ab5223",
+   "c46765b37c6325260882751e9e592c2b55d8b128",
    "testharness"
   ],
   "fetch/sec-metadata/fetch.tentative.https.sub.html": [
-   "dc4b977ac6ec6544215bcf2a41007a6adde2d36e",
+   "bffb4275df8e99c9d06d4fa4ef518e3b1efba747",
    "testharness"
   ],
   "fetch/sec-metadata/font.tentative.https.sub.html": [
-   "9792f2dce9427c3e42f595adb4c67113a6bb29d7",
+   "60163ee714686fc1c3f5f9b7011ce03cf3028e6b",
    "testharness"
   ],
   "fetch/sec-metadata/iframe.tentative.https.sub.html": [
-   "056d8fdba945ddc9e3daed1f6d4f915b084a3da9",
+   "461d9f28fa1e8987e3b05c48cb10fa6f4f8bf326",
    "testharness"
   ],
   "fetch/sec-metadata/iframe.tentative.sub.html": [
@@ -414483,7 +414683,7 @@
    "testharness"
   ],
   "fetch/sec-metadata/img.tentative.https.sub.html": [
-   "b1216db84285bc9ddd979f3ca27240803ae975f7",
+   "b9fc1af2f3bfcc09b7e9989fd755f3c82d6f24d9",
    "testharness"
   ],
   "fetch/sec-metadata/navigation.https.sub.html": [
@@ -414491,11 +414691,11 @@
    "testharness"
   ],
   "fetch/sec-metadata/object.tentative.https.sub.html": [
-   "474962918b03075b0fa97d199a339b5f504d8c20",
+   "b60ae206c78b3dd8e934dde7a7408fe4a7465932",
    "testharness"
   ],
   "fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html": [
-   "1634a29f3791490aa782435a294442d3a925ebda",
+   "06b58744fb5a26f16f5ed8fc923f1c91989e2eb8",
    "testharness"
   ],
   "fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub-expected.txt": [
@@ -414503,7 +414703,7 @@
    "support"
   ],
   "fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html": [
-   "7647a5b1c89a99ab4c8227a6690c457915e07a62",
+   "e173bb053fedd46f346a0e89990f44cf5cbc1856",
    "testharness"
   ],
   "fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub-expected.txt": [
@@ -414511,7 +414711,7 @@
    "support"
   ],
   "fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html": [
-   "e83d6fd97e844f84dc5fb6c39a3acc99e5e9f087",
+   "d19a1896ad77e69923e219292d9ae4c1ef35e70e",
    "testharness"
   ],
   "fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub-expected.txt": [
@@ -414519,7 +414719,7 @@
    "support"
   ],
   "fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html": [
-   "2033eab5979a619f918443d98788ae3caaefecb5",
+   "0b4fdfeec58b6f353000cf290f345b63c4c9cba9",
    "testharness"
   ],
   "fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub-expected.txt": [
@@ -414527,11 +414727,11 @@
    "support"
   ],
   "fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html": [
-   "c5b6830abed5f197f2640fa1ddb18be9f79480fd",
+   "70c0afef22883469a54a874a955a91c89c4336d7",
    "testharness"
   ],
   "fetch/sec-metadata/report.tentative.https.sub.html": [
-   "405964e08923ae7db3becc5178f99994b8614230",
+   "eb6c76b57c75a70e183ebbc8a9e11e42275ff82e",
    "testharness"
   ],
   "fetch/sec-metadata/report.tentative.https.sub.html.sub.headers": [
@@ -414571,7 +414771,7 @@
    "support"
   ],
   "fetch/sec-metadata/script.tentative.https.sub.html": [
-   "a35e753c7898cc89427fa8dc22ce5fd55325ea8e",
+   "b2874a37da1ebb578d76702bae66abf6fa501b8e",
    "testharness"
   ],
   "fetch/sec-metadata/script.tentative.sub.html": [
@@ -414579,31 +414779,31 @@
    "testharness"
   ],
   "fetch/sec-metadata/serviceworker.tentative.https.sub.html": [
-   "590a6c33475d26e54ef31e7a766f596d4b6d5215",
+   "ee436d9265ff85c3e307c7c6f565e55d7cc84961",
    "testharness"
   ],
   "fetch/sec-metadata/sharedworker.tentative.https.sub.html": [
-   "a1c558ad35dfbdb7d2f35535ba26148f31e3f2c6",
+   "ddd4cc9914eb28a3b85d13366dd84ca722d34d52",
    "testharness"
   ],
   "fetch/sec-metadata/style.tentative.https.sub.html": [
-   "46f64f49bde0ce6c642a517aacaf5af8c3074edc",
+   "19cc16d8017cc2a86c72098fcfdd161ea4080bde",
    "testharness"
   ],
   "fetch/sec-metadata/track.tentative.https.sub.html": [
-   "817e4845edb215cd1fdf8183e6d306a4fe4172c4",
+   "fe525caf15523310e4f67dc5200cc7877d5f3456",
    "testharness"
   ],
   "fetch/sec-metadata/trailing-dot.tentative.https.sub.html": [
-   "85f9c73c6ae22fb34a8bd76b624f286367ba0265",
+   "ff0e842d5118cba40b73f09e29cf98be0bcabb12",
    "testharness"
   ],
   "fetch/sec-metadata/window-open.tentative.https.sub.html": [
-   "ef2bc81824ea5f90551da75daf6e10f41ef2cdcd",
+   "0be9f2ce577d5cdd590e0326ed650455d87f1ee4",
    "testharness"
   ],
   "fetch/sec-metadata/worker.tentative.https.sub.html": [
-   "dc21d0324f442a8aa801dbddb04e0b2fe93464d0",
+   "940e25b2a51a41610980601e0c97be5e4614c9b0",
    "testharness"
   ],
   "fetch/sec-metadata/xslt.tentative.https.sub-expected.txt": [
@@ -414611,7 +414811,7 @@
    "support"
   ],
   "fetch/sec-metadata/xslt.tentative.https.sub.html": [
-   "eea2329900e000c43cb0b347c011d6d4adb8bd46",
+   "0d429266288e8a77bcdc5076d3f248bf0ef509b5",
    "testharness"
   ],
   "fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html": [
@@ -431678,14 +431878,6 @@
    "34ea00abc83ce6f8c83c9b31edd77a69d356b214",
    "testharness"
   ],
-  "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html": [
-   "80a298913a09c2cccbdfc917975ddc6e801cf411",
-   "testharness"
-  ],
-  "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html": [
-   "db3d3b1e15a8fa7ab82947c6fd96bb2d1b5fb2dc",
-   "testharness"
-  ],
   "html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-module.html": [
    "b85d446d8dae01be803e190b593aac6ed921ca08",
    "testharness"
@@ -436723,7 +436915,7 @@
    "support"
   ],
   "interfaces/performance-timeline.idl": [
-   "7766bfd7b09e56e4822643b5fdf56697b20ad3e3",
+   "51752b139589e64088364109d8c5e117c7811e95",
    "support"
   ],
   "interfaces/permissions.idl": [
@@ -449935,7 +450127,7 @@
    "testharness"
   ],
   "performance-timeline/idlharness.any-expected.txt": [
-   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
+   "32878d3cf1b0ddece602c8c53ea21166f1ec3d41",
    "support"
   ],
   "performance-timeline/idlharness.any.js": [
@@ -449943,15 +450135,15 @@
    "testharness"
   ],
   "performance-timeline/idlharness.any.serviceworker-expected.txt": [
-   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
+   "32878d3cf1b0ddece602c8c53ea21166f1ec3d41",
    "support"
   ],
   "performance-timeline/idlharness.any.sharedworker-expected.txt": [
-   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
+   "32878d3cf1b0ddece602c8c53ea21166f1ec3d41",
    "support"
   ],
   "performance-timeline/idlharness.any.worker-expected.txt": [
-   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
+   "32878d3cf1b0ddece602c8c53ea21166f1ec3d41",
    "support"
   ],
   "performance-timeline/performanceentry-tojson.any.js": [
@@ -463231,7 +463423,7 @@
    "testharness"
   ],
   "service-workers/service-worker/fetch-request-xhr.https-expected.txt": [
-   "3de90db5c4ece4734df673d0a066fa7772eb4a33",
+   "76e08357b6e2b1edce325a9ebefde287c962681b",
    "support"
   ],
   "service-workers/service-worker/fetch-request-xhr.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-multicol-001.html b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-multicol-001.html
index 5c0dba9b7..81465c0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-multicol-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/contain-size-multicol-001.html
@@ -18,6 +18,7 @@
 #test {
   background: green;
 
+  contain: size;
   columns: 2 40px;
   column-gap: 20px;
   min-height: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition-ref.html b/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition-ref.html
new file mode 100644
index 0000000..72a4c90
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition-ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<ol>
+  <svg style="display: list-item"></svg>
+  <img style="display: list-item">
+  <img style="display: list-item" alt="Foo">
+  <li value="4">Foo
+  <li>Bar
+</ol>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition.html b/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition.html
new file mode 100644
index 0000000..edae4b9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-lists/list-item-definition.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>The definition of what a list-item is only depends on the display value, and doesn't account for pseudo-elements</title>
+<link rel="help" href="https://drafts.csswg.org/css-lists/#list-item">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1539171">
+<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
+<link rel="author" href="https://mozilla.org" title="Mozilla">
+<link rel="match" href="list-item-definition-ref.html">
+<!-- TODO: Test pseudo-elements, see https://github.com/w3c/csswg-drafts/issues/3766 -->
+<ol>
+  <svg style="display: list-item"></svg>
+  <img style="display: list-item">
+  <img style="display: list-item" alt="Foo">
+  <div style="display: list-item">Foo</div>
+  <li>Bar
+</ol>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/position-absolute-crash-chrome-004.html b/third_party/blink/web_tests/external/wpt/css/css-position/position-absolute-crash-chrome-004.html
new file mode 100644
index 0000000..cc6de63
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/position-absolute-crash-chrome-004.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>CSS Position Absolute: Chrome crash</title>
+<link rel="author" href="mailto:atotic@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=946986">
+<meta name="assert" content="Nested abs/fixed/flex do not crash">
+<style>
+  body { overflow: scroll;}
+  .container {
+    position: relative;
+    contain: paint;
+  }
+  .flex {
+    display: flex;
+  }
+  .fixed {
+    position: fixed;
+  }
+  .abs {
+    position: absolute;
+  }
+</style>
+<!-- LayoutNG currently does not support display:flex.
+  Propagation of descendants across flex boundaries is error prone -->
+<div id="one" class="container" style="">
+  <div class="flex">
+    <div class="abs">
+      <div class="flex">
+        <div id="fixed1" class="fixed">
+          <div id="fixed2" class="fixed"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+test(() => {
+}, 'test passes if it does not crash');
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html
new file mode 100644
index 0000000..1f41028
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: scroll;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html
new file mode 100644
index 0000000..4dc92103
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:scroll' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-002-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: scroll;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html
new file mode 100644
index 0000000..2f6ff83
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: auto;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html
new file mode 100644
index 0000000..cf7e231
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:auto' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-003-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: auto;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html
new file mode 100644
index 0000000..c8647b1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    overflow: hidden;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+    background: lightblue;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .floatLBasic-ref {
+    float: left;
+  }
+  .floatLWidth-ref {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  .zeroHeightContents {
+    color: transparent;
+    height: 0px;
+    width: 0px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow. -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLBasic-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic floatLWidth-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="basic">
+      <!-- We use the out-of-flow "innerContents" to create the correct
+           amount of scrollable overflow to match the testcase, and we
+           use the smaller in-flow "zeroHeightContents" to provide a
+           baseline that we can use to verify the testcase's baseline
+           alignment position. -->
+      <div class="innerContents">inner</div>
+      <div class="zeroHeightContents">i</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html
new file mode 100644
index 0000000..b6cca54a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:hidden' block elements should cause them to be sized as if they had no contents</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-block-004-ref.html">
+  <style>
+  .contain {
+    contain: size;
+    overflow: hidden;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+    background: lightblue;
+  }
+  .height {
+    height: 60px;
+    background: lightblue;
+  }
+  .maxWidth {
+    max-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  .floatLBasic {
+    float: left;
+  }
+  .floatLWidth {
+    float: left;
+    width: 60px;
+  }
+  .flexBaselineCheck {
+    display: flex;
+    align-items: baseline;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!--CSS Test: A size-contained scrollable block with no specified size.-->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified max-width -->
+  <div class="contain maxWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with auto size -->
+  <div class="contain floatLBasic"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained floated scrollable block with
+      specified width -->
+  <div class="contain floatLWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!--CSS Test: A size-contained scrollable block in a
+      baseline-aligning flex container: should size as if it's empty, but
+      still baseline-align using its contents' baseline -->
+  <div class="flexBaselineCheck">
+    outside before
+    <div class="contain">
+      <div class="innerContents">inner</div>
+    </div>
+    outside after
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html
new file mode 100644
index 0000000..3d0f38f7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: scroll;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html
new file mode 100644
index 0000000..d3a69e5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:scroll' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-002-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: scroll;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html
new file mode 100644
index 0000000..6079764
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: auto;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow (i.e. it produces scrollbars of the appropriate size for the
+       amount of overflow). -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html
new file mode 100644
index 0000000..7ea2263
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:auto' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-003-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: auto;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html
new file mode 100644
index 0000000..e7f03ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <style>
+  .basic {
+    display: inline-block;
+    overflow: hidden;
+    position: relative;
+    border: 2px solid green;
+  }
+  .height-ref {
+    height: 60px;
+  }
+  .width-ref {
+    width: 60px;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+    position: absolute;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In the reference-case scenarios here, we use the same DOM as in
+       the testcase, and we simply use 'position: absolute' on the descendants
+       wherever the testcase has 'contain: size' on the container.  This
+       produces an accurate reference rendering, because out-of-flow content
+       doesn't contribute to the container's sizing, but does create scrollable
+       overflow. -->
+  <div class="basic"><div class="innerContents">inner</div></div>
+  <br>
+
+  outside before
+  <div class="basic"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic height-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+  <br>
+
+  <div class="basic width-ref"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html
new file mode 100644
index 0000000..1de3934
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>CSS Test: 'contain: size' on 'overflow:hidden' inline-block elements should cause them to be sized as if they had no contents and baseline-aligned regularly.</title>
+  <link rel="author" title="Daniel Holbert" href="mailto:dholbert@mozilla.com">
+  <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu">
+  <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size">
+  <link rel="match" href="contain-size-inline-block-004-ref.html">
+  <style>
+  .contain {
+    display: inline-block;
+    overflow: hidden;
+    contain:size;
+    border: 2px solid green;
+  }
+  .innerContents {
+    color: transparent;
+    height: 100px;
+    width: 100px;
+  }
+  .minHeight {
+    min-height: 60px;
+  }
+  .height {
+    height: 60px;
+  }
+  .minWidth {
+    min-width: 60px;
+  }
+  .width {
+    width: 60px;
+  }
+  </style>
+</head>
+<body>
+  <!-- NOTE: In all cases below, the expectation is that the size-contained
+       element should be sized as if it had no contents (while honoring
+       whatever sizing properties are provided). -->
+
+  <!-- A size-contained scrollable inline-block with no specified size -->
+  <div class="contain"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block should perform baseline
+       alignment regularly, based on contents' baseline. -->
+  outside before
+  <div class="contain"><div class="innerContents">inner</div></div>
+  outside after
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-height -->
+  <div class="contain minHeight"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified height -->
+  <div class="contain height"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified min-width -->
+  <div class="contain minWidth"><div class="innerContents">inner</div></div>
+  <br>
+
+  <!-- A size-contained scrollable inline-block with specified width -->
+  <div class="contain width"><div class="innerContents">inner</div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html
new file mode 100644
index 0000000..fca14667
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<style>
+  .outer {
+    margin-bottom: 2px;
+    height: 60px;
+  }
+  .inner {
+    background: lime;
+    height: 100%;
+  }
+</style>
+<p>Test passes if you see four 60px-tall lime rows (with platform-appropriate scrollbars on the last one).</p>
+
+<div class="outer"><div class="inner"></div></div>
+<div class="outer"><div class="inner"></div></div>
+<div class="outer"><div class="inner"></div></div>
+<div class="outer" style="overflow: scroll"><div class="inner"></div></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html
new file mode 100644
index 0000000..76beb2a4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-definite-sizes-005.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: nested flex containers with definite max-height</title>
+<link rel="match" href="flexbox-definite-sizes-005-ref.html">
+<link rel="author" href="mailto:dholbert@mozilla.com" title="Daniel Holbert">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#definite-sizes">
+<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1503173">
+<style>
+  .horizFlex {
+    display: flex;
+    margin-bottom: 2px;
+  }
+  .vertFlex {
+    display: flex;
+    flex-grow: 1;
+    flex-direction: column;
+    min-height: 60px;
+  }
+  .item {
+    background: lime;
+    height: 100%;
+  }
+</style>
+<p>Test passes if you see four 60px-tall lime rows (with platform-appropriate scrollbars on the last one).</p>
+
+<div class="horizFlex">
+  <div class="vertFlex">
+    <div class="item"></div>
+  </div>
+</div>
+
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: hidden">
+    <div class="item"></div>
+  </div>
+</div>
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: auto">
+    <div class="item"></div>
+  </div>
+</div>
+
+<div class="horizFlex">
+  <div class="vertFlex" style="overflow: scroll">
+    <div class="item"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/embed.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/embed.tentative.https.sub.html
index b97de9e..c46765b3 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/embed.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/embed.tentative.https.sub.html
@@ -16,7 +16,7 @@
       let e = document.createElement('embed');
       e.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"same-origin", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"same-origin", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -35,7 +35,7 @@
       let e = document.createElement('embed');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"same-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"same-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -54,7 +54,7 @@
       let e = document.createElement('embed');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"embed", "site":"cross-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"embed", "site":"cross-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.https.sub.html
index dc4b977..bffb427 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/fetch.tentative.https.sub.html
@@ -11,7 +11,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -24,7 +24,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -37,7 +37,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -51,7 +51,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "same-origin",
           });
         });
@@ -64,7 +64,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -77,7 +77,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "same-origin",
-            "user": "?F",
+            "user": "",
             "mode": "no-cors",
           });
         });
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/font.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/font.tentative.https.sub.html
index 9792f2dc..60163ee 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/font.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/font.tentative.https.sub.html
@@ -46,7 +46,7 @@
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-same-origin";
-        let expected = {"dest":"font", "site":"same-origin", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"same-origin", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -58,7 +58,7 @@
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-same-site";
-        let expected = {"dest":"font", "site":"same-site", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"same-site", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -70,7 +70,7 @@
     promise_test(t => {
       return new Promise((resolve, reject) => {
         let key = "font-cross-site";
-        let expected = {"dest":"font", "site":"cross-site", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"font", "site":"cross-site", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.https.sub.html
index 056d8fd..461d9f2 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/iframe.tentative.https.sub.html
@@ -1,63 +1,85 @@
 <!DOCTYPE html>
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
 <script src=/fetch/sec-metadata/resources/helper.js></script>
+<script src=/common/utils.js></script>
 <body>
 <script>
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+  const USER = true;
+  const FORCED = false;
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "same-origin",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+  function create_test(host, user_activated, expectations) {
+    async_test(t => {
+      let i = document.createElement('iframe');
+      window.addEventListener('message', t.step_func(e => {
+        if (e.source != i.contentWindow)
+          return;
 
-    document.body.appendChild(i);
-  }, "Same-origin iframe");
+        assert_header_equals(e.data, expectations);
+        t.done();
+      }));
 
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+      let url = `https://${host}/fetch/sec-metadata/resources/post-to-owner.py`;
+      if (user_activated == FORCED) {
+        i.src = url;
+        document.body.appendChild(i);
+      } else if (user_activated == USER) {
+        let uuid = token();
+        i.name = uuid;
+        let a = document.createElement('a');
+        a.href = url;
+        a.target = uuid;
+        a.text = "This is a link!";
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "same-site",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+        document.body.appendChild(i);
+        document.body.appendChild(a);
 
-    document.body.appendChild(i);
-  }, "Same-site iframe");
+        test_driver.click(a);
+      }
+    }, `{{host}} -> ${host} iframe: ${user_activated ? "user-activated" : "forced"}`);
+  }
 
-  async_test(t => {
-    let i = document.createElement('iframe');
-    i.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/post-to-owner.py";
-    window.addEventListener('message', t.step_func(e => {
-      if (e.source != i.contentWindow)
-        return;
+  create_test("{{host}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "same-origin",
+    "user": "",
+    "mode": "nested-navigate"
+  });
 
-      assert_header_equals(e.data, {
-        "dest": "nested-document",
-        "site": "cross-site",
-        "user": "?F",
-        "mode": "nested-navigate"
-      });
-      t.done();
-    }));
+  create_test("{{hosts[][www]}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "same-site",
+    "user": "",
+    "mode": "nested-navigate"
+  });
 
-    document.body.appendChild(i);
-  }, "Cross-site iframe");
+  create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", FORCED, {
+    "dest": "nested-document",
+    "site": "cross-site",
+    "user": "",
+    "mode": "nested-navigate"
+  });
+
+  create_test("{{host}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "same-origin",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
+
+  create_test("{{hosts[][www]}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "same-site",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
+
+  create_test("{{hosts[alt][www]}}:{{ports[https][0]}}", USER, {
+    "dest": "nested-document",
+    "site": "cross-site",
+    "user": "?T",
+    "mode": "nested-navigate"
+  });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/img.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/img.tentative.https.sub.html
index b1216db8..b9fc1af 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/img.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/img.tentative.https.sub.html
@@ -23,7 +23,9 @@
         assert_header_equals(got, {
           "dest": "image",
           "site": "same-origin",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
@@ -45,7 +47,9 @@
         assert_header_equals(got, {
           "dest": "image",
           "site": "same-site",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
@@ -67,7 +71,9 @@
         assert_header_equals(got, {
           "dest": "image",
           "site": "cross-site",
-          "user": "?F",
+          // Note that we're using `undefined` here, as opposed to "" elsewhere because of the way
+          // that `image.py` encodes data.
+          "user": undefined,
           "mode": "cors", // Because `loadImageInWindow` tacks on `crossorigin`
         });
       }),
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/object.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/object.tentative.https.sub.html
index 4749629..b60ae20 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/object.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/object.tentative.https.sub.html
@@ -16,7 +16,7 @@
       let e = document.createElement('object');
       e.data = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"same-origin", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"same-origin", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -35,7 +35,7 @@
       let e = document.createElement('object');
       e.data = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"same-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"same-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -54,7 +54,7 @@
       let e = document.createElement('object');
       e.data = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"object", "site":"cross-site", "user":"?F", "mode":"no-cors"};
+        let expected = {"dest":"object", "site":"cross-site", "user":"", "mode":"no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html
index 1634a29..06b5874 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/cross-site-redirect.tentative.https.sub.html
@@ -15,7 +15,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
@@ -41,7 +41,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
@@ -67,7 +67,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
index 7647a5b1..e173bb0 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-cross-site.tentative.https.sub.html
@@ -17,7 +17,7 @@
       e.src = "https://{{host}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-origin
       "https://{{hosts[alt][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// cross-site
       "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;// same-origin
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html
index e83d6fd9..d19a189 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/multiple-redirect-same-site.tentative.https.sub.html
@@ -17,7 +17,7 @@
       e.src = "https://{{host}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-origin
       "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=" +// same-site
       "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;// same-origin
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html
index 2033eab..0b4fdfee 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-origin-redirect.tentative.https.sub.html
@@ -15,7 +15,7 @@
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-origin", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-origin", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -42,7 +42,7 @@
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -69,7 +69,7 @@
 
       let e = document.createElement('img');
       e.src = "/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html
index c5b6830..70c0afe 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/redirect/same-site-redirect.tentative.https.sub.html
@@ -15,7 +15,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -42,7 +42,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"same-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -69,7 +69,7 @@
 
       let e = document.createElement('img');
       e.src = "https://{{hosts[][www]}}:{{ports[https][0]}}/xhr/resources/redirect.py?location=https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
-      let expected = {"dest":"image", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"image", "site":"cross-site", "user":"", "mode": "no-cors"};
 
       e.onload = e => {
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/report.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/report.tentative.https.sub.html
index 405964e..eb6c76b5 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/report.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/report.tentative.https.sub.html
@@ -22,9 +22,9 @@
   document.addEventListener("securitypolicyviolation", (e) => {
     counter++;
     if (counter == 3) {
-      generate_test({"dest":"report", "site":"same-origin", "user":"?F", "mode": "no-cors"}, "same-origin");
-      generate_test({"dest":"report", "site":"same-site", "user":"?F", "mode": "no-cors"}, "same-site");
-      generate_test({"dest":"report", "site":"cross-site", "user":"?F", "mode": "no-cors"}, "cross-site");
+      generate_test({"dest":"report", "site":"same-origin", "user":"", "mode": "no-cors"}, "same-origin");
+      generate_test({"dest":"report", "site":"same-site", "user":"", "mode": "no-cors"}, "same-site");
+      generate_test({"dest":"report", "site":"cross-site", "user":"", "mode": "no-cors"}, "cross-site");
 
       done();
     }
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/script.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/script.tentative.https.sub.html
index a35e753..b2874a3 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/script.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/script.tentative.https.sub.html
@@ -12,7 +12,7 @@
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-origin",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Same-origin script");
@@ -27,7 +27,7 @@
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-site",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Same-site script");
@@ -42,7 +42,7 @@
     assert_header_equals(header, {
       "dest": "script",
       "site": "cross-site",
-      "user": "?F",
+      "user": "",
       "mode": "no-cors",
     });
   }, "Cross-site script");
@@ -57,7 +57,7 @@
     assert_header_equals(header, {
       "dest": "script",
       "site": "same-origin",
-      "user": "?F",
+      "user": "",
       "mode": "cors",
     });
   }, "Same-origin CORS script");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
index 590a6c3..ee436d9 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/serviceworker.tentative.https.sub.html
@@ -38,7 +38,7 @@
   function test_same_origin(){
     promise_test(t => {
     return new Promise((resolve, reject) => {
-      let expected = {"dest":"serviceworker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+      let expected = {"dest":"serviceworker", "site":"same-origin", "user":"", "mode": "same-origin"};
       fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
         .then(response => response.text())
         .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/sharedworker.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/sharedworker.tentative.https.sub.html
index a1c558a..ddd4cc99 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/sharedworker.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/sharedworker.tentative.https.sub.html
@@ -28,7 +28,7 @@
   function test_same_origin(){
     promise_test(t => {
       return new Promise((resolve, reject) => {
-        let expected = {"dest":"sharedworker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+        let expected = {"dest":"sharedworker", "site":"same-origin", "user":"", "mode": "same-origin"};
 
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/style.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/style.tentative.https.sub.html
index 46f64f4..19cc16d 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/style.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/style.tentative.https.sub.html
@@ -17,7 +17,7 @@
       e.rel = "stylesheet";
       e.href = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-origin", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"same-origin", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -37,7 +37,7 @@
       e.rel = "stylesheet";
       e.href = "https://{{hosts[][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-site", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"same-site", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -57,7 +57,7 @@
       e.rel = "stylesheet";
       e.href = "https://{{hosts[alt][www]}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.onload = e => {
-        let expected = {"dest":"style", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+        let expected = {"dest":"style", "site":"cross-site", "user":"", "mode": "no-cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
@@ -78,7 +78,7 @@
       e.href = "https://{{host}}:{{ports[https][0]}}/fetch/sec-metadata/resources/record-header.py?file=" + key;
       e.crossOrigin = "anonymous";
       e.onload = e => {
-        let expected = {"dest":"style", "site":"same-origin", "user":"?F", "mode": "cors"};
+        let expected = {"dest":"style", "site":"same-origin", "user":"", "mode": "cors"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/track.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/track.tentative.https.sub.html
index 817e4845..fe525ca 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/track.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/track.tentative.https.sub.html
@@ -36,7 +36,7 @@
         expected = {
           "dest": "track",
           "site": "same-origin",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -59,7 +59,7 @@
         expected = {
           "dest": "track",
           "site": "same-site",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -84,7 +84,7 @@
         expected = {
           "dest": "track",
           "site": "cross-site",
-          "user": "?F",
+          "user": "",
           "mode": "cors" // Because the `video` element has `crossorigin`
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
@@ -112,7 +112,7 @@
         expected = {
           "dest":"track",
           "site":"same-origin",
-          "user":"?F",
+          "user":"",
           "mode": "same-origin"
         };
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/trailing-dot.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/trailing-dot.tentative.https.sub.html
index 85f9c73..ff0e842 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/trailing-dot.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/trailing-dot.tentative.https.sub.html
@@ -11,7 +11,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -24,7 +24,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
@@ -37,7 +37,7 @@
           assert_header_equals(j, {
             "dest": "empty",
             "site": "cross-site",
-            "user": "?F",
+            "user": "",
             "mode": "cors",
           });
         });
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/window-open.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/window-open.tentative.https.sub.html
index ef2bc81..0be9f2c 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/window-open.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/window-open.tentative.https.sub.html
@@ -17,7 +17,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-origin",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
@@ -34,7 +34,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
@@ -51,7 +51,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "cross-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
       t.done();
@@ -70,7 +70,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-origin",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
@@ -94,7 +94,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "same-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
@@ -118,7 +118,7 @@
       assert_header_equals(e.data, {
         "dest": "document",
         "site": "cross-site",
-        "user": "?F",
+        "user": "",
         "mode": "navigate",
       });
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/worker.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/worker.tentative.https.sub.html
index dc21d032..940e25b2 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/worker.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/worker.tentative.https.sub.html
@@ -13,7 +13,7 @@
       let key = "worker-same-origin" + nonce;
       let w = new Worker("/fetch/sec-metadata/resources/record-header.py?file=" + key);
       w.onmessage = e => {
-        let expected = {"dest":"worker", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+        let expected = {"dest":"worker", "site":"same-origin", "user":"", "mode": "same-origin"};
         fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=" + key)
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected))
diff --git a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
index eea2329..0d42926 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/fetch/sec-metadata/xslt.tentative.https.sub.html
@@ -12,21 +12,21 @@
       return;
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"same-origin", "user":"?F", "mode": "same-origin"};
+      let expected = {"dest":"xslt", "site":"same-origin", "user":"", "mode": "same-origin"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-origin")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-Origin xslt");
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"same-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"xslt", "site":"same-site", "user":"", "mode": "no-cors"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-same-site")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
     }, "Same-site xslt");
 
     promise_test(t => {
-      let expected = {"dest":"xslt", "site":"cross-site", "user":"?F", "mode": "no-cors"};
+      let expected = {"dest":"xslt", "site":"cross-site", "user":"", "mode": "no-cors"};
       return fetch("/fetch/sec-metadata/resources/record-header.py?retrieve=true&file=xslt-cross-site")
           .then(response => response.text())
           .then(text => assert_header_equals(text, expected));
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html
deleted file mode 100644
index 80a2989..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-classic.sub.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>import() doesn't have any integrity metadata when initiated by compiled strings inside a classic script</title>
-<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
-<meta http-equiv="Content-Security-Policy" content="require-sri-for script">
-
-<script src="/resources/testharness.js" integrity="sha384-{{file_hash(sha384, resources/testharness.js)}}"></script>
-<script src="/resources/testharnessreport.js" integrity="sha384-{{file_hash(sha384, resources/testharnessreport.js)}}"></script>
-
-<div id="dummy"></div>
-
-<script>
-function createTestPromise() {
-  return new Promise((resolve, reject) => {
-    window.continueTest = resolve;
-    window.errorTest = reject;
-  });
-}
-
-const dummyDiv = document.querySelector("#dummy");
-
-const evaluators = {
-  eval,
-  setTimeout,
-  "the Function constructor"(x) {
-    Function(x)();
-  },
-  "reflected inline event handlers"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.onclick();
-  },
-  "inline event handlers triggered via UA code"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.click(); // different from .**on**click()
-  }
-};
-
-for (const [label, evaluator] of Object.entries(evaluators)) {
-  promise_test(t => {
-    t.add_cleanup(() => {
-      dummyDiv.removeAttribute("onclick");
-      delete window.evaluated_imports_a;
-    });
-
-    const promise = createTestPromise();
-
-    evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
-
-    return promise_rejects(t, new TypeError(), promise);
-  }, label + " should fail to import");
-};
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html
deleted file mode 100644
index db3d3b1..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/dynamic-import/string-compilation-integrity-module.sub.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>import() doesn't have any integrity metadata when initiated by compiled strings inside a module script</title>
-<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
-<meta http-equiv="Content-Security-Policy" content="require-sri-for script">
-
-<script src="/resources/testharness.js" integrity="sha384-{{file_hash(sha384, resources/testharness.js)}}"></script>
-<script src="/resources/testharnessreport.js" integrity="sha384-{{file_hash(sha384, resources/testharnessreport.js)}}"></script>
-
-<div id="dummy"></div>
-
-<script type="module">
-function createTestPromise() {
-  return new Promise((resolve, reject) => {
-    window.continueTest = resolve;
-    window.errorTest = reject;
-  });
-}
-
-const dummyDiv = document.querySelector("#dummy");
-
-const evaluators = {
-  eval,
-  setTimeout,
-  "the Function constructor"(x) {
-    Function(x)();
-  },
-  "reflected inline event handlers"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.onclick();
-  },
-  "inline event handlers triggered via UA code"(x) {
-    dummyDiv.setAttribute("onclick", x);
-    dummyDiv.click(); // different from .**on**click()
-  }
-};
-
-for (const [label, evaluator] of Object.entries(evaluators)) {
-  promise_test(t => {
-    t.add_cleanup(() => {
-      dummyDiv.removeAttribute("onclick");
-      delete window.evaluated_imports_a;
-    });
-
-    const promise = createTestPromise();
-
-    evaluator(`import('../imports-a.js?label=${label}').then(window.continueTest, window.errorTest);`);
-
-    return promise_rejects(t, new TypeError(), promise);
-  }, label + " should fail to import");
-};
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/performance-timeline.idl b/third_party/blink/web_tests/external/wpt/interfaces/performance-timeline.idl
index 7766bfd..51752b13 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/performance-timeline.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/performance-timeline.idl
@@ -23,7 +23,7 @@
                                              PerformanceObserver observer);
 [Constructor(PerformanceObserverCallback callback), Exposed=(Window,Worker)]
 interface PerformanceObserver {
-  void observe(PerformanceObserverInit options);
+  void observe(optional PerformanceObserverInit options);
   void disconnect();
   PerformanceEntryList takeRecords();
   static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
index 7d03cc1..32878d3 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
+FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
index 7d03cc1..32878d3 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
+FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
index 7d03cc1..32878d3 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
+FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
index 7d03cc1..32878d3 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 71 tests; 68 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 71 tests; 67 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idlharness
 PASS idl_test setup
 PASS Partial interface Performance: original interface defined
@@ -20,7 +20,7 @@
 PASS PerformanceObserver interface: existence and properties of interface prototype object
 PASS PerformanceObserver interface: existence and properties of interface prototype object's "constructor" property
 PASS PerformanceObserver interface: existence and properties of interface prototype object's @@unscopables property
-PASS PerformanceObserver interface: operation observe(PerformanceObserverInit)
+FAIL PerformanceObserver interface: operation observe(PerformanceObserverInit) assert_equals: property has wrong .length expected 0 but got 1
 PASS PerformanceObserver interface: operation disconnect()
 PASS PerformanceObserver interface: operation takeRecords()
 PASS PerformanceObserver interface: attribute supportedEntryTypes
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
index 3de90db..76e0835 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/fetch-request-xhr.https-expected.txt
@@ -1,9 +1,9 @@
 This is a testharness.js-based test.
 PASS initialize global state
-FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 7"
-FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 9"
-FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 7"
-FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 9"
+FAIL event.request has the expected headers for same-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin GET. lengths differ, expected 1 got 6"
+FAIL event.request has the expected headers for same-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for same-origin POST. lengths differ, expected 2 got 8"
+FAIL event.request has the expected headers for cross-origin GET. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin GET. lengths differ, expected 1 got 6"
+FAIL event.request has the expected headers for cross-origin POST. promise_test: Unhandled rejection with value: object "Error: assert_array_equals: event.request has the expected headers for cross-origin POST. lengths differ, expected 2 got 8"
 PASS FetchEvent#request.body contains XHR request data (string)
 PASS FetchEvent#request.body contains XHR request data (blob)
 PASS FetchEvent#request.method is set to XHR method
diff --git a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
index 8a57512c..4669741 100644
--- a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-get-expected.txt
@@ -17,5 +17,4 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = cross-site
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
diff --git a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
index f6dca30..ee7518aa 100644
--- a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-no-referrer-expected.txt
@@ -18,5 +18,4 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = cross-site
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
diff --git a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
index f31564b5..d9a55b6 100644
--- a/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/form-targets-cross-site-frame-post-expected.txt
@@ -19,5 +19,4 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = cross-site
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
diff --git a/third_party/blink/web_tests/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt b/third_party/blink/web_tests/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
index 9d33086d..d12ad014 100644
--- a/third_party/blink/web_tests/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/form-with-enctype-targets-cross-site-frame-expected.txt
@@ -19,5 +19,4 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = cross-site
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
diff --git a/third_party/blink/web_tests/http/tests/navigation/post-basic-expected.txt b/third_party/blink/web_tests/http/tests/navigation/post-basic-expected.txt
index b423b5b0..6753a6b 100644
--- a/third_party/blink/web_tests/http/tests/navigation/post-basic-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/post-basic-expected.txt
@@ -15,7 +15,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/navigation/post-frames-expected.txt b/third_party/blink/web_tests/http/tests/navigation/post-frames-expected.txt
index a017c82..26db48ef 100644
--- a/third_party/blink/web_tests/http/tests/navigation/post-frames-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/post-frames-expected.txt
@@ -20,7 +20,6 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/navigation/post-frames-goback1-expected.txt b/third_party/blink/web_tests/http/tests/navigation/post-frames-goback1-expected.txt
index dc0b80f4..34f9b01 100644
--- a/third_party/blink/web_tests/http/tests/navigation/post-frames-goback1-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/post-frames-goback1-expected.txt
@@ -19,7 +19,6 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/navigation/post-goback1-expected.txt b/third_party/blink/web_tests/http/tests/navigation/post-goback1-expected.txt
index 19cd057..44c870e 100644
--- a/third_party/blink/web_tests/http/tests/navigation/post-goback1-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/post-goback1-expected.txt
@@ -15,7 +15,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/navigation/rename-subframe-goback-expected.txt b/third_party/blink/web_tests/http/tests/navigation/rename-subframe-goback-expected.txt
index bf24f6a..a7147c7 100644
--- a/third_party/blink/web_tests/http/tests/navigation/rename-subframe-goback-expected.txt
+++ b/third_party/blink/web_tests/http/tests/navigation/rename-subframe-goback-expected.txt
@@ -23,5 +23,4 @@
 HTTP_SEC_FETCH_DEST = nested-document
 HTTP_SEC_FETCH_MODE = nested-navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-leak-path-on-redirect-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-leak-path-on-redirect-expected.txt
index ddca9e27..d78cc87 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-leak-path-on-redirect-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-leak-path-on-redirect-expected.txt
@@ -11,7 +11,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-expected.txt
index 8f5d29a..699d86f9 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-expected.txt
@@ -14,7 +14,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-with-redirect-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-with-redirect-expected.txt
index 7c7cb1c..36a810c 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-with-redirect-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-allowed-with-redirect-expected.txt
@@ -11,7 +11,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-expected.txt
index 25c99c6..eee3cd5 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-expected.txt
@@ -14,7 +14,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-with-redirect-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-with-redirect-expected.txt
index c162057c..962a40a 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-with-redirect-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-default-ignored-with-redirect-expected.txt
@@ -11,7 +11,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-expected.txt
index 20bbcf00..6c2b09e 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-expected.txt
@@ -12,7 +12,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-with-redirect-expected.txt b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-with-redirect-expected.txt
index e854d82..4747b394 100644
--- a/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-with-redirect-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/contentSecurityPolicy/1.1/form-action-src-get-allowed-with-redirect-expected.txt
@@ -11,7 +11,6 @@
 HTTP_SEC_FETCH_DEST = document
 HTTP_SEC_FETCH_MODE = navigate
 HTTP_SEC_FETCH_SITE = same-origin
-HTTP_SEC_FETCH_USER = ?F
 HTTP_UPGRADE_INSECURE_REQUESTS = 1
 
 ============== Back Forward List ==============
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
index f94a01a..e98cbba 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
+++ b/third_party/blink/web_tests/http/tests/serviceworker/fetch-event-headers.html
@@ -31,7 +31,7 @@
           header_names.sort();
           assert_array_equals(
             header_names,
-            ["accept", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "sec-fetch-user", "upgrade-insecure-requests", "user-agent"],
+            ["accept", "sec-fetch-dest", "sec-fetch-mode", "sec-fetch-site", "upgrade-insecure-requests", "user-agent"],
             'event.request has the expected headers.');
 
           return service_worker_unregister_and_done(t, scope);
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/callback-alive-after-gc.html b/third_party/blink/web_tests/media/mediasession/mojo/callback-alive-after-gc.html
index 7f516044..fa105d5c 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/callback-alive-after-gc.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/callback-alive-after-gc.html
@@ -5,7 +5,7 @@
 <script src="../../../resources/gc.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/media_session/public/mojom/media_session.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/file-image-removed.html b/third_party/blink/web_tests/media/mediasession/mojo/file-image-removed.html
index dbbf316..f2e2484 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/file-image-removed.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/file-image-removed.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/media-control-action-reaches-client.html b/third_party/blink/web_tests/media/mediasession/mojo/media-control-action-reaches-client.html
index 4640c19..3033ec9 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/media-control-action-reaches-client.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/media-control-action-reaches-client.html
@@ -4,7 +4,7 @@
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/media_session/public/mojom/media_session.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/media-control-set-handler-notifies-service.html b/third_party/blink/web_tests/media/mediasession/mojo/media-control-set-handler-notifies-service.html
index 2806f8ce..6e70a67 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/media-control-set-handler-notifies-service.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/media-control-set-handler-notifies-service.html
@@ -4,7 +4,7 @@
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/media_session/public/mojom/media_session.mojom.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/metadata-async.html b/third_party/blink/web_tests/media/mediasession/mojo/metadata-async.html
index 65cab89..5a726b0 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/metadata-async.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/metadata-async.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated-twice.html b/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated-twice.html
index e11f5c6..dbdff47 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated-twice.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated-twice.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated.html b/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated.html
index d0b55dda..31fa8bf8 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/metadata-propagated.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/metadata-session-link.html b/third_party/blink/web_tests/media/mediasession/mojo/metadata-session-link.html
index 961daef..6d6fe39 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/metadata-session-link.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/metadata-session-link.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/playback-state-propagated.html b/third_party/blink/web_tests/media/mediasession/mojo/playback-state-propagated.html
index de80e260..918aa46 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/playback-state-propagated.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/playback-state-propagated.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/media/mediasession/mojo/set-null-metadata.html b/third_party/blink/web_tests/media/mediasession/mojo/set-null-metadata.html
index f786142a..db3479a 100644
--- a/third_party/blink/web_tests/media/mediasession/mojo/set-null-metadata.html
+++ b/third_party/blink/web_tests/media/mediasession/mojo/set-null-metadata.html
@@ -3,7 +3,7 @@
 <script src="../../../resources/testharness.js"></script>
 <script src="../../../resources/testharnessreport.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
-<script src="file:///gen/third_party/blink/public/platform/modules/mediasession/media_session.mojom.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/mediasession/media_session.mojom.js"></script>
 <script src="resources/mediasessionservice-mock.js"></script>
 <script src="resources/utils.js"></script>
 <script>
diff --git a/third_party/blink/web_tests/platform/mac/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/platform/mac/compositing/lots-of-img-layers-with-opacity-expected.png
deleted file mode 100644
index fdae228..0000000
--- a/third_party/blink/web_tests/platform/mac/compositing/lots-of-img-layers-with-opacity-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png b/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png
deleted file mode 100644
index 511dca7..0000000
--- a/third_party/blink/web_tests/platform/win/compositing/lots-of-img-layers-with-opacity-expected.png
+++ /dev/null
Binary files differ
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index fac3068..f289ed1 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1657,6 +1657,13 @@
   <int value="4" label="Failed: Cannot sign in as previous user"/>
 </enum>
 
+<enum name="AndroidSearchEngineChoiceEvents">
+  <summary>Events related to Search Engine Choice feature.</summary>
+  <int value="0" label="Snackbar shown"/>
+  <int value="1" label="Prompt followed"/>
+  <int value="2" label="Search engine changed"/>
+</enum>
+
 <enum name="AndroidSeccompSandboxStatus">
   <int value="0" label="Not Supported"/>
   <int value="1" label="Detection Failed"/>
@@ -32277,6 +32284,7 @@
   <int value="-1604051051" label="SpecialLocale:disabled"/>
   <int value="-1603404046" label="V8VmFuture:disabled"/>
   <int value="-1599538279" label="enable-md-policy-page"/>
+  <int value="-1596859081" label="WebAuthenticationPINSupport:enabled"/>
   <int value="-1596559650" label="max-tiles-for-interest-area"/>
   <int value="-1594298767" label="FullscreenToolbarReveal:enabled"/>
   <int value="-1586642651" label="MaterialDesignExtensions:disabled"/>
@@ -33717,6 +33725,7 @@
   <int value="723619383" label="TopSitesFromSiteEngagement:enabled"/>
   <int value="724052572" label="EnableFilesystemInIncognito"/>
   <int value="724208771" label="TabsInCBD:enabled"/>
+  <int value="724427456" label="WebAuthenticationPINSupport:disabled"/>
   <int value="725270017" label="ScrollAnchorSerialization:disabled"/>
   <int value="726764779" label="WebXRGamepadSupport:enabled"/>
   <int value="727270087" label="PromosOnLocalNtp:enabled"/>
@@ -50872,6 +50881,7 @@
   <int value="28" label="UMA_TWA_PRIVACY_DISCLOSURE"/>
   <int value="29" label="UMA_AUTOFILL_ASSISTANT_STOP_UNDO"/>
   <int value="30" label="UMA_TAB_CLOSE_MULTIPLE_UNDO"/>
+  <int value="31" label="UMA_SEARCH_ENGINE_CHOICE_NOTIFICATION"/>
 </enum>
 
 <enum name="SnippetOpenMethod">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 77f6098..301718b 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -2775,6 +2775,35 @@
   </summary>
 </histogram>
 
+<histogram name="Android.SearchEngineChoice.ChosenSearchEngine"
+    enum="OmniboxSearchEngineType" expires_after="2020-04-01">
+  <owner>fgorski@chromium.org</owner>
+  <owner>lzbylut@chromium.org</owner>
+  <summary>
+    The type of the search engine chosen by the user in Search Engine Choice
+    flow.
+  </summary>
+</histogram>
+
+<histogram name="Android.SearchEngineChoice.Events"
+    enum="AndroidSearchEngineChoiceEvents" expires_after="2020-04-01">
+  <owner>fgorski@chromium.org</owner>
+  <owner>lzbylut@chromium.org</owner>
+  <summary>
+    Counts occurences of various events related to Search Engine Choice feature.
+  </summary>
+</histogram>
+
+<histogram name="Android.SearchEngineChoice.SearchEngineBeforeChoicePrompt"
+    enum="OmniboxSearchEngineType" expires_after="2020-04-01">
+  <owner>fgorski@chromium.org</owner>
+  <owner>lzbylut@chromium.org</owner>
+  <summary>
+    The type of the search engine used before Search Engine Choice flow was
+    presented.
+  </summary>
+</histogram>
+
 <histogram name="Android.SeccompStatus.PhotoPickerSandbox"
     enum="AndroidSeccompSandboxStatus">
   <owner>peter@chromium.org</owner>
@@ -10045,6 +10074,14 @@
   </summary>
 </histogram>
 
+<histogram name="AutoScreenBrightness.MissingAlsWhenBrightnessChanged"
+    enum="BooleanError" expires_after="2019-12-31">
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    Whether there was no recorded ALS reading when brightness was changed.
+  </summary>
+</histogram>
+
 <histogram name="AutoScreenBrightness.NewCurveSaved.Duration" units="ms"
     expires_after="2019-12-31">
   <owner>jiameng@chromium.org</owner>
diff --git a/ui/android/java/src/org/chromium/ui/resources/ResourceExtractor.java b/ui/android/java/src/org/chromium/ui/resources/ResourceExtractor.java
index 5f6e5a8..123c8f1f 100644
--- a/ui/android/java/src/org/chromium/ui/resources/ResourceExtractor.java
+++ b/ui/android/java/src/org/chromium/ui/resources/ResourceExtractor.java
@@ -5,8 +5,6 @@
 package org.chromium.ui.resources;
 
 import android.content.res.AssetManager;
-import android.os.Handler;
-import android.os.Looper;
 
 import org.chromium.base.BuildConfig;
 import org.chromium.base.BuildInfo;
@@ -17,7 +15,8 @@
 import org.chromium.base.PathUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
-import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
 import org.chromium.ui.base.LocalizationUtils;
 
 import java.io.File;
@@ -27,6 +26,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Handles extracting the necessary resources bundled in an APK and moving them to a location on
@@ -42,9 +42,33 @@
     private static final String COMPRESSED_LOCALES_FALLBACK_DIR = "fallback-locales";
     private static final int BUFFER_SIZE = 16 * 1024;
 
-    private class ExtractTask extends AsyncTask<Void> {
+    private class ExtractTask implements Runnable {
         private final List<Runnable> mCompletionCallbacks = new ArrayList<Runnable>();
         private final String mUiLanguage;
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private boolean mDone;
+
+        public ExtractTask(String uiLanguage) {
+            mUiLanguage = uiLanguage;
+        }
+
+        @Override
+        public void run() {
+            try (TraceEvent e = TraceEvent.scoped("ResourceExtractor.ExtractTask.doInBackground")) {
+                doInBackgroundImpl();
+            }
+            synchronized (this) {
+                mDone = true;
+            }
+            mLatch.countDown();
+
+            PostTask.postTask(mResultTaskTraits, () -> {
+                try (TraceEvent e =
+                                TraceEvent.scoped("ResourceExtractor.ExtractTask.onPostExecute")) {
+                    onPostExecuteImpl();
+                }
+            });
+        }
 
         private void doInBackgroundImpl() {
             final File outputDir = getOutputDir();
@@ -98,40 +122,25 @@
             }
         }
 
-        @Override
-        protected Void doInBackground() {
-            TraceEvent.begin("ResourceExtractor.ExtractTask.doInBackground");
-            try {
-                doInBackgroundImpl();
-            } finally {
-                TraceEvent.end("ResourceExtractor.ExtractTask.doInBackground");
-            }
-            return null;
-        }
-
         private void onPostExecuteImpl() {
+            ThreadUtils.assertOnUiThread();
             for (int i = 0; i < mCompletionCallbacks.size(); i++) {
                 mCompletionCallbacks.get(i).run();
             }
             mCompletionCallbacks.clear();
         }
 
-        @Override
-        protected void onPostExecute(Void result) {
-            TraceEvent.begin("ResourceExtractor.ExtractTask.onPostExecute");
-            try {
-                onPostExecuteImpl();
-            } finally {
-                TraceEvent.end("ResourceExtractor.ExtractTask.onPostExecute");
-            }
+        public void await() throws Exception {
+            mLatch.await();
         }
 
-        public ExtractTask(String uiLanguage) {
-            mUiLanguage = uiLanguage;
+        public synchronized boolean isDone() {
+            return mDone;
         }
     }
 
     private ExtractTask mExtractTask;
+    private TaskTraits mResultTaskTraits;
 
     private static ResourceExtractor sInstance;
 
@@ -264,13 +273,20 @@
         }
 
         try {
-            mExtractTask.get();
+            mExtractTask.await();
         } catch (Exception e) {
             assert false;
         }
     }
 
     /**
+     * Sets the traits to use for the reply task.
+     */
+    public void setResultTraits(TaskTraits traits) {
+        mResultTaskTraits = traits;
+    }
+
+    /**
      * Adds a callback to be notified upon the completion of resource extraction.
      * <p>
      * If the resource task has already completed, the callback will be posted to the UI message
@@ -284,16 +300,14 @@
     public void addCompletionCallback(Runnable callback) {
         ThreadUtils.assertOnUiThread();
 
-        Handler handler = new Handler(Looper.getMainLooper());
         if (shouldSkipPakExtraction()) {
-            handler.post(callback);
+            PostTask.postTask(mResultTaskTraits, callback);
             return;
         }
 
         assert mExtractTask != null;
-        assert !mExtractTask.isCancelled();
-        if (mExtractTask.getStatus() == AsyncTask.Status.FINISHED) {
-            handler.post(callback);
+        if (mExtractTask.isDone()) {
+            PostTask.postTask(mResultTaskTraits, callback);
         } else {
             mExtractTask.mCompletionCallbacks.add(callback);
         }
@@ -303,6 +317,8 @@
      * This will extract the application pak resources in an
      * AsyncTask. Call waitForCompletion() at the point resources
      * are needed to block until the task completes.
+     *
+     * @param uiLanguage The language to extract.
      */
     public void startExtractingResources(String uiLanguage) {
         if (mExtractTask != null) {
@@ -318,7 +334,7 @@
         }
 
         mExtractTask = new ExtractTask(uiLanguage);
-        mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        PostTask.postTask(TaskTraits.USER_BLOCKING, mExtractTask);
     }
 
     private File getAppDataDir() {
diff --git a/ui/gl/android/android_surface_control_compat.cc b/ui/gl/android/android_surface_control_compat.cc
index a2ffb6c5..6cdf316 100644
--- a/ui/gl/android/android_surface_control_compat.cc
+++ b/ui/gl/android/android_surface_control_compat.cc
@@ -7,9 +7,11 @@
 #include <dlfcn.h>
 
 #include "base/android/build_info.h"
+#include "base/atomic_sequence_num.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
+#include "base/trace_event/trace_event.h"
 #include "ui/gfx/color_space.h"
 
 extern "C" {
@@ -100,6 +102,8 @@
 namespace gl {
 namespace {
 
+base::AtomicSequenceNumber g_next_transaction_id;
+
 // Helper function to log errors from dlsym. Calling LOG(ERROR) inside a macro
 // crashes clang code coverage. https://crbug.com/843356
 void LogDlsymError(const char* func) {
@@ -257,6 +261,7 @@
 }
 
 struct TransactionAckCtx {
+  int id = 0;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner;
   SurfaceControl::Transaction::OnCompleteCb callback;
 };
@@ -276,6 +281,7 @@
     std::move(ack_ctx->callback).Run(std::move(transaction_stats));
   }
 
+  TRACE_EVENT_ASYNC_END0("gpu", "SurfaceControlTransaction", ack_ctx->id);
   delete ack_ctx;
 }
 }  // namespace
@@ -331,13 +337,34 @@
 SurfaceControl::TransactionStats& SurfaceControl::TransactionStats::operator=(
     TransactionStats&& other) = default;
 
-SurfaceControl::Transaction::Transaction() {
+SurfaceControl::Transaction::Transaction()
+    : id_(g_next_transaction_id.GetNext()) {
   transaction_ = SurfaceControlMethods::Get().ASurfaceTransaction_createFn();
   DCHECK(transaction_);
 }
 
 SurfaceControl::Transaction::~Transaction() {
-  SurfaceControlMethods::Get().ASurfaceTransaction_deleteFn(transaction_);
+  if (transaction_)
+    SurfaceControlMethods::Get().ASurfaceTransaction_deleteFn(transaction_);
+}
+
+SurfaceControl::Transaction::Transaction(Transaction&& other)
+    : id_(other.id_), transaction_(other.transaction_) {
+  other.transaction_ = nullptr;
+  other.id_ = 0;
+}
+
+SurfaceControl::Transaction& SurfaceControl::Transaction::operator=(
+    Transaction&& other) {
+  if (transaction_)
+    SurfaceControlMethods::Get().ASurfaceTransaction_deleteFn(transaction_);
+
+  transaction_ = other.transaction_;
+  id_ = other.id_;
+
+  other.transaction_ = nullptr;
+  other.id_ = 0;
+  return *this;
 }
 
 void SurfaceControl::Transaction::SetVisibility(const Surface& surface,
@@ -396,12 +423,14 @@
   TransactionAckCtx* ack_ctx = new TransactionAckCtx;
   ack_ctx->callback = std::move(cb);
   ack_ctx->task_runner = std::move(task_runner);
+  ack_ctx->id = id_;
 
   SurfaceControlMethods::Get().ASurfaceTransaction_setOnCompleteFn(
       transaction_, ack_ctx, &OnTransactionCompletedOnAnyThread);
 }
 
 void SurfaceControl::Transaction::Apply() {
+  TRACE_EVENT_ASYNC_BEGIN0("gpu", "SurfaceControlTransaction", id_);
   SurfaceControlMethods::Get().ASurfaceTransaction_applyFn(transaction_);
 }
 
diff --git a/ui/gl/android/android_surface_control_compat.h b/ui/gl/android/android_surface_control_compat.h
index 7a292f4..46d5928 100644
--- a/ui/gl/android/android_surface_control_compat.h
+++ b/ui/gl/android/android_surface_control_compat.h
@@ -92,6 +92,9 @@
     Transaction();
     ~Transaction();
 
+    Transaction(Transaction&& other);
+    Transaction& operator=(Transaction&& other);
+
     void SetVisibility(const Surface& surface, bool show);
     void SetZOrder(const Surface& surface, int32_t z);
     void SetBuffer(const Surface& surface,
@@ -118,7 +121,10 @@
     void Apply();
 
    private:
+    int id_;
     ASurfaceTransaction* transaction_;
+
+    DISALLOW_COPY_AND_ASSIGN(Transaction);
   };
 };
 }  // namespace gl
diff --git a/ui/gl/gl_surface_egl_surface_control.cc b/ui/gl/gl_surface_egl_surface_control.cc
index ec92d2e..78a3eb8 100644
--- a/ui/gl/gl_surface_egl_surface_control.cc
+++ b/ui/gl/gl_surface_egl_surface_control.cc
@@ -161,13 +161,20 @@
       std::move(present_callback), std::move(resources_to_release));
   pending_transaction_->SetOnCompleteCb(std::move(callback), gpu_task_runner_);
 
-  pending_transaction_acks_++;
-  pending_transaction_->Apply();
-  pending_transaction_.reset();
-
-  DCHECK_GE(surface_list_.size(), pending_surfaces_count_);
+  // Cache only those surfaces which were used in this transaction. The surfaces
+  // removed here are persisted in |resources_to_release| so we can release
+  // them after receiving read fences from the framework.
   surface_list_.resize(pending_surfaces_count_);
   pending_surfaces_count_ = 0u;
+
+  if (transaction_ack_pending_) {
+    pending_transaction_queue_.push(std::move(pending_transaction_).value());
+  } else {
+    transaction_ack_pending_ = true;
+    pending_transaction_->Apply();
+  }
+
+  pending_transaction_.reset();
 }
 
 gfx::Size GLSurfaceEGLSurfaceControl::GetSize() {
@@ -311,9 +318,9 @@
     ResourceRefs released_resources,
     SurfaceControl::TransactionStats transaction_stats) {
   DCHECK(gpu_task_runner_->BelongsToCurrentThread());
-  DCHECK_GT(pending_transaction_acks_, 0u);
+  DCHECK(transaction_ack_pending_);
 
-  pending_transaction_acks_--;
+  transaction_ack_pending_ = false;
 
   // The presentation feedback callback must run after swap completion.
   std::move(completion_callback).Run(gfx::SwapResult::SWAP_ACK, nullptr);
@@ -351,6 +358,12 @@
   // which were updated in that transaction, the surfaces with no buffer updates
   // won't be present in the ack.
   released_resources.clear();
+
+  if (!pending_transaction_queue_.empty()) {
+    transaction_ack_pending_ = true;
+    pending_transaction_queue_.front().Apply();
+    pending_transaction_queue_.pop();
+  }
 }
 
 GLSurfaceEGLSurfaceControl::SurfaceState::SurfaceState(
diff --git a/ui/gl/gl_surface_egl_surface_control.h b/ui/gl/gl_surface_egl_surface_control.h
index 80466a68..afb0106 100644
--- a/ui/gl/gl_surface_egl_surface_control.h
+++ b/ui/gl/gl_surface_egl_surface_control.h
@@ -137,26 +137,23 @@
 
   // Holds the surface state changes made since the last call to SwapBuffers.
   base::Optional<SurfaceControl::Transaction> pending_transaction_;
-
-  // The list of Surfaces and the corresponding state. The initial
-  // |pending_surfaces_count_| surfaces in this list are surfaces with state
-  // mutated since the last SwapBuffers with the updates collected in
-  // |pending_transaction_|.
-  // On the next SwapBuffers, the updates in the transaction are applied
-  // atomically and any surfaces in |surface_list_| which are not reused in this
-  // frame are destroyed.
-  std::vector<SurfaceState> surface_list_;
   size_t pending_surfaces_count_ = 0u;
-
   // Resources in the pending frame, for which updates are being
   // collected in |pending_transaction_|. These are resources for which the
   // pending transaction has a ref but they have not been applied and
   // transferred to the framework.
   ResourceRefs pending_frame_resources_;
 
-  // Resources in the current frame sent to the framework. The
-  // framework is assumed to retain ownership of these resources until the next
-  // frame update.
+  // Transactions waiting to be applied once the previous transaction is acked.
+  std::queue<SurfaceControl::Transaction> pending_transaction_queue_;
+
+  // The list of Surfaces and the corresponding state based on the most recent
+  // updates.
+  std::vector<SurfaceState> surface_list_;
+
+  // Resources in the previous transaction sent or queued to be sent to the
+  // framework. The framework is assumed to retain ownership of these resources
+  // until the next frame update.
   ResourceRefs current_frame_resources_;
 
   // The root surface tied to the ANativeWindow that places the content of this
@@ -166,8 +163,8 @@
   // The last context made current with this surface.
   scoped_refptr<GLContext> context_;
 
-  // Number of transaction which have been applied and are awaiting an ack.
-  size_t pending_transaction_acks_ = 0;
+  // Set if a transaction was applied and we are waiting for it to be acked.
+  bool transaction_ack_pending_ = false;
 
   scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
   base::WeakPtrFactory<GLSurfaceEGLSurfaceControl> weak_factory_;
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 52a65e76..f6f61c6 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -156,20 +156,24 @@
 
 void View::ReorderChildView(View* view, int index) {
   DCHECK_EQ(view->parent_, this);
-  if (index < 0)
-    index = child_count() - 1;
-  else if (index >= child_count())
-    return;
-  if (children_[index] == view)
+  const auto i = std::find(children_.begin(), children_.end(), view);
+  DCHECK(i != children_.end());
+
+  // If |view| is already at the desired position, there's nothing to do.
+  const bool move_to_end = (index < 0) || (size_t{index} >= children_.size());
+  const auto pos = move_to_end ? std::prev(children_.end())
+                               : std::next(children_.begin(), index);
+  if (i == pos)
     return;
 
-  const Views::iterator i(std::find(children_.begin(), children_.end(), view));
-  DCHECK(i != children_.end());
+    // Rotate |view| to be at the desired position.
 #if DCHECK_IS_ON()
   DCHECK(!iterating_);
 #endif
-  children_.erase(i);
-  const auto pos = children_.insert(std::next(children_.begin(), index), view);
+  if (pos < i)
+    std::rotate(pos, i, std::next(i));
+  else
+    std::rotate(i, std::next(i), std::next(pos));
 
   // Update focus siblings.  Unhook |view| from the focus cycle first so
   // SetFocusSiblings() won't traverse through it.
diff --git a/ui/views/view_unittest.cc b/ui/views/view_unittest.cc
index 31eb14b..46e3f804 100644
--- a/ui/views/view_unittest.cc
+++ b/ui/views/view_unittest.cc
@@ -3682,10 +3682,10 @@
   EXPECT_EQ(0, v1.GetIndexOf(&v2));
   EXPECT_EQ(1, v1.GetIndexOf(&v3));
 
-  // Check that calling |AddChildView()| does not change the order.
+  // Check that calling AddChildView() moves to the end.
   v1.AddChildView(&v2);
-  EXPECT_EQ(0, v1.GetIndexOf(&v2));
-  EXPECT_EQ(1, v1.GetIndexOf(&v3));
+  EXPECT_EQ(1, v1.GetIndexOf(&v2));
+  EXPECT_EQ(0, v1.GetIndexOf(&v3));
   v1.AddChildView(&v3);
   EXPECT_EQ(0, v1.GetIndexOf(&v2));
   EXPECT_EQ(1, v1.GetIndexOf(&v3));