diff --git a/DEPS b/DEPS
index be4246c..f851cd7 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,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': '3aee4845b5039149a506fb85e7442be1924f69c8',
+  'skia_revision': 'e9bc857b39c22878fd2f46284d83f22fe45540d9',
   # 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': '5269c3133de0093c180c57cc51dac04406293e24',
+  'v8_revision': '056e6aee712040d9139e159d6f409edf82fc2925',
   # 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.
@@ -187,7 +187,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '1b2dd6f92a6ba4ac4738ae796692e4844bfd28c7',
+  'angle_revision': '50a47bd992294216b0fe08db02bdd78746b56d8e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -246,7 +246,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'ca9a03f92fbd213b17466a9393ae3490b3eb1806',
+  'devtools_frontend_revision': '3fffd0d4592e1896c05f2d9dca6e2f4f5f9507d3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -302,7 +302,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '81431f5034b4ce3a0cdc339709bd8ca8856fe427',
+  'dawn_revision': '29d712f9de99c646bbebd97cbe55ad5d81486612',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -933,7 +933,7 @@
     Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'fa1c3c4e673cf12ffa22b8fbe4a7c79314571f1b',
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'bfe4c5957fc51148a0aab6e04bb22020667c1092',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '344bd0889ac98c5369df2bf359d0369e3d235b62',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1228,7 +1228,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'd1dae2a33653ee6648f179eeb28244840fb3f248',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e7603d3ab87813963019bd764a4e0dd1b580fe00',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1448,7 +1448,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '84ee597cdeae08bb26e578fc66a35bcf35f633f4',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'd19513f3ffbb939fd56b5377b678bb31d3154e14',
+    Var('webrtc_git') + '/src.git' + '@' + '7bd282acce658a1a640964e90dc2c54e8f3b1530',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1523,7 +1523,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@04f3ecdadc728b53cb94d8f282eefd3748311912',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8df96b104065304c1bb7bb58a1394c59063be2c1',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 727b21f..93b9fb2 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -24,7 +24,7 @@
       "$target_gen_dir/system_webview_pak_whitelist.txt"
 }
 
-if (public_android_sdk) {
+if (define_upstream_webview_targets) {
   template("standalone_system_webview_apk_tmpl") {
     system_webview_apk_or_module_tmpl(target_name) {
       forward_variables_from(invoker, "*")
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index d56d610..9cf1ddb 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -1007,8 +1007,7 @@
 
         // In fullscreen mode FullScreenView owns the AwViewMethodsImpl and AwContents
         // a NullAwViewMethods.
-        FullScreenView fullScreenView = new FullScreenView(mContext, mAwViewMethods, this,
-                mContainerView.getWidth(), mContainerView.getHeight());
+        FullScreenView fullScreenView = new FullScreenView(mContext, mAwViewMethods, this);
         fullScreenView.setFocusable(true);
         fullScreenView.setFocusableInTouchMode(true);
         boolean wasInitialContainerViewFocused = mContainerView.isFocused();
@@ -1122,8 +1121,15 @@
         } else if (!containerViewAttached && mIsAttachedToWindow) {
             awViewMethodsImpl.onDetachedFromWindow();
         }
-        awViewMethodsImpl.onSizeChanged(
-                mContainerView.getWidth(), mContainerView.getHeight(), 0, 0);
+        // Skip passing size of FullScreenView down. FullScreenView is newly created and detached
+        // so has initial size 0x0 before layout. Avoid this temporary resize to 0x0 which can
+        // cause flickers and sometimes layout problems in the web page.
+        if ((mContainerView instanceof FullScreenView)) {
+            assert !containerViewAttached;
+        } else {
+            awViewMethodsImpl.onSizeChanged(
+                    mContainerView.getWidth(), mContainerView.getHeight(), 0, 0);
+        }
         awViewMethodsImpl.onWindowFocusChanged(mContainerView.hasWindowFocus());
         awViewMethodsImpl.onFocusChanged(mContainerView.hasFocus(), 0, null);
         mContainerView.requestLayout();
diff --git a/android_webview/java/src/org/chromium/android_webview/FullScreenView.java b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
index 372b916..da09864 100644
--- a/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
+++ b/android_webview/java/src/org/chromium/android_webview/FullScreenView.java
@@ -29,11 +29,8 @@
     private final AwContents mAwContents;
     private InternalAccessAdapter mInternalAccessAdapter;
 
-    public FullScreenView(Context context, AwViewMethods awViewMethods, AwContents awContents,
-            int initialWidth, int initialHeight) {
+    public FullScreenView(Context context, AwViewMethods awViewMethods, AwContents awContents) {
         super(context);
-        setRight(initialWidth);
-        setBottom(initialHeight);
         setAwViewMethods(awViewMethods);
         mAwContents = awContents;
         mInternalAccessAdapter = new InternalAccessAdapter();
@@ -139,10 +136,7 @@
     @Override
     public void onSizeChanged(final int w, final int h, final int ow, final int oh) {
         super.onSizeChanged(w, h, ow, oh);
-        // Null check for setting initial size before mAwViewMethods is set.
-        if (mAwViewMethods != null) {
-            mAwViewMethods.onSizeChanged(w, h, ow, oh);
-        }
+        mAwViewMethods.onSizeChanged(w, h, ow, oh);
     }
 
     @Override
diff --git a/android_webview/system_webview_apk_tmpl.gni b/android_webview/system_webview_apk_tmpl.gni
index 8a13d74..30cab84 100644
--- a/android_webview/system_webview_apk_tmpl.gni
+++ b/android_webview/system_webview_apk_tmpl.gni
@@ -46,6 +46,11 @@
       "//android_webview:pak_file_assets",
     ]
 
+    # If the R SDK isn't public yet, include the downstream code to support R.
+    if (!public_android_sdk && android_sdk_release == "r") {
+      deps += [ "//clank/android_webview/next:r_sdk_java" ]
+    }
+
     if (_exclude_weblayer_java) {
       deps += [ "//android_webview:android_webview_no_weblayer_java" ]
     } else {
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index f44427c..8051351 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -20,7 +20,7 @@
   ]
 }
 
-if (public_android_sdk) {
+if (define_upstream_webview_targets) {
   python_library("webview_cts_tests") {
     pydeps_file = "//android_webview/tools/run_cts.pydeps"
     deps = [ "//android_webview:system_webview_apk" ]
diff --git a/android_webview/variables.gni b/android_webview/variables.gni
index aaa4d94..3b6ec80 100644
--- a/android_webview/variables.gni
+++ b/android_webview/variables.gni
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/android/channel.gni")
+import("//build/config/android/config.gni")
 import("//weblayer/variables.gni")
 
 declare_args() {
@@ -21,6 +22,12 @@
 trichrome_webview_32_android_manifest =
     "$root_gen_dir/android_webview/trichrome_webview_32_apk/AndroidManifest.xml"
 
+# TODO(crbug/760187): We're in the process of migrating to always defining the
+# WebView targets upstream. Remove this once it's always true.
+if (!defined(define_upstream_webview_targets)) {
+  define_upstream_webview_targets = public_android_sdk
+}
+
 upstream_only_webview_deps = [
   "//android_webview:platform_service_bridge_upstream_implementation_java",
   "//android_webview/nonembedded:icon_resources",
diff --git a/base/task/thread_pool/job_task_source.cc b/base/task/thread_pool/job_task_source.cc
index a731b10..77039dc 100644
--- a/base/task/thread_pool/job_task_source.cc
+++ b/base/task/thread_pool/job_task_source.cc
@@ -336,11 +336,7 @@
 
 bool JobTaskSource::WaitForConcurrencyIncreaseUpdate(size_t recorded_version) {
   AutoLock auto_lock(version_lock_);
-  // This timeout is meant to catch a JobDelegate which forgets to decrease the 
-  // max-concurrency it returns despite workers returning (hence entering an 
-  // infinite loop of workers being spawned with no work to do). 30 seconds 
-  // catches this error early enough without causing false positives.
-  constexpr TimeDelta timeout = TimeDelta::FromSeconds(30);
+  constexpr TimeDelta timeout = TimeDelta::FromSeconds(1);
   const base::TimeTicks start_time = subtle::TimeTicksNowIgnoringOverride();
   do {
     DCHECK_LE(recorded_version, increase_version_);
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index ef9064f9..2dcb168 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -447,6 +447,8 @@
     <ignore regexp="chrome/android/java/res/values/styles.xml"/>
     <!--TODO(crbug.com/1044658): This suppression was added blindly, and needs investigated.-->
     <ignore regexp="chrome/android/java/res/layout/tab_switcher_action_menu_layout.xml"/>
+    <!--TODO(crbug.com/1052375): Remove this suppression once ConnectionInfoPopAndroid moves to components.-->
+    <ignore regexp="components/page_info/android/java/res/drawable-hdpi/pageinfo_*"/>
   </issue>
   <issue id="UseCompoundDrawables">
     <!-- Upscaling 24dp to 48dp doesn't work as expected with a TextView compound drawable. -->
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 8e7aa7e..bf0d3da2 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200329.2.1
\ No newline at end of file
+0.20200330.1.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 8e7aa7e..bf0d3da2 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200329.2.1
\ No newline at end of file
+0.20200330.1.1
\ No newline at end of file
diff --git a/buildtools/linux64/clang-format.sha1 b/buildtools/linux64/clang-format.sha1
index e2b3199a..62b2d60 100644
--- a/buildtools/linux64/clang-format.sha1
+++ b/buildtools/linux64/clang-format.sha1
@@ -1 +1 @@
-942fc8b1789144b8071d3fc03ff0fcbe1cf81ac8
\ No newline at end of file
+1baf0089e895c989a311b6a38ed94d0e8be4c0a7
\ No newline at end of file
diff --git a/buildtools/mac/clang-format.sha1 b/buildtools/mac/clang-format.sha1
index d32c626..5ef063b 100644
--- a/buildtools/mac/clang-format.sha1
+++ b/buildtools/mac/clang-format.sha1
@@ -1 +1 @@
-025ca7c75f37ef4a40f3a67d81ddd11d7d0cdb9b
\ No newline at end of file
+62bde1baa7196ad9df969fc1f06b66360b1a927b
\ No newline at end of file
diff --git a/buildtools/win/clang-format.exe.sha1 b/buildtools/win/clang-format.exe.sha1
index d31c76f..03c98aa 100644
--- a/buildtools/win/clang-format.exe.sha1
+++ b/buildtools/win/clang-format.exe.sha1
@@ -1 +1 @@
-b5f5d8d5f8a8fcd2edb5b6cae37c0dc3e129c945
\ No newline at end of file
+d4afd4eba27022f5f6d518133aebde57281677c9
\ No newline at end of file
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 49138134..23eb5fc 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1526,10 +1526,6 @@
     sources = [ "browser/ui/android/page_info/page_info_controller_android.h" ]
   }
 
-  java_cpp_enum("page_info_action_javagen") {
-    sources = [ "browser/ui/page_info/page_info.h" ]
-  }
-
   java_cpp_enum("partner_bookmarks_javagen") {
     sources = [ "browser/android/bookmarks/partner_bookmarks_reader.h" ]
   }
diff --git a/chrome/VERSION b/chrome/VERSION
index 3b52afb..ad5f7eb5 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=83
 MINOR=0
-BUILD=4099
+BUILD=4100
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 7f66b0c..a9a800e 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -460,7 +460,6 @@
     "//chrome:download_enum_javagen",
     "//chrome:instant_apps_reasons_enum_javagen",
     "//chrome:offline_pages_enum_javagen",
-    "//chrome:page_info_action_javagen",
     "//chrome:page_info_connection_type_javagen",
     "//chrome:partner_bookmarks_javagen",
     "//chrome:payments_journey_logger_enum_javagen",
@@ -482,6 +481,7 @@
     "//components/ntp_snippets:ntp_snippets_java_enums_srcjar",
     "//components/ntp_tiles:ntp_tiles_enums_java",
     "//components/offline_pages/core:offline_page_model_enums_java",
+    "//components/page_info:page_info_action_javagen",
     "//components/password_manager/core/browser:password_manager_java_enums_srcjar",
     "//components/payments/content/android:method_strings_generated_srcjar",
     "//components/search_engines:search_engine_type_java",
@@ -681,7 +681,8 @@
   package_path = "org/chromium/chrome/browser/resources"
   inputs = [
     "../browser/android/resource_id.h",
-    "//components/permissions/android/resource_id.h",
+    "//components/resources/android/page_info_resource_id.h",
+    "//components/resources/android/permissions_resource_id.h",
   ]
 }
 
@@ -1917,7 +1918,9 @@
       include_64_bit_webview = true
     }
   }
+}
 
+if (define_upstream_webview_targets) {
   trichrome_library_apk_tmpl("trichrome_library_apk") {
     apk_name = "TrichromeLibrary"
     android_manifest = trichrome_library_android_manifest
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index daaeb93b..0bebf1434 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -137,9 +137,6 @@
   "java/res/drawable-hdpi/offline_pin.png",
   "java/res/drawable-hdpi/open_in_new_tab.png",
   "java/res/drawable-hdpi/overlay_side_shadow.9.png",
-  "java/res/drawable-hdpi/pageinfo_bad.png",
-  "java/res/drawable-hdpi/pageinfo_good.png",
-  "java/res/drawable-hdpi/pageinfo_warning.png",
   "java/res/drawable-hdpi/permission_background_sync.png",
   "java/res/drawable-hdpi/permission_javascript.png",
   "java/res/drawable-hdpi/permission_mic.png",
@@ -332,9 +329,6 @@
   "java/res/drawable-mdpi/offline_pin.png",
   "java/res/drawable-mdpi/open_in_new_tab.png",
   "java/res/drawable-mdpi/overlay_side_shadow.9.png",
-  "java/res/drawable-mdpi/pageinfo_bad.png",
-  "java/res/drawable-mdpi/pageinfo_good.png",
-  "java/res/drawable-mdpi/pageinfo_warning.png",
   "java/res/drawable-mdpi/permission_background_sync.png",
   "java/res/drawable-mdpi/permission_javascript.png",
   "java/res/drawable-mdpi/permission_mic.png",
@@ -520,9 +514,6 @@
   "java/res/drawable-xhdpi/offline_pin.png",
   "java/res/drawable-xhdpi/open_in_new_tab.png",
   "java/res/drawable-xhdpi/overlay_side_shadow.9.png",
-  "java/res/drawable-xhdpi/pageinfo_bad.png",
-  "java/res/drawable-xhdpi/pageinfo_good.png",
-  "java/res/drawable-xhdpi/pageinfo_warning.png",
   "java/res/drawable-xhdpi/permission_background_sync.png",
   "java/res/drawable-xhdpi/permission_javascript.png",
   "java/res/drawable-xhdpi/permission_mic.png",
@@ -678,9 +669,6 @@
   "java/res/drawable-xxhdpi/offline_pin.png",
   "java/res/drawable-xxhdpi/open_in_new_tab.png",
   "java/res/drawable-xxhdpi/overlay_side_shadow.9.png",
-  "java/res/drawable-xxhdpi/pageinfo_bad.png",
-  "java/res/drawable-xxhdpi/pageinfo_good.png",
-  "java/res/drawable-xxhdpi/pageinfo_warning.png",
   "java/res/drawable-xxhdpi/permission_background_sync.png",
   "java/res/drawable-xxhdpi/permission_javascript.png",
   "java/res/drawable-xxhdpi/permission_mic.png",
@@ -832,9 +820,6 @@
   "java/res/drawable-xxxhdpi/offline_pin.png",
   "java/res/drawable-xxxhdpi/open_in_new_tab.png",
   "java/res/drawable-xxxhdpi/overlay_side_shadow.9.png",
-  "java/res/drawable-xxxhdpi/pageinfo_bad.png",
-  "java/res/drawable-xxxhdpi/pageinfo_good.png",
-  "java/res/drawable-xxxhdpi/pageinfo_warning.png",
   "java/res/drawable-xxxhdpi/permission_background_sync.png",
   "java/res/drawable-xxxhdpi/permission_javascript.png",
   "java/res/drawable-xxxhdpi/permission_mic.png",
diff --git a/chrome/android/java/ResourceId.template b/chrome/android/java/ResourceId.template
index 9abb1f1..6717cab 100644
--- a/chrome/android/java/ResourceId.template
+++ b/chrome/android/java/ResourceId.template
@@ -12,7 +12,8 @@
 #define LINK_RESOURCE_ID(c_id,java_id) java_id,
 #define DECLARE_RESOURCE_ID(c_id,java_id) java_id,
 #include "chrome/browser/android/resource_id.h"
-#include "components/permissions/android/resource_id.h"
+#include "components/resources/android/permissions_resource_id.h"
+#include "components/resources/android/page_info_resource_id.h"
         };
         return resourceList;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
index b8ad965..fe6932a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
@@ -14,6 +14,7 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChildAccountStatus;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.base.WindowAndroid;
@@ -76,7 +77,7 @@
             return;
         }
 
-        Account account = AccountManagerFacade.createAccountFromName(accountName);
+        Account account = AccountUtils.createAccountFromName(accountName);
         AccountManagerFacadeProvider.getInstance().updateCredentials(account, activity,
                 result
                 -> ChildAccountServiceJni.get().onReauthenticationResult(nativeCallback, result));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 6fca974..c99e916 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -331,7 +331,7 @@
     @Override
     protected boolean requiresFirstRunToBeCompleted(Intent intent) {
         // Custom Tabs can be used to open Chrome help pages before the ToS has been accepted.
-        if (IntentHandler.notSecureIsIntentChromeOrFirstParty(intent)
+        if (CustomTabIntentDataProvider.isTrustedCustomTab(intent, mSession)
                 && IntentUtils.safeGetIntExtra(intent, CustomTabIntentDataProvider.EXTRA_UI_TYPE,
                            CustomTabIntentDataProvider.CustomTabsUiType.DEFAULT)
                         == CustomTabIntentDataProvider.CustomTabsUiType.INFO_PAGE) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 71b7670c..1b6df1d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -219,6 +219,20 @@
     }
 
     /**
+     * Evaluates whether the passed Intent and/or CustomTabsSessionToken are
+     * from a trusted source. Trusted in this case means from the app itself or
+     * via a first-party application.
+     *
+     * @param intent The Intent used to start the custom tabs activity, or null.
+     * @param session The connected session for the custom tabs activity, or null.
+     * @return True if the intent or session are trusted.
+     */
+    public static boolean isTrustedCustomTab(Intent intent, CustomTabsSessionToken session) {
+        return IntentHandler.wasIntentSenderChrome(intent)
+                || CustomTabsConnection.getInstance().isSessionFirstParty(session);
+    }
+
+    /**
      * Constructs a {@link CustomTabIntentDataProvider}.
      *
      * The colorScheme parameter specifies which color scheme the Custom Tab should use.
@@ -233,7 +247,7 @@
         mIntent = intent;
 
         mSession = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
-        mIsTrustedIntent = IntentHandler.notSecureIsIntentChromeOrFirstParty(intent);
+        mIsTrustedIntent = isTrustedCustomTab(intent, mSession);
 
         mAnimationBundle = IntentUtils.safeGetBundleExtra(
                 intent, CustomTabsIntent.EXTRA_EXIT_ANIMATION_BUNDLE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index 48b3982..f5d4eef6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -57,6 +57,7 @@
 import org.chromium.chrome.browser.browserservices.SessionDataHolder;
 import org.chromium.chrome.browser.browserservices.SessionHandler;
 import org.chromium.chrome.browser.device.DeviceClassManager;
+import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.init.ChainedTasks;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
@@ -1065,6 +1066,13 @@
         return mClientManager.getClientPackageNameForSession(session);
     }
 
+    /** @return Whether the client of the {@code session} is a first-party application. */
+    public boolean isSessionFirstParty(CustomTabsSessionToken session) {
+        String packageName = getClientPackageNameForSession(session);
+        if (packageName == null) return false;
+        return ExternalAuthUtils.getInstance().isGoogleSigned(packageName);
+    }
+
     @VisibleForTesting
     void setIgnoreUrlFragmentsForSession(CustomTabsSessionToken session, boolean value) {
         mClientManager.setIgnoreFragmentsForSession(session, value);
@@ -1093,15 +1101,6 @@
     }
 
     /**
-     * Extracts the creator package name from the intent.
-     * @param intent The intent to get the package name from.
-     * @return the package name which can be null.
-     */
-    String extractCreatorPackage(Intent intent) {
-        return null;
-    }
-
-    /**
      * Shows a toast about any possible sign in issues encountered during custom tab startup.
      * @param session The session that corresponding custom tab is assigned.
      * @param intent The intent that launched the custom tab.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index f807a51..0e4695d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -28,6 +28,7 @@
 import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.customtabs.CustomTabDelegateFactory;
+import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
 import org.chromium.chrome.browser.customtabs.CustomTabNavigationEventObserver;
 import org.chromium.chrome.browser.customtabs.CustomTabObserver;
 import org.chromium.chrome.browser.customtabs.CustomTabTabPersistencePolicy;
@@ -440,7 +441,7 @@
 
     /** Sets the initial background color for the Tab, shown before the page content is ready. */
     private void prepareTabBackground(final Tab tab) {
-        if (!IntentHandler.notSecureIsIntentChromeOrFirstParty(mIntent)) return;
+        if (!CustomTabIntentDataProvider.isTrustedCustomTab(mIntent, mSession)) return;
 
         int backgroundColor = mIntentDataProvider.getInitialBackgroundColor();
         if (backgroundColor == Color.TRANSPARENT) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataReductionMainMenuItem.java b/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataReductionMainMenuItem.java
index 545cfb5..52b94346 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataReductionMainMenuItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/datareduction/DataReductionMainMenuItem.java
@@ -87,7 +87,10 @@
         SettingsLauncher.getInstance().launchSettingsPage(
                 getContext(), DataReductionPreferenceFragment.class, fragmentArgs);
 
-        Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
+        // TODO (https://crbug.com/1048632): Use the current profile (i.e., regular profile or
+        // incognito profile) instead of always using regular profile. It works correctly now, but
+        // it is not safe.
+        Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile());
         tracker.notifyEvent(EventConstants.DATA_SAVER_DETAIL_OPENED);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index f163ce68..563eb425 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -639,8 +639,11 @@
      */
     public static void getLoadUrlParamsForOpeningOfflineVersion(final String url, long offlineId,
             final @LaunchLocation int location, Callback<LoadUrlParams> callback) {
+        // TODO(https://crbug.com/1041781): Use the current profile (i.e., regular profile or
+        // incognito profile) instead of always using regular profile. It is wrong and need to be
+        // fixed.
         OfflinePageBridge offlinePageBridge =
-                getInstance().getOfflinePageBridge(Profile.getLastUsedProfile());
+                getInstance().getOfflinePageBridge(Profile.getLastUsedRegularProfile());
         if (offlinePageBridge == null) {
             callback.onResult(null);
             return;
@@ -659,8 +662,11 @@
      */
     public static void getLoadUrlParamsForOpeningMhtmlFileOrContent(
             final String intentUrl, Callback<LoadUrlParams> callback) {
+        // TODO(https://crbug.com/1041781): Use the current profile (i.e., regular profile or
+        // incognito profile) instead of always using regular profile. It is wrong and need to be
+        // fixed.
         OfflinePageBridge offlinePageBridge =
-                getInstance().getOfflinePageBridge(Profile.getLastUsedProfile());
+                getInstance().getOfflinePageBridge(Profile.getLastUsedRegularProfile());
         if (offlinePageBridge == null) {
             callback.onResult(new LoadUrlParams(intentUrl));
             return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
index ea59021..1f92592 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninHelper.java
@@ -20,9 +20,9 @@
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.browser.signin.SigninManager.SignInCallback;
 import org.chromium.chrome.browser.sync.ProfileSyncService;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountTrackerService;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.components.signin.metrics.SignoutReason;
@@ -200,7 +200,7 @@
 
     private void performResignin(String newName) {
         // This is the correct account now.
-        final Account account = AccountManagerFacade.createAccountFromName(newName);
+        final Account account = AccountUtils.createAccountFromName(newName);
 
         mSigninManager.signIn(SigninAccessPoint.ACCOUNT_RENAMED, account, new SignInCallback() {
             @Override
@@ -264,7 +264,7 @@
                         // We have found a rename event of the current account.
                         // We need to check if that account is further renamed.
                         newName = name;
-                        if (!accountExists(AccountManagerFacade.createAccountFromName(newName))) {
+                        if (!accountExists(AccountUtils.createAccountFromName(newName))) {
                             newIndex = 0; // Start from the beginning of the new account.
                             continue outerLoop;
                         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
index bd789475..2f14873f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
@@ -22,8 +22,8 @@
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountTrackerService;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.ClearAccountsAction;
@@ -369,8 +369,7 @@
     public void signIn(@SigninAccessPoint int accessPoint, CoreAccountInfo accountInfo,
             @Nullable SignInCallback callback) {
         assert accountInfo != null;
-        signIn(accessPoint, AccountManagerFacade.createAccountFromName(accountInfo.getEmail()),
-                callback);
+        signIn(accessPoint, AccountUtils.createAccountFromName(accountInfo.getEmail()), callback);
     }
 
     /**
@@ -697,7 +696,7 @@
         // Cache the signed-in account name. This must be done after the native call, otherwise
         // sync tries to start without being signed in the native code and crashes.
         mAndroidSyncSettings.updateAccount(
-                AccountManagerFacade.createAccountFromName(accountInfo.getEmail()));
+                AccountUtils.createAccountFromName(accountInfo.getEmail()));
         mAndroidSyncSettings.enableChromeSync();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index 3bc3033..0904088e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -958,7 +958,7 @@
 
         // Mark the intent as trusted so it can show more than one action button.
         IntentHandler.addTrustedIntentExtras(intent);
-        Assert.assertTrue(IntentHandler.notSecureIsIntentChromeOrFirstParty(intent));
+        Assert.assertTrue(IntentHandler.wasIntentSenderChrome(intent));
 
         ArrayList<Bundle> toolbarItems = new ArrayList<>(2);
         final PendingIntent pi1 = PendingIntent.getBroadcast(
@@ -1033,7 +1033,7 @@
         Intent intent = createMinimalCustomTabIntent();
 
         // By default, the intent should not be trusted.
-        Assert.assertFalse(IntentHandler.notSecureIsIntentChromeOrFirstParty(intent));
+        Assert.assertFalse(IntentHandler.wasIntentSenderChrome(intent));
 
         ArrayList<Bundle> toolbarItems = new ArrayList<>(2);
         final PendingIntent pi = PendingIntent.getBroadcast(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
index 24df8be..a7bb111 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunUtilsTest.java
@@ -18,8 +18,8 @@
 import org.chromium.base.test.util.AdvancedMockContext;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 
@@ -33,7 +33,7 @@
     private Account mTestAccount;
 
     public FirstRunUtilsTest() {
-        mTestAccount = AccountManagerFacade.createAccountFromName("Dummy");
+        mTestAccount = AccountUtils.createAccountFromName("Dummy");
     }
 
     @Before
@@ -77,7 +77,7 @@
     public void testHasGoogleAccountCorrectlyDetected() {
         // Set up an account manager mock that returns Google account types
         // when queried.
-        setUpAccountManager(AccountManagerFacade.GOOGLE_ACCOUNT_TYPE);
+        setUpAccountManager(AccountUtils.GOOGLE_ACCOUNT_TYPE);
         addTestAccount();
 
         ContextUtils.initApplicationContextForTests(mAccountTestingContext);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordViewingTypeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordViewingTypeTest.java
index c5509c2..adb9ac5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordViewingTypeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordViewingTypeTest.java
@@ -26,7 +26,7 @@
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.SettingsLauncher;
 import org.chromium.chrome.browser.sync.ProfileSyncService;
-import org.chromium.components.signin.AccountManagerFacade;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.AccountManagerTestRule;
 import org.chromium.components.sync.AndroidSyncSettings;
@@ -72,7 +72,7 @@
     }
 
     private void setupTestAccount() {
-        mAccount = AccountManagerFacade.createAccountFromName("account@example.com");
+        mAccount = AccountUtils.createAccountFromName("account@example.com");
         AccountHolder.Builder accountHolder = AccountHolder.builder(mAccount).alwaysAccept(true);
         mAccountManagerTestRule.addAccount(accountHolder.build());
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/DecoderServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/DecoderServiceTest.java
index ec4deb5..303f5ce 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/DecoderServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/photo_picker/DecoderServiceTest.java
@@ -41,6 +41,11 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class DecoderServiceTest {
+    // By default, the test will wait for 3 seconds to create the decoder process, which (at least
+    // in the emulators) brushes up against the actual time it takes to create the process, so these
+    // tests are frequently flaky when run locally.
+    private static final int DECODER_STARTUP_TIMEOUT_IN_MS = 7500;
+
     @Rule
     public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
             new ChromeActivityTestRule<>(ChromeActivity.class);
@@ -99,7 +104,7 @@
             public boolean isSatisfied() {
                 return mBound;
             }
-        });
+        }, DECODER_STARTUP_TIMEOUT_IN_MS, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
     }
 
     private void decode(String filePath, FileDescriptor fd, int width,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerDialogFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerDialogFragmentTest.java
index 9c1151b..badba0f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerDialogFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/AccountPickerDialogFragmentTest.java
@@ -34,7 +34,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.components.signin.AccountManagerFacade;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ProfileDataSource;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.AccountManagerTestRule;
@@ -135,7 +135,7 @@
     }
 
     private void addAccount(String accountName, String fullName) {
-        Account account = AccountManagerFacade.createAccountFromName(accountName);
+        Account account = AccountUtils.createAccountFromName(accountName);
         AccountHolder.Builder accountHolder = AccountHolder.builder(account).alwaysAccept(true);
         ProfileDataSource.ProfileData profileData =
                 new ProfileDataSource.ProfileData(accountName, null, fullName, null);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/IdentityManagerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/IdentityManagerIntegrationTest.java
index 47ebf05..444b6a5a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/IdentityManagerIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/IdentityManagerIntegrationTest.java
@@ -15,8 +15,8 @@
 
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.RetryOnFailure;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.base.CoreAccountId;
 import org.chromium.components.signin.base.CoreAccountInfo;
@@ -46,11 +46,11 @@
     private static final String TEST_ACCOUNT1 = "foo@gmail.com";
     private static final String TEST_ACCOUNT2 = "bar@gmail.com";
     private static final AccountHolder TEST_ACCOUNT_HOLDER_1 =
-            AccountHolder.builder(AccountManagerFacade.createAccountFromName(TEST_ACCOUNT1))
+            AccountHolder.builder(AccountUtils.createAccountFromName(TEST_ACCOUNT1))
                     .alwaysAccept(true)
                     .build();
     private static final AccountHolder TEST_ACCOUNT_HOLDER_2 =
-            AccountHolder.builder(AccountManagerFacade.createAccountFromName(TEST_ACCOUNT2))
+            AccountHolder.builder(AccountUtils.createAccountFromName(TEST_ACCOUNT2))
                     .alwaysAccept(true)
                     .build();
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
index 620d022..f77a67a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninHelperTest.java
@@ -17,8 +17,8 @@
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.signin.MockChangeEventChecker;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.AccountManagerTestRule;
@@ -110,7 +110,7 @@
         mEventChecker.insertRenameEvent("B", "C");
         mEventChecker.insertRenameEvent("C", "D");
         mEventChecker.insertRenameEvent("D", "A"); // Looped.
-        Account account = AccountManagerFacade.createAccountFromName("D");
+        Account account = AccountUtils.createAccountFromName("D");
         AccountHolder accountHolder = AccountHolder.builder(account).build();
         mAccountManagerTestRule.addAccount(accountHolder);
         SigninHelper.updateAccountRenameData(mEventChecker);
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 11f55b0..2b15c8a 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -4384,6 +4384,9 @@
   <message name="IDS_PLUGIN_VM_INSTALLER_NOT_ALLOWED_TITLE" desc="Title of the Plugin VM installer if Plugin VM is disallowed.">
     Plugin VM needs permission to run
   </message>
+  <message name="IDS_PLUGIN_VM_INSTALLER_LOW_DISK_SPACE_MESSAGE" desc="Text of the Plugin VM installer that warns them of low disk space.">
+    Your device is low on storage space. At least <ph name="RECOMMENDED_SPACE">$1<ex>32GB</ex></ph> of free space is recommended to use Plugin VM. To free up space, delete files from device storage.
+  </message>
   <message name="IDS_PLUGIN_VM_INSTALLER_START_DOWNLOADING_MESSAGE" desc="Text of the Plugin VM installer that informs the user that setup may take a while.">
     This may take awhile
   </message>
@@ -4423,6 +4426,9 @@
   <message name="IDS_PLUGIN_VM_INSTALLER_TIME_LEFT_MESSAGE" desc="Text of the Plugin VM installer that shows estimated time left before current installation stage ends.">
     <ph name="TIME_LEFT">$1<ex>30 mins</ex></ph> left
   </message>
+  <message name="IDS_PLUGIN_VM_INSTALLER_CONTINUE_BUTTON" desc="Label for the button in the Plugin VM installer to continue installation.">
+    Continue
+  </message>
   <message name="IDS_PLUGIN_VM_INSTALLER_LAUNCH_BUTTON" desc="Label for the button in the Plugin VM installer to launch Plugin VM after successful installation.">
     Launch
   </message>
@@ -4468,8 +4474,8 @@
   <message name="IDS_PLUGIN_VM_DLC_NEED_REBOOT_FAILED_MESSAGE" desc="Error message shown if Plugin VM installation fails because a pending update requires the device to be restarted.">
     Please restart your device to use Plugin VM.
   </message>
-  <message name="IDS_PLUGIN_VM_DLC_NEED_SPACE_FAILED_MESSAGE" desc="Error message shown if Plugin VM installation fails because there is not enough free space on the device.">
-    Your device is low on storage space. Please free up space to use Plugin VM.
+  <message name="IDS_PLUGIN_VM_INSUFFICIENT_DISK_SPACE_MESSAGE" desc="Error message shown if Plugin VM installation fails because there is not enough free space on the device.">
+    Your device is low on storage space. At least <ph name="MINIMUM_SPACE">$1<ex>16GB</ex></ph> of free space is required to use Plugin VM, and over <ph name="RECOMMENDED_SPACE">$2<ex>32GB</ex></ph> of free space is recommended. To free up space, delete files from device storage.
   </message>
 
   <!-- Strings for Account Manager error screen -->
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_DLC_NEED_SPACE_FAILED_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_DLC_NEED_SPACE_FAILED_MESSAGE.png.sha1
deleted file mode 100644
index 0bc8db9..0000000
--- a/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_DLC_NEED_SPACE_FAILED_MESSAGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ab2aaafa34246f7a3c88c095348306050c987c4e
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSTALLER_LOW_DISK_SPACE_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSTALLER_LOW_DISK_SPACE_MESSAGE.png.sha1
new file mode 100644
index 0000000..3841f82
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSTALLER_LOW_DISK_SPACE_MESSAGE.png.sha1
@@ -0,0 +1 @@
+9be927ad30f67d387e96ef4eb99a0e9aa518670c
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSUFFICIENT_DISK_SPACE_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSUFFICIENT_DISK_SPACE_MESSAGE.png.sha1
new file mode 100644
index 0000000..f32be7ec
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_PLUGIN_VM_INSUFFICIENT_DISK_SPACE_MESSAGE.png.sha1
@@ -0,0 +1 @@
+4088af357e5509c43f1b0bbaa0cb1ca29b9243e3
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 13f8ca4..739cf3a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -2525,9 +2525,6 @@
       <message name="IDS_LIST_BULLET" desc="Unicode bullet for list items in permission bubbles.">
         •  <ph name="LIST_ITEM_TEXT">$1<ex>You might need permission to continue ...</ex></ph>
       </message>
-      <message name="IDS_AUTOMATIC_DOWNLOADS_TAB_LABEL" desc="Header for multiple automatic downloads page on Content Settings dialog">
-        Automatic Downloads
-      </message>
       <message name="IDS_BLOCKED_DOWNLOAD_NO_ACTION" desc="Radio button to keep blocking automatic downloads">
         Continue blocking automatic downloads of multiple files
       </message>
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 2069ad4..fbba6e2d 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -15,22 +15,18 @@
     "account_child.icon",
     "account_child_circle.icon",
     "add.icon",
-    "ads.icon",
     "apps.icon",
     "autofill/webauthn_dialog_header.icon",
     "autofill/webauthn_dialog_header_dark.icon",
-    "blocked_badge.icon",
     "blocked_redirect.icon",
     "browser_tools.icon",
     "browser_tools_error.icon",
     "browser_tools_update.icon",
     "caret_down.icon",
     "caret_up.icon",
-    "certificate.icon",
     "click_to_call_illustration.icon",
     "click_to_call_illustration_dark.icon",
     "close_all.icon",
-    "code.icon",
 
     # Alternative squarer content_paste icon optimised for display at 18x18dip.
     # Currently only used in the Page Info bubble.
@@ -47,7 +43,6 @@
     "default_touch_favicon_mask.icon",
     "eol.icon",
     "extension_crashed.icon",
-    "file_download.icon",
     "file_download_shelf.icon",
     "fingerprint.icon",
     "forward_arrow_touch.icon",
@@ -62,7 +57,6 @@
     "input.icon",
     "key.icon",
     "laptop.icon",
-    "launch.icon",
     "media_toolbar_button.icon",
     "media_toolbar_button_touch.icon",
     "mixed_content.icon",
@@ -73,13 +67,10 @@
     "navigate_stop_touch.icon",
     "nfc.icon",
     "overflow_chevron.icon",
-    "page_info_content_paste.icon",
     "paintbrush.icon",
-    "photo.icon",
     "photo_camera.icon",
     "picture_in_picture_control_background.icon",
     "picture_in_picture_alt.icon",
-    "protected_content.icon",
     "qrcode_generator.icon",
     "reader_mode.icon",
     "reader_mode_disabled.icon",
@@ -88,12 +79,10 @@
     "remove_box.icon",
     "resize_handle.icon",
     "sad_tab.icon",
-    "save_original_file.icon",
     "security.icon",
     "send_tab_to_self.icon",
     "eye_crossed.icon",
     "eye.icon",
-    "sensors.icon",
     "signin_button_drop_down_arrow.icon",
     "sign_out.icon",
     "smartphone.icon",
@@ -101,7 +90,6 @@
     "speaker_group.icon",
     "supervisor_account.icon",
     "supervisor_account_circle.icon",
-    "sync.icon",
     "sync_circle.icon",
     "sync_error_circle.icon",
     "sync_paused.icon",
@@ -128,7 +116,6 @@
     "user_account_avatar.icon",
     "user_menu_guest.icon",
     "user_menu_right_arrow.icon",
-    "volume_up.icon",
     "warning_badge.icon",
     "web.icon",
     "webauthn/webauthn_ble.icon",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 9768ca7..1940ec8 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -170,7 +170,7 @@
   "+components/optimization_guide",
   "+components/os_crypt",
   "+components/ownership",
-  "+components/page_info/android",
+  "+components/page_info",
   "+components/page_load_metrics/browser",
   "+components/page_load_metrics/common",
   "+components/paint_preview/features",
diff --git a/chrome/browser/android/DEPS b/chrome/browser/android/DEPS
index 617640f..0ebd7fa 100644
--- a/chrome/browser/android/DEPS
+++ b/chrome/browser/android/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "-components/devtools_bridge",
+  "+components/resources/android",
   "+cc/layers/layer.h",
   "+chrome/android/test_support_jni_headers",
   "+chrome_jni_registration/chrome_jni_registration.h",
diff --git a/chrome/browser/android/android_theme_resources.h b/chrome/browser/android/android_theme_resources.h
index 7459b98..865482fc 100644
--- a/chrome/browser/android/android_theme_resources.h
+++ b/chrome/browser/android/android_theme_resources.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_ANDROID_ANDROID_THEME_RESOURCES_H_
 #define CHROME_BROWSER_ANDROID_ANDROID_THEME_RESOURCES_H_
 
-#include "components/permissions/android/theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 
 // LINK_RESOURCE_ID will use an ID defined by grit, so no-op.
 #define LINK_RESOURCE_ID(c_id, java_id)
diff --git a/chrome/browser/android/resource_id.h b/chrome/browser/android/resource_id.h
index 0937162..01eb4a6 100644
--- a/chrome/browser/android/resource_id.h
+++ b/chrome/browser/android/resource_id.h
@@ -51,24 +51,6 @@
 LINK_RESOURCE_ID(IDR_AUTOFILL_GOOGLE_PAY_WITH_DIVIDER,
                  R.drawable.google_pay_with_divider)
 
-// PageInfoUI images, used in ConnectionInfoPopup
-// Good:
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_GOOD, R.drawable.pageinfo_good)
-// Warnings:
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_WARNING_MINOR, R.drawable.pageinfo_warning)
-// Bad:
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_BAD, R.drawable.pageinfo_bad)
-// Should never occur, use warning just in case:
-// Enterprise managed: ChromeOS only.
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_ENTERPRISE_MANAGED,
-                    R.drawable.pageinfo_warning)
-// Info: Only shown on chrome:// urls, which don't show the connection info
-// popup.
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_INFO, R.drawable.pageinfo_warning)
-// Major warning: Used on insecure pages, which don't show the connection info
-// popup.
-DECLARE_RESOURCE_ID(IDR_PAGEINFO_WARNING_MAJOR, R.drawable.pageinfo_warning)
-
 // Autofill popup and keyboard accessory images.
 // We use Android's |VectorDrawableCompat| for the following images that are
 // displayed using |DropdownAdapter|.
diff --git a/chrome/browser/android/resource_mapper.cc b/chrome/browser/android/resource_mapper.cc
index a89476d..af7af86 100644
--- a/chrome/browser/android/resource_mapper.cc
+++ b/chrome/browser/android/resource_mapper.cc
@@ -53,7 +53,8 @@
 #define DECLARE_RESOURCE_ID(c_id, java_id) \
   g_id_map.Get()[c_id] = resource_id_list[next_id++];
 #include "chrome/browser/android/resource_id.h"
-#include "components/permissions/android/resource_id.h"
+#include "components/resources/android/page_info_resource_id.h"
+#include "components/resources/android/permissions_resource_id.h"
 #undef LINK_RESOURCE_ID
 #undef DECLARE_RESOURCE_ID
   // Make sure ID list sizes match up.
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index 233c9cb90..fc8abdb0 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -119,6 +119,7 @@
     "//components/content_settings/core/browser",
     "//components/language/core/browser",
     "//components/omnibox/browser",
+    "//components/page_info",
     "//components/permissions",
     "//components/rappor",
     "//components/search_engines:search_engines",
diff --git a/chrome/browser/android/vr/vr_shell.h b/chrome/browser/android/vr/vr_shell.h
index 882f30f..ad734a7 100644
--- a/chrome/browser/android/vr/vr_shell.h
+++ b/chrome/browser/android/vr/vr_shell.h
@@ -16,7 +16,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/string16.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/ui/toolbar/chrome_location_bar_model_delegate.h"
 #include "chrome/browser/vr/assets_load_status.h"
 #include "chrome/browser/vr/exit_vr_prompt_choice.h"
@@ -26,6 +25,7 @@
 #include "chrome/browser/vr/ui_browser_interface.h"
 #include "chrome/browser/vr/ui_initial_state.h"
 #include "chrome/browser/vr/ui_unsupported_mode.h"
+#include "components/page_info/page_info_ui.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device.h"
diff --git a/chrome/browser/browser_switcher/bho/bho.cc b/chrome/browser/browser_switcher/bho/bho.cc
index 7aec413..6fd3b8a 100644
--- a/chrome/browser/browser_switcher/bho/bho.cc
+++ b/chrome/browser/browser_switcher/bho/bho.cc
@@ -38,7 +38,7 @@
 CBrowserSwitcherBHO::~CBrowserSwitcherBHO() = default;
 
 // Implementation of IObjectWithSiteImpl::SetSite.
-STDMETHODIMP CBrowserSwitcherBHO::SetSite(IUnknown* site) {
+STDMETHODIMP CBrowserSwitcherBHO::SetSite(IUnknown* site) noexcept {
   if (site != NULL) {
     HRESULT hr = site->QueryInterface(IID_PPV_ARGS(&web_browser_));
     if (SUCCEEDED(hr)) {
diff --git a/chrome/browser/browser_switcher/bho/browser_switcher_core.cc b/chrome/browser/browser_switcher/bho/browser_switcher_core.cc
index e2ffb29..aad2117e 100644
--- a/chrome/browser/browser_switcher/bho/browser_switcher_core.cc
+++ b/chrome/browser/browser_switcher/bho/browser_switcher_core.cc
@@ -499,7 +499,7 @@
   // In almost every case should this be enough for the sanitization because
   // any ASCII char will expand to at most 3 chars - %[0-9A-F][0-9A-F].
   DWORD length = static_cast<DWORD>(url.length() * 3 + 1);
-  std::auto_ptr<wchar_t> buffer(new wchar_t[length]);
+  std::unique_ptr<wchar_t[]> buffer(new wchar_t[length]);
   if (!::InternetCanonicalizeUrl(url.c_str(), buffer.get(), &length, 0)) {
     DWORD error = ::GetLastError();
     if (error == ERROR_INSUFFICIENT_BUFFER) {
@@ -540,7 +540,7 @@
   // any ASCII char will expand to at most 3 chars - %[0-9A-F][0-9A-F].
   std::wstring::const_iterator it = url.begin();
   std::wstring untranslated_chars(L".:/\\_-@~();");
-  std::auto_ptr<wchar_t> sanitized_url(new wchar_t[url.length() * 3 + 1]);
+  std::unique_ptr<wchar_t[]> sanitized_url(new wchar_t[url.length() * 3 + 1]);
   wchar_t* output = sanitized_url.get();
 
   while (it != url.end()) {
@@ -613,7 +613,7 @@
              << std::endl;
     return std::wstring();
   }
-  std::auto_ptr<wchar_t> browser_path(new wchar_t[length]);
+  std::unique_ptr<wchar_t[]> browser_path(new wchar_t[length]);
   if (ERROR_SUCCESS !=
       ::RegQueryValueEx(key, name, NULL, NULL,
                         reinterpret_cast<LPBYTE>(browser_path.get()),
@@ -632,7 +632,7 @@
   expanded_size = ::ExpandEnvironmentStrings(str.c_str(), NULL, expanded_size);
   if (expanded_size != 0) {
     // The expected buffer length as defined in MSDN is chars + null + 1.
-    std::auto_ptr<wchar_t> expanded_path(new wchar_t[expanded_size + 2]);
+    std::unique_ptr<wchar_t[]> expanded_path(new wchar_t[expanded_size + 2]);
     expanded_size = ::ExpandEnvironmentStrings(str.c_str(), expanded_path.get(),
                                                expanded_size);
     if (expanded_size != 0)
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 27d55fdf..d2dde69 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -914,6 +914,8 @@
     "crostini/crostini_remover.h",
     "crostini/crostini_reporting_util.cc",
     "crostini/crostini_reporting_util.h",
+    "crostini/crostini_shelf_utils.cc",
+    "crostini/crostini_shelf_utils.h",
     "crostini/crostini_simple_types.h",
     "crostini/crostini_terminal.cc",
     "crostini/crostini_terminal.h",
@@ -2817,6 +2819,7 @@
     "crostini/crostini_port_forwarder_unittest.cc",
     "crostini/crostini_registry_service_unittest.cc",
     "crostini/crostini_reporting_util_unittest.cc",
+    "crostini/crostini_shelf_utils_unittest.cc",
     "crostini/crostini_unsupported_action_notifier_unittest.cc",
     "crostini/crostini_util_unittest.cc",
     "crostini/crosvm_metrics_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
index 192ac33..1127656a 100644
--- a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/memory/weak_ptr.h"
+#include "base/strings/pattern.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/accessibility/speech_monitor.h"
 #include "chrome/browser/profiles/profile.h"
@@ -73,7 +74,7 @@
     ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
   }
 
-  SpeechMonitor sm_;
+  SpeechMonitor speech_monitor_;
   std::unique_ptr<ui::test::EventGenerator> generator_;
 
   gfx::Rect GetWebContentsBounds() const {
@@ -139,8 +140,8 @@
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
-  void ExecuteJavaScriptAsync(const std::string& script) {
-    content::ExecuteScriptAsync(GetWebContents(), script);
+  void ExecuteJavaScriptInForeground(const std::string& script) {
+    CHECK(content::ExecuteScript(GetWebContents(), script));
   }
 
  private:
@@ -175,8 +176,8 @@
   generator_->ReleaseLeftButton();
   generator_->ReleaseKey(ui::VKEY_LWIN, 0 /* flags */);
 
-  sm_.ExpectSpeechPattern("Status tray*");
-  sm_.Replay();
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(), "Status tray*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ActivatesWithTapOnSelectToSpeakTray) {
@@ -198,8 +199,8 @@
                           bounds.y() + bounds.height());
   generator_->ReleaseLeftButton();
 
-  sm_.ExpectSpeechPattern("This is some text*");
-  sm_.Replay();
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "This is some text*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SelectToSpeakTrayNotSpoken) {
@@ -217,9 +218,8 @@
   // The next should be the first thing spoken -- the tray was not spoken.
   ActivateSelectToSpeakInWindowBounds(
       "data:text/html;charset=utf-8,<p>This is some text</p>");
-
-  sm_.ExpectSpeechPattern("This is some text*");
-  sm_.Replay();
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "This is some text*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossInlineUrl) {
@@ -230,8 +230,9 @@
   // Should combine nodes in a paragraph into one utterance.
   // Includes some wildcards between words because there may be extra
   // spaces. Spaces are not pronounced, so extra spaces do not impact output.
-  sm_.ExpectSpeechPattern("This is some text*with a node*in the middle*");
-  sm_.Replay();
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                         "This is some text*with a node*in the middle*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossMultipleLines) {
@@ -244,8 +245,9 @@
   // spaces, for example at line wraps. Extra wildcards included to
   // reduce flakyness in case wrapping is not consistent.
   // Spaces are not pronounced, so extra spaces do not impact output.
-  sm_.ExpectSpeechPattern("This is some*text*with*a*node*in*the*middle*");
-  sm_.Replay();
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                         "This is some*text*with*a*node*in*the*middle*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossFormattedText) {
@@ -257,8 +259,9 @@
   // Should combine nodes in a paragraph into one utterance.
   // Includes some wildcards between words because there may be extra
   // spaces. Spaces are not pronounced, so extra spaces do not impact output.
-  sm_.ExpectSpeechPattern("This is some text*with a node*in the middle*");
-  sm_.Replay();
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                         "This is some text*with a node*in the middle*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest,
@@ -266,9 +269,8 @@
   // Bold or formatted text
   ActivateSelectToSpeakInWindowBounds(
       "data:text/html;charset=utf-8,<canvas>This is some text</canvas>");
-
-  sm_.ExpectSpeechPattern("This is some text*");
-  sm_.Replay();
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "This is some text*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, BreaksAtParagraphBounds) {
@@ -277,9 +279,10 @@
       "<p>Second paragraph</p></div>");
 
   // Should keep each paragraph as its own utterance.
-  sm_.ExpectSpeechPattern("First paragraph*");
-  sm_.ExpectSpeechPattern("Second paragraph*");
-  sm_.Replay();
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "First paragraph*"));
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "Second paragraph*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, LanguageBoundsIgnoredByDefault) {
@@ -290,8 +293,9 @@
       "<span lang='en-US'>The first paragraph</span>"
       "<span lang='fr-FR'>la deuxième paragraphe</span></div>");
 
-  sm_.ExpectSpeechPattern("The first paragraph* la deuxième paragraphe*");
-  sm_.Replay();
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                         "The first paragraph* la deuxième paragraphe*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTestWithLanguageDetection,
@@ -301,9 +305,15 @@
       "<span lang='en-US'>The first paragraph</span>"
       "<span lang='fr-FR'>la deuxième paragraphe</span></div>");
 
-  sm_.ExpectSpeechPatternWithLocale("The first paragraph*", "en-US");
-  sm_.ExpectSpeechPatternWithLocale("la deuxième paragraphe*", "fr-FR");
-  sm_.Replay();
+  SpeechMonitorUtterance result1 =
+      speech_monitor_.GetNextUtteranceWithLanguage();
+  EXPECT_TRUE(base::MatchPattern(result1.text, "The first paragraph*"));
+  EXPECT_EQ("en-US", result1.lang);
+
+  SpeechMonitorUtterance result2 =
+      speech_monitor_.GetNextUtteranceWithLanguage();
+  EXPECT_TRUE(base::MatchPattern(result2.text, "la deuxième paragraphe*"));
+  EXPECT_EQ("fr-FR", result2.lang);
 }
 
 // Flaky test. https://crbug.com/950049
@@ -389,16 +399,15 @@
       "<p>Second paragraph is longer than 300 pixels and will wrap when "
       "resized</p></div>");
 
-  sm_.ExpectSpeechPattern("First paragraph*");
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "First paragraph*"));
 
   // Resize before second is spoken. If resizing caused errors finding the
   // inlineTextBoxes in the node, speech would be stopped early.
-  sm_.Call([this]() {
-    ExecuteJavaScriptAsync(
-        "document.getElementById('resize').style.width='100px'");
-  });
-  sm_.ExpectSpeechPattern("*when*resized*");
-  sm_.Replay();
+  ExecuteJavaScriptInForeground(
+      "document.getElementById('resize').style.width='100px'");
+  EXPECT_TRUE(
+      base::MatchPattern(speech_monitor_.GetNextUtterance(), "*when*resized*"));
 }
 
 IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, WorksWithStickyKeys) {
@@ -420,12 +429,11 @@
                           bounds.y() + bounds.height());
   generator_->ReleaseLeftButton();
 
-  sm_.ExpectSpeechPattern("This is some text*");
+  EXPECT_TRUE(base::MatchPattern(speech_monitor_.GetNextUtterance(),
+                                 "This is some text*"));
 
   // Reset state.
-  sm_.Call([]() { AccessibilityManager::Get()->EnableStickyKeys(false); });
-
-  sm_.Replay();
+  AccessibilityManager::Get()->EnableStickyKeys(false);
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/accessibility/speech_monitor.cc b/chrome/browser/chromeos/accessibility/speech_monitor.cc
index 6516aa3..0d84ff4 100644
--- a/chrome/browser/chromeos/accessibility/speech_monitor.cc
+++ b/chrome/browser/chromeos/accessibility/speech_monitor.cc
@@ -12,9 +12,12 @@
 namespace chromeos {
 
 namespace {
+const char kChromeVoxEnabledMessage[] = "ChromeVox spoken feedback is ready";
+const char kChromeVoxAlertMessage[] = "Alert";
+const char kChromeVoxUpdate1[] = "chrome vox Updated Press chrome vox o,";
+const char kChromeVoxUpdate2[] = "n to learn more about chrome vox Next.";
 
 constexpr int kPrintExpectationDelayMs = 3000;
-
 }  // namespace
 
 SpeechMonitor::SpeechMonitor() {
@@ -28,6 +31,52 @@
     CHECK(replay_called_) << "Expectation was made, but Replay() not called.";
 }
 
+std::string SpeechMonitor::GetNextUtterance() {
+  return GetNextUtteranceWithLanguage().text;
+}
+
+SpeechMonitorUtterance SpeechMonitor::GetNextUtteranceWithLanguage() {
+  if (utterance_queue_.empty()) {
+    loop_runner_ = new content::MessageLoopRunner();
+    loop_runner_->Run();
+    loop_runner_.reset();
+  }
+  SpeechMonitorUtterance result = utterance_queue_.front();
+  utterance_queue_.pop_front();
+  return result;
+}
+
+bool SpeechMonitor::SkipChromeVoxEnabledMessage() {
+  return SkipChromeVoxMessage(kChromeVoxEnabledMessage);
+}
+
+bool SpeechMonitor::DidStop() {
+  return did_stop_;
+}
+
+void SpeechMonitor::BlockUntilStop() {
+  if (!did_stop_) {
+    loop_runner_ = new content::MessageLoopRunner();
+    loop_runner_->Run();
+    loop_runner_.reset();
+  }
+}
+
+bool SpeechMonitor::SkipChromeVoxMessage(const std::string& message) {
+  while (true) {
+    if (utterance_queue_.empty()) {
+      loop_runner_ = new content::MessageLoopRunner();
+      loop_runner_->Run();
+      loop_runner_.reset();
+    }
+    SpeechMonitorUtterance result = utterance_queue_.front();
+    utterance_queue_.pop_front();
+    if (result.text == message)
+      return true;
+  }
+  return false;
+}
+
 bool SpeechMonitor::PlatformImplAvailable() {
   return true;
 }
@@ -46,6 +95,7 @@
 }
 
 bool SpeechMonitor::StopSpeaking() {
+  did_stop_ = true;
   return true;
 }
 
@@ -64,8 +114,21 @@
 void SpeechMonitor::WillSpeakUtteranceWithVoice(
     content::TtsUtterance* utterance,
     const content::VoiceData& voice_data) {
+  // Blacklist some phrases.
+  // Filter out empty utterances which can be used to trigger a start event from
+  // tts as an earcon sync.
+  if (utterance->GetText() == "" ||
+      utterance->GetText() == kChromeVoxAlertMessage ||
+      utterance->GetText() == kChromeVoxUpdate1 ||
+      utterance->GetText() == kChromeVoxUpdate2)
+    return;
+
+  VLOG(0) << "Speaking " << utterance->GetText();
   utterance_queue_.emplace_back(utterance->GetText(), utterance->GetLang());
   delay_for_last_utterance_ms_ = CalculateUtteranceDelayMS();
+  if (loop_runner_.get())
+    loop_runner_->Quit();
+
   MaybeContinueReplay();
 }
 
@@ -120,19 +183,11 @@
 
 void SpeechMonitor::ExpectSpeechPattern(const std::string& pattern,
                                         const base::Location& location) {
-  ExpectSpeechPatternWithLocale(pattern, "", location);
-}
-
-void SpeechMonitor::ExpectSpeechPatternWithLocale(
-    const std::string& pattern,
-    const std::string& locale,
-    const base::Location& location) {
   CHECK(!replay_loop_runner_.get());
-  replay_queue_.push_back({[this, pattern, locale]() {
+  replay_queue_.push_back({[this, pattern]() {
                              for (auto it = utterance_queue_.begin();
                                   it != utterance_queue_.end(); it++) {
-                               if (base::MatchPattern(it->text, pattern) &&
-                                   (locale.empty() || it->lang == locale)) {
+                               if (base::MatchPattern(it->text, pattern)) {
                                  // Erase all utterances that came before the
                                  // match as well as the match itself.
                                  utterance_queue_.erase(
diff --git a/chrome/browser/chromeos/accessibility/speech_monitor.h b/chrome/browser/chromeos/accessibility/speech_monitor.h
index 368de795..eb319a6 100644
--- a/chrome/browser/chromeos/accessibility/speech_monitor.h
+++ b/chrome/browser/chromeos/accessibility/speech_monitor.h
@@ -25,13 +25,35 @@
 };
 
 // For testing purpose installs itself as the platform speech synthesis engine,
-// allowing it to intercept all speech calls. Provides an api to make
-// asynchronous function calls and expectations about resulting speech.
+// allowing it to intercept all speech calls, and then provides a method to
+// block until the next utterance is spoken.
 class SpeechMonitor : public content::TtsPlatform {
  public:
   SpeechMonitor();
   virtual ~SpeechMonitor();
 
+  // Blocing api.
+  // Use the following apis to write a synchronous test e.g.
+  // DoSomething();
+  // EXPECT_EQ("foo", speech_monitor_.GetNextUtterance());
+
+  // Blocks until the next utterance is spoken, and returns its text.
+  std::string GetNextUtterance();
+  // Blocks until the next utterance is spoken, and returns its text.
+  SpeechMonitorUtterance GetNextUtteranceWithLanguage();
+
+  // Wait for next utterance and return true if next utterance is ChromeVox
+  // enabled message.
+  bool SkipChromeVoxEnabledMessage();
+  bool SkipChromeVoxMessage(const std::string& message);
+
+  // Returns true if StopSpeaking() was called on TtsController.
+  bool DidStop();
+
+  // Blocks until StopSpeaking() is called on TtsController.
+  void BlockUntilStop();
+
+  // Non-blocking api.
   // Use these apis if you want to write an async test e.g.
   // sm_.ExpectSpeech("foo");
   // sm_.Call([this]() { DoSomething(); })
@@ -45,10 +67,6 @@
                     const base::Location& location = FROM_HERE);
   void ExpectSpeechPattern(const std::string& pattern,
                            const base::Location& location = FROM_HERE);
-  void ExpectSpeechPatternWithLocale(
-      const std::string& pattern,
-      const std::string& locale,
-      const base::Location& location = FROM_HERE);
   void ExpectNextSpeechIsNot(const std::string& text,
                              const base::Location& location = FROM_HERE);
   void ExpectNextSpeechIsNotPattern(const std::string& pattern,
@@ -93,17 +111,17 @@
   void MaybeContinueReplay();
   void MaybePrintExpectations();
 
+  scoped_refptr<content::MessageLoopRunner> loop_runner_;
   // Our list of utterances and specified language.
   base::circular_deque<SpeechMonitorUtterance> utterance_queue_;
-
+  bool did_stop_ = false;
   std::string error_;
 
+  // Delayed utterances.
   // Calculates the milliseconds elapsed since the last call to Speak().
   double CalculateUtteranceDelayMS();
-
   // Stores the milliseconds elapsed since the last call to Speak().
   double delay_for_last_utterance_ms_;
-
   // Stores the last time Speak() was called.
   std::chrono::steady_clock::time_point time_of_last_utterance_;
 
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
index 37f144a..055e0effa 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -8,12 +8,15 @@
 #include "ash/app_list/test/test_search_result.h"
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/shell.h"
+#include "base/strings/pattern.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/app_list/app_list_client_impl.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "components/account_id/account_id.h"
 #include "components/user_manager/user_names.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -101,8 +104,8 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest, LauncherStateTransition) {
   EnableChromeVox();
 
-  sm_.Call(
-      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
+
   sm_.ExpectSpeechPattern("Launcher");
   sm_.ExpectSpeech("Button");
   sm_.ExpectSpeech("Shelf");
@@ -135,8 +138,8 @@
                        DisabledFullscreenExpandButton) {
   EnableChromeVox();
 
-  sm_.Call(
-      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
+
   sm_.ExpectSpeech("Shelf");
 
   // Press space on the launcher button in shelf, this opens peeking launcher.
@@ -172,8 +175,8 @@
 
   EnableChromeVox();
 
-  sm_.Call(
-      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
+
   sm_.ExpectSpeech("Press Search plus Space to activate");
   // Press space on the launcher button in shelf, this opens peeking
   // launcher.
@@ -209,8 +212,8 @@
 
   EnableChromeVox();
 
-  sm_.Call(
-      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
+
   sm_.ExpectSpeech("Press Search plus Space to activate");
   // Press space on the launcher button in shelf, this opens peeking
   // launcher.
@@ -254,12 +257,10 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackAppListTest, NavigateAppLauncher) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    // Add one app to the applist.
-    PopulateApps(1);
+  // Add one app to the applist.
+  PopulateApps(1);
 
-    EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
 
   // Wait for it to say "Launcher", "Button", "Shelf", "Tool bar".
   sm_.ExpectSpeechPattern("Launcher");
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
index 00ae998c..201bab8 100644
--- a/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/spoken_feedback_browsertest.cc
@@ -17,25 +17,38 @@
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/command_line.h"
 #include "base/macros.h"
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
 #include "base/task/post_task.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/login/login_manager_test.h"
+#include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/ui/login_display_host.h"
+#include "chrome/browser/chromeos/login/ui/webui_login_view.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "components/account_id/account_id.h"
 #include "components/user_manager/user_names.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/tts_controller.h"
+#include "content/public/common/url_constants.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_host.h"
@@ -148,23 +161,36 @@
 
 void LoggedInSpokenFeedbackTest::EnableChromeVox() {
   // Test setup.
-  // Enable ChromeVox, wait for something to be spoken, and disable earcons.
+  // Enable ChromeVox, skip welcome message/notification, and disable earcons.
   ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
 
   AccessibilityManager::Get()->EnableSpokenFeedback(true);
-  sm_.ExpectSpeechPattern("*");
-  sm_.Call([this]() { DisableEarcons(); });
+  EXPECT_TRUE(sm_.SkipChromeVoxEnabledMessage());
+  DisableEarcons();
+}
+
+void LoggedInSpokenFeedbackTest::PressRepeatedlyUntilUtterance(
+    ui::KeyboardCode key,
+    const std::string& expected_utterance) {
+  // This helper function is needed when you want to poll for something
+  // that happens asynchronously. Keep pressing |key|, until
+  // the speech feedback that follows is |expected_utterance|.
+  // Note that this doesn't work if pressing that key doesn't speak anything
+  // at all before the asynchronous event occurred.
+  while (true) {
+    SendKeyPress(key);
+    const std::string& utterance = sm_.GetNextUtterance();
+    if (utterance == expected_utterance)
+      break;
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, AddBookmark) {
   EnableChromeVox();
-
-  sm_.Call(
-      [this]() { chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR); });
+  chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR);
 
   // Create a bookmark with title "foo".
-  sm_.Call(
-      [this]() { chrome::ExecuteCommand(browser(), IDC_BOOKMARK_THIS_TAB); });
+  chrome::ExecuteCommand(browser(), IDC_BOOKMARK_THIS_TAB);
 
   sm_.ExpectSpeech("Bookmark name");
   sm_.ExpectSpeech("about:blank");
@@ -215,9 +241,7 @@
 IN_PROC_BROWSER_TEST_F(LoggedInSpokenFeedbackTest, NavigateNotificationCenter) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_MESSAGE_CENTER_BUBBLE));
-  });
+  EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_MESSAGE_CENTER_BUBBLE));
   sm_.ExpectSpeech(
       "Quick Settings, Press search plus left to access the notification "
       "center., window");
@@ -264,25 +288,21 @@
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, EnableSpokenFeedback) {
   EnableChromeVox();
-  sm_.Replay();
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, FocusToolbar) {
   EnableChromeVox();
-  sm_.Call([this]() { chrome::ExecuteCommand(browser(), IDC_FOCUS_TOOLBAR); });
-  sm_.ExpectSpeech("Reload");
-  sm_.ExpectSpeech("Button");
-
-  sm_.Replay();
+  chrome::ExecuteCommand(browser(), IDC_FOCUS_TOOLBAR);
+  while (sm_.GetNextUtterance() != "Reload") {
+  }
+  EXPECT_EQ("Button", sm_.GetNextUtterance());
 }
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TypeInOmnibox) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(
-        browser(), GURL("data:text/html;charset=utf-8,<p>unused</p>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;charset=utf-8,<p>unused</p>"));
 
   sm_.Call([this]() { SendKeyPressWithControl(ui::VKEY_L); });
   sm_.ExpectSpeech("Address and search bar");
@@ -314,18 +334,22 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, FocusShelf) {
   EnableChromeVox();
 
-  sm_.Call(
-      [this]() { EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF)); });
-  sm_.ExpectSpeechPattern("Launcher");
-  sm_.ExpectSpeech("Button");
-  sm_.ExpectSpeech("Shelf");
-  sm_.ExpectSpeech("Tool bar");
-  sm_.ExpectSpeech(", window");
-  sm_.ExpectSpeech("Press Search plus Space to activate");
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
+  while (true) {
+    std::string utterance = sm_.GetNextUtterance();
+    if (base::MatchPattern(utterance, "Launcher"))
+      break;
+  }
+  EXPECT_EQ("Button", sm_.GetNextUtterance());
 
-  sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
-  sm_.ExpectSpeechPattern("Button");
-  sm_.Replay();
+  EXPECT_EQ("Shelf", sm_.GetNextUtterance());
+  EXPECT_EQ("Tool bar", sm_.GetNextUtterance());
+  EXPECT_EQ(", window", sm_.GetNextUtterance());
+  EXPECT_EQ("Press Search plus Space to activate", sm_.GetNextUtterance());
+
+  SendKeyPress(ui::VKEY_TAB);
+  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "*"));
+  EXPECT_TRUE(base::MatchPattern(sm_.GetNextUtterance(), "Button"));
 }
 
 // Verifies that pressing right arrow button with search button should move
@@ -338,11 +362,9 @@
   // when an extension is enabled, the ShelfItems which are not recorded as
   // pinned apps in user preference will be removed.
   EnableChromeVox();
-  sm_.Call([controller, title]() {
-    controller->CreateAppShortcutLauncherItem(
-        ash::ShelfID("FakeApp"), controller->shelf_model()->item_count(),
-        base::ASCIIToUTF16(title));
-  });
+  controller->CreateAppShortcutLauncherItem(
+      ash::ShelfID("FakeApp"), controller->shelf_model()->item_count(),
+      base::ASCIIToUTF16(title));
 
   // Focus on the shelf.
   sm_.Call([this]() { PerformAcceleratorAction(ash::FOCUS_SHELF); });
@@ -374,35 +396,31 @@
   // pinned apps in user preference will be removed.
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html;charset=utf-8,<button "
-                                      "autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
 
   sm_.ExpectSpeech("Click me");
 
-  sm_.Call([this]() {
-    // Add three Shelf buttons. Wait for the change on ShelfModel to reach ash.
-    ChromeLauncherController* controller = ChromeLauncherController::instance();
-    const int base_index = controller->shelf_model()->item_count();
-    const std::string title("MockApp");
-    const std::string id("FakeApp");
-    const int insert_app_num = 3;
-    for (int i = 0; i < insert_app_num; i++) {
-      std::string app_title = title + base::NumberToString(i);
-      std::string app_id = id + base::NumberToString(i);
-      controller->CreateAppShortcutLauncherItem(
-          ash::ShelfID(app_id), base_index + i, base::ASCIIToUTF16(app_title));
-    }
+  // Add three Shelf buttons. Wait for the change on ShelfModel to reach ash.
+  ChromeLauncherController* controller = ChromeLauncherController::instance();
+  const int base_index = controller->shelf_model()->item_count();
+  const std::string title("MockApp");
+  const std::string id("FakeApp");
+  const int insert_app_num = 3;
+  for (int i = 0; i < insert_app_num; i++) {
+    std::string app_title = title + base::NumberToString(i);
+    std::string app_id = id + base::NumberToString(i);
+    controller->CreateAppShortcutLauncherItem(
+        ash::ShelfID(app_id), base_index + i, base::ASCIIToUTF16(app_title));
+  }
 
-    // Enable the function of speaking text under mouse.
-    ash::EventRewriterController::Get()->SetSendMouseEventsToDelegate(true);
+  // Enable the function of speaking text under mouse.
+  ash::EventRewriterController::Get()->SetSendMouseEventsToDelegate(true);
 
-    // Focus on the Shelf because voice text for focusing on Shelf is fixed.
-    // Wait until voice announcements are finished.
-    EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
-  });
+  // Focus on the Shelf because voice text for focusing on Shelf is fixed. Wait
+  // until voice announcements are finished.
+  EXPECT_TRUE(PerformAcceleratorAction(ash::FOCUS_SHELF));
   sm_.ExpectSpeechPattern("Launcher");
 
   // Hover mouse on the Shelf button. Verifies that text under mouse is spoken.
@@ -428,13 +446,15 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OpenStatusTray) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE));
-  });
-  sm_.ExpectSpeech(
-      "Quick Settings, Press search plus left to access the notification "
-      "center., window");
-  sm_.Replay();
+  EXPECT_TRUE(PerformAcceleratorAction(ash::TOGGLE_SYSTEM_TRAY_BUBBLE));
+  while (true) {
+    std::string utterance = sm_.GetNextUtterance();
+    // TODO: this seems like a regression.
+    if (base::MatchPattern(utterance,
+                           "Quick Settings, Press search plus left to access "
+                           "the notification center., window"))
+      break;
+  }
 }
 
 // Fails on ASAN. See http://crbug.com/776308 . (Note MAYBE_ doesn't work well
@@ -491,11 +511,9 @@
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, OverviewMode) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html;charset=utf-8,<button "
-                                      "autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
 
   sm_.ExpectSpeech("Click me");
 
@@ -515,16 +533,14 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxFindInPage) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html;charset=utf-8,<button "
-                                      "autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
 
   sm_.ExpectSpeech("Click me");
 
   // Press Search+/ to enter ChromeVox's "find in page".
-  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_OEM_2); });
+  SendKeyPressWithSearch(ui::VKEY_OEM_2);
   sm_.ExpectSpeech("Find in page");
   sm_.Replay();
 }
@@ -532,12 +548,11 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNavigateAndSelect) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html;charset=utf-8,"
-                                      "<h1>Title</h1>"
-                                      "<button autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL("data:text/html;charset=utf-8,"
+                                    "<h1>Title</h1>"
+                                    "<button autofocus>Click me</button>"));
+
   sm_.ExpectSpeech("Click me");
 
   // Press Search+Left to navigate to the previous item.
@@ -560,11 +575,10 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxStickyMode) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html;charset=utf-8,<button "
-                                      "autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(),
+      GURL("data:text/html;charset=utf-8,<button autofocus>Click me</button>"));
+
   sm_.ExpectSpeech("Click me");
 
   // Press the sticky-key sequence: Search Search.
@@ -583,7 +597,7 @@
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, TouchExploreStatusTray) {
   EnableChromeVox();
-  sm_.Call([this]() { SimulateTouchScreenInChromeVox(); });
+  SimulateTouchScreenInChromeVox();
 
   // Send an accessibility hover event on the system tray, which is
   // what we get when you tap it on a touch screen when ChromeVox is on.
@@ -603,42 +617,108 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ChromeVoxNextTabRecovery) {
   EnableChromeVox();
 
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(
-        browser(), GURL("data:text/html;charset=utf-8,"
-                        "<button id='b1' autofocus>11</button>"
-                        "<button>22</button>"
-                        "<button>33</button>"
-                        "<h1>Middle</h1>"
-                        "<button>44</button>"
-                        "<button>55</button>"
-                        "<div id=console aria-live=polite></div>"
-                        "<script>"
-                        "var b1 = document.getElementById('b1');"
-                        "b1.addEventListener('blur', function() {"
-                        "  document.getElementById('console').innerText = "
-                        "'button lost focus';"
-                        "});"
-                        "</script>"));
-  });
-  sm_.ExpectSpeech("Button");
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html;charset=utf-8,"
+                      "<button id='b1' autofocus>11</button>"
+                      "<button>22</button>"
+                      "<button>33</button>"
+                      "<h1>Middle</h1>"
+                      "<button>44</button>"
+                      "<button>55</button>"
+                      "<div id=console aria-live=polite></div>"
+                      "<script>"
+                      "var b1 = document.getElementById('b1');"
+                      "b1.addEventListener('blur', function() {"
+                      "  document.getElementById('console').innerText = "
+                      "'button lost focus';"
+                      "});"
+                      "</script>"));
+  while ("Button" != sm_.GetNextUtterance()) {
+  }
 
   // Press Search+H to go to the next heading
-  sm_.Call([this]() { SendKeyPressWithSearch(ui::VKEY_H); });
+  SendKeyPressWithSearch(ui::VKEY_H);
 
   sm_.ExpectSpeech("Middle");
-
   // To ensure that the setSequentialFocusNavigationStartingPoint has
   // executed before pressing Tab, the page has an event handler waiting
   // for the 'blur' event on the button, and when it loses focus it
   // triggers a live region announcement that we wait for, here.
   sm_.ExpectSpeech("button lost focus");
-
   // Now we know that focus has left the button, so the sequential focus
   // navigation starting point must be on the heading. Press Tab and
   // ensure that we land on the first link past the heading.
   sm_.Call([this]() { SendKeyPress(ui::VKEY_TAB); });
   sm_.ExpectSpeech("44");
+  sm_.Replay();
+}
+
+//
+// Spoken feedback tests that run only in guest mode.
+//
+
+class GuestSpokenFeedbackTest : public LoggedInSpokenFeedbackTest {
+ protected:
+  GuestSpokenFeedbackTest() {}
+  ~GuestSpokenFeedbackTest() override {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(chromeos::switches::kGuestSession);
+    command_line->AppendSwitch(::switches::kIncognito);
+    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
+    command_line->AppendSwitchASCII(
+        switches::kLoginUser, user_manager::GuestAccountId().GetUserEmail());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GuestSpokenFeedbackTest);
+};
+
+IN_PROC_BROWSER_TEST_F(GuestSpokenFeedbackTest, FocusToolbar) {
+  EnableChromeVox();
+  chrome::ExecuteCommand(browser(), IDC_FOCUS_TOOLBAR);
+  sm_.ExpectSpeech("Reload");
+  sm_.ExpectSpeech("Button");
+  sm_.Replay();
+}
+
+//
+// Spoken feedback tests of the out-of-box experience.
+//
+
+class OobeSpokenFeedbackTest : public LoginManagerTest {
+ protected:
+  OobeSpokenFeedbackTest()
+      : LoginManagerTest(false, true /* should_initialize_webui */) {}
+  ~OobeSpokenFeedbackTest() override {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    LoginManagerTest::SetUpCommandLine(command_line);
+    // Many bots don't have keyboard/mice which triggers the HID detection
+    // dialog in the OOBE.  Avoid confusing the tests with that.
+    command_line->AppendSwitch(chromeos::switches::kDisableHIDDetectionOnOOBE);
+  }
+
+  SpeechMonitor sm_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OobeSpokenFeedbackTest);
+};
+
+IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, SpokenFeedbackInOobe) {
+  ui_controls::EnableUIControls();
+  ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
+  AccessibilityManager::Get()->EnableSpokenFeedback(true);
+
+  // The Let's go button gets initial focus.
+  sm_.ExpectSpeech("Let's go");
+
+  sm_.Call([]() {
+    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
+        nullptr, ui::VKEY_TAB, false, false, false, false));
+  });
+  sm_.ExpectSpeech("Shut down");
+  sm_.ExpectSpeech("Button");
 
   sm_.Replay();
 }
@@ -646,10 +726,9 @@
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest,
                        MoveByCharacterPhoneticSpeechAndHints) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(
-        browser(), GURL("data:text/html,<button autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html,<button autofocus>Click me</button>"));
+  sm_.ExpectSpeech("Web Content");
   sm_.ExpectSpeech("Click me");
   sm_.ExpectSpeech("Button");
   sm_.ExpectSpeech("Press Search plus Space to activate");
@@ -709,10 +788,8 @@
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, ResetTtsSettings) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(
-        browser(), GURL("data:text/html,<button autofocus>Click me</button>"));
-  });
+  ui_test_utils::NavigateToURL(
+      browser(), GURL("data:text/html,<button autofocus>Click me</button>"));
 
   sm_.ExpectSpeech("Click me");
 
@@ -741,11 +818,9 @@
 
 IN_PROC_BROWSER_TEST_P(SpokenFeedbackTest, SmartStickyMode) {
   EnableChromeVox();
-  sm_.Call([this]() {
-    ui_test_utils::NavigateToURL(browser(),
-                                 GURL("data:text/html,<p>start</p><input "
-                                      "autofocus type='text'><p>end</p>"));
-  });
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL("data:text/html,<p>start</p><input "
+                                    "autofocus type='text'><p>end</p>"));
 
   // The input is autofocused.
   sm_.ExpectSpeech("Edit text");
@@ -795,45 +870,4 @@
   sm_.Replay();
 }
 
-//
-// Spoken feedback tests of the out-of-box experience.
-//
-
-class OobeSpokenFeedbackTest : public LoginManagerTest {
- protected:
-  OobeSpokenFeedbackTest()
-      : LoginManagerTest(false, true /* should_initialize_webui */) {}
-  ~OobeSpokenFeedbackTest() override {}
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    LoginManagerTest::SetUpCommandLine(command_line);
-    // Many bots don't have keyboard/mice which triggers the HID detection
-    // dialog in the OOBE.  Avoid confusing the tests with that.
-    command_line->AppendSwitch(chromeos::switches::kDisableHIDDetectionOnOOBE);
-  }
-
-  SpeechMonitor sm_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(OobeSpokenFeedbackTest);
-};
-
-IN_PROC_BROWSER_TEST_F(OobeSpokenFeedbackTest, SpokenFeedbackInOobe) {
-  ui_controls::EnableUIControls();
-  ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
-  AccessibilityManager::Get()->EnableSpokenFeedback(true);
-
-  // The Let's go button gets initial focus.
-  sm_.ExpectSpeech("Let's go");
-
-  sm_.Call([]() {
-    ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
-        nullptr, ui::VKEY_TAB, false, false, false, false));
-  });
-  sm_.ExpectSpeech("Shut down");
-  sm_.ExpectSpeech("Button");
-
-  sm_.Replay();
-}
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.cc b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
index 22d39a1..0a634180 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/task/post_task.h"
@@ -21,7 +20,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/crostini/crostini_features.h"
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
 #include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service.h"
@@ -40,31 +38,14 @@
 
 namespace {
 
-// Prefix of the ApplicationId set on exo windows for X apps.
-constexpr char kCrostiniWindowAppIdPrefix[] = "org.chromium.termina.";
-// This comes after kCrostiniWindowAppIdPrefix
-constexpr char kWMClassPrefix[] = "wmclass.";
+// This prefix is used when generating the crostini app list id.
+constexpr char kCrostiniAppIdPrefix[] = "crostini:";
 
 constexpr char kCrostiniIconFolder[] = "crostini.icons";
 
 constexpr char kCrostiniAppsInstalledHistogram[] =
     "Crostini.AppsInstalledAtLogin";
 
-const std::string* GetAppNameForWMClass(base::StringPiece wmclass) {
-  // A hard-coded mapping from WMClass to app names.
-  // This is used to deal with the Linux apps that don't specify the correct
-  // WMClass in their desktop files so that their aura windows can be identified
-  // with their respective app IDs.
-  static const base::NoDestructor<std::map<std::string, std::string>>
-      kWMClassToNname({{"Octave-gui", "GNU Octave"},
-                       {"MuseScore2", "MuseScore 2"},
-                       {"XnViewMP", "XnView Multi Platform"}});
-  const auto it = kWMClassToNname->find(wmclass.as_string());
-  if (it == kWMClassToNname->end())
-    return nullptr;
-  return &it->second;
-}
-
 std::string GenerateAppId(const std::string& desktop_file_id,
                           const std::string& vm_name,
                           const std::string& container_name) {
@@ -171,76 +152,6 @@
       base::TimeDelta::FromMicroseconds(time));
 }
 
-bool MatchingString(const std::string& search_string,
-                    const std::string& value_string,
-                    bool ignore_space) {
-  std::string search = search_string;
-  std::string value = value_string;
-  if (ignore_space) {
-    base::RemoveChars(search, " ", &search);
-    base::RemoveChars(value, " ", &value);
-  }
-  return base::EqualsCaseInsensitiveASCII(search, value);
-}
-
-enum class FindAppIdResult { NoMatch, UniqueMatch, NonUniqueMatch };
-// Looks for an app where prefs_key is set to search_value. Returns the apps id
-// if there was only one app matching, otherwise returns an empty string.
-FindAppIdResult FindAppId(const base::DictionaryValue* prefs,
-                          base::StringPiece prefs_key,
-                          base::StringPiece search_value,
-                          std::string* result,
-                          bool require_startup_notify = false,
-                          bool need_display = false,
-                          bool ignore_space = false) {
-  result->clear();
-  for (const auto& item : prefs->DictItems()) {
-    if (item.first == GetTerminalId())
-      continue;
-
-    if (require_startup_notify &&
-        !item.second
-             .FindKeyOfType(guest_os::prefs::kAppStartupNotifyKey,
-                            base::Value::Type::BOOLEAN)
-             ->GetBool())
-      continue;
-
-    if (need_display) {
-      const base::Value* no_display = item.second.FindKeyOfType(
-          guest_os::prefs::kAppNoDisplayKey, base::Value::Type::BOOLEAN);
-      if (no_display && no_display->GetBool())
-        continue;
-    }
-
-    const base::Value* value = item.second.FindKey(prefs_key);
-    if (!value)
-      continue;
-    if (value->type() == base::Value::Type::STRING) {
-      if (!MatchingString(search_value.as_string(), value->GetString(),
-                          ignore_space)) {
-        continue;
-      }
-    } else if (value->type() == base::Value::Type::DICTIONARY) {
-      // Look at the unlocalized name to see if that matches.
-      value = value->FindKeyOfType("", base::Value::Type::STRING);
-      if (!value || !MatchingString(search_value.as_string(),
-                                    value->GetString(), ignore_space)) {
-        continue;
-      }
-    } else {
-      continue;
-    }
-
-    if (!result->empty())
-      return FindAppIdResult::NonUniqueMatch;
-    *result = item.first;
-  }
-
-  if (!result->empty())
-    return FindAppIdResult::UniqueMatch;
-  return FindAppIdResult::NoMatch;
-}
-
 bool EqualsExcludingTimestamps(const base::Value& left,
                                const base::Value& right) {
   auto left_items = left.DictItems();
@@ -566,107 +477,6 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
-// The code follows these steps to identify apps and returns the first match:
-// 1) If the Startup Id is set, look for a matching desktop file id.
-// 2) Ignore windows if the App Id is not set.
-// 3) If the App Id is not prefixed by org.chromium.termina., it's an app with
-// native Wayland support. Look for a matching desktop file id.
-// 4) If the App Id is prefixed by org.chromium.termina.wmclass.:
-// 4.1) Look for an app where StartupWMClass is matches the suffix.
-// 4.2) Look for an app where the desktop file id matches the suffix.
-// 4.3) Look for an app where the unlocalized name matches the suffix. This
-//      handles the xterm & uxterm examples.
-// 5) If we couldn't find a match, prefix the app id with 'crostini:' so we can
-// easily identify shelf entries as Crostini apps.
-std::string CrostiniRegistryService::GetCrostiniShelfAppId(
-    const std::string* window_app_id,
-    const std::string* window_startup_id) {
-  const base::DictionaryValue* apps =
-      prefs_->GetDictionary(guest_os::prefs::kGuestOsRegistry);
-  std::string app_id;
-
-  if (window_startup_id) {
-    // TODO(timloh): We should use a value that is unique so we can handle
-    // an app installed in multiple containers.
-    if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey,
-                  *window_startup_id, &app_id,
-                  true) == FindAppIdResult::UniqueMatch)
-      return app_id;
-    LOG(ERROR) << "Startup ID was set to '" << *window_startup_id
-               << "' but not matched";
-    // Try a lookup with the window app id.
-  }
-
-  if (!window_app_id)
-    return std::string();
-
-  // Wayland apps won't be prefixed with org.chromium.termina.
-  if (!base::StartsWith(*window_app_id, kCrostiniWindowAppIdPrefix,
-                        base::CompareCase::SENSITIVE)) {
-    if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey, *window_app_id,
-                  &app_id) == FindAppIdResult::UniqueMatch) {
-      return app_id;
-    }
-    return kCrostiniAppIdPrefix + *window_app_id;
-  }
-
-  base::StringPiece suffix(
-      window_app_id->begin() + strlen(kCrostiniWindowAppIdPrefix),
-      window_app_id->end());
-
-  // If we don't have an id to match to a desktop file, use the window app id.
-  if (!base::StartsWith(suffix, kWMClassPrefix, base::CompareCase::SENSITIVE))
-    return kCrostiniAppIdPrefix + *window_app_id;
-
-  // If an app had StartupWMClass set to the given WM class, use that,
-  // otherwise look for a desktop file id matching the WM class.
-  base::StringPiece key = suffix.substr(strlen(kWMClassPrefix));
-  FindAppIdResult result = FindAppId(
-      apps, guest_os::prefs::kAppStartupWMClassKey, key, &app_id,
-      false /* require_startup_notification */, true /* need_display */);
-  if (result == FindAppIdResult::UniqueMatch)
-    return app_id;
-  if (result == FindAppIdResult::NonUniqueMatch)
-    return kCrostiniAppIdPrefix + *window_app_id;
-
-  if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey, key, &app_id) ==
-      FindAppIdResult::UniqueMatch) {
-    return app_id;
-  }
-
-  if (FindAppId(apps, guest_os::prefs::kAppNameKey, key, &app_id,
-                false /* require_startup_notification */,
-                true /* need_display */,
-                true /* ignore_space */) == FindAppIdResult::UniqueMatch) {
-    return app_id;
-  }
-
-  const std::string* app_name = GetAppNameForWMClass(key);
-  if (app_name &&
-      FindAppId(apps, guest_os::prefs::kAppNameKey, *app_name, &app_id,
-                false /* require_startup_notification */,
-                true /* need_display */) == FindAppIdResult::UniqueMatch) {
-    return app_id;
-  }
-
-  return kCrostiniAppIdPrefix + *window_app_id;
-}
-
-bool CrostiniRegistryService::IsCrostiniShelfAppId(
-    const std::string& shelf_app_id) {
-  if (base::StartsWith(shelf_app_id, kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
-    return true;
-  }
-  if (shelf_app_id == GetTerminalId())
-    return true;
-  // TODO(timloh): We need to handle desktop files that have been removed.
-  // For example, running windows with a no-longer-valid app id will try to
-  // use the ExtensionContextMenuModel.
-  return prefs_->GetDictionary(guest_os::prefs::kGuestOsRegistry)
-             ->FindKey(shelf_app_id) != nullptr;
-}
-
 std::map<std::string, CrostiniRegistryService::Registration>
 CrostiniRegistryService::GetRegisteredApps() const {
   const base::DictionaryValue* apps =
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service.h b/chrome/browser/chromeos/crostini/crostini_registry_service.h
index cbc7e77..b245bdc 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service.h
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service.h
@@ -35,10 +35,6 @@
 
 namespace crostini {
 
-// This prefix is used when generating the crostini app list id, and used as a
-// prefix when generating shelf ids for windows we couldn't match to an app.
-constexpr char kCrostiniAppIdPrefix[] = "crostini:";
-
 // The CrostiniRegistryService stores information about Desktop Entries (apps)
 // in Crostini. We store this in prefs so that it is readily available even when
 // the VM isn't running. The registrations here correspond to .desktop files,
@@ -137,21 +133,6 @@
 
   base::WeakPtr<CrostiniRegistryService> GetWeakPtr();
 
-  // Returns a shelf app id for an exo window startup id or app id.
-  //
-  // First try to return a desktop file id matching the |window_startup_id|.
-  //
-  // If the app id is empty, returns empty string. If we can uniquely identify
-  // a registry entry, returns the crostini app id for that. Otherwise, returns
-  // the string pointed to by |window_app_id|, prefixed by "crostini:".
-  //
-  // As the window app id is derived from fields set by the app itself, it is
-  // possible for an app to masquerade as a different app.
-  std::string GetCrostiniShelfAppId(const std::string* window_app_id,
-                                    const std::string* window_startup_id);
-  // Returns whether the app_id is a Crostini app id.
-  bool IsCrostiniShelfAppId(const std::string& shelf_app_id);
-
   // Return all installed apps. This always includes the Terminal app.
   std::map<std::string, CrostiniRegistryService::Registration>
   GetRegisteredApps() const;
diff --git a/chrome/browser/chromeos/crostini/crostini_registry_service_unittest.cc b/chrome/browser/chromeos/crostini/crostini_registry_service_unittest.cc
index bac1368..95564be5 100644
--- a/chrome/browser/chromeos/crostini/crostini_registry_service_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_registry_service_unittest.cc
@@ -53,10 +53,6 @@
                       const std::vector<std::string>&));
   };
 
-  std::string WindowIdForWMClass(const std::string& wm_class) {
-    return "org.chromium.termina.wmclass." + wm_class;
-  }
-
   CrostiniRegistryService* service() { return service_.get(); }
   Profile* profile() { return &profile_; }
 
@@ -332,115 +328,6 @@
                                          app_id_1, app_id_2, GetTerminalId()));
 }
 
-TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdNoStartupID) {
-  ApplicationList app_list =
-      CrostiniTestHelper::BasicAppList("app", "vm", "container");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("cool.app");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("super");
-  service()->UpdateApplicationList(app_list);
-
-  service()->UpdateApplicationList(
-      CrostiniTestHelper::BasicAppList("super", "vm 2", "container"));
-
-  EXPECT_THAT(service()->GetRegisteredApps(), testing::SizeIs(5));
-
-  EXPECT_TRUE(service()->GetCrostiniShelfAppId(nullptr, nullptr).empty());
-
-  std::string window_app_id = WindowIdForWMClass("App");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
-
-  window_app_id = WindowIdForWMClass("cool.app");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("cool.app", "vm", "container"));
-
-  window_app_id = WindowIdForWMClass("super");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            "crostini:" + WindowIdForWMClass("super"));
-
-  window_app_id = "org.chromium.termina.wmclientleader.1234";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            "crostini:org.chromium.termina.wmclientleader.1234");
-
-  window_app_id = "org.chromium.termina.xid.654321";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            "crostini:org.chromium.termina.xid.654321");
-
-  window_app_id = "cool.app";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("cool.app", "vm", "container"));
-
-  window_app_id = "fancy.app";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            "crostini:fancy.app");
-}
-
-TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdStartupWMClass) {
-  ApplicationList app_list =
-      CrostiniTestHelper::BasicAppList("app", "vm", "container");
-  app_list.mutable_apps(0)->set_startup_wm_class("app_start");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("app2");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("app3");
-  app_list.mutable_apps(1)->set_startup_wm_class("app2");
-  app_list.mutable_apps(2)->set_startup_wm_class("app2");
-  service()->UpdateApplicationList(app_list);
-
-  EXPECT_THAT(service()->GetRegisteredApps(), testing::SizeIs(4));
-
-  std::string window_app_id = WindowIdForWMClass("app_start");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
-
-  window_app_id = WindowIdForWMClass("app2");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            "crostini:" + WindowIdForWMClass("app2"));
-}
-
-TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdStartupNotify) {
-  ApplicationList app_list =
-      CrostiniTestHelper::BasicAppList("app", "vm", "container");
-  app_list.mutable_apps(0)->set_startup_notify(true);
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("app2");
-  service()->UpdateApplicationList(app_list);
-
-  std::string window_app_id = "whatever";
-  std::string startup_id = "app";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, &startup_id),
-            CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
-
-  startup_id = "app2";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, &startup_id),
-            "crostini:whatever");
-
-  startup_id = "app";
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(nullptr, &startup_id),
-            CrostiniTestHelper::GenerateAppId("app", "vm", "container"));
-}
-
-TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdName) {
-  ApplicationList app_list =
-      CrostiniTestHelper::BasicAppList("app", "vm", "container");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("app2", "name2");
-  service()->UpdateApplicationList(app_list);
-
-  std::string window_app_id = WindowIdForWMClass("name2");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("app2", "vm", "container"));
-}
-
-TEST_F(CrostiniRegistryServiceTest, GetCrostiniAppIdNameSkipNoDisplay) {
-  ApplicationList app_list =
-      CrostiniTestHelper::BasicAppList("app", "vm", "container");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("app2", "name2");
-  *app_list.add_apps() = CrostiniTestHelper::BasicApp("another_app2", "name2",
-                                                      true /* no_display */);
-  service()->UpdateApplicationList(app_list);
-
-  std::string window_app_id = WindowIdForWMClass("name2");
-  EXPECT_EQ(service()->GetCrostiniShelfAppId(&window_app_id, nullptr),
-            CrostiniTestHelper::GenerateAppId("app2", "vm", "container"));
-}
-
 TEST_F(CrostiniRegistryServiceTest, IsScaledReturnFalseWhenNotSet) {
   std::string app_id =
       CrostiniTestHelper::GenerateAppId("app", "vm", "container");
diff --git a/chrome/browser/chromeos/crostini/crostini_shelf_utils.cc b/chrome/browser/chromeos/crostini/crostini_shelf_utils.cc
new file mode 100644
index 0000000..c3a56d7
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_shelf_utils.cc
@@ -0,0 +1,220 @@
+// Copyright 2020 The Chromium 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/crostini/crostini_shelf_utils.h"
+
+#include "base/no_destructor.h"
+#include "base/strings/string_util.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_util.h"
+#include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/prefs/pref_service.h"
+
+namespace crostini {
+
+namespace {
+
+// This prefix is used as a prefix when generating shelf ids for windows we
+// couldn't match to an app.
+constexpr char kCrostiniShelfIdPrefix[] = "crostini:";
+// Prefix of the ApplicationId set on exo windows for X apps.
+constexpr char kCrostiniWindowAppIdPrefix[] = "org.chromium.termina.";
+// This comes after kCrostiniWindowAppIdPrefix
+constexpr char kWMClassPrefix[] = "wmclass.";
+
+const std::string* GetAppNameForWMClass(base::StringPiece wmclass) {
+  // A hard-coded mapping from WMClass to app names.
+  // This is used to deal with the Linux apps that don't specify the correct
+  // WMClass in their desktop files so that their aura windows can be identified
+  // with their respective app IDs.
+  static const base::NoDestructor<std::map<std::string, std::string>>
+      kWMClassToNname({{"Octave-gui", "GNU Octave"},
+                       {"MuseScore2", "MuseScore 2"},
+                       {"XnViewMP", "XnView Multi Platform"}});
+  const auto it = kWMClassToNname->find(wmclass.as_string());
+  if (it == kWMClassToNname->end())
+    return nullptr;
+  return &it->second;
+}
+
+bool MatchingString(const std::string& search_string,
+                    const std::string& value_string,
+                    bool ignore_space) {
+  std::string search = search_string;
+  std::string value = value_string;
+  if (ignore_space) {
+    base::RemoveChars(search, " ", &search);
+    base::RemoveChars(value, " ", &value);
+  }
+  return base::EqualsCaseInsensitiveASCII(search, value);
+}
+
+enum class FindAppIdResult { NoMatch, UniqueMatch, NonUniqueMatch };
+// Looks for an app where prefs_key is set to search_value. Returns the apps id
+// if there was only one app matching, otherwise returns an empty string.
+FindAppIdResult FindAppId(const base::DictionaryValue* prefs,
+                          base::StringPiece prefs_key,
+                          base::StringPiece search_value,
+                          std::string* result,
+                          bool require_startup_notify = false,
+                          bool need_display = false,
+                          bool ignore_space = false) {
+  result->clear();
+  for (const auto& item : prefs->DictItems()) {
+    if (item.first == GetTerminalId())
+      continue;
+
+    if (require_startup_notify &&
+        !item.second
+             .FindKeyOfType(guest_os::prefs::kAppStartupNotifyKey,
+                            base::Value::Type::BOOLEAN)
+             ->GetBool())
+      continue;
+
+    if (need_display) {
+      const base::Value* no_display = item.second.FindKeyOfType(
+          guest_os::prefs::kAppNoDisplayKey, base::Value::Type::BOOLEAN);
+      if (no_display && no_display->GetBool())
+        continue;
+    }
+
+    const base::Value* value = item.second.FindKey(prefs_key);
+    if (!value)
+      continue;
+    if (value->type() == base::Value::Type::STRING) {
+      if (!MatchingString(search_value.as_string(), value->GetString(),
+                          ignore_space)) {
+        continue;
+      }
+    } else if (value->type() == base::Value::Type::DICTIONARY) {
+      // Look at the unlocalized name to see if that matches.
+      value = value->FindKeyOfType("", base::Value::Type::STRING);
+      if (!value || !MatchingString(search_value.as_string(),
+                                    value->GetString(), ignore_space)) {
+        continue;
+      }
+    } else {
+      continue;
+    }
+
+    if (!result->empty())
+      return FindAppIdResult::NonUniqueMatch;
+    *result = item.first;
+  }
+
+  if (!result->empty())
+    return FindAppIdResult::UniqueMatch;
+  return FindAppIdResult::NoMatch;
+}
+
+}  // namespace
+
+// The code follows these steps to identify apps and returns the first match:
+// 1) If the Startup Id is set, look for a matching desktop file id.
+// 2) Ignore windows if the App Id is not set.
+// 3) If the App Id is not prefixed by org.chromium.termina., it's an app with
+// native Wayland support. Look for a matching desktop file id.
+// 4) If the App Id is prefixed by org.chromium.termina.wmclass.:
+// 4.1) Look for an app where StartupWMClass is matches the suffix.
+// 4.2) Look for an app where the desktop file id matches the suffix.
+// 4.3) Look for an app where the unlocalized name matches the suffix. This
+//      handles the xterm & uxterm examples.
+// 5) If we couldn't find a match, prefix the app id with 'crostini:' so we can
+// easily identify shelf entries as Crostini apps.
+std::string GetCrostiniShelfAppId(const Profile* profile,
+                                  const std::string* window_app_id,
+                                  const std::string* window_startup_id) {
+  const base::DictionaryValue* apps =
+      profile->GetPrefs()->GetDictionary(guest_os::prefs::kGuestOsRegistry);
+  std::string app_id;
+
+  if (window_startup_id) {
+    // TODO(timloh): We should use a value that is unique so we can handle
+    // an app installed in multiple containers.
+    if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey,
+                  *window_startup_id, &app_id,
+                  true) == FindAppIdResult::UniqueMatch)
+      return app_id;
+    LOG(ERROR) << "Startup ID was set to '" << *window_startup_id
+               << "' but not matched";
+    // Try a lookup with the window app id.
+  }
+
+  if (!window_app_id)
+    return std::string();
+
+  // Wayland apps won't be prefixed with org.chromium.termina.
+  if (!base::StartsWith(*window_app_id, kCrostiniWindowAppIdPrefix,
+                        base::CompareCase::SENSITIVE)) {
+    if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey, *window_app_id,
+                  &app_id) == FindAppIdResult::UniqueMatch) {
+      return app_id;
+    }
+    return kCrostiniShelfIdPrefix + *window_app_id;
+  }
+
+  base::StringPiece suffix(
+      window_app_id->begin() + strlen(kCrostiniWindowAppIdPrefix),
+      window_app_id->end());
+
+  // If we don't have an id to match to a desktop file, use the window app id.
+  if (!base::StartsWith(suffix, kWMClassPrefix, base::CompareCase::SENSITIVE))
+    return kCrostiniShelfIdPrefix + *window_app_id;
+
+  // If an app had StartupWMClass set to the given WM class, use that,
+  // otherwise look for a desktop file id matching the WM class.
+  base::StringPiece key = suffix.substr(strlen(kWMClassPrefix));
+  FindAppIdResult result = FindAppId(
+      apps, guest_os::prefs::kAppStartupWMClassKey, key, &app_id,
+      false /* require_startup_notification */, true /* need_display */);
+  if (result == FindAppIdResult::UniqueMatch)
+    return app_id;
+  if (result == FindAppIdResult::NonUniqueMatch)
+    return kCrostiniShelfIdPrefix + *window_app_id;
+
+  if (FindAppId(apps, guest_os::prefs::kAppDesktopFileIdKey, key, &app_id) ==
+      FindAppIdResult::UniqueMatch) {
+    return app_id;
+  }
+
+  if (FindAppId(apps, guest_os::prefs::kAppNameKey, key, &app_id,
+                false /* require_startup_notification */,
+                true /* need_display */,
+                true /* ignore_space */) == FindAppIdResult::UniqueMatch) {
+    return app_id;
+  }
+
+  const std::string* app_name = GetAppNameForWMClass(key);
+  if (app_name &&
+      FindAppId(apps, guest_os::prefs::kAppNameKey, *app_name, &app_id,
+                false /* require_startup_notification */,
+                true /* need_display */) == FindAppIdResult::UniqueMatch) {
+    return app_id;
+  }
+
+  return kCrostiniShelfIdPrefix + *window_app_id;
+}
+
+bool IsUnmatchedCrostiniShelfAppId(base::StringPiece shelf_app_id) {
+  return base::StartsWith(shelf_app_id, kCrostiniShelfIdPrefix,
+                          base::CompareCase::SENSITIVE);
+}
+
+bool IsCrostiniShelfAppId(const Profile* profile,
+                          base::StringPiece shelf_app_id) {
+  if (IsUnmatchedCrostiniShelfAppId(shelf_app_id)) {
+    return true;
+  }
+  if (shelf_app_id == GetTerminalId())
+    return true;
+  // TODO(timloh): We need to handle desktop files that have been removed.
+  // For example, running windows with a no-longer-valid app id will try to
+  // use the ExtensionContextMenuModel.
+  return profile->GetPrefs()
+             ->GetDictionary(guest_os::prefs::kGuestOsRegistry)
+             ->FindKey(shelf_app_id) != nullptr;
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/crostini/crostini_shelf_utils.h b/chrome/browser/chromeos/crostini/crostini_shelf_utils.h
new file mode 100644
index 0000000..40a3bcd
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_shelf_utils.h
@@ -0,0 +1,38 @@
+// Copyright 2020 The Chromium 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_CROSTINI_CROSTINI_SHELF_UTILS_H_
+#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHELF_UTILS_H_
+
+#include <string>
+#include "base/strings/string_piece_forward.h"
+
+class Profile;
+
+namespace crostini {
+
+// Returns a shelf app id for an exo window startup id or app id.
+//
+// First try to return a desktop file id matching the |window_startup_id|.
+//
+// If the app id is empty, returns empty string. If we can uniquely identify
+// a registry entry, returns the crostini app id for that. Otherwise, returns
+// the string pointed to by |window_app_id|, prefixed by "crostini:".
+//
+// As the window app id is derived from fields set by the app itself, it is
+// possible for an app to masquerade as a different app.
+std::string GetCrostiniShelfAppId(const Profile* profile,
+                                  const std::string* window_app_id,
+                                  const std::string* window_startup_id);
+
+// Returns whether the app_id is an unmatched Crostini app id.
+bool IsUnmatchedCrostiniShelfAppId(base::StringPiece shelf_app_id);
+
+// Returns whether the app_id is a Crostini app id.
+bool IsCrostiniShelfAppId(const Profile* profile,
+                          base::StringPiece shelf_app_id);
+
+}  // namespace crostini
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_SHELF_UTILS_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_shelf_utils_unittest.cc b/chrome/browser/chromeos/crostini/crostini_shelf_utils_unittest.cc
new file mode 100644
index 0000000..5977299
--- /dev/null
+++ b/chrome/browser/chromeos/crostini/crostini_shelf_utils_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright 2020 The Chromium 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/crostini/crostini_shelf_utils.h"
+#include <iterator>
+#include <memory>
+
+#include "base/optional.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
+#include "chrome/browser/chromeos/guest_os/guest_os_pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crostini {
+
+namespace {
+
+struct App {
+  std::string desktop_file_id;
+  std::string vm_name = "vm";
+  std::string container_name = "container";
+  std::string app_name = "";
+  base::Optional<std::string> startup_wm_class;
+  base::Optional<bool> startup_notify;
+  base::Optional<bool> no_display;
+};
+
+struct WindowIds {
+  base::Optional<std::string> app_id;
+  base::Optional<std::string> startup_id;
+};
+
+std::string GenAppId(const App& app) {
+  return CrostiniTestHelper::GenerateAppId(app.desktop_file_id, app.vm_name,
+                                           app.container_name);
+}
+
+}  // namespace
+
+class CrostiniShelfUtilsTest : public testing::Test {
+ public:
+  std::string GetShelfAppId(WindowIds window_ids) const {
+    return GetCrostiniShelfAppId(
+        &testing_profile_, base::OptionalOrNullptr(window_ids.app_id),
+        base::OptionalOrNullptr(window_ids.startup_id));
+  }
+
+  void SetGuestOsRegistry(std::vector<App> apps) {
+    using AppLists = std::map<std::pair<std::string, std::string>,
+                              vm_tools::apps::ApplicationList>;
+    AppLists app_lists;
+    for (App& in_app : apps) {
+      AppLists::key_type key(std::move(in_app.vm_name),
+                             std::move(in_app.container_name));
+      AppLists::iterator app_list_it = app_lists.find(key);
+      if (app_list_it == app_lists.cend()) {
+        vm_tools::apps::ApplicationList app_list;
+        app_list.set_vm_name(key.first);
+        app_list.set_container_name(key.second);
+        app_list_it = app_lists.emplace_hint(app_list_it, std::move(key),
+                                             std::move(app_list));
+      }
+      vm_tools::apps::App& out_app = *app_list_it->second.add_apps();
+      out_app.set_desktop_file_id(std::move(in_app.desktop_file_id));
+      out_app.mutable_name()->add_values()->set_value(
+          std::move(in_app.app_name));
+      if (in_app.startup_wm_class)
+        out_app.set_startup_wm_class(std::move(*in_app.startup_wm_class));
+      if (in_app.startup_notify)
+        out_app.set_startup_notify(*in_app.startup_notify);
+      if (in_app.no_display)
+        out_app.set_no_display(*in_app.no_display);
+    }
+    CrostiniRegistryService service(&testing_profile_);
+    for (AppLists::value_type& value : app_lists) {
+      service.UpdateApplicationList(std::move(value.second));
+    }
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  TestingProfile testing_profile_;
+};
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdReturnsEmptyIdWhenCalledWithoutAnyParametersSet) {
+  SetGuestOsRegistry({});
+
+  EXPECT_EQ(GetShelfAppId(WindowIds()), "");
+}
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdFindsAppWithEitherWindowIdOrAppId) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "cool.app"},
+  });
+
+  // App is found using wm app_id.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.cool.app"}),
+            GenAppId({.desktop_file_id = "cool.app"}));
+
+  // App is found using app_id.
+  EXPECT_EQ(GetShelfAppId({.app_id = "cool.app"}),
+            GenAppId({.desktop_file_id = "cool.app"}));
+}
+
+TEST_F(CrostiniShelfUtilsTest, GetCrostiniShelfAppIdIgnoresWindowAppIdsCase) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app"},
+  });
+
+  // App is found using capitalized App.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.App"}),
+            GenAppId({.desktop_file_id = "app"}));
+}
+
+TEST_F(
+    CrostiniShelfUtilsTest,
+    GetCrostiniShelfAppIdCantFindAppWhenMultipleAppsInDifferentVmsShareDesktopFileIds) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "super"},
+      {.desktop_file_id = "super", .vm_name = "vm 2"},
+  });
+
+  // Neither app is found, as they can't be disambiguated.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.super"}),
+            "crostini:org.chromium.termina.wmclass.super");
+}
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdDoesntFindAppWhenGivenUnregisteredAppIds) {
+  SetGuestOsRegistry({});
+
+  EXPECT_EQ(
+      GetShelfAppId({.app_id = "org.chromium.termina.wmclientleader.1234"}),
+      "crostini:org.chromium.termina.wmclientleader.1234");
+
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.xid.654321"}),
+            "crostini:org.chromium.termina.xid.654321");
+
+  EXPECT_EQ(GetShelfAppId({.app_id = "fancy.app"}), "crostini:fancy.app");
+}
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdCanFindAppUsingItsStartupWmClass) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app", .startup_wm_class = "app_start"},
+  });
+
+  // App is found using it's startup_wm_class.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.app_start"}),
+            GenAppId({.desktop_file_id = "app"}));
+}
+
+TEST_F(
+    CrostiniShelfUtilsTest,
+    GetCrostiniShelfAppIdCantFindAppIfMultipleAppsStartupWmClassesAreTheSame) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app2", .startup_wm_class = "app2"},
+      {.desktop_file_id = "app3", .startup_wm_class = "app2"},
+  });
+
+  // Neither app is found, as they can't be disambiguated.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.app2"}),
+            "crostini:org.chromium.termina.wmclass.app2");
+}
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdCanFindAppUsingStartupIdIfStartupNotifyIsTrue) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app", .startup_notify = true},
+      {.desktop_file_id = "app2", .startup_notify = false},
+  });
+
+  // App's startup_notify is true, so it can be found using startup_id.
+  EXPECT_EQ(GetShelfAppId({.startup_id = "app"}),
+            GenAppId({.desktop_file_id = "app"}));
+
+  // App's startup_notify is true, so it can be found using startup_id, even if
+  // app_id is set.
+  EXPECT_EQ(GetShelfAppId({.app_id = "unknown_app_id", .startup_id = "app"}),
+            GenAppId({.desktop_file_id = "app"}));
+
+  // App's startup_notify is false, so startup_id is ignored and no app was
+  // found.
+  EXPECT_EQ(GetShelfAppId({.app_id = "unknown_app_id", .startup_id = "app2"}),
+            "crostini:unknown_app_id");
+}
+
+TEST_F(CrostiniShelfUtilsTest, GetCrostiniShelfAppIdCanFindAppsByName) {
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app", .app_name = "name"},
+  });
+
+  // App found by app_name: "name".
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.name"}),
+            GenAppId({.desktop_file_id = "app"}));
+}
+
+TEST_F(CrostiniShelfUtilsTest,
+       GetCrostiniShelfAppIdDoesntFindAppsByNameIfTheyHaveNoDisplaySet) {
+  // One no_display app.
+  SetGuestOsRegistry({
+      {.desktop_file_id = "another_app",
+       .app_name = "name",
+       .no_display = true},
+  });
+
+  // No app is found.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.name"}),
+            "crostini:org.chromium.termina.wmclass.name");
+
+  // Two apps with the same name, where one is no_display.
+  SetGuestOsRegistry({
+      {.desktop_file_id = "app", .app_name = "name"},
+      {.desktop_file_id = "another_app",
+       .app_name = "name",
+       .no_display = true},
+  });
+
+  // The app without no_display set is found.
+  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.termina.wmclass.name"}),
+            GenAppId({.desktop_file_id = "app"}));
+}
+
+}  // namespace crostini
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
index 0bbc42b..2aa689d 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
@@ -19,7 +19,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_setup_test_utils.h"
-#include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/mock_network_state_helper.h"
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/screens/demo_setup_screen.h"
@@ -27,6 +26,7 @@
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/enrollment_helper_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/test/test_condition_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
@@ -139,22 +139,13 @@
 }  // namespace
 
 // Basic tests for demo mode setup flow.
-class DemoSetupTest : public LoginManagerTest {
+class DemoSetupTestBase : public OobeBaseTest {
  public:
-  DemoSetupTest()
-      : LoginManagerTest(false, true /* should_initialize_webui */) {}
-  ~DemoSetupTest() override = default;
-
-  // LoginTestManager:
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    LoginManagerTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitchASCII(switches::kArcAvailability,
-                                    "officially-supported");
-    ASSERT_TRUE(arc::IsArcAvailable());
-  }
+  DemoSetupTestBase() = default;
+  ~DemoSetupTestBase() override = default;
 
   void SetUpOnMainThread() override {
-    LoginManagerTest::SetUpOnMainThread();
+    OobeBaseTest::SetUpOnMainThread();
     DisableConfirmationDialogAnimations();
     branded_build_override_ = WizardController::ForceBrandedBuildForTesting();
     DisconnectAllNetworks();
@@ -466,10 +457,25 @@
   policy::MockCloudPolicyStore mock_policy_store_;
   std::unique_ptr<base::AutoReset<bool>> branded_build_override_;
 
-  DISALLOW_COPY_AND_ASSIGN(DemoSetupTest);
+  DISALLOW_COPY_AND_ASSIGN(DemoSetupTestBase);
 };
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, ShowConfirmationDialogAndProceed) {
+class DemoSetupArcSupportedTest : public DemoSetupTestBase {
+ public:
+  DemoSetupArcSupportedTest() = default;
+  ~DemoSetupArcSupportedTest() override = default;
+
+  // DemoSetupTestBase:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    DemoSetupTestBase::SetUpCommandLine(command_line);
+    command_line->AppendSwitchASCII(switches::kArcAvailability,
+                                    "officially-supported");
+    ASSERT_TRUE(arc::IsArcAvailable());
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       ShowConfirmationDialogAndProceed) {
   EXPECT_FALSE(IsConfirmationDialogShown());
 
   InvokeDemoModeWithAccelerator();
@@ -481,14 +487,8 @@
   EXPECT_TRUE(IsScreenShown(DemoPreferencesScreenView::kScreenId));
 }
 
-#if defined(OS_CHROMEOS)
-// Flaky on ChromeOS. crbug.com/895120
-#define MAYBE_ShowConfirmationDialogAndCancel \
-  DISABLED_ShowConfirmationDialogAndCancel
-#else
-#define MAYBE_ShowConfirmationDialogAndCancel ShowConfirmationDialogAndCancel
-#endif
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, MAYBE_ShowConfirmationDialogAndCancel) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       ShowConfirmationDialogAndCancel) {
   EXPECT_FALSE(IsConfirmationDialogShown());
 
   InvokeDemoModeWithAccelerator();
@@ -500,7 +500,7 @@
   EXPECT_FALSE(IsScreenShown(DemoPreferencesScreenView::kScreenId));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, InvokeWithTaps) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, InvokeWithTaps) {
   // Use fake time to avoid flakiness.
   SetFakeTimeForMultiTapDetector(base::Time::UnixEpoch());
   EXPECT_FALSE(IsConfirmationDialogShown());
@@ -509,7 +509,8 @@
   EXPECT_TRUE(IsConfirmationDialogShown());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, DoNotInvokeWithNonConsecutiveTaps) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       DoNotInvokeWithNonConsecutiveTaps) {
   // Use fake time to avoid flakiness.
   const base::Time kFakeTime = base::Time::UnixEpoch();
   SetFakeTimeForMultiTapDetector(kFakeTime);
@@ -527,7 +528,7 @@
   EXPECT_FALSE(IsConfirmationDialogShown());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OnlineSetupFlowSuccess) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, OnlineSetupFlowSuccess) {
   // Simulate successful online setup.
   enrollment_helper_.ExpectEnrollmentMode(
       policy::EnrollmentConfig::MODE_ATTESTATION);
@@ -584,7 +585,7 @@
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest,
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
                        OnlineSetupFlowSuccessWithCountryCustomization) {
   // Simulate successful online setup.
   enrollment_helper_.ExpectEnrollmentMode(
@@ -674,7 +675,7 @@
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OnlineSetupFlowErrorDefault) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, OnlineSetupFlowErrorDefault) {
   // Simulate online setup failure.
   enrollment_helper_.ExpectEnrollmentMode(
       policy::EnrollmentConfig::MODE_ATTESTATION);
@@ -738,7 +739,8 @@
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OnlineSetupFlowErrorPowerwashRequired) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       OnlineSetupFlowErrorPowerwashRequired) {
   // Simulate online setup failure that requires powerwash.
   enrollment_helper_.ExpectEnrollmentMode(
       policy::EnrollmentConfig::MODE_ATTESTATION);
@@ -801,7 +803,8 @@
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OnlineSetupFlowCrosComponentFailure) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       OnlineSetupFlowCrosComponentFailure) {
   // Simulate failure to load demo resources CrOS component.
   // There is no enrollment attempt, as process fails earlier.
   enrollment_helper_.ExpectNoEnrollment();
@@ -858,7 +861,7 @@
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineDemoModeUnavailable) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, OfflineDemoModeUnavailable) {
   SimulateNetworkDisconnected();
 
   InvokeDemoModeWithAccelerator();
@@ -881,7 +884,7 @@
   EXPECT_FALSE(IsCustomNetworkListElementShown("offlineDemoSetupListItemName"));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowSuccess) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, OfflineSetupFlowSuccess) {
   // Simulate offline setup success.
   enrollment_helper_.ExpectOfflineEnrollmentSuccess();
   SimulateNetworkDisconnected();
@@ -938,7 +941,8 @@
   EXPECT_TRUE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowErrorDefault) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       OfflineSetupFlowErrorDefault) {
   // Simulate offline setup failure.
   enrollment_helper_.ExpectOfflineEnrollmentError(
       policy::EnrollmentStatus::ForStatus(
@@ -1003,7 +1007,8 @@
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, OfflineSetupFlowErrorPowerwashRequired) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       OfflineSetupFlowErrorPowerwashRequired) {
   // Simulate offline setup failure.
   enrollment_helper_.ExpectOfflineEnrollmentError(
       policy::EnrollmentStatus::ForLockError(
@@ -1067,7 +1072,7 @@
   EXPECT_FALSE(StartupUtils::IsDeviceRegistered());
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, NextDisabledOnNetworkScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, NextDisabledOnNetworkScreen) {
   SimulateNetworkDisconnected();
   SkipToScreen(NetworkScreenView::kScreenId);
   EXPECT_FALSE(IsScreenDialogElementEnabled(NetworkScreenView::kScreenId,
@@ -1081,7 +1086,7 @@
   EXPECT_TRUE(IsScreenShown(NetworkScreenView::kScreenId));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, ClickNetworkOnNetworkScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, ClickNetworkOnNetworkScreen) {
   SkipToScreen(NetworkScreenView::kScreenId);
   EXPECT_FALSE(IsScreenDialogElementEnabled(NetworkScreenView::kScreenId,
                                             DemoSetupDialog::kNetwork,
@@ -1094,7 +1099,8 @@
   EXPECT_TRUE(IsScreenShown(EulaView::kScreenId));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, ClickConnectedNetworkOnNetworkScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       ClickConnectedNetworkOnNetworkScreen) {
   SimulateNetworkConnected();
   SkipToScreen(NetworkScreenView::kScreenId);
   EXPECT_TRUE(IsScreenDialogElementEnabled(NetworkScreenView::kScreenId,
@@ -1107,7 +1113,7 @@
   EXPECT_TRUE(IsScreenShown(EulaView::kScreenId));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, BackOnNetworkScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, BackOnNetworkScreen) {
   SimulateNetworkConnected();
   SkipToScreen(NetworkScreenView::kScreenId);
 
@@ -1118,7 +1124,7 @@
   EXPECT_TRUE(IsScreenShown(DemoPreferencesScreenView::kScreenId));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, BackOnArcTermsScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, BackOnArcTermsScreen) {
   // User cannot go to ARC ToS screen without accepting eula - simulate that.
   StartupUtils::MarkEulaAccepted();
 
@@ -1130,7 +1136,7 @@
   OobeScreenWaiter(NetworkScreenView::kScreenId).Wait();
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, BackOnErrorScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, BackOnErrorScreen) {
   SkipToErrorDialog();
 
   ClickScreenDialogButton(DemoSetupScreenView::kScreenId,
@@ -1140,7 +1146,7 @@
   OobeScreenWaiter(WelcomeView::kScreenId).Wait();
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, RetryOnErrorScreen) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest, RetryOnErrorScreen) {
   SkipToErrorDialog();
 
   // We need to create another mock after showing error dialog.
@@ -1157,7 +1163,8 @@
   OobeScreenWaiter(GaiaView::kScreenId).Wait();
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, ShowOfflineSetupOptionOnNetworkList) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       ShowOfflineSetupOptionOnNetworkList) {
   auto* const wizard_controller = WizardController::default_controller();
   wizard_controller->SimulateDemoModeSetupForTesting();
   SimulateOfflineEnvironment();
@@ -1166,19 +1173,20 @@
   EXPECT_TRUE(IsCustomNetworkListElementShown("offlineDemoSetupListItemName"));
 }
 
-IN_PROC_BROWSER_TEST_F(DemoSetupTest, NoOfflineSetupOptionOnNetworkList) {
+IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
+                       NoOfflineSetupOptionOnNetworkList) {
   SkipToScreen(NetworkScreenView::kScreenId);
   EXPECT_FALSE(IsCustomNetworkListElementShown("offlineDemoSetupListItemName"));
 }
 
-class DemoSetupArcUnsupportedTest : public DemoSetupTest {
+class DemoSetupArcUnsupportedTest : public DemoSetupTestBase {
  public:
   DemoSetupArcUnsupportedTest() = default;
   ~DemoSetupArcUnsupportedTest() override = default;
 
-  // DemoSetupTest:
+  // DemoSetupTestBase:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    LoginManagerTest::SetUpCommandLine(command_line);
+    DemoSetupTestBase::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII(switches::kArcAvailability, "none");
     ASSERT_FALSE(arc::IsArcAvailable());
   }
@@ -1204,7 +1212,7 @@
 }
 
 // Demo setup tests related to Force Re-Enrollment.
-class DemoSetupFRETest : public DemoSetupTest {
+class DemoSetupFRETest : public DemoSetupArcSupportedTest {
  protected:
   DemoSetupFRETest() {
     statistics_provider_.SetMachineStatistic(system::kSerialNumberKeyForTest,
@@ -1212,10 +1220,8 @@
   }
   ~DemoSetupFRETest() override = default;
 
-  void SetUpOnMainThread() override { DemoSetupTest::SetUpOnMainThread(); }
-
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    DemoSetupTest::SetUpCommandLine(command_line);
+    DemoSetupArcSupportedTest::SetUpCommandLine(command_line);
 
     command_line->AppendSwitchASCII(
         switches::kEnterpriseEnableForcedReEnrollment,
diff --git a/chrome/browser/chromeos/login/enable_debugging_browsertest.cc b/chrome/browser/chromeos/login/enable_debugging_browsertest.cc
index 5abc8017..c0822a5 100644
--- a/chrome/browser/chromeos/login/enable_debugging_browsertest.cc
+++ b/chrome/browser/chromeos/login/enable_debugging_browsertest.cc
@@ -13,9 +13,9 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chromeos/login/login_manager_test.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
 #include "chrome/browser/chromeos/login/ui/login_display_host.h"
 #include "chrome/browser/chromeos/login/ui/webui_login_view.h"
@@ -150,21 +150,18 @@
   int num_remove_protection_;
 };
 
-class EnableDebuggingTest : public LoginManagerTest {
+class EnableDebuggingTestBase : public OobeBaseTest {
  public:
-  EnableDebuggingTest()
-      : LoginManagerTest(false, true /* should_initialize_webui */) {}
-  ~EnableDebuggingTest() override {}
+  EnableDebuggingTestBase() = default;
+  ~EnableDebuggingTestBase() override = default;
 
+  // OobeBaseTest:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    LoginManagerTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitch(chromeos::switches::kSystemDevMode);
+    OobeBaseTest::SetUpCommandLine(command_line);
     // Disable HID detection because it takes precedence and could block
     // enable-debugging UI.
     command_line->AppendSwitch(chromeos::switches::kDisableHIDDetectionOnOOBE);
   }
-
-  // LoginManagerTest overrides:
   void SetUpInProcessBrowserTestFixture() override {
     std::unique_ptr<DBusThreadManagerSetter> dbus_setter =
         chromeos::DBusThreadManager::GetSetterForTesting();
@@ -172,20 +169,7 @@
     dbus_setter->SetDebugDaemonClient(
         std::unique_ptr<DebugDaemonClient>(debug_daemon_client_));
 
-    LoginManagerTest::SetUpInProcessBrowserTestFixture();
-  }
-
-  void WaitUntilJSIsReady() {
-    LoginDisplayHost* host = LoginDisplayHost::default_host();
-    if (!host)
-      return;
-    chromeos::OobeUI* oobe_ui = host->GetOobeUI();
-    if (!oobe_ui)
-      return;
-    base::RunLoop run_loop;
-    const bool oobe_ui_ready = oobe_ui->IsJSReady(run_loop.QuitClosure());
-    if (!oobe_ui_ready)
-      run_loop.Run();
+    OobeBaseTest::SetUpInProcessBrowserTestFixture();
   }
 
   void InvokeEnableDebuggingScreen() {
@@ -224,7 +208,7 @@
   void ShowRemoveProtectionScreen() {
     debug_daemon_client_->SetDebuggingFeaturesStatus(
         DebugDaemonClient::DEV_FEATURE_NONE);
-    WaitUntilJSIsReady();
+    OobeBaseTest::WaitForOobeUI();
     test::OobeJS().ExpectHidden("debugging");
     InvokeEnableDebuggingScreen();
     test::OobeJS().ExpectVisible("debugging");
@@ -245,7 +229,7 @@
   void ShowSetupScreen() {
     debug_daemon_client_->SetDebuggingFeaturesStatus(
         debugd::DevFeatureFlag::DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED);
-    WaitUntilJSIsReady();
+    OobeBaseTest::WaitForOobeUI();
     test::OobeJS().ExpectHidden("debugging");
     InvokeEnableDebuggingScreen();
     test::OobeJS().ExpectVisible("debugging");
@@ -266,11 +250,23 @@
   TestDebugDaemonClient* debug_daemon_client_ = nullptr;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(EnableDebuggingTest);
+  DISALLOW_COPY_AND_ASSIGN(EnableDebuggingTestBase);
+};
+
+class EnableDebuggingDevTest : public EnableDebuggingTestBase {
+ public:
+  EnableDebuggingDevTest() = default;
+  ~EnableDebuggingDevTest() override = default;
+
+  // EnableDebuggingTestBase:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    EnableDebuggingTestBase::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(chromeos::switches::kSystemDevMode);
+  }
 };
 
 // Show remove protection screen, click on [Cancel] button.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, ShowAndCancelRemoveProtection) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, ShowAndCancelRemoveProtection) {
   ShowRemoveProtectionScreen();
   CloseEnableDebuggingScreen();
   test::OobeJS().ExpectHidden("debugging");
@@ -282,7 +278,7 @@
 
 // Show remove protection, click on [Remove protection] button and wait for
 // reboot.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, ShowAndRemoveProtection) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, ShowAndRemoveProtection) {
   ShowRemoveProtectionScreen();
   debug_daemon_client_->ResetWait();
   ClickRemoveProtectionButton();
@@ -297,7 +293,7 @@
 }
 
 // Show setup screen. Click on [Enable] button. Wait until done screen is shown.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, ShowSetup) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, ShowSetup) {
   ShowSetupScreen();
   debug_daemon_client_->ResetWait();
   ClickEnableButton();
@@ -309,7 +305,7 @@
 
 // Show setup screen. Type in matching passwords.
 // Click on [Enable] button. Wait until done screen is shown.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, SetupMatchingPasswords) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, SetupMatchingPasswords) {
   ShowSetupScreen();
   debug_daemon_client_->ResetWait();
   test::OobeJS().TypeIntoPath("test0000", {"enable-debugging-password"});
@@ -325,7 +321,7 @@
 // Show setup screen. Type in different passwords.
 // Click on [Enable] button. Assert done screen is not shown.
 // Then confirm that typing in matching passwords enables debugging features.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, SetupNotMatchingPasswords) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, SetupNotMatchingPasswords) {
   ShowSetupScreen();
   debug_daemon_client_->ResetWait();
   test::OobeJS().TypeIntoPath("test0000", {"enable-debugging-password"});
@@ -349,11 +345,11 @@
 
 // Test images come with some features enabled but still has rootfs protection.
 // Invoking debug screen should show remove protection screen.
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, ShowOnTestImages) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, ShowOnTestImages) {
   debug_daemon_client_->SetDebuggingFeaturesStatus(
       debugd::DevFeatureFlag::DEV_FEATURE_SSH_SERVER_CONFIGURED |
       debugd::DevFeatureFlag::DEV_FEATURE_SYSTEM_ROOT_PASSWORD_SET);
-  WaitUntilJSIsReady();
+  OobeBaseTest::WaitForOobeUI();
   test::OobeJS().ExpectHidden("debugging");
   InvokeEnableDebuggingScreen();
   test::OobeJS().ExpectVisible("debugging");
@@ -366,12 +362,12 @@
   EXPECT_EQ(debug_daemon_client_->num_remove_protection(), 0);
 }
 
-IN_PROC_BROWSER_TEST_F(EnableDebuggingTest, WaitForDebugDaemon) {
+IN_PROC_BROWSER_TEST_F(EnableDebuggingDevTest, WaitForDebugDaemon) {
   // Stat with service not ready.
   debug_daemon_client_->SetServiceIsAvailable(false);
   debug_daemon_client_->SetDebuggingFeaturesStatus(
       DebugDaemonClient::DEV_FEATURE_NONE);
-  WaitUntilJSIsReady();
+  OobeBaseTest::WaitForOobeUI();
 
   // Invoking UI and it should land on wait-view.
   test::OobeJS().ExpectHidden("debugging");
@@ -386,22 +382,16 @@
   VerifyRemoveProtectionScreen();
 }
 
-class EnableDebuggingNonDevTest : public EnableDebuggingTest {
+class EnableDebuggingNonDevTest : public EnableDebuggingTestBase {
  public:
-  EnableDebuggingNonDevTest() {}
+  EnableDebuggingNonDevTest() = default;
 
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    // Skip EnableDebuggingTest::SetUpCommandLine().
-    LoginManagerTest::SetUpCommandLine(command_line);
-  }
-
-  // LoginManagerTest overrides:
   void SetUpInProcessBrowserTestFixture() override {
     std::unique_ptr<DBusThreadManagerSetter> dbus_setter =
         chromeos::DBusThreadManager::GetSetterForTesting();
     dbus_setter->SetDebugDaemonClient(
         std::unique_ptr<DebugDaemonClient>(new FakeDebugDaemonClient));
-    LoginManagerTest::SetUpInProcessBrowserTestFixture();
+    EnableDebuggingTestBase::SetUpInProcessBrowserTestFixture();
   }
 };
 
@@ -419,11 +409,11 @@
   test::OobeJS().ExpectHasNoClass("wait-view", {"debugging"});
 }
 
-class EnableDebuggingRequestedTest : public EnableDebuggingTest {
+class EnableDebuggingRequestedTest : public EnableDebuggingDevTest {
  public:
   EnableDebuggingRequestedTest() {}
 
-  // EnableDebuggingTest overrides:
+  // EnableDebuggingDevTest overrides:
   bool SetUpUserDataDirectory() override {
     base::DictionaryValue local_state_dict;
     local_state_dict.SetBoolean(prefs::kDebuggingFeaturesRequested, true);
@@ -435,10 +425,10 @@
     CHECK(
         JSONFileValueSerializer(local_state_path).Serialize(local_state_dict));
 
-    return EnableDebuggingTest::SetUpUserDataDirectory();
+    return EnableDebuggingDevTest::SetUpUserDataDirectory();
   }
   void SetUpInProcessBrowserTestFixture() override {
-    EnableDebuggingTest::SetUpInProcessBrowserTestFixture();
+    EnableDebuggingDevTest::SetUpInProcessBrowserTestFixture();
 
     debug_daemon_client_->SetDebuggingFeaturesStatus(
         debugd::DevFeatureFlag::DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED);
diff --git a/chrome/browser/chromeos/login/reset_browsertest.cc b/chrome/browser/chromeos/login/reset_browsertest.cc
index 526fa7e..c7945741 100644
--- a/chrome/browser/chromeos/login/reset_browsertest.cc
+++ b/chrome/browser/chromeos/login/reset_browsertest.cc
@@ -54,40 +54,42 @@
 constexpr char kTpmUpdate[] = "tpmFirmwareUpdate";
 constexpr char kTpmUpdateCheckbox[] = "tpmFirmwareUpdateCheckbox";
 
+constexpr char kCancelDialogButton[] = "resetCancel";
+constexpr char kTriggerPowerwashButton[] = "powerwash";
+constexpr char kConfirmPowerwashButton[] = "confirmPowerwash";
+constexpr char kCancelPowerwashButton[] = "cancelButton";
+constexpr char kRestartButton[] = "restart";
+
 void InvokeRollbackOption() {
   test::ExecuteOobeJS("cr.ui.Oobe.handleAccelerator('reset');");
 }
 
-void RequestCloseResetScreen() {
-  test::ExecuteOobeJS(
-      "chrome.send('login.ResetScreen.userActed', ['cancel-reset']);");
+void ClickCancelButton() {
+  test::OobeJS().TapOnPath({kResetScreen, kCancelDialogButton});
 }
 
-void CloseResetScreen() {
-  RequestCloseResetScreen();
+void CloseResetScreenAndWait() {
+  test::OobeJS().TapOnPath({kResetScreen, kCancelDialogButton});
   OobeScreenExitWaiter(ResetView::kScreenId).Wait();
-  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
+  test::OobeJS()
+      .CreateVisibilityWaiter(false /* visible */, {kResetScreen})
+      ->Wait();
 }
 
 void ClickResetButton() {
-  test::ExecuteOobeJS(
-      "chrome.send('login.ResetScreen.userActed', ['powerwash-pressed']);");
+  test::OobeJS().TapOnPath({kResetScreen, kConfirmPowerwashButton});
 }
 
 void ClickRestartButton() {
-  test::ExecuteOobeJS(
-      "chrome.send('login.ResetScreen.userActed', ['restart-pressed']);");
+  test::OobeJS().TapOnPath({kResetScreen, kRestartButton});
 }
 
 void ClickToConfirmButton() {
-  test::ExecuteOobeJS(
-      "chrome.send('login.ResetScreen.userActed', ['show-confirmation']);");
+  test::OobeJS().TapOnPath({kResetScreen, kTriggerPowerwashButton});
 }
 
 void ClickDismissConfirmationButton() {
-  test::ExecuteOobeJS(
-      "chrome.send('login.ResetScreen.userActed', "
-      "['reset-confirm-dismissed']);");
+  test::OobeJS().TapOnPath({kResetScreen, kCancelPowerwashButton});
 }
 
 void WaitForConfirmationDialogToOpen() {
@@ -171,6 +173,9 @@
     chromeos::LoginDisplayHost::default_host()->ShowResetScreen();
     EXPECT_TRUE(login_prompt_visible_observer_->signal_emitted());
     OobeScreenWaiter(ResetView::kScreenId).Wait();
+    test::OobeJS()
+        .CreateVisibilityWaiter(true /* visible */, {kResetScreen})
+        ->Wait();
     EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
     ExpectConfirmationDialogClosed();
   }
@@ -199,7 +204,7 @@
 
   // Simulates reset screen request from OOBE UI.
   void InvokeResetScreen() {
-    test::ExecuteOobeJS("cr.ui.Oobe.handleAccelerator('reset');");
+    InvokeRollbackOption();
     OobeScreenWaiter(ResetView::kScreenId).Wait();
     EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
     ExpectConfirmationDialogClosed();
@@ -295,7 +300,8 @@
 IN_PROC_BROWSER_TEST_F(ResetTest, ShowAndCancel) {
   EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
   InvokeResetScreen();
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
 }
 
 IN_PROC_BROWSER_TEST_F(ResetTest, RestartBeforePowerwash) {
@@ -330,9 +336,8 @@
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
   InvokeResetScreen();
 
-  RequestCloseResetScreen();
+  ClickCancelButton();
   OobeScreenWaiter(WelcomeView::kScreenId).Wait();
-  test::OobeJS().ExpectHidden(kResetScreen);
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
 
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
@@ -361,7 +366,8 @@
   // Rollback unavailable. Show and cancel.
   update_engine_client_->set_can_rollback_check_result(false);
   InvokeResetScreen();
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
 
   // Go to confirmation phase, cancel from there in 2 steps.
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
@@ -375,7 +381,8 @@
 
   test::OobeJS().ExpectVisible(kResetScreen);
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
 
   // Rollback available. Show and cancel from confirmation screen.
   update_engine_client_->set_can_rollback_check_result(true);
@@ -390,7 +397,8 @@
   WaitForConfirmationDialogToClose();
 
   test::OobeJS().ExpectVisible(kResetScreen);
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, PRE_ShowAfterBootIfRequested) {
@@ -405,7 +413,8 @@
 
   test::OobeJS().CreateVisibilityWaiter(true, {kResetScreen})->Wait();
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTest, PRE_RollbackUnavailable) {
@@ -425,7 +434,7 @@
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
   EXPECT_EQ(1, FakeSessionManagerClient::Get()->start_device_wipe_call_count());
   EXPECT_EQ(0, update_engine_client_->rollback_call_count());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
 
   // Next invocation leads to rollback view.
   PrefService* prefs = g_browser_process->local_state();
@@ -436,7 +445,7 @@
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
   EXPECT_EQ(2, FakeSessionManagerClient::Get()->start_device_wipe_call_count());
   EXPECT_EQ(0, update_engine_client_->rollback_call_count());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
 }
 
 IN_PROC_BROWSER_TEST_F(ResetFirstAfterBootTestWithRollback,
@@ -466,7 +475,7 @@
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
   EXPECT_EQ(1, FakeSessionManagerClient::Get()->start_device_wipe_call_count());
   EXPECT_EQ(0, update_engine_client_->rollback_call_count());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
 
   // Next invocation leads to simple reset, not rollback view.
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
@@ -475,14 +484,15 @@
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
   ClickDismissConfirmationButton();
   EXPECT_FALSE(ash::LoginScreenTestApi::IsGuestButtonShown());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
+  EXPECT_TRUE(ash::LoginScreenTestApi::IsGuestButtonShown());
   InvokeResetScreen();
   ClickToConfirmButton();
   ClickResetButton();
   EXPECT_EQ(0, FakePowerManagerClient::Get()->num_request_restart_calls());
   EXPECT_EQ(2, FakeSessionManagerClient::Get()->start_device_wipe_call_count());
   EXPECT_EQ(0, update_engine_client_->rollback_call_count());
-  CloseResetScreen();
+  CloseResetScreenAndWait();
 
   prefs->SetBoolean(prefs::kFactoryResetRequested, true);
   InvokeResetScreen();
@@ -557,7 +567,7 @@
       .CreateHasClassWaiter(true, "rollback-proposal-view", {kResetScreen})
       ->Wait();
 
-  CloseResetScreen();
+  CloseResetScreenAndWait();
   InvokeResetScreen();
 
   InvokeRollbackOption();
diff --git a/chrome/browser/chromeos/login/screens/packaged_license_screen.h b/chrome/browser/chromeos/login/screens/packaged_license_screen.h
index 750900a..2465714 100644
--- a/chrome/browser/chromeos/login/screens/packaged_license_screen.h
+++ b/chrome/browser/chromeos/login/screens/packaged_license_screen.h
@@ -32,6 +32,10 @@
   PackagedLicenseScreen& operator=(const PackagedLicenseScreen&) = delete;
   ~PackagedLicenseScreen() override;
 
+  void set_exit_callback_for_testing(const ScreenExitCallback& exit_callback) {
+    exit_callback_ = exit_callback;
+  }
+
  protected:
   // BaseScreen
   void ShowImpl() override;
diff --git a/chrome/browser/chromeos/login/screens/packaged_license_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/packaged_license_screen_browsertest.cc
new file mode 100644
index 0000000..e4a07aa
--- /dev/null
+++ b/chrome/browser/chromeos/login/screens/packaged_license_screen_browsertest.cc
@@ -0,0 +1,87 @@
+// Copyright 2020 The Chromium 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/packaged_license_screen.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "chrome/browser/chromeos/login/screen_manager.h"
+#include "chrome/browser/chromeos/login/test/js_checker.h"
+#include "chrome/browser/chromeos/login/test/oobe_base_test.h"
+#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/chromeos/login/wizard_controller.h"
+#include "chrome/browser/chromeos/policy/enrollment_config.h"
+#include "chrome/browser/ui/webui/chromeos/login/packaged_license_screen_handler.h"
+
+namespace chromeos {
+
+class PackagedLicenseScreenTest : public OobeBaseTest {
+ public:
+  PackagedLicenseScreenTest() {}
+  ~PackagedLicenseScreenTest() override = default;
+
+  void SetUpOnMainThread() override {
+    PackagedLicenseScreen* screen = static_cast<PackagedLicenseScreen*>(
+        WizardController::default_controller()->screen_manager()->GetScreen(
+            PackagedLicenseView::kScreenId));
+    screen->set_exit_callback_for_testing(base::BindRepeating(
+        &PackagedLicenseScreenTest::HandleScreenExit, base::Unretained(this)));
+    policy::EnrollmentConfig config;
+    config.is_license_packaged_with_device = true;
+    WizardController::default_controller()
+        ->set_prescribed_enrollment_config_for_testing(config);
+
+    OobeBaseTest::SetUpOnMainThread();
+  }
+
+  void ShowPackagedLicenseScreen() {
+    WizardController::default_controller()->AdvanceToScreen(
+        PackagedLicenseView::kScreenId);
+    OobeScreenWaiter(PackagedLicenseView::kScreenId).Wait();
+  }
+
+  void WaitForScreenExit() {
+    if (screen_exited_)
+      return;
+    base::RunLoop run_loop;
+    screen_exit_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  void CheckResult(PackagedLicenseScreen::Result result) {
+    EXPECT_EQ(result_, result);
+  }
+
+ private:
+  void HandleScreenExit(PackagedLicenseScreen::Result result) {
+    screen_exited_ = true;
+    result_ = result;
+    if (screen_exit_callback_)
+      std::move(screen_exit_callback_).Run();
+  }
+
+  bool screen_exited_ = false;
+  PackagedLicenseScreen::Result result_;
+  base::RepeatingClosure screen_exit_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(PackagedLicenseScreenTest, DontEnroll) {
+  ShowPackagedLicenseScreen();
+
+  test::OobeJS().TapOnPath({"packaged-license", "dont-enroll-button"});
+
+  WaitForScreenExit();
+  CheckResult(PackagedLicenseScreen::Result::DONT_ENROLL);
+}
+
+IN_PROC_BROWSER_TEST_F(PackagedLicenseScreenTest, Enroll) {
+  ShowPackagedLicenseScreen();
+
+  test::OobeJS().TapOnPath({"packaged-license", "enroll-button"});
+
+  WaitForScreenExit();
+  CheckResult(PackagedLicenseScreen::Result::ENROLL);
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index 114a522d..1be5a63 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -142,6 +142,11 @@
            !prescribed_enrollment_config_.should_enroll();
   }
 
+  void set_prescribed_enrollment_config_for_testing(
+      policy::EnrollmentConfig config) {
+    prescribed_enrollment_config_ = config;
+  }
+
   // Returns true if a given screen exists.
   bool HasScreen(OobeScreenId screen);
 
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.cc
index 18ac7e1..83dfd53 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.cc
@@ -13,6 +13,7 @@
 #include "base/files/scoped_file.h"
 #include "base/guid.h"
 #include "base/strings/string_util.h"
+#include "base/system/sys_info.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_drive_image_download_service.h"
@@ -35,6 +36,13 @@
 
 namespace {
 
+// Size to use for calculating progress when the actual size isn't available.
+constexpr int64_t kDownloadSizeFallbackEstimate = 15LL * 1024 * 1024 * 1024;
+
+constexpr char kFailureReasonHistogram[] = "PluginVm.SetupFailureReason";
+
+constexpr char kHomeDirectory[] = "/home";
+
 chromeos::ConciergeClient* GetConciergeClient() {
   return chromeos::DBusThreadManager::Get()->GetConciergeClient();
 }
@@ -75,7 +83,7 @@
                << " another PluginVm image is currently being processed "
                << "in state " << GetStateName(state_) << ", "
                << GetInstallingStateName(installing_state_);
-    OnDownloadFailed(FailureReason::OPERATION_IN_PROGRESS);
+    InstallFailed(FailureReason::OPERATION_IN_PROGRESS);
     return;
   }
   // Defensive check preventing any download attempts when PluginVm is
@@ -84,11 +92,31 @@
   if (!IsPluginVmAllowedForProfile(profile_)) {
     LOG(ERROR) << "Download of PluginVm image cannot be started because "
                << "the user is not allowed to run PluginVm";
-    OnDownloadFailed(FailureReason::NOT_ALLOWED);
+    InstallFailed(FailureReason::NOT_ALLOWED);
     return;
   }
 
   state_ = State::kInstalling;
+  installing_state_ = InstallingState::kCheckingDiskSpace;
+  progress_ = 0;
+
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace,
+                     base::FilePath(kHomeDirectory)),
+      base::BindOnce(&PluginVmInstaller::OnAvailableDiskSpace,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PluginVmInstaller::Continue() {
+  if (state_ != State::kInstalling ||
+      installing_state_ != InstallingState::kPausedLowDiskSpace) {
+    LOG(ERROR) << "Tried to continue installation in unexpected state "
+               << GetStateName(state_) << ", "
+               << GetInstallingStateName(installing_state_);
+    return;
+  }
+
   StartDlcDownload();
 }
 
@@ -100,6 +128,10 @@
   }
   state_ = State::kCancelling;
   switch (installing_state_) {
+    case InstallingState::kPausedLowDiskSpace:
+      CancelFinished();
+      return;
+    case InstallingState::kCheckingDiskSpace:
     case InstallingState::kCheckingForExistingVm:
     case InstallingState::kDownloadingDlc:
       // These can't be cancelled, so we wait for completion. For DLC, we also
@@ -116,6 +148,40 @@
   }
 }
 
+void PluginVmInstaller::OnAvailableDiskSpace(int64_t bytes) {
+  if (state_ == State::kCancelling) {
+    CancelFinished();
+    return;
+  }
+
+  if (free_disk_space_for_testing_ != -1)
+    bytes = free_disk_space_for_testing_;
+
+  // We allow the installer to fail for users who already set up a VM via vmc
+  // and have low disk space as it's simpler to check for existing VMs after
+  // installing DLC and this case should be very rare.
+
+  if (bytes < kMinimumFreeDiskSpace) {
+    InstallFailed(FailureReason::INSUFFICIENT_DISK_SPACE);
+    return;
+  }
+
+  if (bytes < kRecommendedFreeDiskSpace) {
+    // If there's no observer, we would get stuck in the paused state.
+    if (!observer_) {
+      InstallFinished();
+      return;
+    }
+    observer_->OnCheckedDiskSpace(/*low_disk_space=*/true);
+    installing_state_ = InstallingState::kPausedLowDiskSpace;
+    return;
+  }
+
+  if (observer_)
+    observer_->OnCheckedDiskSpace(/*low_disk_space=*/false);
+  StartDlcDownload();
+}
+
 void PluginVmInstaller::OnUpdateVmState(bool default_vm_exists) {
   if (state_ == State::kCancelling) {
     CancelFinished();
@@ -139,19 +205,17 @@
 void PluginVmInstaller::OnUpdateVmStateFailed() {
   // Either the dispatcher failed to start or ListVms didn't work.
   // PluginVmManager logs the details.
-  OnDownloadFailed(FailureReason::DISPATCHER_NOT_AVAILABLE);
+  InstallFailed(FailureReason::DISPATCHER_NOT_AVAILABLE);
 }
 
 void PluginVmInstaller::StartDlcDownload() {
   installing_state_ = InstallingState::kDownloadingDlc;
 
   if (!GetPluginVmImageDownloadUrl().is_valid()) {
-    OnDownloadFailed(FailureReason::INVALID_IMAGE_URL);
+    InstallFailed(FailureReason::INVALID_IMAGE_URL);
     return;
   }
 
-  dlc_download_start_tick_ = base::TimeTicks::Now();
-
   chromeos::DlcserviceClient::Get()->Install(
       GetPluginVmDlcModuleList(),
       base::BindOnce(&PluginVmInstaller::OnDlcDownloadCompleted,
@@ -163,11 +227,12 @@
 void PluginVmInstaller::StartDownload() {
   DCHECK_EQ(installing_state_, InstallingState::kDownloadingDlc);
   installing_state_ = InstallingState::kDownloadingImage;
+  UpdateProgress(/*state_progress=*/0);
 
   GURL url = GetPluginVmImageDownloadUrl();
   // This may have changed since running StartDlcDownload.
   if (!url.is_valid()) {
-    OnDownloadFailed(FailureReason::INVALID_IMAGE_URL);
+    InstallFailed(FailureReason::INVALID_IMAGE_URL);
     return;
   }
 
@@ -203,9 +268,7 @@
   if (state_ == State::kCancelling)
     return;
 
-  if (observer_)
-    observer_->OnDlcDownloadProgressUpdated(
-        progress, base::TimeTicks::Now() - dlc_download_start_tick_);
+  UpdateProgress(progress);
 }
 
 void PluginVmInstaller::OnDlcDownloadCompleted(
@@ -258,22 +321,23 @@
   }
 
   RecordPluginVmDlcUseResultHistogram(result);
-  if (observer_)
-    observer_->OnDownloadFailed(reason);
-  InstallFinished();
+  InstallFailed(reason);
 }
 
 void PluginVmInstaller::OnDownloadStarted() {
-  download_start_tick_ = base::TimeTicks::Now();
 }
 
 void PluginVmInstaller::OnDownloadProgressUpdated(uint64_t bytes_downloaded,
                                                   int64_t content_length) {
-  if (observer_) {
-    observer_->OnDownloadProgressUpdated(
-        bytes_downloaded, content_length,
-        base::TimeTicks::Now() - download_start_tick_);
-  }
+  DCHECK_EQ(installing_state_, InstallingState::kDownloadingImage);
+  if (observer_)
+    observer_->OnDownloadProgressUpdated(bytes_downloaded, content_length);
+
+  if (content_length <= 0)
+    content_length = kDownloadSizeFallbackEstimate;
+
+  UpdateProgress(
+      std::min(1., static_cast<double>(bytes_downloaded) / content_length));
 }
 
 void PluginVmInstaller::OnDownloadCompleted(
@@ -318,14 +382,13 @@
     using_drive_download_service_ = false;
   }
 
-  if (observer_)
-    observer_->OnDownloadFailed(reason);
-  InstallFinished();
+  InstallFailed(reason);
 }
 
 void PluginVmInstaller::StartImport() {
   DCHECK_EQ(installing_state_, InstallingState::kDownloadingImage);
   installing_state_ = InstallingState::kImporting;
+  UpdateProgress(/*state_progress=*/0);
 
   base::ThreadPool::PostTaskAndReply(
       FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
@@ -335,6 +398,49 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
+void PluginVmInstaller::UpdateProgress(double state_progress) {
+  DCHECK_EQ(state_, State::kInstalling);
+  if (state_progress < 0 || state_progress > 1) {
+    LOG(ERROR) << "Unexpected progress value " << state_progress
+               << " in installing state "
+               << GetInstallingStateName(installing_state_);
+    return;
+  }
+
+  double start_range = 0;
+  double end_range = 0;
+  switch (installing_state_) {
+    case InstallingState::kDownloadingDlc:
+      start_range = 0;
+      end_range = 0.01;
+      break;
+    case InstallingState::kDownloadingImage:
+      start_range = 0.01;
+      end_range = 0.45;
+      break;
+    case InstallingState::kImporting:
+      start_range = 0.45;
+      end_range = 1;
+      break;
+    default:
+      // Other states take a negligible amount of time so we don't send progress
+      // updates.
+      NOTREACHED();
+  }
+
+  double new_progress =
+      start_range + (end_range - start_range) * state_progress;
+  if (new_progress < progress_) {
+    LOG(ERROR) << "Progress went backwards from " << progress_ << " to "
+               << progress_;
+    return;
+  }
+
+  progress_ = new_progress;
+  if (observer_)
+    observer_->OnProgressUpdated(new_progress);
+}
+
 void PluginVmInstaller::DetectImageType() {
   creating_new_vm_ = IsIsoImage(downloaded_image_);
 }
@@ -455,7 +561,6 @@
   }
 
   VLOG(1) << "Disk image creation/import is now in progress";
-  import_start_tick_ = base::TimeTicks::Now();
   current_import_command_uuid_ = response.command_uuid();
   // Image in progress. Waiting for progress signals...
   // TODO(https://crbug.com/966398): think about adding a timeout here,
@@ -479,10 +584,7 @@
       RequestFinalStatus();
       return;
     case vm_tools::concierge::DiskImageStatus::DISK_STATUS_IN_PROGRESS:
-      if (observer_) {
-        observer_->OnImportProgressUpdated(
-            percent_completed, base::TimeTicks::Now() - import_start_tick_);
-      }
+      UpdateProgress(percent_completed / 100.);
       return;
     default:
       LOG(ERROR) << "Disk image status signal has status: " << status
@@ -534,11 +636,7 @@
       LOG(ERROR) << "New VM creation failed";
     else
       LOG(ERROR) << "Image import failed";
-    if (observer_) {
-      observer_->OnImportFailed(*failure_reason);
-    }
-
-    InstallFinished();
+    InstallFailed(*failure_reason);
     return;
   }
 
@@ -649,6 +747,10 @@
   switch (state) {
     case InstallingState::kInactive:
       return "kInactive";
+    case InstallingState::kCheckingDiskSpace:
+      return "kCheckingDiskSpace";
+    case InstallingState::kPausedLowDiskSpace:
+      return "kPausedLowDiskSpace";
     case InstallingState::kCheckingForExistingVm:
       return "kCheckingForExistingVm";
     case InstallingState::kDownloadingDlc:
@@ -752,6 +854,14 @@
     observer_->OnCancelFinished();
 }
 
+void PluginVmInstaller::InstallFailed(FailureReason reason) {
+  state_ = State::kIdle;
+  installing_state_ = InstallingState::kInactive;
+  base::UmaHistogramEnumeration(kFailureReasonHistogram, reason);
+  if (observer_)
+    observer_->OnError(reason);
+}
+
 void PluginVmInstaller::InstallFinished() {
   state_ = State::kIdle;
   installing_state_ = InstallingState::kInactive;
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.h b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.h
index fc4e18d4..50f5373 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.h
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer.h
@@ -10,7 +10,6 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "base/time/time.h"
 #include "chromeos/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/dbus/concierge_client.h"
 #include "chromeos/dbus/dlcservice/dlcservice_client.h"
@@ -39,8 +38,8 @@
 class PluginVmInstaller : public KeyedService,
                           public chromeos::ConciergeClient::DiskImageObserver {
  public:
-  // FailureReasons values can be shown to the user. Do not reorder or renumber
-  // these values without careful consideration.
+  // FailureReasons values are logged to UMA and shown to users. Do not change
+  // or re-use enum values.
   enum class FailureReason {
     // LOGIC_ERROR = 0,
     SIGNAL_NOT_CONNECTED = 1,
@@ -65,38 +64,50 @@
     DLC_BUSY = 20,
     DLC_NEED_REBOOT = 21,
     DLC_NEED_SPACE = 22,
+    INSUFFICIENT_DISK_SPACE = 23,
+
+    kMaxValue = INSUFFICIENT_DISK_SPACE,
   };
 
   enum class InstallingState {
     kInactive,
+    kCheckingDiskSpace,
+    kPausedLowDiskSpace,
     kDownloadingDlc,
     kCheckingForExistingVm,
     kDownloadingImage,
     kImporting,
   };
 
+  static constexpr int64_t kMinimumFreeDiskSpace = 16LL * 1024 * 1024 * 1024;
+  static constexpr int64_t kRecommendedFreeDiskSpace =
+      32LL * 1024 * 1024 * 1024;
+
   // Observer class for the PluginVm image related events.
+  // TODO(timloh): Merge OnFooFailed functions as the failure reason is enough
+  // to distinguish where we failed.
   class Observer {
    public:
     virtual ~Observer() = default;
 
-    virtual void OnDlcDownloadProgressUpdated(double progress,
-                                              base::TimeDelta elapsed_time) = 0;
+    virtual void OnProgressUpdated(double fraction_complete) = 0;
+
+    // If |low_disk_space| is true, the device doesn't have the recommended
+    // amount of free disk space and the install will pause until Continue() or
+    // Cancel() is called.
+    virtual void OnCheckedDiskSpace(bool low_disk_space) = 0;
+
     virtual void OnDlcDownloadCompleted() = 0;
 
     // If |has_vm| is true, the install is done.
     virtual void OnExistingVmCheckCompleted(bool has_vm) = 0;
 
     virtual void OnDownloadProgressUpdated(uint64_t bytes_downloaded,
-                                           int64_t content_length,
-                                           base::TimeDelta elapsed_time) = 0;
+                                           int64_t content_length) = 0;
     virtual void OnDownloadCompleted() = 0;
-    virtual void OnDownloadFailed(FailureReason reason) = 0;
-    virtual void OnImportProgressUpdated(int percent_completed,
-                                         base::TimeDelta elapsed_time) = 0;
     virtual void OnCreated() = 0;
     virtual void OnImported() = 0;
-    virtual void OnImportFailed(FailureReason reason) = 0;
+    virtual void OnError(FailureReason reason) = 0;
 
     virtual void OnCancelFinished() = 0;
   };
@@ -108,6 +119,8 @@
 
   // Start the installation. Progress updates will be sent to the observer.
   void Start();
+  // Continue the installation if it was paused due to low disk space.
+  void Continue();
   // Cancel the installation.
   void Cancel();
 
@@ -137,6 +150,9 @@
   // Public for testing purposes.
   bool VerifyDownload(const std::string& downloaded_archive_hash);
 
+  void SetFreeDiskSpaceForTesting(int64_t bytes) {
+    free_disk_space_for_testing_ = bytes;
+  }
   void SetDownloadServiceForTesting(
       download::DownloadService* download_service);
   void SetDownloadedImageForTesting(const base::FilePath& downloaded_image);
@@ -146,13 +162,16 @@
   std::string GetCurrentDownloadGuidForTesting();
 
  private:
+  void OnAvailableDiskSpace(int64_t bytes);
+  void StartDlcDownload();
   void OnUpdateVmState(bool default_vm_exists);
   void OnUpdateVmStateFailed();
-  void StartDlcDownload();
   void StartDownload();
   void DetectImageType();
   void StartImport();
 
+  void UpdateProgress(double state_progress);
+
   // Cancels the download of PluginVm image finishing the image processing.
   // Downloaded PluginVm image archive is being deleted.
   void CancelDownload();
@@ -161,6 +180,7 @@
   // Reset state and call observers.
   void CancelFinished();
 
+  void InstallFailed(FailureReason reason);
   // Reset state, callers also need to call the appropriate observer functions.
   void InstallFinished();
 
@@ -182,12 +202,13 @@
   // -1 when is not yet determined.
   int64_t downloaded_image_size_ = -1;
   bool creating_new_vm_ = false;
-  base::TimeTicks dlc_download_start_tick_;
-  base::TimeTicks download_start_tick_;
-  base::TimeTicks import_start_tick_;
+  double progress_ = 0;
   std::unique_ptr<PluginVmDriveImageDownloadService> drive_download_service_;
   bool using_drive_download_service_ = false;
 
+  // -1 indicates not set
+  int64_t free_disk_space_for_testing_ = -1;
+
   ~PluginVmInstaller() override;
 
   // Get string representation of state for logging purposes.
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
index 2e2297b..5d58b98e 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_installer_unittest.cc
@@ -49,11 +49,16 @@
 
 using ::base::test::RunClosure;
 using ::testing::_;
+using ::testing::AnyNumber;
 using ::testing::AtLeast;
+using ::testing::DoubleEq;
 using ::testing::Invoke;
 using ::testing::InvokeWithoutArgs;
+using ::testing::Mock;
 using ::testing::StrictMock;
 
+using FailureReason = PluginVmInstaller::FailureReason;
+
 const char kProfileName[] = "p1";
 const char kUrl[] = "http://example.com";
 const char kDriveUrl[] = "https://drive.google.com/open?id=fakedriveid";
@@ -71,29 +76,22 @@
 // File size set in test_download_service.
 const int kDownloadedPluginVmImageSizeInMb = 123456789u / (1024 * 1024);
 
+constexpr char kFailureReasonHistogram[] = "PluginVm.SetupFailureReason";
+
 }  // namespace
 
 class MockObserver : public PluginVmInstaller::Observer {
  public:
+  MOCK_METHOD1(OnProgressUpdated, void(double));
+  MOCK_METHOD1(OnCheckedDiskSpace, void(bool));
   MOCK_METHOD1(OnExistingVmCheckCompleted, void(bool));
-  MOCK_METHOD2(OnDlcDownloadProgressUpdated,
-               void(double progress, base::TimeDelta elapsed_time));
   MOCK_METHOD0(OnDlcDownloadCompleted, void());
-  MOCK_METHOD1(OnDlcDownloadFailed,
-               void(plugin_vm::PluginVmInstaller::FailureReason));
-  MOCK_METHOD3(OnDownloadProgressUpdated,
-               void(uint64_t bytes_downloaded,
-                    int64_t content_length,
-                    base::TimeDelta elapsed_time));
+  MOCK_METHOD2(OnDownloadProgressUpdated,
+               void(uint64_t bytes_downloaded, int64_t content_length));
   MOCK_METHOD0(OnDownloadCompleted, void());
-  MOCK_METHOD1(OnDownloadFailed,
-               void(plugin_vm::PluginVmInstaller::FailureReason));
-  MOCK_METHOD2(OnImportProgressUpdated,
-               void(int percent_completed, base::TimeDelta elapsed_time));
   MOCK_METHOD0(OnCreated, void());
   MOCK_METHOD0(OnImported, void());
-  MOCK_METHOD1(OnImportFailed,
-               void(plugin_vm::PluginVmInstaller::FailureReason));
+  MOCK_METHOD1(OnError, void(FailureReason));
   MOCK_METHOD0(OnCancelFinished, void());
 };
 
@@ -170,7 +168,9 @@
     installer_ = PluginVmInstallerFactory::GetForProfile(profile_.get());
     observer_ = std::make_unique<StrictMock<MockObserver>>();
     installer_->SetObserver(observer_.get());
+    installer_->SetFreeDiskSpaceForTesting(std::numeric_limits<int64_t>::max());
 
+    SetDefaultExpectations();
     // These actions are required to make the RunUntil* functions work, so tests
     // probably shouldn't override them.
     ON_CALL(*observer_, OnExistingVmCheckCompleted(false))
@@ -213,6 +213,11 @@
     return zip_file_path;
   }
 
+  void VerifyExpectations() {
+    Mock::VerifyAndClearExpectations(observer_.get());
+    SetDefaultExpectations();
+  }
+
   // Helper functions for starting and progressing the installer.
 
   void RunUntilDownloading() {
@@ -280,6 +285,11 @@
       std::move(download_completed_closure_).Run();
   }
 
+  void SetDefaultExpectations() {
+    // Suppress progress updates.
+    EXPECT_CALL(*observer_, OnProgressUpdated(_)).Times(AnyNumber());
+  }
+
   base::ScopedTempDir profiles_dir_;
 
   DISALLOW_COPY_AND_ASSIGN(PluginVmInstallerTestBase);
@@ -384,6 +394,64 @@
   DISALLOW_COPY_AND_ASSIGN(PluginVmInstallerDriveTest);
 };
 
+TEST_F(PluginVmInstallerDownloadServiceTest, ProgressUpdates) {
+  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
+
+  // Override default expectation so unexpected calls will fail the test.
+  EXPECT_CALL(*observer_, OnProgressUpdated(_)).Times(0);
+
+  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.01)));
+  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.45)));
+  EXPECT_CALL(*observer_, OnProgressUpdated(DoubleEq(0.725)));
+
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
+  EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
+  EXPECT_CALL(*observer_, OnDownloadCompleted());
+  EXPECT_CALL(*observer_, OnImported());
+  StartAndRunToCompletion();
+}
+
+TEST_F(PluginVmInstallerDownloadServiceTest, InsufficientDisk) {
+  installer_->SetFreeDiskSpaceForTesting(
+      PluginVmInstaller::kMinimumFreeDiskSpace - 1);
+  EXPECT_CALL(*observer_, OnError(FailureReason::INSUFFICIENT_DISK_SPACE));
+  StartAndRunToCompletion();
+  histogram_tester_->ExpectUniqueSample(
+      kFailureReasonHistogram, FailureReason::INSUFFICIENT_DISK_SPACE, 1);
+}
+
+TEST_F(PluginVmInstallerDownloadServiceTest, LowDiskCancel) {
+  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
+  installer_->SetFreeDiskSpaceForTesting(
+      PluginVmInstaller::kMinimumFreeDiskSpace);
+
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(/*low_disk_space=*/true));
+  StartAndRunToCompletion();
+  VerifyExpectations();
+
+  EXPECT_CALL(*observer_, OnCancelFinished());
+  installer_->Cancel();
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(PluginVmInstallerDownloadServiceTest, LowDiskContinue) {
+  SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
+  installer_->SetFreeDiskSpaceForTesting(
+      PluginVmInstaller::kRecommendedFreeDiskSpace - 1);
+
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(/*low_disk_space=*/true));
+  StartAndRunToCompletion();
+  VerifyExpectations();
+
+  EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
+  EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
+  EXPECT_CALL(*observer_, OnDownloadCompleted());
+  EXPECT_CALL(*observer_, OnImported());
+  installer_->Continue();
+  task_environment_.RunUntilIdle();
+}
+
 TEST_F(PluginVmInstallerDownloadServiceTest, VmExists) {
   vm_tools::plugin_dispatcher::ListVmResponse list_vms_response;
   list_vms_response.add_vm_info()->set_state(
@@ -392,6 +460,7 @@
       chromeos::DBusThreadManager::Get()->GetVmPluginDispatcherClient())
       ->set_list_vms_response(list_vms_response);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(true));
   StartAndRunToCompletion();
@@ -399,6 +468,7 @@
 
 TEST_F(PluginVmInstallerDownloadServiceTest, CancelOnVmExistsCheck) {
   base::RunLoop run_loop;
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted)
       .WillOnce(RunClosure(run_loop.QuitClosure()));
   EXPECT_CALL(*observer_, OnCancelFinished());
@@ -412,10 +482,10 @@
 TEST_F(PluginVmInstallerDownloadServiceTest, DownloadPluginVmImageParamsTest) {
   SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnDownloadCompleted());
-  EXPECT_CALL(*observer_, OnImportProgressUpdated(50.0, _));
   EXPECT_CALL(*observer_, OnImported());
 
   StartAndRunUntilDownloading();
@@ -435,10 +505,10 @@
 TEST_F(PluginVmInstallerDownloadServiceTest, OnlyOneImageIsProcessedTest) {
   SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnDownloadCompleted());
-  EXPECT_CALL(*observer_, OnImportProgressUpdated(50.0, _));
   EXPECT_CALL(*observer_, OnImported());
 
   StartAndRunUntilDownloading();
@@ -461,10 +531,10 @@
        CanProceedWithANewImageWhenSucceededTest) {
   SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false)).Times(2);
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted()).Times(2);
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false)).Times(2);
   EXPECT_CALL(*observer_, OnDownloadCompleted()).Times(2);
-  EXPECT_CALL(*observer_, OnImportProgressUpdated(50.0, _)).Times(2);
   EXPECT_CALL(*observer_, OnImported()).Times(2);
 
   StartAndRunToCompletion();
@@ -483,13 +553,11 @@
        CanProceedWithANewImageWhenFailedTest) {
   SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
 
-  EXPECT_CALL(*observer_,
-              OnDownloadFailed(
-                  PluginVmInstaller::FailureReason::DOWNLOAD_FAILED_ABORTED));
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false)).Times(2);
+  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_ABORTED));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted()).Times(2);
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false)).Times(2);
   EXPECT_CALL(*observer_, OnDownloadCompleted());
-  EXPECT_CALL(*observer_, OnImportProgressUpdated(50.0, _));
   EXPECT_CALL(*observer_, OnImported());
 
   StartAndRunUntilDownloading();
@@ -506,6 +574,7 @@
 }
 
 TEST_F(PluginVmInstallerDownloadServiceTest, CancelledDownloadTest) {
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnCancelFinished());
@@ -517,17 +586,17 @@
   installer_->OnDownloadCancelled();
 
   histogram_tester_->ExpectTotalCount(kPluginVmImageDownloadedSizeHistogram, 0);
+  histogram_tester_->ExpectTotalCount(kFailureReasonHistogram, 0);
 }
 
 TEST_F(PluginVmInstallerDownloadServiceTest, ImportNonExistingImageTest) {
   SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnDownloadCompleted());
-  EXPECT_CALL(
-      *observer_,
-      OnImportFailed(PluginVmInstaller::FailureReason::COULD_NOT_OPEN_IMAGE));
+  EXPECT_CALL(*observer_, OnError(FailureReason::COULD_NOT_OPEN_IMAGE));
 
   fake_downloaded_plugin_vm_image_archive_ = base::FilePath();
   StartAndRunToCompletion();
@@ -541,6 +610,7 @@
   SetupConciergeForCancelDiskImageOperation(fake_concierge_client_,
                                             true /* success */);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnDownloadCompleted());
@@ -553,12 +623,13 @@
 
 TEST_F(PluginVmInstallerDownloadServiceTest, EmptyPluginVmImageUrlTest) {
   SetPluginVmImagePref("", kHash);
-  EXPECT_CALL(
-      *observer_,
-      OnDownloadFailed(PluginVmInstaller::FailureReason::INVALID_IMAGE_URL));
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::INVALID_IMAGE_URL));
   StartAndRunToCompletion();
 
   histogram_tester_->ExpectTotalCount(kPluginVmImageDownloadedSizeHistogram, 0);
+  histogram_tester_->ExpectUniqueSample(kFailureReasonHistogram,
+                                        FailureReason::INVALID_IMAGE_URL, 1);
 }
 
 TEST_F(PluginVmInstallerDownloadServiceTest, VerifyDownloadTest) {
@@ -571,8 +642,7 @@
 TEST_F(PluginVmInstallerDownloadServiceTest, CannotStartIfPluginVmIsDisabled) {
   profile_->ScopedCrosSettingsTestHelper()->SetBoolean(
       chromeos::kPluginVmAllowed, false);
-  EXPECT_CALL(*observer_,
-              OnDownloadFailed(PluginVmInstaller::FailureReason::NOT_ALLOWED));
+  EXPECT_CALL(*observer_, OnError(FailureReason::NOT_ALLOWED));
   installer_->Start();
   task_environment_.RunUntilIdle();
 }
@@ -580,11 +650,10 @@
 TEST_F(PluginVmInstallerDriveTest, InvalidDriveUrlTest) {
   SetPluginVmImagePref(kDriveUrl2, kHash);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
-  EXPECT_CALL(
-      *observer_,
-      OnDownloadFailed(PluginVmInstaller::FailureReason::INVALID_IMAGE_URL));
+  EXPECT_CALL(*observer_, OnError(FailureReason::INVALID_IMAGE_URL));
   StartAndRunToCompletion();
 }
 
@@ -592,22 +661,21 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_drive_service_->set_offline(true);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
-  EXPECT_CALL(*observer_,
-              OnDownloadFailed(
-                  PluginVmInstaller::FailureReason::DOWNLOAD_FAILED_NETWORK));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_NETWORK));
   StartAndRunToCompletion();
 }
 
 TEST_F(PluginVmInstallerDriveTest, WrongHashDriveTest) {
   SetPluginVmImagePref(kDriveUrl, kHash2);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
-  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, _, _)).Times(2);
-  EXPECT_CALL(*observer_, OnDownloadFailed(
-                              PluginVmInstaller::FailureReason::HASH_MISMATCH));
+  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, _)).Times(2);
+  EXPECT_CALL(*observer_, OnError(FailureReason::HASH_MISMATCH));
 
   StartAndRunToCompletion();
 }
@@ -616,13 +684,12 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   SimpleFakeDriveService* fake_drive_service = SetUpSimpleFakeDriveService();
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
-  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100, _));
-  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(10, 100, _));
-  EXPECT_CALL(*observer_,
-              OnDownloadFailed(
-                  PluginVmInstaller::FailureReason::DOWNLOAD_FAILED_NETWORK));
+  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100));
+  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(10, 100));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DOWNLOAD_FAILED_NETWORK));
 
   StartAndRunToCompletion();
 
@@ -640,9 +707,10 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   SimpleFakeDriveService* fake_drive_service = SetUpSimpleFakeDriveService();
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
-  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100, _));
+  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(5, 100));
   EXPECT_CALL(*observer_, OnCancelFinished());
 
   StartAndRunToCompletion();
@@ -659,13 +727,13 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorNone);
 
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
   EXPECT_CALL(*observer_, OnDlcDownloadCompleted());
   EXPECT_CALL(*observer_, OnExistingVmCheckCompleted(false));
   EXPECT_CALL(*observer_, OnDownloadCompleted());
-  EXPECT_CALL(*observer_,
-              OnDownloadProgressUpdated(_, std::strlen(kContent), _))
+  EXPECT_CALL(*observer_, OnDownloadProgressUpdated(_, std::strlen(kContent)))
       .Times(AtLeast(1));
-  EXPECT_CALL(*observer_, OnImportFailed(_));
+  EXPECT_CALL(*observer_, OnError(_));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
@@ -676,7 +744,8 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorInternal);
 
-  EXPECT_CALL(*observer_, OnDownloadFailed(_)).Times(1);
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_INTERNAL));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
@@ -688,7 +757,8 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorBusy);
 
-  EXPECT_CALL(*observer_, OnDownloadFailed(_)).Times(1);
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_BUSY));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
@@ -699,7 +769,8 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorNeedReboot);
 
-  EXPECT_CALL(*observer_, OnDownloadFailed(_)).Times(1);
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_REBOOT));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(
@@ -711,7 +782,8 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorAllocation);
 
-  EXPECT_CALL(*observer_, OnDownloadFailed(_)).Times(1);
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_NEED_SPACE));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(
@@ -723,7 +795,8 @@
   SetPluginVmImagePref(kDriveUrl, kHash);
   fake_dlcservice_client_->SetInstallError(dlcservice::kErrorInvalidDlc);
 
-  EXPECT_CALL(*observer_, OnDownloadFailed(_)).Times(1);
+  EXPECT_CALL(*observer_, OnCheckedDiskSpace(false));
+  EXPECT_CALL(*observer_, OnError(FailureReason::DLC_UNSUPPORTED));
 
   StartAndRunToCompletion();
   histogram_tester_->ExpectUniqueSample(kPluginVmDlcUseResultHistogram,
diff --git a/chrome/browser/chromeos/plugin_vm/plugin_vm_metrics_util.h b/chrome/browser/chromeos/plugin_vm/plugin_vm_metrics_util.h
index b76fca6..62e2591d 100644
--- a/chrome/browser/chromeos/plugin_vm/plugin_vm_metrics_util.h
+++ b/chrome/browser/chromeos/plugin_vm/plugin_vm_metrics_util.h
@@ -34,22 +34,22 @@
 // numeric values should never be reused.
 enum class PluginVmSetupResult {
   kSuccess = 0,
-
-  kPluginVmIsNotAllowed = 1,
-
-  kErrorDownloadingPluginVmImage = 2,
-  kErrorImportingPluginVmImage = 3,
-
+  // kPluginVmIsNotAllowed = 1,
+  // kErrorDownloadingPluginVmImage = 2,
+  // kErrorImportingPluginVmImage = 3,
   kUserCancelledDownloadingPluginVmImage = 4,
   kUserCancelledImportingPluginVmImage = 5,
-
-  kErrorDownloadingPluginVmDlc = 6,
+  // kErrorDownloadingPluginVmDlc = 6,
   kUserCancelledDownloadingPluginVmDlc = 7,
-
   kVmAlreadyExists = 8,
   kUserCancelledCheckingForExistingVm = 9,
+  // kErrorInsufficientDiskSpace = 10,
+  kUserCancelledLowDiskSpace = 11,
+  kUserCancelledCheckingDiskSpace = 12,
+  // Failure reasons are broken down in PluginVm.SetupFailureReason.
+  kError = 13,
 
-  kMaxValue = kUserCancelledCheckingForExistingVm,
+  kMaxValue = kError,
 };
 
 enum class PluginVmDlcUseResult {
diff --git a/chrome/browser/chromeos/smb_client/smb_service.cc b/chrome/browser/chromeos/smb_client/smb_service.cc
index 23289be..e3b6f2b 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service.cc
@@ -180,6 +180,12 @@
   }
 }
 
+void SmbService::Shutdown() {
+  // Unmount and destroy all smbfs instances explicitly before destruction,
+  // since SmbFsShare accesses KeyedServices on destruction.
+  smbfs_shares_.clear();
+}
+
 // static
 void SmbService::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
diff --git a/chrome/browser/chromeos/smb_client/smb_service.h b/chrome/browser/chromeos/smb_client/smb_service.h
index 1bc2d6d..95c4fa6 100644
--- a/chrome/browser/chromeos/smb_client/smb_service.h
+++ b/chrome/browser/chromeos/smb_client/smb_service.h
@@ -62,6 +62,9 @@
   SmbService(Profile* profile, std::unique_ptr<base::TickClock> tick_clock);
   ~SmbService() override;
 
+  // KeyedService override.
+  void Shutdown() override;
+
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
   // Starts the process of mounting an SMB file system.
diff --git a/chrome/browser/chromeos/smb_client/smb_service_factory.cc b/chrome/browser/chromeos/smb_client/smb_service_factory.cc
index 6b63c97..008093e 100644
--- a/chrome/browser/chromeos/smb_client/smb_service_factory.cc
+++ b/chrome/browser/chromeos/smb_client/smb_service_factory.cc
@@ -8,6 +8,7 @@
 
 #include "base/time/default_tick_clock.h"
 #include "chrome/browser/chromeos/authpolicy/authpolicy_credentials_manager.h"
+#include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
 #include "chrome/browser/chromeos/file_system_provider/service_factory.h"
 #include "chrome/browser/chromeos/kerberos/kerberos_credentials_manager_factory.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -54,6 +55,7 @@
   DependsOn(file_system_provider::ServiceFactory::GetInstance());
   DependsOn(AuthPolicyCredentialsManagerFactory::GetInstance());
   DependsOn(KerberosCredentialsManagerFactory::GetInstance());
+  DependsOn(file_manager::VolumeManagerFactory::GetInstance());
 }
 
 SmbServiceFactory::~SmbServiceFactory() {}
diff --git a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
index 35b65cf..0a651d9 100644
--- a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
+++ b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
@@ -601,7 +601,8 @@
   ASSERT_FLOAT_EQ(kScale, fontSize / oldFontSize);
 }
 
-IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest, UISetsPrefs) {
+IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
+                       DISABLED_UISetsPrefs) {
   content::WebContents* contents =
       browser()->tab_strip_model()->GetActiveWebContents();
 
diff --git a/chrome/browser/download/download_permission_request.cc b/chrome/browser/download/download_permission_request.cc
index c49c0b4..d90f115 100644
--- a/chrome/browser/download/download_permission_request.cc
+++ b/chrome/browser/download/download_permission_request.cc
@@ -12,7 +12,7 @@
 #include "components/url_formatter/elide_url.h"
 #include "url/origin.h"
 #else
-#include "chrome/app/vector_icons/vector_icons.h"
+#include "components/vector_icons/vector_icons.h"
 #endif
 
 DownloadPermissionRequest::DownloadPermissionRequest(
@@ -27,7 +27,7 @@
 #if defined(OS_ANDROID)
   return IDR_ANDROID_INFOBAR_MULTIPLE_DOWNLOADS;
 #else
-  return kFileDownloadIcon;
+  return vector_icons::kFileDownloadIcon;
 #endif
 }
 
diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
index bdd45750..ea248c27 100644
--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
@@ -338,6 +338,19 @@
   ASSERT_TRUE(RunExtensionTest("debugger")) << message_;
 }
 
+// Tests that an extension is not allowed to inspect a worker through the
+// inspectWorker debugger command.
+// Regression test for https://crbug.com/1059577.
+IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest,
+                       DebuggerNotAllowedToInvokeInspectWorker) {
+  GURL url(embedded_test_server()->GetURL(
+      "/extensions/api_test/debugger_inspect_worker/inspected_page.html"));
+
+  EXPECT_TRUE(
+      RunExtensionTestWithArg("debugger_inspect_worker", url.spec().c_str()))
+      << message_;
+}
+
 class SitePerProcessDebuggerExtensionApiTest : public DebuggerExtensionApiTest {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
index 3bffa0d..8931e0c 100644
--- a/chrome/browser/extensions/api/permissions/permissions_apitest.cc
+++ b/chrome/browser/extensions/api/permissions/permissions_apitest.cc
@@ -69,6 +69,12 @@
       << message_;
 }
 
+// TODO(crbug/1065399): Flaky on ChromeOS and Linux non-dbg builds.
+#if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(NDEBUG)
+#define MAYBE_FaviconPermission DISABLED_FaviconPermission
+#else
+#define MAYBE_FaviconPermission MAYBE_FaviconPermission
+#endif
 IN_PROC_BROWSER_TEST_F(PermissionsApiTest, FaviconPermission) {
   base::HistogramTester tester;
   ASSERT_TRUE(RunExtensionTest("permissions/favicon")) << message_;
diff --git a/chrome/browser/notifications/notification_platform_bridge_mac.mm b/chrome/browser/notifications/notification_platform_bridge_mac.mm
index ebd61c7..acf1849a 100644
--- a/chrome/browser/notifications/notification_platform_bridge_mac.mm
+++ b/chrome/browser/notifications/notification_platform_bridge_mac.mm
@@ -114,7 +114,10 @@
 base::string16 CreateNotificationTitle(
     const message_center::Notification& notification) {
   base::string16 title;
-  if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
+  // Show progress percentage if available. We don't support indeterminate
+  // states on macOS native notifications.
+  if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS &&
+      notification.progress() >= 0 && notification.progress() <= 100) {
     title += base::FormatPercent(notification.progress());
     title += base::UTF8ToUTF16(" - ");
   }
diff --git a/chrome/browser/resources/chromeos/login/oobe_reset.html b/chrome/browser/resources/chromeos/login/oobe_reset.html
index 39d24fb..7706065 100644
--- a/chrome/browser/resources/chromeos/login/oobe_reset.html
+++ b/chrome/browser/resources/chromeos/login/oobe_reset.html
@@ -81,15 +81,15 @@
         <div class="flex"></div>
         <!-- Cancel button, only disabled when reverting -->
         <oobe-text-button border on-tap="onCancelTap_" text-key="cancelButton"
-            disabled="[[inRevertState_]]"></oobe-text-button>
+            id="resetCancel" disabled="[[inRevertState_]]"></oobe-text-button>
         <!-- Restart button -->
         <oobe-text-button inverse on-tap="onRestartTap_" class="focus-on-show"
-            text-key="resetButtonRestart"
+            id="restart" text-key="resetButtonRestart"
             hidden="[[!inRestartRequiredState_]]">
         </oobe-text-button>
         <!-- Powerwash button (depends on powerwash mode) -->
         <oobe-text-button inverse on-tap="onPowerwashTap_" class="focus-on-show"
-            hidden="[[!inPowerwashState_]]"
+            id="powerwash" hidden="[[!inPowerwashState_]]"
             text-key="[[powerwashButtonTextKey_]]"
             disabled="[[powerwashButtonDisabled_]]">
         </oobe-text-button>
@@ -104,6 +104,7 @@
         <oobe-text-button id="cancelButton" border on-tap="onDialogCancelTap_"
             text-key="cancel"></oobe-text-button>
         <oobe-text-button inverse on-tap="onDialogContinueTap_"
+            id="confirmPowerwash"
             text-key="confirmResetButton"></oobe-text-button>
       </div>
     </oobe-help-dialog>
diff --git a/chrome/browser/resources/media/media_data_table.js b/chrome/browser/resources/media/media_data_table.js
index 758ace61..8efaad5 100644
--- a/chrome/browser/resources/media/media_data_table.js
+++ b/chrome/browser/resources/media/media_data_table.js
@@ -80,7 +80,7 @@
             key = k;
           });
 
-          this.delegate_.insertDataField(td, data, key, dataRow);
+          this.delegate_.insertDataField(td, data, key);
           tr.appendChild(td);
         });
       });
@@ -103,10 +103,8 @@
      * @param {Element} td
      * @param {?Object} data
      * @param {string} key
-     * @param {Object} dataRow This is the row itself in case we need extra
-     *   data to render the field.
      */
-    insertDataField(td, data, key, dataRow) {}
+    insertDataField(td, data, key) {}
 
     /**
      * Compares two objects based on |sortKey|.
diff --git a/chrome/browser/resources/media/media_feeds.html b/chrome/browser/resources/media/media_feeds.html
index 1083adcf..3f848c5 100644
--- a/chrome/browser/resources/media/media_feeds.html
+++ b/chrome/browser/resources/media/media_feeds.html
@@ -124,8 +124,6 @@
         </th>
         <th data-key="logos">
           Logos
-        <th data-key="actions">
-          Actions
         </th>
       </tr>
     </thead>
@@ -133,71 +131,22 @@
     </tbody>
   </table>
 
-  <div id="feed-content" style="display:none;">
-    <hr>
-    <h2>Feed Contents: <span id="current-feed"></span></h2>
-    <table id="feed-items-table">
-      <thead>
-        <tr>
-          <th sort-key="type" class="sort-column" sort-reverse>
-            Type
-          </th>
-          <th sort-key="name">
-            Name
-          </th>
-          <th sort-key="author">
-            Author
-          </th>
-          <th sort-key="datePublished">
-            Date Published
-          </th>
-          <th sort-key="isFamilyFriendly">
-            Family Friendly
-          </th>
-          <th sort-key="actionStatus">
-            Action Status
-          </th>
-          <th sort-key="action.url">
-            Action URL
-          </th>
-          <th sort-key="action.startTime">
-            Action Start Time (secs)
-          </th>
-          <th sort-key="interactionCounters">
-            Interaction Counters
-          </th>
-          <th sort-key="contentRatings">
-            Content Ratings
-          </th>
-          <th sort-key="genre">
-            Genre
-          </th>
-          <th sort-key="live">
-            Live Details
-          </th>
-          <th sort-key="tvEpisode">
-            TV Episode
-          </th>
-          <th sort-key="playNextCandidate">
-            Play Next Candidate
-          </th>
-          <th sort-key="identifiers">
-            Identifiers
-          </th>
-          <th sort-key="shownCount">
-            Shown Count
-          </th>
-          <th sort-key="clicked">
-            Clicked
-          </th>
-          <th sort-key="images">
-            Images
-          </th>
-        </tr>
-      </thead>
-      <tbody>
-      </tbody>
-    </table>
-  </div>
+  <template id="datarow">
+    <tr>
+      <td class="id-cell"></td>
+      <td class="url-cell"></td>
+      <td></td>
+      <td class="last-discovery-time-cell"></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+      <td></td>
+    </tr>
+  </template>
 </body>
 </html>
diff --git a/chrome/browser/resources/media/media_feeds.js b/chrome/browser/resources/media/media_feeds.js
index cb517fb..13830a4 100644
--- a/chrome/browser/resources/media/media_feeds.js
+++ b/chrome/browser/resources/media/media_feeds.js
@@ -11,16 +11,10 @@
   return mediaFeedsPageIsPopulatedResolver.promise;
 }
 
-const mediaFeedItemsPageIsPopulatedResolver = new PromiseResolver();
-function whenFeedTableIsPopulatedForTest() {
-  return mediaFeedItemsPageIsPopulatedResolver.promise;
-}
-
 (function() {
 
 let delegate = null;
 let feedsTable = null;
-let feedItemsTable = null;
 let store = null;
 
 /** @implements {cr.ui.MediaDataTableDelegate} */
@@ -31,28 +25,8 @@
    * @param {Element} td
    * @param {?Object} data
    * @param {string} key
-   * @param {Object} dataRow
    */
-  insertDataField(td, data, key, dataRow) {
-    if (key == 'actions') {
-      const a = document.createElement('a');
-      a.href = '#feed-content';
-      a.textContent = 'Show Contents';
-      td.appendChild(a);
-
-      a.addEventListener('click', () => {
-        store.getItemsForMediaFeed(dataRow.id).then(response => {
-          feedItemsTable.setData(response.items);
-
-          // Show the feed items section.
-          $('current-feed').textContent = dataRow.url.url;
-          $('feed-content').style.display = 'block';
-
-          mediaFeedItemsPageIsPopulatedResolver.resolve();
-        });
-      });
-    }
-
+  insertDataField(td, data, key) {
     if (data === undefined || data === null) {
       return;
     }
@@ -62,11 +36,11 @@
       td.textContent = data.url;
     } else if (
         key === 'lastDiscoveryTime' || key === 'lastFetchTime' ||
-        key === 'cacheExpiryTime' || key === 'datePublished') {
+        key === 'cacheExpiryTime') {
       // Format a mojo time.
       td.textContent =
           convertMojoTimeToJS(/** @type {mojoBase.mojom.Time} */ (data))
-              .toLocaleString();
+              .toString();
     } else if (key === 'userStatus') {
       // Format a FeedUserStatus.
       if (data == mediaFeeds.mojom.FeedUserStatus.kAuto) {
@@ -100,7 +74,7 @@
 
       td.textContent =
           contentTypes.length === 0 ? 'None' : contentTypes.join(',');
-    } else if (key === 'logos' || key === 'images') {
+    } else if (key === 'logos') {
       // Format an array of mojo media images.
       data.forEach((image) => {
         const a = document.createElement('a');
@@ -110,129 +84,6 @@
         td.appendChild(a);
         td.appendChild(document.createElement('br'));
       });
-    } else if (key == 'type') {
-      // Format a MediaFeedItemType.
-      switch (parseInt(data, 10)) {
-        case mediaFeeds.mojom.MediaFeedItemType.kVideo:
-          td.textContent = 'Video';
-          break;
-        case mediaFeeds.mojom.MediaFeedItemType.kTVSeries:
-          td.textContent = 'TV Series';
-          break;
-        case mediaFeeds.mojom.MediaFeedItemType.kMovie:
-          td.textContent = 'Movie';
-          break;
-      }
-    } else if (key == 'isFamilyFriendly' || key == 'clicked') {
-      // Format a boolean.
-      td.textContent = data ? 'Yes' : 'No';
-    } else if (key == 'actionStatus') {
-      // Format a MediaFeedItemActionStatus.
-      switch (parseInt(data, 10)) {
-        case mediaFeeds.mojom.MediaFeedItemActionStatus.kUnknown:
-          td.textContent = 'Unknown';
-          break;
-        case mediaFeeds.mojom.MediaFeedItemActionStatus.kActive:
-          td.textContent = 'Active';
-          break;
-        case mediaFeeds.mojom.MediaFeedItemActionStatus.kPotential:
-          td.textContent = 'Potential';
-          break;
-        case mediaFeeds.mojom.MediaFeedItemActionStatus.kCompleted:
-          td.textContent = 'Completed';
-          break;
-      }
-    } else if (key == 'startTime') {
-      // Format a start time.
-      td.textContent =
-          timeDeltaToSeconds(/** @type {mojoBase.mojom.TimeDelta} */ (data));
-    } else if (key == 'interactionCounters') {
-      // Format interaction counters.
-      const counters = [];
-
-      Object.keys(data).forEach((key) => {
-        let keyString = '';
-
-        switch (parseInt(key, 10)) {
-          case mediaFeeds.mojom.InteractionCounterType.kWatch:
-            keyString = 'Watch';
-            break;
-          case mediaFeeds.mojom.InteractionCounterType.kLike:
-            keyString = 'Like';
-            break;
-          case mediaFeeds.mojom.InteractionCounterType.kDislike:
-            keyString = 'Dislike';
-            break;
-        }
-
-        counters.push(keyString + '=' + data[key]);
-      });
-
-      td.textContent = counters.join(' ');
-    } else if (key == 'contentRatings') {
-      // Format content ratings.
-      const ratings = [];
-
-      data.forEach((rating) => {
-        ratings.push(rating.agency + ' ' + rating.value);
-      });
-
-      td.textContent = ratings.join(', ');
-    } else if (key == 'author') {
-      // Format a mojom author.
-      const a = document.createElement('a');
-      a.href = data.url;
-      a.textContent = data.name;
-      a.target = '_blank';
-      td.appendChild(a);
-    } else if (key == 'name' || key == 'genre') {
-      // Format a mojo string16.
-      td.textContent =
-          decodeString16(/** @type {mojoBase.mojom.String16} */ (data));
-    } else if (key == 'live') {
-      // Format LiveDetails.
-      td.textContent = 'Live';
-
-      if (data.startTime) {
-        td.textContent += ' ' +
-            'StartTime=' +
-            convertMojoTimeToJS(
-                /** @type {mojoBase.mojom.Time} */ (data.startTime))
-                .toLocaleString();
-      }
-
-      if (data.endTime) {
-        td.textContent += ' ' +
-            'EndTime=' +
-            convertMojoTimeToJS(
-                /** @type {mojoBase.mojom.Time} */ (data.endTime))
-                .toLocaleString();
-      }
-    } else if (key == 'tvEpisode') {
-      // Format a TV Episode.
-      td.textContent = data.name + ' EpisodeNumber=' + data.episodeNumber +
-          ' SeasonNumber=' + data.seasonNumber + ' ' +
-          formatIdentifiers(/** @type {Array<mediaFeeds.mojom.Identifier>} */ (
-              data.identifiers));
-    } else if (key == 'playNextCandidate') {
-      // Format a Play Next Candidate.
-      td.textContent = data.name + ' EpisodeNumber=' + data.episodeNumber +
-          ' SeasonNumber=' + data.seasonNumber + ' ' +
-          formatIdentifiers(
-                           /** @type {Array<mediaFeeds.mojom.Identifier>} */ (
-                               data.identifiers)) +
-          ' ActionURL=' + data.action.url.url;
-
-      if (data.action.startTime) {
-        td.textContent +=
-            ' ActionStartTimeSecs=' + timeDeltaToSeconds(data.action.startTime);
-      }
-
-      td.textContent += ' DurationSecs=' + timeDeltaToSeconds(data.duration);
-    } else if (key == 'identifiers') {
-      // Format identifiers.
-      td.textContent = formatIdentifiers(
-          /** @type {Array<mediaFeeds.mojom.Identifier>} */ (data));
     } else {
       td.textContent = data;
     }
@@ -271,57 +122,6 @@
 }
 
 /**
- * Convert a time delta to seconds.
- * @param {mojoBase.mojom.TimeDelta} timeDelta
- * @returns {number}
- */
-function timeDeltaToSeconds(timeDelta) {
-  return timeDelta.microseconds / 1000 / 1000;
-}
-
-/**
- * Formats an array of identifiers for display.
- * @param {Array<mediaFeeds.mojom.Identifier>} mojoIdentifiers
- * @returns {string}
- */
-function formatIdentifiers(mojoIdentifiers) {
-  const identifiers = [];
-
-  mojoIdentifiers.forEach((identifier) => {
-    let keyString = '';
-
-    switch (identifier.type) {
-      case mediaFeeds.mojom.Identifier_Type.kTMSRootId:
-        keyString = 'TMSRootId';
-        break;
-      case mediaFeeds.mojom.Identifier_Type.kTMSId:
-        keyString = 'TMSId';
-        break;
-      case mediaFeeds.mojom.Identifier_Type.kPartnerId:
-        keyString = 'PartnerId';
-        break;
-    }
-
-    identifiers.push(keyString + '=' + identifier.value);
-  });
-
-  return identifiers.join(' ');
-}
-
-/**
- * Parses utf16 coded string.
- * @param {?mojoBase.mojom.String16} arr
- * @return {string}
- */
-function decodeString16(arr) {
-  if (arr == null) {
-    return '';
-  }
-
-  return arr.data.map(ch => String.fromCodePoint(ch)).join('');
-}
-
-/**
  * Converts a mojo time to a JS time.
  * @param {mojoBase.mojom.Time} mojoTime
  * @return {Date}
@@ -360,7 +160,6 @@
 
   delegate = new MediaFeedsTableDelegate();
   feedsTable = new cr.ui.MediaDataTable($('feeds-table'), delegate);
-  feedItemsTable = new cr.ui.MediaDataTable($('feed-items-table'), delegate);
 
   updateFeedsTable();
 
diff --git a/chrome/browser/resources/settings/autofill_page/BUILD.gn b/chrome/browser/resources/settings/autofill_page/BUILD.gn
index 9a5034e1..96b464b6 100644
--- a/chrome/browser/resources/settings/autofill_page/BUILD.gn
+++ b/chrome/browser/resources/settings/autofill_page/BUILD.gn
@@ -100,6 +100,7 @@
     ":password_manager_proxy",
     "..:plural_string_proxy",
     "../prefs:prefs_behavior",
+    "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:i18n_behavior",
   ]
 }
@@ -296,6 +297,7 @@
     "..:plural_string_proxy.m",
     "../prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
   extra_deps = [ ":password_check_module" ]
@@ -318,6 +320,7 @@
     ":password_manager_proxy.m",
     "..:open_window_proxy.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:i18n_behavior",
   ]
   extra_deps = [ ":password_check_list_item_module" ]
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.html b/chrome/browser/resources/settings/autofill_page/password_check.html
index 97c649f..d7f0496 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.html
+++ b/chrome/browser/resources/settings/autofill_page/password_check.html
@@ -136,7 +136,10 @@
       </div>
     </div>
     <cr-action-menu id="moreActionsMenu" role-description="$i18n{menu}">
-      <!-- TODO(crbug.com/1047726) Add show / hide functionality -->
+      <button id="menuShowPassword" class="dropdown-item"
+          on-click="onMenuShowPasswordClick_">
+        [[showHideMenuTitle_]]
+      </button>
       <button id="menuEditPassword" class="dropdown-item"
           on-click="onMenuEditPasswordClick_">
         $i18n{editCompromisedPassword}
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.js b/chrome/browser/resources/settings/autofill_page/password_check.js
index 057e14c0..83767f8 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check.js
@@ -103,6 +103,12 @@
       type: Boolean,
       computed:
           'computeShowNoCompromisedPasswordsLabel(syncStatus_, prefs.*, status_, leakedPasswords)',
+    },
+
+    /** @private */
+    showHideMenuTitle_: {
+      type: String,
+      computed: 'computeShowHideMenuTitle(activePassword_)',
     }
   },
 
@@ -130,6 +136,12 @@
    */
   activeDialogAnchor_: null,
 
+  /**
+   * The password_check_list_item that the user is interacting with now.
+   * @private {?EventTarget}
+   */
+  activeListItem_: null,
+
   /** @override */
   attached() {
     // Set the manager. These can be overridden by tests.
@@ -239,7 +251,17 @@
     const target = event.detail.moreActionsButton;
     this.$.moreActionsMenu.showAt(target);
     this.activeDialogAnchor_ = target;
-    this.activePassword_ = event.target.item;
+    this.activeListItem_ = event.target;
+    this.activePassword_ = this.activeListItem_.item;
+  },
+
+  /** @private */
+  onMenuShowPasswordClick_() {
+    this.activePassword_.password ? this.activeListItem_.hidePassword() :
+                                    this.activeListItem_.showPassword();
+    this.$.moreActionsMenu.close();
+    this.activePassword_ = null;
+    this.activeDialogAnchor_ = null;
   },
 
   /** @private */
@@ -281,6 +303,12 @@
     this.activeDialogAnchor_ = null;
   },
 
+  computeShowHideMenuTitle() {
+    return this.i18n(
+        this.activeListItem_.isPasswordVisible_ ? 'hideCompromisedPassword' :
+                                                  'showCompromisedPassword');
+  },
+
   /**
    * Returns the icon (warning, info or error) indicating the check status.
    * @return {string}
@@ -560,6 +588,7 @@
       if (map.has(item.id)) {
         // Replace old version with new
         resultList.push(map.get(item.id));
+        resultList[resultList.length - 1].password = item.password;
         map.delete(item.id);
       }
     });
@@ -573,7 +602,8 @@
       }
       return rhs.compromiseTime - lhs.compromiseTime;
     });
-    this.leakedPasswords = resultList.concat(addedResults);
+    addedResults.forEach(item => resultList.push(item));
+    this.leakedPasswords = resultList;
   },
 
   /**
diff --git a/chrome/browser/resources/settings/autofill_page/password_check_list_item.html b/chrome/browser/resources/settings/autofill_page/password_check_list_item.html
index 35e0b6f..5ecf82d9 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check_list_item.html
+++ b/chrome/browser/resources/settings/autofill_page/password_check_list_item.html
@@ -3,6 +3,7 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_icons_css.html">
 <link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/cr/ui/focus_row_behavior.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="../open_window_proxy.html">
 <link rel="import" href="../settings_shared_css.html">
@@ -84,8 +85,11 @@
             <span class="no-min-width text-elide secondary">
               [[item.username]]
             </span>
-            <input class="no-min-width secondary" id="leakedPassword"
-                type="password" value="[[password_]]" readonly disabled>
+            <input class="no-min-width secondary text-elide" id="leakedPassword"
+                focus-row-control focus-type="passwordField" readonly
+                type="[[getInputType_(isPasswordVisible_)]]"
+                value="[[password_]]" on-click="onReadonlyInputTap_"
+                disabled$="[[!isPasswordVisible_]]">
           </div>
           <div class="secondary" id="leakType">
             [[getCompromiseType_(item)]]
diff --git a/chrome/browser/resources/settings/autofill_page/password_check_list_item.js b/chrome/browser/resources/settings/autofill_page/password_check_list_item.js
index 1a744ec25..dc83263 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check_list_item.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check_list_item.js
@@ -11,16 +11,34 @@
   is: 'password-check-list-item',
 
   properties: {
-    password_: {
-      type: String,
-      value: ' '.repeat(10),
-    },
-
     /**
      * The password that is being displayed.
      * @type {!PasswordManagerProxy.CompromisedCredential}
      */
     item: Object,
+
+    /** @private */
+    isPasswordVisible_: {
+      type: Boolean,
+      computed: 'computePasswordVisibility_(item.password)',
+    },
+
+    /** @private */
+    password_: {
+      type: String,
+      computed: 'computePassword_(item.password)',
+    },
+  },
+
+  /**
+   * @private {?PasswordManagerProxy}
+   */
+  passwordManager_: null,
+
+  /** @override */
+  attached() {
+    // Set the manager. These can be overridden by tests.
+    this.passwordManager_ = PasswordManagerImpl.getInstance();
   },
 
   /**
@@ -59,4 +77,63 @@
   onMoreClick_(event) {
     this.fire('more-actions-click', {moreActionsButton: event.target});
   },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  getInputType_() {
+    return this.isPasswordVisible_ ? 'text' : 'password';
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computePasswordVisibility_() {
+    return !!this.item.password;
+  },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  computePassword_() {
+    const NUM_PLACEHOLDERS = 10;
+    return this.item.password || ' '.repeat(NUM_PLACEHOLDERS);
+  },
+
+  /**
+   * @public
+   */
+  hidePassword() {
+    this.set('item.password', null);
+  },
+
+  /**
+   * @public
+   */
+  showPassword() {
+    this.passwordManager_.recordPasswordCheckInteraction(
+        PasswordManagerProxy.PasswordCheckInteraction.SHOW_PASSWORD);
+    this.passwordManager_
+        .getPlaintextCompromisedPassword(
+            assert(this.item), chrome.passwordsPrivate.PlaintextReason.VIEW)
+        .then(
+            compromisedCredential => {
+              this.set('item', compromisedCredential);
+            },
+            error => {
+              this.hidePassword();
+            });
+  },
+
+  /**
+   * @private
+   */
+  onReadonlyInputTap_() {
+    if (this.isPasswordVisible_) {
+      this.$$('#leakedPassword').select();
+    }
+  },
 });
diff --git a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
index 6fdd2058..b749952 100644
--- a/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
+++ b/chrome/browser/resources/settings/autofill_page/password_manager_proxy.js
@@ -292,8 +292,9 @@
   CHANGE_PASSWORD: 3,
   EDIT_PASSWORD: 4,
   REMOVE_PASSWORD: 5,
+  SHOW_PASSWORD: 6,
   // Must be last.
-  COUNT: 6,
+  COUNT: 7,
 };
 
 /**
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 8fed398..930b195 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/browser_sync",
     "//components/keyed_service/content",
     "//components/language/core/browser",
+    "//components/page_info",
     "//components/password_manager/core/browser",
     "//components/password_manager/core/browser:hash_password_manager",
     "//components/pref_registry",
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
index beab5887..ad9f53b 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/sessions/content/session_tab_helper.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
diff --git a/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc b/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc
index d7eb9d9..605003ef 100644
--- a/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc
+++ b/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc
@@ -17,6 +17,7 @@
 #include "base/task/post_task.h"
 #include "base/task/thread_pool.h"
 #include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
@@ -332,12 +333,25 @@
     // This might happen if we don't know the total size of the image but we
     // still want to show how many bytes have been transferred.
     notification.set_progress(-1);
+#if defined(OS_MACOSX)
+    // On macOS we only have the title and message available. The progress is
+    // prepended to the title and the message should be the context.
+    notification.set_message(context);
+#else
     notification.set_progress_status(context);
+#endif  // defined(OS_MACOSX)
   } else {
     notification.set_progress(image_content_progress_ * 100 /
                               image_content_length_);
-    notification.set_progress_status(
-        GetProgressString(image_content_progress_, image_content_length_));
+    base::string16 progress =
+        GetProgressString(image_content_progress_, image_content_length_);
+#if defined(OS_MACOSX)
+    // On macOS we only have the title and message available. The progress is
+    // prepended to the title and the message should be the progress.
+    notification.set_message(progress);
+#else
+    notification.set_progress_status(progress);
+#endif  // defined(OS_MACOSX)
   }
 
   NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
@@ -502,6 +516,13 @@
                      base::Unretained(this), old_sequence_number,
                      base::TimeTicks::Now(), /*is_image=*/true));
 
+#if defined(OS_MACOSX)
+  // On macOS we can't replace a persistent notification with a non-persistent
+  // one because they are posted from different sources (app vs xpc). To avoid
+  // having both notifications on screen, remove the progress one first.
+  CancelProgressNotification();
+#endif  // defined(OS_MACOSX)
+
   std::string notification_id = image_notification_id_;
   if (notification_id.empty()) {
     notification_id = base::GenerateGUID();
@@ -523,13 +544,23 @@
     const std::string& notification_id) {
   TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::ShowNotification");
 
+  gfx::Image icon;
+  message_center::RichNotificationData rich_notification_data;
+
   bool use_image_notification =
       base::FeatureList::IsEnabled(kRemoteCopyImageNotification) &&
       !image.drawsNothing();
 
-  message_center::RichNotificationData rich_notification_data;
-  if (use_image_notification)
+  if (use_image_notification) {
+#if defined(OS_MACOSX)
+    // On macOS notifications do not support large images so use the icon
+    // instead.
+    icon = gfx::Image::CreateFrom1xBitmap(image);
+#else
     rich_notification_data.image = gfx::Image::CreateFrom1xBitmap(image);
+#endif  // defined(OS_MACOSX)
+  }
+
   rich_notification_data.vector_small_image = &kSendTabToSelfIcon;
 
   message_center::NotificationType type =
@@ -543,7 +574,7 @@
       l10n_util::GetStringFUTF16(
           IDS_SHARING_REMOTE_COPY_NOTIFICATION_DESCRIPTION,
           paste_accelerator.GetShortcutText()),
-      /*icon=*/gfx::Image(),
+      icon,
       /*display_source=*/base::string16(),
       /*origin_url=*/GURL(), message_center::NotifierId(),
       rich_notification_data,
diff --git a/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler_unittest.cc b/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler_unittest.cc
index 329ac4fb..c3ec61b 100644
--- a/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler_unittest.cc
+++ b/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/sharing/mock_sharing_service.h"
 #include "chrome/browser/sharing/proto/remote_copy_message.pb.h"
@@ -222,9 +223,17 @@
                 IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_IMAGE_CONTENT,
                 base::ASCIIToUTF16(kDeviceNameInMessage)),
             notification.title());
+
+#if defined(OS_MACOSX)
+  // On macOS the progress status is shown in the message.
+  base::string16 progress_status = notification.message();
+#else
+  base::string16 progress_status = notification.progress_status();
+#endif  // defined(OS_MACOSX)
+
   EXPECT_EQ(l10n_util::GetStringUTF16(
                 IDS_SHARING_REMOTE_COPY_NOTIFICATION_PREPARING_DOWNLOAD),
-            notification.progress_status());
+            progress_status);
   EXPECT_EQ(-1, notification.progress());
 
   // Calling GetDefaultStoragePartition creates tasks that need to run before
@@ -260,7 +269,13 @@
 
   // Expect an image notification showing the image.
   auto notification = GetImageNotification();
+
+#if defined(OS_MACOSX)
+  // On macOS we show the image as the icon instead.
+  EXPECT_FALSE(notification.icon().IsEmpty());
+#else
   EXPECT_FALSE(notification.image().IsEmpty());
+#endif  // defined(OS_MACOSX)
 
   // Calling GetDefaultStoragePartition creates tasks that need to run before
   // the ScopedFeatureList is destroyed. See crbug.com/1060869
@@ -338,7 +353,13 @@
 
   // Expect an image notification showing the image.
   auto notification = GetImageNotification();
+
+#if defined(OS_MACOSX)
+  // On macOS we show the image as the icon instead.
+  EXPECT_FALSE(notification.icon().IsEmpty());
+#else
   EXPECT_FALSE(notification.image().IsEmpty());
+#endif  // defined(OS_MACOSX)
 
   // Calling GetDefaultStoragePartition creates tasks that need to run before
   // the ScopedFeatureList is destroyed. See crbug.com/1060869
diff --git a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
index 74122d0..bbdf253 100644
--- a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
+++ b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
+#include "chrome/browser/ui/webui/signin/signin_email_confirmation_dialog.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/signin/core/browser/account_reconcilor.h"
 #include "components/signin/public/identity_manager/account_info.h"
@@ -462,7 +463,7 @@
 // profile is created. Checks that Sync to account 2 is enabled in the new
 // profile. Checks that account 2 was removed from the original profile.
 IN_PROC_BROWSER_TEST_F(LiveSignInTest,
-                       MANUAL_SyncSecondAccountCreateNewProfile) {
+                       MANUAL_SyncSecondAccount_CreateNewProfile) {
   // Enable and disable sync for the first account.
   TestAccount test_account_1;
   CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_1", test_account_1));
@@ -531,5 +532,105 @@
   EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
 }
 
+// This test can pass. Marked as manual because it TIMED_OUT on Win7.
+// See crbug.com/1025335.
+// Enables and disables sync to account 1. Enables sync to account 2 and clicks
+// on "This was me" in the email confirmation dialog. Checks that Sync to
+// account 2 is enabled in the current profile.
+IN_PROC_BROWSER_TEST_F(LiveSignInTest,
+                       MANUAL_SyncSecondAccount_InExistingProfile) {
+  // Enable and disable sync for the first account.
+  TestAccount test_account_1;
+  CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_1", test_account_1));
+  TurnOnSync(test_account_1, 0);
+  TurnOffSync();
+
+  // Start enable sync for the second account.
+  TestAccount test_account_2;
+  CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_2", test_account_2));
+  SignInFromSettings(test_account_2, 0);
+
+  // Check there is only one profile.
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  EXPECT_EQ(profile_manager->GetNumberOfProfiles(), 1U);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), 1U);
+
+  // Click "This was me" on the email confirmation dialog, confirm sync and wait
+  // for a primary account to be set.
+  SignInTestObserver observer(identity_manager(), account_reconcilor());
+  EXPECT_TRUE(login_ui_test_utils::CompleteSigninEmailConfirmationDialog(
+      browser(), base::TimeDelta::FromSeconds(3),
+      SigninEmailConfirmationDialog::START_SYNC));
+  EXPECT_TRUE(login_ui_test_utils::ConfirmSyncConfirmationDialog(
+      browser(), base::TimeDelta::FromSeconds(5)));
+  observer.WaitForAccountChanges(1, PrimarySyncAccountWait::kWaitForAdded);
+
+  // Check no profile was created.
+  EXPECT_EQ(profile_manager->GetNumberOfProfiles(), 1U);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), 1U);
+
+  // Check accounts in cookies.
+  const AccountsInCookieJarInfo& accounts_in_cookie_jar =
+      identity_manager()->GetAccountsInCookieJar();
+  EXPECT_TRUE(accounts_in_cookie_jar.accounts_are_fresh);
+  ASSERT_EQ(1u, accounts_in_cookie_jar.signed_in_accounts.size());
+  const gaia::ListedAccount& account =
+      accounts_in_cookie_jar.signed_in_accounts[0];
+  EXPECT_TRUE(gaia::AreEmailsSame(test_account_2.user, account.email));
+
+  // Check the primary account is set and syncing.
+  const CoreAccountInfo& primary_account =
+      identity_manager()->GetPrimaryAccountInfo();
+  EXPECT_FALSE(primary_account.IsEmpty());
+  EXPECT_TRUE(gaia::AreEmailsSame(test_account_2.user, primary_account.email));
+  EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
+      primary_account.account_id));
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+}
+
+// This test can pass. Marked as manual because it TIMED_OUT on Win7.
+// See crbug.com/1025335.
+// Enables and disables sync to account 1. Enables sync to account 2 and clicks
+// on "Cancel" in the email confirmation dialog. Checks that the signin flow is
+// canceled and no accounts are added to Chrome.
+IN_PROC_BROWSER_TEST_F(LiveSignInTest,
+                       MANUAL_SyncSecondAccount_CancelOnEmailConfirmation) {
+  // Enable and disable sync for the first account.
+  TestAccount test_account_1;
+  CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_1", test_account_1));
+  TurnOnSync(test_account_1, 0);
+  TurnOffSync();
+
+  // Start enable sync for the second account.
+  TestAccount test_account_2;
+  CHECK(GetTestAccountsUtil()->GetAccount("TEST_ACCOUNT_2", test_account_2));
+  SignInFromSettings(test_account_2, 0);
+
+  // Check there is only one profile.
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  EXPECT_EQ(profile_manager->GetNumberOfProfiles(), 1U);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), 1U);
+
+  // Click "Cancel" on the email confirmation dialog and wait for an account to
+  // removed from Chrome.
+  SignInTestObserver observer(identity_manager(), account_reconcilor());
+  EXPECT_TRUE(login_ui_test_utils::CompleteSigninEmailConfirmationDialog(
+      browser(), base::TimeDelta::FromSeconds(3),
+      SigninEmailConfirmationDialog::CLOSE));
+  observer.WaitForAccountChanges(0, PrimarySyncAccountWait::kWaitForCleared);
+
+  // Check no profile was created.
+  EXPECT_EQ(profile_manager->GetNumberOfProfiles(), 1U);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), 1U);
+
+  // Check Chrome has no accounts.
+  const AccountsInCookieJarInfo& accounts_in_cookie_jar =
+      identity_manager()->GetAccountsInCookieJar();
+  EXPECT_TRUE(accounts_in_cookie_jar.accounts_are_fresh);
+  EXPECT_TRUE(accounts_in_cookie_jar.signed_in_accounts.empty());
+  EXPECT_TRUE(identity_manager()->GetAccountsWithRefreshTokens().empty());
+  EXPECT_FALSE(identity_manager()->HasPrimaryAccount());
+}
+
 }  // namespace test
 }  // namespace signin
diff --git a/chrome/browser/sync/test/integration/password_manager_sync_test.cc b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
index 59514cf..c53d1da 100644
--- a/chrome/browser/sync/test/integration/password_manager_sync_test.cc
+++ b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
@@ -33,12 +33,19 @@
 
 using testing::ElementsAre;
 using testing::IsEmpty;
+using testing::UnorderedElementsAre;
 
 MATCHER_P2(MatchesLogin, username, password, "") {
   return arg->username_value == base::UTF8ToUTF16(username) &&
          arg->password_value == base::UTF8ToUTF16(password);
 }
 
+MATCHER_P3(MatchesLoginAndRealm, username, password, signon_realm, "") {
+  return arg->username_value == base::UTF8ToUTF16(username) &&
+         arg->password_value == base::UTF8ToUTF16(password) &&
+         arg->signon_realm == signon_realm;
+}
+
 // Note: This helper applies to ChromeOS too, but is currently unused there. So
 // define it out to prevent a compile error due to the unused function.
 #if !defined(OS_CHROMEOS)
@@ -96,29 +103,60 @@
     ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS));
   }
 
+  GURL GetWWWOrigin() {
+    return embedded_test_server()->GetURL("www.example.com", "/");
+  }
+  GURL GetPSLOrigin() {
+    return embedded_test_server()->GetURL("psl.example.com", "/");
+  }
+
+  // Returns a credential for the origin returned by GetWWWOrigin().
   autofill::PasswordForm CreateTestPasswordForm(const std::string& username,
                                                 const std::string& password) {
-    GURL origin = embedded_test_server()->GetURL("/");
     autofill::PasswordForm form;
-    form.signon_realm = origin.spec();
-    form.origin = origin;
+    form.signon_realm = GetWWWOrigin().spec();
+    form.origin = GetWWWOrigin();
     form.username_value = base::UTF8ToUTF16(username);
     form.password_value = base::UTF8ToUTF16(password);
     form.date_created = base::Time::Now();
     return form;
   }
 
-  void AddPasswordToFakeServer(const std::string& username,
-                               const std::string& password) {
-    passwords_helper::InjectKeystoreEncryptedServerPassword(
-        CreateTestPasswordForm(username, password), GetFakeServer());
+  // Returns a credential for the origin returned by GetPSLOrigin().
+  autofill::PasswordForm CreateTestPSLPasswordForm(
+      const std::string& username,
+      const std::string& password) {
+    autofill::PasswordForm form = CreateTestPasswordForm(username, password);
+    form.signon_realm = GetPSLOrigin().spec();
+    form.origin = GetPSLOrigin();
+    return form;
   }
 
-  void AddLocalPassword(const std::string& username,
-                        const std::string& password) {
+  // Adds a credential to the Sync fake server for the origin returned by
+  // GetWWWOrigin().
+  void AddCredentialToFakeServer(const std::string& username,
+                                 const std::string& password) {
+    AddCredentialToFakeServer(CreateTestPasswordForm(username, password));
+  }
+
+  // Adds a credential to the Sync fake server.
+  void AddCredentialToFakeServer(const autofill::PasswordForm& form) {
+    passwords_helper::InjectKeystoreEncryptedServerPassword(form,
+                                                            GetFakeServer());
+  }
+
+  // Adds a credential to the local store for the origin returned by
+  // GetWWWOrigin().
+  void AddLocalCredential(const std::string& username,
+                          const std::string& password) {
+    AddLocalCredential(CreateTestPasswordForm(username, password));
+  }
+
+  // Adds a credential to the local store.
+  void AddLocalCredential(const autofill::PasswordForm& form) {
     scoped_refptr<password_manager::PasswordStore> password_store =
         passwords_helper::GetPasswordStore(0);
-    password_store->AddLogin(CreateTestPasswordForm(username, password));
+    password_store->AddLogin(form);
     // Do a roundtrip to the DB thread, to make sure the new password is stored
     // before doing anything else that might depend on it.
     GetAllLoginsFromProfilePasswordStore();
@@ -151,7 +189,7 @@
     ASSERT_EQ(web_contents,
               GetBrowser(0)->tab_strip_model()->GetActiveWebContents());
     NavigationObserver observer(web_contents);
-    GURL url = embedded_test_server()->GetURL(path);
+    GURL url = embedded_test_server()->GetURL("www.example.com", path);
     ui_test_utils::NavigateToURL(GetBrowser(0), url);
     observer.Wait();
     // After navigation, the password manager retrieves any matching credentials
@@ -243,7 +281,7 @@
 IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest, UpdateInProfileStore) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
-  AddLocalPassword("user", "localpass");
+  AddLocalCredential("user", "localpass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -269,7 +307,7 @@
 IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest, UpdateInAccountStore) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
-  AddPasswordToFakeServer("user", "accountpass");
+  AddCredentialToFakeServer("user", "accountpass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -296,8 +334,8 @@
                        UpdateMatchingCredentialInBothStores) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
-  AddPasswordToFakeServer("user", "pass");
-  AddLocalPassword("user", "pass");
+  AddCredentialToFakeServer("user", "pass");
+  AddLocalCredential("user", "pass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -324,8 +362,8 @@
                        UpdateMismatchingCredentialInBothStores) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
 
-  AddPasswordToFakeServer("user", "accountpass");
-  AddLocalPassword("user", "localpass");
+  AddCredentialToFakeServer("user", "accountpass");
+  AddLocalCredential("user", "localpass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -357,8 +395,8 @@
 
   // Add credentials for the same username, but with different passwords, to the
   // two stores.
-  AddPasswordToFakeServer("user", "accountpass");
-  AddLocalPassword("user", "localpass");
+  AddCredentialToFakeServer("user", "accountpass");
+  AddLocalCredential("user", "localpass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -395,8 +433,8 @@
 
   // Add credentials for the same username, but with different passwords, to the
   // two stores.
-  AddPasswordToFakeServer("user", "accountpass");
-  AddLocalPassword("user", "localpass");
+  AddCredentialToFakeServer("user", "accountpass");
+  AddLocalCredential("user", "localpass");
 
   SetupSyncTransportWithPasswordAccountStorage();
 
@@ -422,6 +460,66 @@
   EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
               ElementsAre(MatchesLogin("user", "accountpass")));
 }
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       AutoUpdatePSLMatchInAccountStoreOnSuccessfulUse) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  // Add the same credential to both stores, but the account one is a PSL match
+  // (i.e. it's stored for psl.example.com instead of www.example.com).
+  AddCredentialToFakeServer(CreateTestPSLPasswordForm("user", "pass"));
+  AddLocalCredential(CreateTestPasswordForm("user", "pass"));
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form (on www.) and submit it with the saved credentials.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "pass");
+
+  // Now the PSL-matched credential should have been automatically saved for
+  // www. as well (in the account store).
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              UnorderedElementsAre(
+                  MatchesLoginAndRealm("user", "pass", GetWWWOrigin()),
+                  MatchesLoginAndRealm("user", "pass", GetPSLOrigin())));
+  // In the profile store, there was already a credential for www. so nothing
+  // should have changed.
+  ASSERT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "pass")));
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       AutoUpdatePSLMatchInProfileStoreOnSuccessfulUse) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  // Add the same credential to both stores, but the local one is a PSL match
+  // (i.e. it's stored for psl.example.com instead of www.example.com).
+  AddCredentialToFakeServer(CreateTestPasswordForm("user", "pass"));
+  AddLocalCredential(CreateTestPSLPasswordForm("user", "pass"));
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form (on www.) and submit it with the saved credentials.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "pass");
+
+  // Now the PSL-matched credential should have been automatically saved for
+  // www. as well (in the profile store).
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              UnorderedElementsAre(
+                  MatchesLoginAndRealm("user", "pass", GetWWWOrigin()),
+                  MatchesLoginAndRealm("user", "pass", GetPSLOrigin())));
+  // In the account store, there was already a credential for www. so nothing
+  // should have changed.
+  ASSERT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "pass")));
+}
 #endif  // !defined(OS_CHROMEOS)
 
 }  // namespace
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index 335edb6..d4185be 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -93,7 +93,7 @@
 // change default theme assets, if you need themes to recreate their generated
 // images (which are cached), or if you changed how missing values are
 // generated.
-const int kThemePackVersion = 74;
+const int kThemePackVersion = 75;
 
 // IDs that are in the DataPack won't clash with the positive integer
 // uint16_t. kHeaderID should always have the maximum value because we want the
@@ -149,6 +149,8 @@
     {PRS::kNtpAttribution, IDR_THEME_NTP_ATTRIBUTION, "theme_ntp_attribution"},
     {PRS::kWindowControlBackground, IDR_THEME_WINDOW_CONTROL_BACKGROUND,
      "theme_window_control_background"},
+
+    // NOTE! If you make any changes here, please update kThemePackVersion.
 };
 
 BrowserThemePack::PersistentID GetPersistentIDByName(const std::string& key) {
@@ -207,6 +209,8 @@
     {"frame_incognito", TP::TINT_FRAME_INCOGNITO},
     {"frame_incognito_inactive", TP::TINT_FRAME_INCOGNITO_INACTIVE},
     {"background_tab", TP::TINT_BACKGROUND_TAB},
+
+    // NOTE! If you make any changes here, please update kThemePackVersion.
 };
 const size_t kTintTableLength = base::size(kTintTable);
 
@@ -241,6 +245,8 @@
     {"ntp_header", TP::COLOR_NTP_HEADER},
     {"ntp_link", TP::COLOR_NTP_LINK},
     {"ntp_text", TP::COLOR_NTP_TEXT},
+
+    // NOTE! If you make any changes here, please update kThemePackVersion.
 };
 constexpr size_t kOverwritableColorTableLength =
     base::size(kOverwritableColorTable);
@@ -258,7 +264,10 @@
     TP::COLOR_TOOLBAR_BUTTON_ICON_PRESSED,
     TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_ACTIVE,
     TP::COLOR_TAB_BACKGROUND_ACTIVE_FRAME_INACTIVE,
-    TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE};
+    TP::COLOR_TAB_FOREGROUND_ACTIVE_FRAME_INACTIVE
+
+    // NOTE! If you make any changes here, please update kThemePackVersion.
+};
 constexpr size_t kNonOverwritableColorTableLength =
     base::size(kNonOverwritableColorTable);
 
@@ -272,6 +281,8 @@
     {"ntp_background_alignment", TP::NTP_BACKGROUND_ALIGNMENT},
     {"ntp_background_repeat", TP::NTP_BACKGROUND_TILING},
     {"ntp_logo_alternate", TP::NTP_LOGO_ALTERNATE},
+
+    // NOTE! If you make any changes here, please update kThemePackVersion.
 };
 const size_t kDisplayPropertiesSize = base::size(kDisplayProperties);
 
@@ -677,6 +688,8 @@
   DCHECK(extension->is_theme());
   DCHECK(!pack->is_valid());
 
+  // NOTE! If you make any changes here, please update kThemePackVersion.
+
   pack->InitEmptyPack();
   pack->set_extension_id(extension->id());
   pack->SetHeaderId(extension);
@@ -794,6 +807,8 @@
   // differently.
   pack->InitSourceImages();
 
+  // NOTE! If you make any changes here, please update kThemePackVersion.
+
   pack->SetColor(TP::COLOR_FRAME_ACTIVE, colors.frame_color);
   pack->SetColor(TP::COLOR_TAB_BACKGROUND_INACTIVE_FRAME_ACTIVE,
                  colors.frame_color);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e522eafb..4568bb5 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -128,12 +128,6 @@
     "page_info/chrome_page_info_delegate.h",
     "page_info/chrome_page_info_ui_delegate.cc",
     "page_info/chrome_page_info_ui_delegate.h",
-    "page_info/page_info.cc",
-    "page_info/page_info.h",
-    "page_info/page_info_delegate.h",
-    "page_info/page_info_ui.cc",
-    "page_info/page_info_ui.h",
-    "page_info/page_info_ui_delegate.h",
     "passwords/account_avatar_fetcher.cc",
     "passwords/account_avatar_fetcher.h",
     "passwords/manage_passwords_state.cc",
@@ -834,6 +828,7 @@
       "//components/javascript_dialogs/android:jni_headers",
       "//components/navigation_interception",
       "//components/optimization_guide/proto:optimization_guide_proto",
+      "//components/page_info",
       "//components/page_info/android:android",
       "//components/subresource_filter/core/browser",
       "//crypto:platform",
@@ -3480,6 +3475,7 @@
       "//chrome/common/qr_code_generator",
       "//components/constrained_window",
       "//components/media_message_center",
+      "//components/page_info",
       "//components/payments/content",
       "//components/payments/core",
       "//components/tab_count_metrics",
diff --git a/chrome/browser/ui/android/page_info/connection_info_popup_android.cc b/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
index bad3143..dd904fd8 100644
--- a/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
+++ b/chrome/browser/ui/android/page_info/connection_info_popup_android.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_delegate.h"
-#include "chrome/browser/ui/page_info/page_info.h"
+#include "components/page_info/page_info.h"
 #include "components/security_state/core/security_state.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_context.h"
diff --git a/chrome/browser/ui/android/page_info/connection_info_popup_android.h b/chrome/browser/ui/android/page_info/connection_info_popup_android.h
index 3cee4ed..6a2c206 100644
--- a/chrome/browser/ui/android/page_info/connection_info_popup_android.h
+++ b/chrome/browser/ui/android/page_info/connection_info_popup_android.h
@@ -11,7 +11,7 @@
 
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 
 namespace content {
 class WebContents;
diff --git a/chrome/browser/ui/android/page_info/page_info_controller_android.cc b/chrome/browser/ui/android/page_info/page_info_controller_android.cc
index 3db7efd..6fabd3bb 100644
--- a/chrome/browser/ui/android/page_info/page_info_controller_android.cc
+++ b/chrome/browser/ui/android/page_info/page_info_controller_android.cc
@@ -13,12 +13,12 @@
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_delegate.h"
-#include "chrome/browser/ui/page_info/page_info.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/common/chrome_features.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/page_info/page_info.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/security_state/core/security_state.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_controller.h"
diff --git a/chrome/browser/ui/android/page_info/page_info_controller_android.h b/chrome/browser/ui/android/page_info/page_info_controller_android.h
index 294aa00c..b57d821d 100644
--- a/chrome/browser/ui/android/page_info/page_info_controller_android.h
+++ b/chrome/browser/ui/android/page_info/page_info_controller_android.h
@@ -12,7 +12,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/macros.h"
 #include "base/optional.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 
 namespace content {
 class WebContents;
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
index 34c5e23..fb920f87 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_icon_loader.cc
@@ -6,7 +6,7 @@
 
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
@@ -61,8 +61,7 @@
   // with the prefix "crostini:".
   if (proxy && (proxy->AppRegistryCache().GetAppType(app_id) !=
                     apps::mojom::AppType::kUnknown ||
-                base::StartsWith(app_id, crostini::kCrostiniAppIdPrefix,
-                                 base::CompareCase::SENSITIVE))) {
+                crostini::IsUnmatchedCrostiniShelfAppId(app_id))) {
     return true;
   }
 
@@ -135,8 +134,7 @@
 
   // When Crostini generates shelf id as the app_id, which couldn't match to an
   // app, the default penguin icon should be loaded.
-  if (base::StartsWith(app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(app_id)) {
     apps::mojom::IconKeyPtr icon_key = apps::mojom::IconKey::New();
     proxy->LoadIconFromIconKey(
         apps::mojom::AppType::kCrostini, std::string(), std::move(icon_key),
diff --git a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
index f6179e5..c1738e2 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_context_menu.cc
@@ -173,9 +173,8 @@
           command_id < ash::USE_LAUNCH_TYPE_COMMAND_END) {
         auto* provider = web_app::WebAppProvider::Get(profile());
         DCHECK(provider);
-        if (command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW) {
-          return provider->registrar().IsInExperimentalTabbedWindowMode(
-              app_id());
+        if (provider->registrar().IsInExperimentalTabbedWindowMode(app_id())) {
+          return command_id == ash::USE_LAUNCH_TYPE_TABBED_WINDOW;
         }
 
         web_app::DisplayMode effective_display_mode =
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
index 9a04fc7..6a84ce4 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_crostini_tracker.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_force_close_watcher.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -96,18 +97,20 @@
   const AccountId& primary_account_id =
       user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
 
-  crostini::CrostiniRegistryService* registry_service =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(
-          chromeos::ProfileHelper::Get()->GetProfileByAccountId(
-              primary_account_id));
+  Profile* primary_account_profile =
+      chromeos::ProfileHelper::Get()->GetProfileByAccountId(primary_account_id);
 
   // Windows without an application id set will get filtered out here.
-  const std::string& crostini_shelf_app_id =
-      registry_service->GetCrostiniShelfAppId(
-          exo::GetShellApplicationId(window), exo::GetShellStartupId(window));
+  const std::string& crostini_shelf_app_id = crostini::GetCrostiniShelfAppId(
+      primary_account_profile, exo::GetShellApplicationId(window),
+      exo::GetShellStartupId(window));
   if (crostini_shelf_app_id.empty())
     return;
 
+  crostini::CrostiniRegistryService* registry_service =
+      crostini::CrostiniRegistryServiceFactory::GetForProfile(
+          primary_account_profile);
+
   // At this point, all remaining windows are Crostini windows. Firstly, we add
   // support for forcibly closing it. We use the registration to retrieve the
   // app's name, but this may be null in the case of apps with no associated
@@ -130,8 +133,7 @@
   // respective apps take at most another few seconds to start.
   // Work is ongoing to make this occur as infrequently as possible.
   // See https://crbug.com/854911.
-  if (base::StartsWith(shelf_app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(shelf_app_id)) {
     ChromeLauncherController::instance()
         ->GetShelfSpinnerController()
         ->CloseCrostiniSpinners();
@@ -249,14 +251,12 @@
   // Currently Crostini can only be used from the primary profile. In the
   // future, this may be replaced by some way of matching the container that
   // runs this app with the user that owns it.
-  const AccountId& primary_account_id =
-      user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
-  crostini::CrostiniRegistryService* registry_service =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(
-          chromeos::ProfileHelper::Get()->GetProfileByAccountId(
-              primary_account_id));
-  std::string shelf_app_id = registry_service->GetCrostiniShelfAppId(
-      exo::GetShellApplicationId(window), exo::GetShellStartupId(window));
+  const Profile* primary_account_profile =
+      chromeos::ProfileHelper::Get()->GetProfileByAccountId(
+          user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId());
+  std::string shelf_app_id = crostini::GetCrostiniShelfAppId(
+      primary_account_profile, exo::GetShellApplicationId(window),
+      exo::GetShellStartupId(window));
   return shelf_app_id;
 }
 
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
index 178011a9..3493297 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_launcher_controller.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
 #include "chrome/browser/chromeos/crostini/crostini_features.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
@@ -300,8 +301,7 @@
     std::string app_id = update.AppId();
     if (proxy_->AppRegistryCache().GetAppType(app_id) ==
             apps::mojom::AppType::kCrostini ||
-        base::StartsWith(app_id, crostini::kCrostiniAppIdPrefix,
-                         base::CompareCase::SENSITIVE)) {
+        crostini::IsUnmatchedCrostiniShelfAppId(app_id)) {
       window->SetProperty(aura::client::kAppType,
                           static_cast<int>(ash::AppType::CROSTINI_APP));
     }
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
index 34b7f254..87fd4eee 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_terminal.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_manager.h"
@@ -86,8 +87,7 @@
       apps::AppServiceProxyFactory::GetForProfile(controller->profile());
   DCHECK(proxy);
 
-  if (base::StartsWith(item->id.app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(item->id.app_id)) {
     // For Crostini app_id with the prefix "crostini:", set app_type as Unknown
     // to skip the ArcAppShelfId valid. App type can't be set as Crostini,
     // because the pin item should not be added for it.
@@ -207,12 +207,13 @@
     case apps::mojom::AppType::kWeb: {
       auto* provider = web_app::WebAppProvider::Get(controller()->profile());
       DCHECK(provider);
-      if (command_id == ash::LAUNCH_TYPE_TABBED_WINDOW) {
-        return provider->registrar().IsInExperimentalTabbedWindowMode(
-            item().id.app_id);
-      }
-      if (command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
-          command_id <= ash::LAUNCH_TYPE_WINDOW) {
+      if ((command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
+           command_id <= ash::LAUNCH_TYPE_WINDOW) ||
+          command_id == ash::LAUNCH_TYPE_TABBED_WINDOW) {
+        if (provider->registrar().IsInExperimentalTabbedWindowMode(
+                item().id.app_id)) {
+          return command_id == ash::LAUNCH_TYPE_TABBED_WINDOW;
+        }
         web_app::DisplayMode effective_display_mode =
             provider->registrar().GetAppEffectiveDisplayMode(item().id.app_id);
         return effective_display_mode != web_app::DisplayMode::kUndefined &&
@@ -305,8 +306,7 @@
   // When Crostini generates shelf id with the prefix "crostini:", AppService
   // can't generate the menu items, because the app_id doesn't match, so add the
   // menu items at UI side, based on the app running status.
-  if (base::StartsWith(item().id.app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(item().id.app_id)) {
     BuildCrostiniAppMenu(menu_model.get());
   }
 
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
index 932f9e3..850d10b7 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc
@@ -2333,46 +2333,48 @@
   chromeos::SpeechMonitor speech_monitor;
 
   // Enable ChromeVox.
+  {
     ASSERT_FALSE(
         chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
     chromeos::AccessibilityManager::Get()->EnableSpokenFeedback(true);
+    EXPECT_TRUE(speech_monitor.SkipChromeVoxEnabledMessage());
+
+    // Disable earcons (https://crbug.com/396507).
+    const std::string script("ChromeVox.earcons.playEarcon = function() {};");
+    extensions::ExtensionHost* host =
+        extensions::ProcessManager::Get(browser()->profile())
+            ->GetBackgroundHostForExtension(
+                extension_misc::kChromeVoxExtensionId);
+    ASSERT_TRUE(content::ExecuteScript(host->host_contents(), script));
+  }
 
   ash::RootWindowController* controller =
       ash::Shell::GetRootWindowControllerWithDisplayId(
           display::Screen::GetScreen()->GetPrimaryDisplay().id());
+
+  // Gesture tap at the home button.
   views::View* home_button = ash::ShelfTestApi().GetHomeButton();
   ui::test::EventGenerator event_generator(controller->GetRootWindow());
-  auto* generator_ptr = &event_generator;
+  event_generator.GestureTapAt(home_button->GetBoundsInScreen().CenterPoint());
 
-  // Wait for ChromeVox to start reading anything.
-  speech_monitor.ExpectSpeechPattern("*");
-  speech_monitor.Call([generator_ptr, home_button]() {
-    // Gesture tap at the home button.
-    generator_ptr->GestureTapAt(home_button->GetBoundsInScreen().CenterPoint());
-  });
-  speech_monitor.ExpectSpeech("Launcher");
-  speech_monitor.ExpectSpeech("Button");
-  speech_monitor.ExpectSpeech("Shelf");
-  speech_monitor.ExpectSpeech("Tool bar");
-  speech_monitor.ExpectSpeech(", window");
+  ASSERT_EQ("Launcher", speech_monitor.GetNextUtterance());
+  ASSERT_EQ("Button", speech_monitor.GetNextUtterance());
+  ASSERT_EQ("Shelf", speech_monitor.GetNextUtterance());
+  ASSERT_EQ("Tool bar", speech_monitor.GetNextUtterance());
+  ASSERT_EQ(", window", speech_monitor.GetNextUtterance());
 
-  speech_monitor.Call([controller]() {
-    // Hotseat is expected to be extended if spoken feedback is enabled.
-    ASSERT_EQ(ash::HotseatState::kExtended,
-              controller->shelf()->shelf_layout_manager()->hotseat_state());
-  });
+  // Hotseat is expected to be extended if spoken feedback is enabled.
+  ASSERT_EQ(ash::HotseatState::kExtended,
+            controller->shelf()->shelf_layout_manager()->hotseat_state());
 
-  speech_monitor.Call([generator_ptr]() {
-    // Press the search + right. Expects that the browser icon receives the
-    // accessibility focus and the hotseat remains in kExtended state.
-    generator_ptr->PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN);
-  });
+  // Press the search + right. Expects that the browser icon receives the
+  // accessibility focus and the hotseat remains in kExtended state.
+  event_generator.PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN);
   const int browser_index =
       ash::ShelfModel::Get()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
-  speech_monitor.ExpectSpeech(
-      base::UTF16ToASCII(ash::ShelfModel::Get()->items()[browser_index].title));
-  speech_monitor.Replay();
-
+  EXPECT_EQ(
+      base::UTF16ToASCII(ash::ShelfModel::Get()->items()[browser_index].title),
+      speech_monitor.GetNextUtterance());
   EXPECT_EQ(ash::HotseatState::kExtended,
             controller->shelf()->shelf_layout_manager()->hotseat_state());
 
diff --git a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
index 01653ce5..1392d08c 100644
--- a/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/launcher/crostini_app_window_shelf_controller.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_force_close_watcher.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -208,16 +209,20 @@
   const AccountId& primary_account_id =
       user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
 
-  crostini::CrostiniRegistryService* registry_service =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(
-          chromeos::ProfileHelper::Get()->GetProfileByAccountId(
-              primary_account_id));
-  const std::string& shelf_app_id = registry_service->GetCrostiniShelfAppId(
-      exo::GetShellApplicationId(window), exo::GetShellStartupId(window));
+  Profile* primary_account_profile =
+      chromeos::ProfileHelper::Get()->GetProfileByAccountId(primary_account_id);
+
+  const std::string& shelf_app_id = crostini::GetCrostiniShelfAppId(
+      primary_account_profile, exo::GetShellApplicationId(window),
+      exo::GetShellStartupId(window));
   // Windows without an application id set will get filtered out here.
   if (shelf_app_id.empty())
     return;
 
+  crostini::CrostiniRegistryService* registry_service =
+      crostini::CrostiniRegistryServiceFactory::GetForProfile(
+          primary_account_profile);
+
   // At this point, all remaining windows are Crostini windows. Firstly, we add
   // support for forcibly closing it. We use the registration to retrieve the
   // app's name, but this may be null in the case of apps with no associated
@@ -240,8 +245,7 @@
   // respective apps take at most another few seconds to start.
   // Work is ongoing to make this occur as infrequently as possible.
   // See https://crbug.com/854911.
-  if (base::StartsWith(shelf_app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(shelf_app_id)) {
     owner()->GetShelfSpinnerController()->CloseCrostiniSpinners();
   }
 
diff --git a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
index 04dd788..846ab98 100644
--- a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
@@ -14,8 +14,7 @@
 #include "chrome/browser/chromeos/arc/arc_util.h"
 #include "chrome/browser/chromeos/arc/session/arc_session_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_features.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -228,8 +227,7 @@
 
 bool LauncherControllerHelper::IsValidIDFromAppService(
     const std::string& app_id) const {
-  if (base::StartsWith(app_id, crostini::kCrostiniAppIdPrefix,
-                       base::CompareCase::SENSITIVE)) {
+  if (crostini::IsUnmatchedCrostiniShelfAppId(app_id)) {
     return true;
   }
 
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
index 1fee29a..c6ed3bb 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu.cc
@@ -13,8 +13,7 @@
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
@@ -71,12 +70,10 @@
 
     // AppServiceShelfContextMenu supports context menus for apps registered in
     // AppService, Arc shortcuts and Crostini apps with the prefix "crostini:".
-    if (proxy &&
-        (proxy->AppRegistryCache().GetAppType(item->id.app_id) !=
-             apps::mojom::AppType::kUnknown ||
-         base::StartsWith(item->id.app_id, crostini::kCrostiniAppIdPrefix,
-                          base::CompareCase::SENSITIVE) ||
-         arc::IsArcItem(controller->profile(), item->id.app_id))) {
+    if (proxy && (proxy->AppRegistryCache().GetAppType(item->id.app_id) !=
+                      apps::mojom::AppType::kUnknown ||
+                  crostini::IsUnmatchedCrostiniShelfAppId(item->id.app_id) ||
+                  arc::IsArcItem(controller->profile(), item->id.app_id))) {
       return std::make_unique<AppServiceShelfContextMenu>(controller, item,
                                                           display_id);
     }
@@ -91,11 +88,7 @@
     return std::make_unique<ArcShelfContextMenu>(controller, item, display_id);
 
   // Use CrostiniShelfContextMenu for crostini apps and Terminal System App.
-  crostini::CrostiniRegistryService* crostini_registry_service =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(
-          controller->profile());
-  if ((crostini_registry_service &&
-       crostini_registry_service->IsCrostiniShelfAppId(item->id.app_id)) ||
+  if (crostini::IsCrostiniShelfAppId(controller->profile(), item->id.app_id) ||
       item->id.app_id == crostini::kCrostiniTerminalSystemAppId) {
     return std::make_unique<CrostiniShelfContextMenu>(controller, item,
                                                       display_id);
diff --git a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
index b696967..81ea1a1 100644
--- a/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_context_menu_unittest.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/chromeos/crostini/crostini_manager.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/crostini/crostini_test_helper.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
@@ -634,9 +635,8 @@
 
   const std::string fake_window_app_id = "foo";
   const std::string fake_window_startup_id = "bar";
-  const std::string app_id =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(profile())
-          ->GetCrostiniShelfAppId(&fake_window_app_id, &fake_window_startup_id);
+  const std::string app_id = crostini::GetCrostiniShelfAppId(
+      profile(), &fake_window_app_id, &fake_window_startup_id);
   controller()->PinAppWithID(app_id);
   const ash::ShelfItem* item = controller()->GetItem(ash::ShelfID(app_id));
   ASSERT_TRUE(item);
diff --git a/chrome/browser/ui/ash/launcher/shelf_spinner_controller.cc b/chrome/browser/ui/ash/launcher/shelf_spinner_controller.cc
index 290e1556..c5fab7ea 100644
--- a/chrome/browser/ui/ash/launcher/shelf_spinner_controller.cc
+++ b/chrome/browser/ui/ash/launcher/shelf_spinner_controller.cc
@@ -9,9 +9,9 @@
 #include "ash/public/cpp/shelf_model.h"
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
-#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
+#include "chrome/browser/chromeos/crostini/crostini_shelf_utils.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
 #include "chrome/browser/ui/ash/launcher/shelf_spinner_item_controller.h"
 #include "components/user_manager/user_manager.h"
@@ -232,12 +232,11 @@
 
 void ShelfSpinnerController::CloseCrostiniSpinners() {
   std::vector<std::string> app_ids_to_close;
-  crostini::CrostiniRegistryService* registry_service =
-      crostini::CrostiniRegistryServiceFactory::GetForProfile(
-          chromeos::ProfileHelper::Get()->GetProfileByAccountId(
-              current_account_id_));
+  const Profile* profile =
+      chromeos::ProfileHelper::Get()->GetProfileByAccountId(
+          current_account_id_);
   for (const auto& app_id_controller_pair : app_controller_map_) {
-    if (registry_service->IsCrostiniShelfAppId(app_id_controller_pair.first))
+    if (crostini::IsCrostiniShelfAppId(profile, app_id_controller_pair.first))
       app_ids_to_close.push_back(app_id_controller_pair.first);
   }
   for (const auto& app_id : app_ids_to_close)
diff --git a/chrome/browser/ui/content_settings/content_setting_image_model.cc b/chrome/browser/ui/content_settings/content_setting_image_model.cc
index de103df3..acd86aa 100644
--- a/chrome/browser/ui/content_settings/content_setting_image_model.cc
+++ b/chrome/browser/ui/content_settings/content_setting_image_model.cc
@@ -224,9 +224,10 @@
 const ContentSettingsImageDetails kImageDetails[] = {
     {ContentSettingsType::COOKIES, vector_icons::kCookieIcon,
      IDS_BLOCKED_COOKIES_MESSAGE, 0, IDS_ACCESSED_COOKIES_MESSAGE},
-    {ContentSettingsType::IMAGES, kPhotoIcon, IDS_BLOCKED_IMAGES_MESSAGE, 0, 0},
-    {ContentSettingsType::JAVASCRIPT, kCodeIcon, IDS_BLOCKED_JAVASCRIPT_MESSAGE,
-     0, 0},
+    {ContentSettingsType::IMAGES, vector_icons::kPhotoIcon,
+     IDS_BLOCKED_IMAGES_MESSAGE, 0, 0},
+    {ContentSettingsType::JAVASCRIPT, vector_icons::kCodeIcon,
+     IDS_BLOCKED_JAVASCRIPT_MESSAGE, 0, 0},
     {ContentSettingsType::PLUGINS, vector_icons::kExtensionIcon,
      IDS_BLOCKED_PLUGINS_MESSAGE, IDS_BLOCKED_PLUGIN_EXPLANATORY_TEXT, 0},
     {ContentSettingsType::MIXEDSCRIPT, kMixedContentIcon,
@@ -234,8 +235,8 @@
     {ContentSettingsType::PPAPI_BROKER, vector_icons::kExtensionIcon,
      IDS_BLOCKED_PPAPI_BROKER_MESSAGE, 0, IDS_ALLOWED_PPAPI_BROKER_MESSAGE},
     {ContentSettingsType::SOUND, kTabAudioIcon, IDS_BLOCKED_SOUND_TITLE, 0, 0},
-    {ContentSettingsType::ADS, kAdsIcon, IDS_BLOCKED_ADS_PROMPT_TOOLTIP,
-     IDS_BLOCKED_ADS_PROMPT_TITLE, 0},
+    {ContentSettingsType::ADS, vector_icons::kAdsIcon,
+     IDS_BLOCKED_ADS_PROMPT_TOOLTIP, IDS_BLOCKED_ADS_PROMPT_TITLE, 0},
 };
 
 const ContentSettingsImageDetails* GetImageDetails(ContentSettingsType type) {
@@ -456,7 +457,7 @@
   if (type == ContentSettingsType::PPAPI_BROKER)
     badge_id = &kWarningBadgeIcon;
   else if (content_settings->IsContentBlocked(type))
-    badge_id = &kBlockedBadgeIcon;
+    badge_id = &vector_icons::kBlockedBadgeIcon;
 
   const gfx::VectorIcon* icon = &image_details->icon;
   // Touch mode uses a different tab audio icon.
@@ -494,7 +495,8 @@
   usages_state.GetDetailedInfo(nullptr, &state_flags);
   bool allowed =
       !!(state_flags & ContentSettingsUsagesState::TABSTATE_HAS_ANY_ALLOWED);
-  set_icon(kMyLocationIcon, allowed ? gfx::kNoneIcon : kBlockedBadgeIcon);
+  set_icon(kMyLocationIcon,
+           allowed ? gfx::kNoneIcon : vector_icons::kBlockedBadgeIcon);
   set_tooltip(l10n_util::GetStringUTF16(allowed
                                             ? IDS_GEOLOCATION_ALLOWED_TOOLTIP
                                             : IDS_GEOLOCATION_BLOCKED_TOOLTIP));
@@ -546,7 +548,7 @@
   bool allowed =
       !!(state_flags & ContentSettingsUsagesState::TABSTATE_HAS_ANY_ALLOWED);
   set_icon(vector_icons::kMidiIcon,
-           allowed ? gfx::kNoneIcon : kBlockedBadgeIcon);
+           allowed ? gfx::kNoneIcon : vector_icons::kBlockedBadgeIcon);
   set_tooltip(l10n_util::GetStringUTF16(allowed
                                             ? IDS_MIDI_SYSEX_ALLOWED_TOOLTIP
                                             : IDS_MIDI_SYSEX_BLOCKED_TOOLTIP));
@@ -571,12 +573,13 @@
 
   switch (download_request_limiter->GetDownloadUiStatus(web_contents)) {
     case DownloadRequestLimiter::DOWNLOAD_UI_ALLOWED:
-      set_icon(kFileDownloadIcon, gfx::kNoneIcon);
+      set_icon(vector_icons::kFileDownloadIcon, gfx::kNoneIcon);
       set_explanatory_string_id(0);
       set_tooltip(l10n_util::GetStringUTF16(IDS_ALLOWED_DOWNLOAD_TITLE));
       return true;
     case DownloadRequestLimiter::DOWNLOAD_UI_BLOCKED:
-      set_icon(kFileDownloadIcon, kBlockedBadgeIcon);
+      set_icon(vector_icons::kFileDownloadIcon,
+               vector_icons::kBlockedBadgeIcon);
       set_explanatory_string_id(IDS_BLOCKED_DOWNLOADS_EXPLANATION);
       set_tooltip(l10n_util::GetStringUTF16(IDS_BLOCKED_DOWNLOAD_TITLE));
       return true;
@@ -607,7 +610,7 @@
     return false;
 
   set_icon(vector_icons::kContentPasteIcon,
-           allowed ? gfx::kNoneIcon : kBlockedBadgeIcon);
+           allowed ? gfx::kNoneIcon : vector_icons::kBlockedBadgeIcon);
   set_tooltip(l10n_util::GetStringUTF16(
       allowed ? IDS_ALLOWED_CLIPBOARD_MESSAGE : IDS_BLOCKED_CLIPBOARD_MESSAGE));
   return true;
@@ -645,11 +648,11 @@
 
     if (IsCamAccessed() && IsMicAccessed()) {
       if (IsCameraBlockedOnSiteLevel() || IsMicBlockedOnSiteLevel()) {
-        set_icon(vector_icons::kVideocamIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kVideocamIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED));
       } else if (DidCameraAccessFailBecauseOfSystemLevelBlock() ||
                  DidMicAccessFailBecauseOfSystemLevelBlock()) {
-        set_icon(vector_icons::kVideocamIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kVideocamIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED));
         if (content_settings->camera_was_just_granted_on_site_level() ||
             content_settings->mic_was_just_granted_on_site_level()) {
@@ -669,10 +672,10 @@
 
     if (IsCamAccessed()) {
       if (IsCameraBlockedOnSiteLevel()) {
-        set_icon(vector_icons::kVideocamIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kVideocamIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED));
       } else if (DidCameraAccessFailBecauseOfSystemLevelBlock()) {
-        set_icon(vector_icons::kVideocamIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kVideocamIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED));
         if (content_settings->camera_was_just_granted_on_site_level()) {
           set_should_auto_open_bubble(true);
@@ -688,10 +691,10 @@
 
     if (IsMicAccessed()) {
       if (IsMicBlockedOnSiteLevel()) {
-        set_icon(vector_icons::kMicIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kMicIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED));
       } else if (DidMicAccessFailBecauseOfSystemLevelBlock()) {
-        set_icon(vector_icons::kMicIcon, kBlockedBadgeIcon);
+        set_icon(vector_icons::kMicIcon, vector_icons::kBlockedBadgeIcon);
         set_tooltip(l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED));
         if (content_settings->mic_was_just_granted_on_site_level()) {
           set_should_auto_open_bubble(true);
@@ -711,7 +714,7 @@
 
   int id = IDS_CAMERA_BLOCKED;
   if (IsMicBlockedOnSiteLevel() || IsCameraBlockedOnSiteLevel()) {
-    set_icon(vector_icons::kVideocamIcon, kBlockedBadgeIcon);
+    set_icon(vector_icons::kVideocamIcon, vector_icons::kBlockedBadgeIcon);
     if (IsMicAccessed())
       id = IsCamAccessed() ? IDS_MICROPHONE_CAMERA_BLOCKED
                            : IDS_MICROPHONE_BLOCKED;
@@ -790,7 +793,7 @@
   if (!FramebustBlockTabHelper::FromWebContents(web_contents)->HasBlockedUrls())
     return false;
 
-  set_icon(kBlockedRedirectIcon, kBlockedBadgeIcon);
+  set_icon(kBlockedRedirectIcon, vector_icons::kBlockedBadgeIcon);
   set_explanatory_string_id(IDS_REDIRECT_BLOCKED_TITLE);
   set_tooltip(l10n_util::GetStringUTF16(IDS_REDIRECT_BLOCKED_TOOLTIP));
   return true;
@@ -832,7 +835,8 @@
     return false;
   }
 
-  set_icon(kSensorsIcon, !blocked ? gfx::kNoneIcon : kBlockedBadgeIcon);
+  set_icon(vector_icons::kSensorsIcon,
+           !blocked ? gfx::kNoneIcon : vector_icons::kBlockedBadgeIcon);
   if (base::FeatureList::IsEnabled(features::kGenericSensorExtraClasses)) {
     set_tooltip(l10n_util::GetStringUTF16(
         !blocked ? IDS_SENSORS_ALLOWED_TOOLTIP : IDS_SENSORS_BLOCKED_TOOLTIP));
@@ -856,7 +860,7 @@
       TabSpecificContentSettings::FromWebContents(web_contents);
   if (!content_settings || !content_settings->IsContentBlocked(content_type()))
     return false;
-  set_icon(kWebIcon, kBlockedBadgeIcon);
+  set_icon(kWebIcon, vector_icons::kBlockedBadgeIcon);
   set_explanatory_string_id(IDS_BLOCKED_POPUPS_EXPLANATORY_TEXT);
   set_tooltip(l10n_util::GetStringUTF16(IDS_BLOCKED_POPUPS_TOOLTIP));
   return true;
diff --git a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
index b55023c..eddbd217 100644
--- a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
+++ b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
@@ -9,7 +9,6 @@
 #include "base/mac/mac_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/content_settings/tab_specific_content_settings.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
@@ -22,6 +21,7 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/pref_service.h"
+#include "components/vector_icons/vector_icons.h"
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -103,10 +103,10 @@
         l10n_util::GetStringUTF16(IDS_CAMERA_ACCESSED), 0, &gfx::kNoneIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kDenied);
     content_setting_image_model->Update(web_contents());
-    ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
-                          true /*has_icon*/,
-                          l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED),
-                          IDS_CAMERA_TURNED_OFF, &kBlockedBadgeIcon);
+    ExpectImageModelState(
+        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED), IDS_CAMERA_TURNED_OFF,
+        &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
     content_setting_image_model->Update(web_contents());
     EXPECT_FALSE(content_setting_image_model->is_visible());
@@ -127,7 +127,7 @@
     ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
                           true /*has_icon*/,
                           l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED),
-                          IDS_MIC_TURNED_OFF, &kBlockedBadgeIcon);
+                          IDS_MIC_TURNED_OFF, &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
     content_setting_image_model->Update(web_contents());
     EXPECT_FALSE(content_setting_image_model->is_visible());
@@ -153,7 +153,7 @@
     ExpectImageModelState(
         *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
         l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED),
-        IDS_CAMERA_TURNED_OFF, &kBlockedBadgeIcon);
+        IDS_CAMERA_TURNED_OFF, &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
     content_setting_image_model->Update(web_contents());
@@ -176,9 +176,10 @@
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
-      ExpectImageModelState(
-          *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
-          l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED), 0, &kBlockedBadgeIcon);
+      ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
+                            true /*has_icon*/,
+                            l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED), 0,
+                            &vector_icons::kBlockedBadgeIcon);
     }
 
     // Microphone blocked per site.
@@ -193,7 +194,7 @@
       ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
                             true /*has_icon*/,
                             l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED),
-                            0, &kBlockedBadgeIcon);
+                            0, &vector_icons::kBlockedBadgeIcon);
     }
 
     // Microphone & camera blocked per site
@@ -210,7 +211,7 @@
       ExpectImageModelState(
           *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
           l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED), 0,
-          &kBlockedBadgeIcon);
+          &vector_icons::kBlockedBadgeIcon);
     }
   }
 }
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
index dad2560..9f38fd7 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
@@ -7,7 +7,7 @@
 
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/local_shared_objects_container.h"
-#include "chrome/browser/ui/page_info/page_info_delegate.h"
+#include "components/page_info/page_info_delegate.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "url/gurl.h"
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
index 05317cc..674ef665 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_UI_PAGE_INFO_CHROME_PAGE_INFO_UI_DELEGATE_H_
 
 #include "build/build_config.h"
-#include "chrome/browser/ui/page_info/page_info_ui_delegate.h"
+#include "components/page_info/page_info_ui_delegate.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index bce9a87..034d799 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/page_info/page_info.h"
+#include "components/page_info/page_info.h"
 
 #include <memory>
 #include <set>
@@ -23,7 +23,6 @@
 #include "chrome/browser/ssl/tls_deprecation_test_utils.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_delegate.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/common/chrome_features.h"
@@ -33,6 +32,7 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/infobars/core/infobar.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/security_state/core/features.h"
diff --git a/chrome/browser/ui/page_info/permission_menu_model.h b/chrome/browser/ui/page_info/permission_menu_model.h
index 1a406a8..6fb08ae1 100644
--- a/chrome/browser/ui/page_info/permission_menu_model.h
+++ b/chrome/browser/ui/page_info/permission_menu_model.h
@@ -6,9 +6,9 @@
 #define CHROME_BROWSER_UI_PAGE_INFO_PERMISSION_MENU_MODEL_H_
 
 #include "base/macros.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/page_info/page_info_ui.h"
 #include "ui/base/models/simple_menu_model.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
index a63d959..1e296d5 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_icon_view.cc
@@ -15,6 +15,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -135,7 +136,7 @@
   ManageMigrationUiController* controller = GetController();
   if (controller && controller->GetFlowStep() ==
                         LocalCardMigrationFlowStep::MIGRATION_FAILED) {
-    return kBlockedBadgeIcon;
+    return vector_icons::kBlockedBadgeIcon;
   }
   return gfx::kNoneIcon;
 }
diff --git a/chrome/browser/ui/views/autofill/payments/save_payment_icon_view.cc b/chrome/browser/ui/views/autofill/payments/save_payment_icon_view.cc
index 92ad7cd..b9ed9cc 100644
--- a/chrome/browser/ui/views/autofill/payments/save_payment_icon_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_payment_icon_view.cc
@@ -14,6 +14,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 
@@ -81,7 +82,7 @@
 const gfx::VectorIcon& SavePaymentIconView::GetVectorIconBadge() const {
   SavePaymentIconController* controller = GetController();
   if (controller && controller->ShouldShowSaveFailureBadge())
-    return kBlockedBadgeIcon;
+    return vector_icons::kBlockedBadgeIcon;
 
   return gfx::kNoneIcon;
 }
diff --git a/chrome/browser/ui/views/content_setting_bubble_contents.cc b/chrome/browser/ui/views/content_setting_bubble_contents.cc
index c817c73..f807bbf 100644
--- a/chrome/browser/ui/views/content_setting_bubble_contents.cc
+++ b/chrome/browser/ui/views/content_setting_bubble_contents.cc
@@ -241,7 +241,8 @@
         *item_icon, CONTEXT_BODY_TEXT_SMALL, views::style::STYLE_PRIMARY);
     item_icon->SetImage(CreateVectorIconWithBadge(
         *item.image, GetLayoutConstant(LOCATION_BAR_ICON_SIZE), icon_color,
-        item.has_blocked_badge ? kBlockedBadgeIcon : gfx::kNoneIcon));
+        item.has_blocked_badge ? vector_icons::kBlockedBadgeIcon
+                               : gfx::kNoneIcon));
   }
 
   std::unique_ptr<views::View> item_contents;
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
index ea10050..18e89b56 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_access_icon_view.cc
@@ -111,7 +111,7 @@
 }
 
 const gfx::VectorIcon& NativeFileSystemAccessIconView::GetVectorIcon() const {
-  return has_write_access_ ? kSaveOriginalFileIcon
+  return has_write_access_ ? vector_icons::kSaveOriginalFileIcon
                            : vector_icons::kInsertDriveFileOutlineIcon;
 }
 
diff --git a/chrome/browser/ui/views/page_info/chosen_object_view.cc b/chrome/browser/ui/views/page_info/chosen_object_view.cc
index 7c5d43c..07f5478c 100644
--- a/chrome/browser/ui/views/page_info/chosen_object_view.cc
+++ b/chrome/browser/ui/views/page_info/chosen_object_view.cc
@@ -7,11 +7,11 @@
 #include <memory>
 #include <utility>
 
-#include "chrome/browser/ui/page_info/page_info_delegate.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/page_info/chosen_object_view_observer.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
+#include "components/page_info/page_info_delegate.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
diff --git a/chrome/browser/ui/views/page_info/chosen_object_view.h b/chrome/browser/ui/views/page_info/chosen_object_view.h
index 7e50cf2..92bbe43 100644
--- a/chrome/browser/ui/views/page_info/chosen_object_view.h
+++ b/chrome/browser/ui/views/page_info/chosen_object_view.h
@@ -7,7 +7,7 @@
 
 #include "base/macros.h"
 #include "base/strings/string16.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/view.h"
 
diff --git a/chrome/browser/ui/views/page_info/chosen_object_view_observer.h b/chrome/browser/ui/views/page_info/chosen_object_view_observer.h
index 636415c4..b3db127 100644
--- a/chrome/browser/ui/views/page_info/chosen_object_view_observer.h
+++ b/chrome/browser/ui/views/page_info/chosen_object_view_observer.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PAGE_INFO_CHOSEN_OBJECT_VIEW_OBSERVER_H_
 #define CHROME_BROWSER_UI_VIEWS_PAGE_INFO_CHOSEN_OBJECT_VIEW_OBSERVER_H_
 
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 
 class ChosenObjectViewObserver {
  public:
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index 0e095ba2..68f826e 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -27,7 +27,6 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_delegate.h"
-#include "chrome/browser/ui/page_info/page_info.h"
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/view_ids.h"
@@ -49,6 +48,7 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/dom_distiller/core/url_utils.h"
+#include "components/page_info/page_info.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/strings/grit/components_chromium_strings.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.h b/chrome/browser/ui/views/page_info/page_info_bubble_view.h
index 466f205..d1bfbf3 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.h
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.h
@@ -14,7 +14,6 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/reputation/safety_tip_ui.h"
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/ui/views/bubble_anchor_util_views.h"
 #include "chrome/browser/ui/views/hover_button.h"
 #include "chrome/browser/ui/views/page_info/chosen_object_view_observer.h"
@@ -22,6 +21,7 @@
 #include "chrome/browser/ui/views/page_info/page_info_hover_button.h"
 #include "chrome/browser/ui/views/page_info/permission_selector_row.h"
 #include "chrome/browser/ui/views/page_info/permission_selector_row_observer.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/security_state/core/security_state.h"
 #include "ui/gfx/native_widget_types.h"
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_base.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view_base.cc
index ef61d91c..f0d7e3d 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view_base.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_base.cc
@@ -6,7 +6,7 @@
 
 #include "base/strings/string16.h"
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/buildflags.h"
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_base.h b/chrome/browser/ui/views/page_info/page_info_bubble_view_base.h
index 6529dd4..49e1e50 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view_base.h
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_base.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PAGE_INFO_PAGE_INFO_BUBBLE_VIEW_BASE_H_
 #define CHROME_BROWSER_UI_VIEWS_PAGE_INFO_PAGE_INFO_BUBBLE_VIEW_BASE_H_
 
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc
index f54d281..c463423 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view_browsertest.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/page_info/page_info.h"
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -28,6 +27,7 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/content_settings/core/browser/content_settings_registry.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/page_info/page_info.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/safe_browsing/content/password_protection/metrics_util.h"
 #include "components/safe_browsing/core/features.h"
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row.cc b/chrome/browser/ui/views/page_info/permission_selector_row.cc
index b36361b1..89a12de7 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row.cc
+++ b/chrome/browser/ui/views/page_info/permission_selector_row.cc
@@ -9,12 +9,12 @@
 #include "base/macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/ui/page_info/permission_menu_model.h"
 #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row.h b/chrome/browser/ui/views/page_info/permission_selector_row.h
index 14d22086..bd6850c1 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row.h
+++ b/chrome/browser/ui/views/page_info/permission_selector_row.h
@@ -10,11 +10,11 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/ui/page_info/permission_menu_model.h"
 #include "chrome/browser/ui/views/page_info/permission_selector_row_observer.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/page_info/page_info_ui.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row_observer.h b/chrome/browser/ui/views/page_info/permission_selector_row_observer.h
index 37685bba..5b579b3 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row_observer.h
+++ b/chrome/browser/ui/views/page_info/permission_selector_row_observer.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_PAGE_INFO_PERMISSION_SELECTOR_ROW_OBSERVER_H_
 #define CHROME_BROWSER_UI_VIEWS_PAGE_INFO_PERMISSION_SELECTOR_ROW_OBSERVER_H_
 
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 
 class PermissionSelectorRowObserver {
  public:
diff --git a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.cc b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.cc
index 862700d8..0582182 100644
--- a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.cc
+++ b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.cc
@@ -19,7 +19,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/l10n/time_format.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/text/bytes_formatting.h"
 #include "ui/chromeos/devicetype_utils.h"
@@ -39,16 +38,6 @@
 constexpr int kWindowWidth = 768;
 constexpr int kWindowHeight = 636;
 
-base::Optional<double> GetFractionComplete(double units_processed,
-                                           double total_units) {
-  if (total_units <= 0)
-    return base::nullopt;
-  double fraction_complete = units_processed / total_units;
-  if (fraction_complete < 0.0 || fraction_complete > 1.0)
-    return base::nullopt;
-  return base::make_optional(fraction_complete);
-}
-
 }  // namespace
 
 void plugin_vm::ShowPluginVmInstallerView(Profile* profile) {
@@ -120,11 +109,9 @@
   upper_container_view->AddChildView(big_message_label_);
 
   views::View* message_container_view = new views::View();
-  views::BoxLayout* message_container_layout =
-      message_container_view->SetLayoutManager(
-          std::make_unique<views::BoxLayout>(
-              views::BoxLayout::Orientation::kVertical,
-              gfx::Insets(kMessageHeight - kMessageFontSize, 0, 0, 0)));
+  message_container_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical,
+      gfx::Insets(kMessageHeight - kMessageFontSize, 0, 0, 0)));
   upper_container_view->AddChildView(message_container_view);
 
   message_label_ = new views::Label(GetMessage(), {kMessageFont});
@@ -132,13 +119,6 @@
   message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   message_container_view->AddChildView(message_label_);
 
-  time_left_message_label_ = new views::Label(base::string16(), {kMessageFont});
-  time_left_message_label_->SetEnabledColor(gfx::kGoogleGrey700);
-  time_left_message_label_->SetMultiLine(false);
-  time_left_message_label_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
-  message_container_view->AddChildView(time_left_message_label_);
-  message_container_layout->SetFlexForView(time_left_message_label_, 1);
-
   progress_bar_ = new views::ProgressBar(kProgressBarHeight);
   progress_bar_->SetProperty(
       views::kMarginsKey,
@@ -184,6 +164,12 @@
     plugin_vm::PluginVmManager::GetForProfile(profile_)->LaunchPluginVm();
     return true;
   }
+  if (state_ == State::LOW_DISK_SPACE) {
+    state_ = State::DOWNLOADING_DLC;
+    OnStateUpdated();
+    plugin_vm_installer_->Continue();
+    return false;
+  }
   DCHECK_EQ(state_, State::ERROR);
   // Retry button has been clicked to retry setting of PluginVm environment
   // after error occurred.
@@ -194,6 +180,14 @@
 bool PluginVmInstallerView::Cancel() {
   switch (state_) {
     case State::STARTING:
+    case State::CHECKING_DISK_SPACE:
+      plugin_vm::RecordPluginVmSetupResultHistogram(
+          plugin_vm::PluginVmSetupResult::kUserCancelledCheckingDiskSpace);
+      break;
+    case State::LOW_DISK_SPACE:
+      plugin_vm::RecordPluginVmSetupResultHistogram(
+          plugin_vm::PluginVmSetupResult::kUserCancelledLowDiskSpace);
+      break;
     case State::DOWNLOADING_DLC:
       plugin_vm::RecordPluginVmSetupResultHistogram(
           plugin_vm::PluginVmSetupResult::kUserCancelledDownloadingPluginVmDlc);
@@ -226,13 +220,19 @@
   return gfx::Size(kWindowWidth, kWindowHeight);
 }
 
-void PluginVmInstallerView::OnDlcDownloadProgressUpdated(
-    double progress,
-    base::TimeDelta elapsed_time) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK_EQ(state_, State::DOWNLOADING_DLC);
+void PluginVmInstallerView::OnProgressUpdated(double fraction_complete) {
+  progress_bar_->SetValue(fraction_complete);
+}
 
-  UpdateOperationProgress(progress * 100, 100.0, elapsed_time);
+void PluginVmInstallerView::OnCheckedDiskSpace(bool low_disk_space) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK_EQ(state_, State::CHECKING_DISK_SPACE);
+
+  if (low_disk_space)
+    state_ = State::LOW_DISK_SPACE;
+  else
+    state_ = State::DOWNLOADING_DLC;
+  OnStateUpdated();
 }
 
 void PluginVmInstallerView::OnDlcDownloadCompleted() {
@@ -270,10 +270,8 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 }
 
-void PluginVmInstallerView::OnDownloadProgressUpdated(
-    uint64_t bytes_downloaded,
-    int64_t content_length,
-    base::TimeDelta elapsed_time) {
+void PluginVmInstallerView::OnDownloadProgressUpdated(uint64_t bytes_downloaded,
+                                                      int64_t content_length) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK_EQ(state_, State::DOWNLOADING);
 
@@ -281,7 +279,6 @@
       GetDownloadProgressMessage(bytes_downloaded, content_length));
   download_progress_message_label_->NotifyAccessibilityEvent(
       ax::mojom::Event::kTextChanged, true);
-  UpdateOperationProgress(bytes_downloaded, content_length, elapsed_time);
 }
 
 void PluginVmInstallerView::OnDownloadCompleted() {
@@ -292,44 +289,6 @@
   OnStateUpdated();
 }
 
-void PluginVmInstallerView::OnDownloadFailed(
-    plugin_vm::PluginVmInstaller::FailureReason reason) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  state_ = State::ERROR;
-  reason_ = reason;
-  OnStateUpdated();
-
-  if (reason == plugin_vm::PluginVmInstaller::FailureReason::NOT_ALLOWED) {
-    plugin_vm::RecordPluginVmSetupResultHistogram(
-        plugin_vm::PluginVmSetupResult::kPluginVmIsNotAllowed);
-  } else {
-    plugin_vm::RecordPluginVmSetupResultHistogram(
-        plugin_vm::PluginVmSetupResult::kErrorDownloadingPluginVmImage);
-  }
-}
-
-void PluginVmInstallerView::OnImportProgressUpdated(
-    int percent_completed,
-    base::TimeDelta elapsed_time) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DCHECK_EQ(state_, State::IMPORTING);
-
-  UpdateOperationProgress(percent_completed, 100.0, elapsed_time);
-}
-
-void PluginVmInstallerView::OnImportFailed(
-    plugin_vm::PluginVmInstaller::FailureReason reason) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-
-  state_ = State::ERROR;
-  reason_ = reason;
-  OnStateUpdated();
-
-  plugin_vm::RecordPluginVmSetupResultHistogram(
-      plugin_vm::PluginVmSetupResult::kErrorImportingPluginVmImage);
-}
-
 void PluginVmInstallerView::OnImported() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK_EQ(state_, State::IMPORTING);
@@ -356,9 +315,23 @@
                                               setup_start_tick_);
 }
 
+void PluginVmInstallerView::OnError(
+    plugin_vm::PluginVmInstaller::FailureReason reason) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  state_ = State::ERROR;
+  reason_ = reason;
+  OnStateUpdated();
+
+  plugin_vm::RecordPluginVmSetupResultHistogram(
+      plugin_vm::PluginVmSetupResult::kError);
+}
+
 base::string16 PluginVmInstallerView::GetBigMessage() const {
   switch (state_) {
     case State::STARTING:
+    case State::CHECKING_DISK_SPACE:
+    case State::LOW_DISK_SPACE:
     case State::DOWNLOADING_DLC:
     case State::CHECKING_VMS:
     case State::DOWNLOADING:
@@ -382,7 +355,15 @@
 
 base::string16 PluginVmInstallerView::GetMessage() const {
   switch (state_) {
+    case State::LOW_DISK_SPACE:
+      return l10n_util::GetStringFUTF16(
+          IDS_PLUGIN_VM_INSTALLER_LOW_DISK_SPACE_MESSAGE,
+          ui::FormatBytesWithUnits(
+              plugin_vm::PluginVmInstaller::kRecommendedFreeDiskSpace,
+              ui::DATA_UNITS_GIBIBYTE,
+              /*show_units=*/true));
     case State::STARTING:
+    case State::CHECKING_DISK_SPACE:
     case State::DOWNLOADING_DLC:
     case State::CHECKING_VMS:
       return l10n_util::GetStringUTF16(
@@ -449,9 +430,18 @@
         case Reason::DLC_NEED_REBOOT:
           return l10n_util::GetStringUTF16(
               IDS_PLUGIN_VM_DLC_NEED_REBOOT_FAILED_MESSAGE);
+        case Reason::INSUFFICIENT_DISK_SPACE:
         case Reason::DLC_NEED_SPACE:
-          return l10n_util::GetStringUTF16(
-              IDS_PLUGIN_VM_DLC_NEED_SPACE_FAILED_MESSAGE);
+          return l10n_util::GetStringFUTF16(
+              IDS_PLUGIN_VM_INSUFFICIENT_DISK_SPACE_MESSAGE,
+              ui::FormatBytesWithUnits(
+                  plugin_vm::PluginVmInstaller::kMinimumFreeDiskSpace,
+                  ui::DATA_UNITS_GIBIBYTE,
+                  /*show_units=*/true),
+              ui::FormatBytesWithUnits(
+                  plugin_vm::PluginVmInstaller::kRecommendedFreeDiskSpace,
+                  ui::DATA_UNITS_GIBIBYTE,
+                  /*show_units=*/true));
       }
   }
 }
@@ -469,11 +459,14 @@
 int PluginVmInstallerView::GetCurrentDialogButtons() const {
   switch (state_) {
     case State::STARTING:
+    case State::CHECKING_DISK_SPACE:
     case State::DOWNLOADING_DLC:
     case State::CHECKING_VMS:
     case State::DOWNLOADING:
     case State::IMPORTING:
       return ui::DIALOG_BUTTON_CANCEL;
+    case State::LOW_DISK_SPACE:
+      return ui::DIALOG_BUTTON_CANCEL | ui::DIALOG_BUTTON_OK;
     case State::IMPORTED:
     case State::CREATED:
       return ui::DIALOG_BUTTON_OK;
@@ -492,6 +485,7 @@
     ui::DialogButton button) const {
   switch (state_) {
     case State::STARTING:
+    case State::CHECKING_DISK_SPACE:
     case State::DOWNLOADING_DLC:
     case State::CHECKING_VMS:
     case State::DOWNLOADING:
@@ -504,6 +498,11 @@
       DCHECK_EQ(button, ui::DIALOG_BUTTON_OK);
       return l10n_util::GetStringUTF16(IDS_PLUGIN_VM_INSTALLER_LAUNCH_BUTTON);
     }
+    case State::LOW_DISK_SPACE:
+      return l10n_util::GetStringUTF16(
+          button == ui::DIALOG_BUTTON_OK
+              ? IDS_PLUGIN_VM_INSTALLER_CONTINUE_BUTTON
+              : IDS_APP_CANCEL);
     case State::ERROR: {
       DCHECK(reason_);
       switch (*reason_) {
@@ -546,15 +545,10 @@
   }
 
   const bool progress_bar_visible =
-      state_ == State::STARTING || state_ == State::DOWNLOADING_DLC ||
-      state_ == State::CHECKING_VMS || state_ == State::DOWNLOADING ||
-      state_ == State::IMPORTING;
+      state_ == State::STARTING || state_ == State::CHECKING_DISK_SPACE ||
+      state_ == State::DOWNLOADING_DLC || state_ == State::CHECKING_VMS ||
+      state_ == State::DOWNLOADING || state_ == State::IMPORTING;
   progress_bar_->SetVisible(progress_bar_visible);
-  // Values outside the range [0,1] display an infinite loading animation.
-  progress_bar_->SetValue(-1);
-
-  // This will be shown once we receive download/import progress messages.
-  time_left_message_label_->SetVisible(false);
 
   const bool download_progress_message_label_visible =
       state_ == State::DOWNLOADING;
@@ -576,11 +570,8 @@
     int64_t content_length) const {
   DCHECK_EQ(state_, State::DOWNLOADING);
 
-  base::Optional<double> fraction_complete =
-      GetFractionComplete(bytes_downloaded, content_length);
-
   // If download size isn't known |fraction_complete| should be empty.
-  if (fraction_complete.has_value()) {
+  if (content_length > 0) {
     return l10n_util::GetStringFUTF16(
         IDS_PLUGIN_VM_INSTALLER_DOWNLOAD_PROGRESS_MESSAGE,
         ui::FormatBytesWithUnits(bytes_downloaded, ui::DATA_UNITS_GIBIBYTE,
@@ -595,37 +586,6 @@
   }
 }
 
-void PluginVmInstallerView::UpdateOperationProgress(
-    double units_processed,
-    double total_units,
-    base::TimeDelta elapsed_time) const {
-  DCHECK(state_ == State::DOWNLOADING_DLC || state_ == State::CHECKING_VMS ||
-         state_ == State::DOWNLOADING || state_ == State::IMPORTING);
-
-  base::Optional<double> maybe_fraction_complete =
-      GetFractionComplete(units_processed, total_units);
-
-  if (!maybe_fraction_complete || units_processed == 0 ||
-      elapsed_time.is_zero()) {
-    progress_bar_->SetValue(-1);
-    time_left_message_label_->SetVisible(false);
-    return;
-  }
-
-  const double fraction_complete = *maybe_fraction_complete;
-  const double fraction_remaining = 1 - fraction_complete;
-
-  progress_bar_->SetValue(fraction_complete);
-  time_left_message_label_->SetVisible(true);
-  base::TimeDelta remaining =
-      fraction_remaining / fraction_complete * elapsed_time;
-  time_left_message_label_->SetText(
-      ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
-                             ui::TimeFormat::LENGTH_SHORT, remaining));
-  time_left_message_label_->NotifyAccessibilityEvent(
-      ax::mojom::Event::kTextChanged, true);
-}
-
 void PluginVmInstallerView::SetBigMessageLabel() {
   big_message_label_->SetText(GetBigMessage());
   big_message_label_->SetVisible(true);
@@ -657,7 +617,8 @@
   // retry button is clicked).
   setup_start_tick_ = base::TimeTicks::Now();
 
-  state_ = State::DOWNLOADING_DLC;
+  state_ = State::CHECKING_DISK_SPACE;
+  progress_bar_->SetValue(0);
   OnStateUpdated();
 
   plugin_vm_installer_->SetObserver(this);
diff --git a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.h b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.h
index 0c4a148..70355d2 100644
--- a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.h
+++ b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view.h
@@ -33,23 +33,16 @@
   gfx::Size CalculatePreferredSize() const override;
 
   // plugin_vm::PluginVmImageDownload::Observer implementation.
-  void OnDlcDownloadProgressUpdated(double progress,
-                                    base::TimeDelta elapsed_time) override;
+  void OnProgressUpdated(double fraction_complete) override;
+  void OnCheckedDiskSpace(bool low_disk_space) override;
   void OnDlcDownloadCompleted() override;
   void OnExistingVmCheckCompleted(bool has_vm) override;
   void OnDownloadProgressUpdated(uint64_t bytes_downloaded,
-                                 int64_t content_length,
-                                 base::TimeDelta elapsed_time) override;
+                                 int64_t content_length) override;
   void OnDownloadCompleted() override;
-  void OnDownloadFailed(
-      plugin_vm::PluginVmInstaller::FailureReason reason) override;
-  void OnImportProgressUpdated(int percent_completed,
-                               base::TimeDelta elapsed_time) override;
   void OnCreated() override;
   void OnImported() override;
-  void OnImportFailed(
-      plugin_vm::PluginVmInstaller::FailureReason reason) override;
-
+  void OnError(plugin_vm::PluginVmInstaller::FailureReason reason) override;
   void OnCancelFinished() override;
 
   // Public for testing purposes.
@@ -60,8 +53,11 @@
       base::OnceCallback<void(bool success)> callback);
 
  private:
+  // TODO(crbug.com/1063748): Re-use PluginVmInstaller::InstallingState.
   enum class State {
-    STARTING,         // View was just created, installation hasn't yet started
+    STARTING,  // View was just created, installation hasn't yet started
+    CHECKING_DISK_SPACE,  // Checking there is available free disk space.
+    LOW_DISK_SPACE,   // Prompt user to continue or abort due to low disk space.
     DOWNLOADING_DLC,  // PluginVm DLC downloading and installing in progress.
     CHECKING_VMS,     // Checking for existing VMs.
     DOWNLOADING,      // Image download (ISO or VM) is in progress.
@@ -82,10 +78,6 @@
 
   base::string16 GetDownloadProgressMessage(uint64_t downlaoded_bytes,
                                             int64_t content_length) const;
-  // Updates the progress bar and shows a time left message if available.
-  void UpdateOperationProgress(double units_processed,
-                               double total_units,
-                               base::TimeDelta elapsed_time) const;
   void SetBigMessageLabel();
   void SetMessageLabel();
   void SetBigImage();
@@ -98,7 +90,6 @@
   views::Label* message_label_ = nullptr;
   views::ProgressBar* progress_bar_ = nullptr;
   views::Label* download_progress_message_label_ = nullptr;
-  views::Label* time_left_message_label_ = nullptr;
   views::ImageView* big_image_ = nullptr;
   base::TimeTicks setup_start_tick_;
 
diff --git a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc
index a01603c..e6d785a 100644
--- a/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc
@@ -227,7 +227,7 @@
 
   histogram_tester_->ExpectUniqueSample(
       plugin_vm::kPluginVmSetupResultHistogram,
-      plugin_vm::PluginVmSetupResult::kErrorDownloadingPluginVmImage, 1);
+      plugin_vm::PluginVmSetupResult::kError, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(PluginVmInstallerViewBrowserTestWithFeatureEnabled,
@@ -245,7 +245,7 @@
 
   histogram_tester_->ExpectUniqueSample(
       plugin_vm::kPluginVmSetupResultHistogram,
-      plugin_vm::PluginVmSetupResult::kErrorImportingPluginVmImage, 1);
+      plugin_vm::PluginVmSetupResult::kError, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(PluginVmInstallerViewBrowserTestWithFeatureEnabled,
@@ -273,9 +273,9 @@
 
   CheckSetupIsFinishedSuccessfully();
 
-  histogram_tester_->ExpectBucketCount(
-      plugin_vm::kPluginVmSetupResultHistogram,
-      plugin_vm::PluginVmSetupResult::kErrorDownloadingPluginVmImage, 1);
+  histogram_tester_->ExpectBucketCount(plugin_vm::kPluginVmSetupResultHistogram,
+                                       plugin_vm::PluginVmSetupResult::kError,
+                                       1);
   histogram_tester_->ExpectBucketCount(plugin_vm::kPluginVmSetupResultHistogram,
                                        plugin_vm::PluginVmSetupResult::kSuccess,
                                        1);
@@ -294,5 +294,5 @@
 
   histogram_tester_->ExpectUniqueSample(
       plugin_vm::kPluginVmSetupResultHistogram,
-      plugin_vm::PluginVmSetupResult::kPluginVmIsNotAllowed, 1);
+      plugin_vm::PluginVmSetupResult::kError, 1);
 }
diff --git a/chrome/browser/ui/views/sharing/sharing_icon_view.cc b/chrome/browser/ui/views/sharing/sharing_icon_view.cc
index 5b6579e9..4c5e71e 100644
--- a/chrome/browser/ui/views/sharing/sharing_icon_view.cc
+++ b/chrome/browser/ui/views/sharing/sharing_icon_view.cc
@@ -144,7 +144,7 @@
 }
 
 const gfx::VectorIcon& SharingIconView::GetVectorIconBadge() const {
-  return should_show_error_ ? kBlockedBadgeIcon : gfx::kNoneIcon;
+  return should_show_error_ ? vector_icons::kBlockedBadgeIcon : gfx::kNoneIcon;
 }
 
 void SharingIconView::OnExecuting(
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.cc b/chrome/browser/ui/webui/settings/safety_check_handler.cc
index 1024243..1373c1f4 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/webui/settings/safety_check_handler.h"
 
 #include "base/bind.h"
+#include "base/i18n/number_formatting.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/string16.h"
@@ -255,18 +256,20 @@
 }
 
 void SafetyCheckHandler::OnPasswordsCheckResult(PasswordsStatus status,
-                                                int num_compromised) {
+                                                Compromised compromised,
+                                                Done done,
+                                                Total total) {
   base::DictionaryValue event;
   event.SetIntKey(kNewState, static_cast<int>(status));
   if (status == PasswordsStatus::kCompromisedExist) {
-    event.SetIntKey(kPasswordsCompromised, num_compromised);
+    event.SetIntKey(kPasswordsCompromised, compromised.value());
     event.SetStringKey(
         kButtonString,
         l10n_util::GetPluralStringFUTF16(
-            IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_BUTTON, num_compromised));
+            IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_BUTTON, compromised.value()));
   }
   event.SetStringKey(kDisplayString,
-                     GetStringForPasswords(status, num_compromised));
+                     GetStringForPasswords(status, compromised, done, total));
   FireWebUIListener(kPasswordsEvent, event);
 }
 
@@ -341,17 +344,27 @@
   }
 }
 
-base::string16 SafetyCheckHandler::GetStringForPasswords(PasswordsStatus status,
-                                                         int num_compromised) {
+base::string16 SafetyCheckHandler::GetStringForPasswords(
+    PasswordsStatus status,
+    Compromised compromised,
+    Done done,
+    Total total) {
   switch (status) {
-    case PasswordsStatus::kChecking:
-      return l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_CHECK_RUNNING);
+    case PasswordsStatus::kChecking: {
+      // Unable to get progress for some reason.
+      if (total.value() == 0) {
+        return l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_CHECK_RUNNING);
+      }
+      return l10n_util::GetStringFUTF16(
+          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_PROGRESS,
+          base::FormatNumber(done.value()), base::FormatNumber(total.value()));
+    }
     case PasswordsStatus::kSafe:
       return l10n_util::GetStringUTF16(
           IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SAFE);
     case PasswordsStatus::kCompromisedExist:
       return l10n_util::GetPluralStringFUTF16(
-          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_COMPROMISED, num_compromised);
+          IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_COMPROMISED, compromised.value());
     case PasswordsStatus::kOffline:
       return l10n_util::GetStringUTF16(
           IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_OFFLINE);
@@ -471,7 +484,7 @@
         passwords) {
   OnPasswordsCheckResult(passwords.empty() ? PasswordsStatus::kNoPasswords
                                            : PasswordsStatus::kSafe,
-                         0);
+                         Compromised(0), Done(0), Total(0));
 }
 
 void SafetyCheckHandler::OnStateChanged(
@@ -488,27 +501,32 @@
                            base::Unretained(this)));
       } else {
         OnPasswordsCheckResult(PasswordsStatus::kCompromisedExist,
-                               num_compromised);
+                               Compromised(num_compromised), Done(0), Total(0));
       }
       break;
     }
     case BulkLeakCheckService::State::kRunning:
-      OnPasswordsCheckResult(PasswordsStatus::kChecking, 0);
+      OnPasswordsCheckResult(PasswordsStatus::kChecking, Compromised(0),
+                             Done(0), Total(0));
       // Non-terminal state, so nothing else needs to be done.
       return;
     case BulkLeakCheckService::State::kSignedOut:
-      OnPasswordsCheckResult(PasswordsStatus::kSignedOut, 0);
+      OnPasswordsCheckResult(PasswordsStatus::kSignedOut, Compromised(0),
+                             Done(0), Total(0));
       break;
     case BulkLeakCheckService::State::kNetworkError:
-      OnPasswordsCheckResult(PasswordsStatus::kOffline, 0);
+      OnPasswordsCheckResult(PasswordsStatus::kOffline, Compromised(0), Done(0),
+                             Total(0));
       break;
     case BulkLeakCheckService::State::kQuotaLimit:
-      OnPasswordsCheckResult(PasswordsStatus::kQuotaLimit, 0);
+      OnPasswordsCheckResult(PasswordsStatus::kQuotaLimit, Compromised(0),
+                             Done(0), Total(0));
       break;
     case BulkLeakCheckService::State::kTokenRequestFailure:
     case BulkLeakCheckService::State::kHashingFailure:
     case BulkLeakCheckService::State::kServiceError:
-      OnPasswordsCheckResult(PasswordsStatus::kError, 0);
+      OnPasswordsCheckResult(PasswordsStatus::kError, Compromised(0), Done(0),
+                             Total(0));
       break;
   }
 
@@ -520,8 +538,17 @@
 void SafetyCheckHandler::OnCredentialDone(
     const password_manager::LeakCheckCredential& credential,
     password_manager::IsLeaked is_leaked) {
-  // Do nothing because we only want to know the total number of compromised
-  // credentials at the end of the bulk leak check.
+  extensions::api::passwords_private::PasswordCheckStatus status =
+      passwords_delegate_->GetPasswordCheckStatus();
+  // Send progress updates only if the check is still running.
+  if (status.state ==
+          extensions::api::passwords_private::PASSWORD_CHECK_STATE_RUNNING &&
+      status.already_processed && status.remaining_in_queue) {
+    Done done = Done(*(status.already_processed));
+    Total total = Total(*(status.remaining_in_queue) + done.value());
+    OnPasswordsCheckResult(PasswordsStatus::kChecking, Compromised(0), done,
+                           total);
+  }
 }
 
 void SafetyCheckHandler::OnJavascriptAllowed() {}
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler.h b/chrome/browser/ui/webui/settings/safety_check_handler.h
index 23b4a092..045bda6 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler.h
+++ b/chrome/browser/ui/webui/settings/safety_check_handler.h
@@ -99,6 +99,9 @@
  private:
   // These ensure integers are passed in the correct possitions in the extension
   // check methods.
+  using Compromised = util::StrongAlias<class CompromisedTag, int>;
+  using Done = util::StrongAlias<class DoneTag, int>;
+  using Total = util::StrongAlias<class TotalTag, int>;
   using Blocklisted = util::StrongAlias<class BlocklistedTag, int>;
   using ReenabledUser = util::StrongAlias<class ReenabledUserTag, int>;
   using ReenabledAdmin = util::StrongAlias<class ReenabledAdminTag, int>;
@@ -135,7 +138,10 @@
                            int64_t update_size,
                            const base::string16& message);
   void OnSafeBrowsingCheckResult(SafeBrowsingStatus status);
-  void OnPasswordsCheckResult(PasswordsStatus status, int num_compromised);
+  void OnPasswordsCheckResult(PasswordsStatus status,
+                              Compromised compromised,
+                              Done done,
+                              Total total);
   void OnExtensionsCheckResult(ExtensionsStatus status,
                                Blocklisted blocklisted,
                                ReenabledUser reenabled_user,
@@ -146,7 +152,9 @@
   base::string16 GetStringForUpdates(UpdateStatus status);
   base::string16 GetStringForSafeBrowsing(SafeBrowsingStatus status);
   base::string16 GetStringForPasswords(PasswordsStatus status,
-                                       int num_compromised);
+                                       Compromised compromised,
+                                       Done done,
+                                       Total total);
   base::string16 GetStringForExtensions(ExtensionsStatus status,
                                         Blocklisted blocklisted,
                                         ReenabledUser reenabled_user,
diff --git a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
index b47be1c..d1e12bf 100644
--- a/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_handler_unittest.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/optional.h"
+#include "base/strings/string16.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -23,6 +24,7 @@
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/crx_file/id_util.h"
 #include "components/password_manager/core/browser/bulk_leak_check_service.h"
+#include "components/password_manager/core/browser/leak_detection/bulk_leak_check.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -98,6 +100,11 @@
     state_ = state;
   }
 
+  void SetProgress(int done, int total) {
+    done_ = done;
+    total_ = total;
+  }
+
   std::vector<extensions::api::passwords_private::CompromisedCredential>
   GetCompromisedCredentials() override {
     std::vector<extensions::api::passwords_private::CompromisedCredential>
@@ -112,12 +119,18 @@
   GetPasswordCheckStatus() override {
     extensions::api::passwords_private::PasswordCheckStatus status;
     status.state = state_;
+    if (total_ != 0) {
+      status.already_processed = std::make_unique<int>(done_);
+      status.remaining_in_queue = std::make_unique<int>(total_ - done_);
+    }
     return status;
   }
 
  private:
   password_manager::BulkLeakCheckService* leak_service_ = nullptr;
   int compromised_password_count_ = 0;
+  int done_ = 0;
+  int total_ = 0;
   extensions::api::passwords_private::PasswordCheckState state_ =
       extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE;
 };
@@ -223,8 +236,11 @@
 SafetyCheckHandlerTest::GetSafetyCheckStatusChangedWithDataIfExists(
     const std::string& component,
     int new_state) {
-  for (const auto& it : test_web_ui_.call_data()) {
-    const content::TestWebUI::CallData& data = *it;
+  // Return the latest update if multiple, so iterate from the end.
+  const std::vector<std::unique_ptr<content::TestWebUI::CallData>>& call_data =
+      test_web_ui_.call_data();
+  for (int i = call_data.size() - 1; i >= 0; --i) {
+    const content::TestWebUI::CallData& data = *(call_data[i]);
     if (data.function_name() != "cr.webUIListenerCallback") {
       continue;
     }
@@ -683,6 +699,52 @@
   VerifyDisplayString(event, "No saved passwords");
 }
 
+TEST_F(SafetyCheckHandlerTest, CheckPasswords_Progress) {
+  auto credential = password_manager::LeakCheckCredential(
+      base::UTF8ToUTF16("test"), base::UTF8ToUTF16("test"));
+  auto is_leaked = password_manager::IsLeaked(false);
+  safety_check_->PerformSafetyCheck();
+  test_passwords_delegate_.SetPasswordCheckState(
+      extensions::api::passwords_private::PASSWORD_CHECK_STATE_RUNNING);
+  test_passwords_delegate_.SetProgress(1, 3);
+  static_cast<password_manager::BulkLeakCheckService::Observer*>(
+      safety_check_.get())
+      ->OnCredentialDone(credential, is_leaked);
+  const base::DictionaryValue* event =
+      GetSafetyCheckStatusChangedWithDataIfExists(
+          kPasswords,
+          static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
+  EXPECT_TRUE(event);
+  VerifyDisplayString(event, base::UTF8ToUTF16("1/3 passwords checked…"));
+
+  test_passwords_delegate_.SetProgress(2, 3);
+  static_cast<password_manager::BulkLeakCheckService::Observer*>(
+      safety_check_.get())
+      ->OnCredentialDone(credential, is_leaked);
+  const base::DictionaryValue* event2 =
+      GetSafetyCheckStatusChangedWithDataIfExists(
+          kPasswords,
+          static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
+  EXPECT_TRUE(event2);
+  VerifyDisplayString(event2, base::UTF8ToUTF16("2/3 passwords checked…"));
+
+  // Final update comes after status change, so no new progress message should
+  // be present.
+  test_passwords_delegate_.SetPasswordCheckState(
+      extensions::api::passwords_private::PASSWORD_CHECK_STATE_IDLE);
+  test_passwords_delegate_.SetProgress(3, 3);
+  static_cast<password_manager::BulkLeakCheckService::Observer*>(
+      safety_check_.get())
+      ->OnCredentialDone(credential, is_leaked);
+  const base::DictionaryValue* event3 =
+      GetSafetyCheckStatusChangedWithDataIfExists(
+          kPasswords,
+          static_cast<int>(SafetyCheckHandler::PasswordsStatus::kChecking));
+  EXPECT_TRUE(event3);
+  // Still 2/3 event.
+  VerifyDisplayString(event3, base::UTF8ToUTF16("2/3 passwords checked…"));
+}
+
 TEST_F(SafetyCheckHandlerTest, CheckExtensions_NoExtensions) {
   safety_check_->PerformSafetyCheck();
   EXPECT_TRUE(GetSafetyCheckStatusChangedWithDataIfExists(
diff --git a/chrome/browser/wake_lock/wake_lock_permission_context.cc b/chrome/browser/wake_lock/wake_lock_permission_context.cc
index 457bff0..5696552 100644
--- a/chrome/browser/wake_lock/wake_lock_permission_context.cc
+++ b/chrome/browser/wake_lock/wake_lock_permission_context.cc
@@ -10,9 +10,12 @@
 WakeLockPermissionContext::WakeLockPermissionContext(
     content::BrowserContext* browser_context,
     ContentSettingsType content_settings_type)
-    : PermissionContextBase(browser_context,
-                            content_settings_type,
-                            blink::mojom::FeaturePolicyFeature::kWakeLock),
+    : PermissionContextBase(
+          browser_context,
+          content_settings_type,
+          content_settings_type == ContentSettingsType::WAKE_LOCK_SCREEN
+              ? blink::mojom::FeaturePolicyFeature::kScreenWakeLock
+              : blink::mojom::FeaturePolicyFeature::kNotFound),
       content_settings_type_(content_settings_type) {
   DCHECK(content_settings_type == ContentSettingsType::WAKE_LOCK_SCREEN ||
          content_settings_type == ContentSettingsType::WAKE_LOCK_SYSTEM);
diff --git a/chrome/browser/web_applications/components/app_registry_controller.cc b/chrome/browser/web_applications/components/app_registry_controller.cc
index 2368f66..6a2be8e 100644
--- a/chrome/browser/web_applications/components/app_registry_controller.cc
+++ b/chrome/browser/web_applications/components/app_registry_controller.cc
@@ -17,8 +17,8 @@
 
 void AppRegistryController::SetExperimentalTabbedWindowMode(const AppId& app_id,
                                                             bool enabled) {
-  DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip));
   if (enabled) {
+    DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsTabStrip));
     UpdateBoolWebAppPref(profile()->GetPrefs(), app_id,
                          kExperimentalTabbedWindowMode, true);
 
diff --git a/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.cc b/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.cc
index f7c648d5..6aa65dc2 100644
--- a/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.cc
+++ b/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.cc
@@ -5,12 +5,12 @@
 #include "chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h"
 
 #include "base/metrics/histogram_functions.h"
-#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
 
 namespace {
 
 // Common prefix for all webrtc metric in Sharing service.
-const char kMetricsPrefix[] = "Sharing.WebRtc.";
+const char kMetricsPrefix[] = "Sharing.WebRtc";
 
 // Return values must be kept in sync with "SharingWebRtcTimingEvent" in
 // src/tools/metrics/histograms/histograms.xml.
@@ -47,6 +47,15 @@
   }
 }
 
+// Return values must be kept in sync with "SharingWebRtcTimingEventRole" in
+// src/tools/metrics/histograms/histograms.xml.
+std::string SenderReceiverSuffix(base::Optional<bool> is_sender) {
+  if (!is_sender.has_value())
+    return "Unknown";
+
+  return *is_sender ? "Sender" : "Receiver";
+}
+
 }  // namespace
 
 namespace sharing {
@@ -69,46 +78,57 @@
 }
 
 void LogWebRtcAddIceCandidate(bool success) {
-  base::UmaHistogramBoolean(base::StrCat({kMetricsPrefix, "AddIceCandidate"}),
-                            success);
+  base::UmaHistogramBoolean(
+      base::JoinString({kMetricsPrefix, "AddIceCandidate"}, "."), success);
 }
 
 void LogWebRtcIceConfigFetched(int count) {
   base::UmaHistogramExactLinear(
-      base::StrCat({kMetricsPrefix, "IceConfigFetched"}), count,
+      base::JoinString({kMetricsPrefix, "IceConfigFetched"}, "."), count,
       /*value_max=*/10);
 }
 
 void LogWebRtcTimeout(WebRtcTimeoutState state) {
-  base::UmaHistogramEnumeration(base::StrCat({kMetricsPrefix, "Timeout"}),
-                                state);
+  base::UmaHistogramEnumeration(
+      base::JoinString({kMetricsPrefix, "Timeout"}, "."), state);
 }
 
 void LogWebRtcConnectionType(WebRtcConnectionType type) {
   base::UmaHistogramEnumeration(
-      base::StrCat({kMetricsPrefix, "ConnectionType"}), type);
+      base::JoinString({kMetricsPrefix, "ConnectionType"}, "."), type);
 }
 
 void LogWebRtcSendMessageResult(WebRtcSendMessageResult result) {
   base::UmaHistogramEnumeration(
-      base::StrCat({kMetricsPrefix, "SendMessageResult"}), result);
+      base::JoinString({kMetricsPrefix, "SendMessageResult"}, "."), result);
 }
 
 void LogWebRtcConnectionErrorReason(WebRtcConnectionErrorReason reason) {
   base::UmaHistogramEnumeration(
-      base::StrCat({kMetricsPrefix, "ConnectionErrorReason"}), reason);
+      base::JoinString({kMetricsPrefix, "ConnectionErrorReason"}, "."), reason);
 }
 
-void LogWebRtcTimingEvent(WebRtcTimingEvent event, base::TimeDelta delay) {
-  base::UmaHistogramMediumTimes(base::StrCat({kMetricsPrefix, "TimingEvents.",
-                                              TimingEventToString(event)}),
-                                delay);
+void LogWebRtcTimingEvent(WebRtcTimingEvent event,
+                          base::TimeDelta delay,
+                          base::Optional<bool> is_sender) {
+  base::UmaHistogramMediumTimes(
+      base::JoinString(
+          {kMetricsPrefix, "TimingEvents", TimingEventToString(event)}, "."),
+      delay);
+
+  base::UmaHistogramMediumTimes(
+      base::JoinString(
+          {kMetricsPrefix, "TimingEvents", SenderReceiverSuffix(is_sender),
+           TimingEventToString(event)},
+          "."),
+      delay);
 }
 
 void LogSharingWebRtcOnMessageReceivedResult(
     WebRtcOnMessageReceivedResult result) {
   base::UmaHistogramEnumeration(
-      base::StrCat({kMetricsPrefix, "OnMessageReceivedResult"}), result);
+      base::JoinString({kMetricsPrefix, "OnMessageReceivedResult"}, "."),
+      result);
 }
 
 }  // namespace sharing
diff --git a/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h b/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h
index b06580b..7c4468f2 100644
--- a/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h
+++ b/chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/optional.h"
 #include "base/time/time.h"
 
 namespace sharing {
@@ -124,7 +125,9 @@
 void LogWebRtcConnectionErrorReason(WebRtcConnectionErrorReason reason);
 
 // Logs the timing for |event| to UMA.
-void LogWebRtcTimingEvent(WebRtcTimingEvent event, base::TimeDelta delay);
+void LogWebRtcTimingEvent(WebRtcTimingEvent event,
+                          base::TimeDelta delay,
+                          base::Optional<bool> is_sender);
 
 // Logs the result of receiving a message via WebRTC.
 void LogSharingWebRtcOnMessageReceivedResult(
diff --git a/chrome/services/sharing/webrtc/sharing_webrtc_connection.cc b/chrome/services/sharing/webrtc/sharing_webrtc_connection.cc
index c5d4247..7140265 100644
--- a/chrome/services/sharing/webrtc/sharing_webrtc_connection.cc
+++ b/chrome/services/sharing/webrtc/sharing_webrtc_connection.cc
@@ -260,6 +260,10 @@
     const std::string& offer,
     OnOfferReceivedCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Only the receiver receives an offer from the sender and it's the first
+  // thing it does.
+  timing_recorder_.set_is_sender(false);
+
   timing_recorder_.LogEvent(WebRtcTimingEvent::kOfferReceived);
 
   std::unique_ptr<webrtc::SessionDescriptionInterface> description(
@@ -296,6 +300,11 @@
 void SharingWebRtcConnection::SendMessage(const std::vector<uint8_t>& message,
                                           SendMessageCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // We know that we're the sender if there is no data channel yet because the
+  // receiver only sends ack messages after receiving via a data channel.
+  if (!channel_)
+    timing_recorder_.set_is_sender(true);
+
   timing_recorder_.LogEvent(WebRtcTimingEvent::kQueuingMessage);
 
   if (message.empty()) {
diff --git a/chrome/services/sharing/webrtc/sharing_webrtc_connection_integration_test.cc b/chrome/services/sharing/webrtc/sharing_webrtc_connection_integration_test.cc
index 2e47cfbe..11a82d2 100644
--- a/chrome/services/sharing/webrtc/sharing_webrtc_connection_integration_test.cc
+++ b/chrome/services/sharing/webrtc/sharing_webrtc_connection_integration_test.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/services/sharing/webrtc/sharing_webrtc_connection.h"
 
+#include "base/strings/strcat.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "chrome/services/sharing/webrtc/test/mock_sharing_connection_host.h"
 #include "jingle/glue/thread_wrapper.h"
@@ -168,10 +170,29 @@
             base::Unretained(this)));
   }
 
+  enum class Role { kSender, kReceiver, kSenderAndReceiver };
+
+  void ExpectTimingHistogram(const std::string& name, Role role) {
+    int expected_count = role == Role::kSenderAndReceiver ? 2 : 1;
+    histograms_.ExpectTotalCount(
+        base::StrCat({"Sharing.WebRtc.TimingEvents.", name}), expected_count);
+
+    if (role == Role::kSender || role == Role::kSenderAndReceiver) {
+      histograms_.ExpectTotalCount(
+          base::StrCat({"Sharing.WebRtc.TimingEvents.Sender.", name}), 1);
+    }
+
+    if (role == Role::kReceiver || role == Role::kSenderAndReceiver) {
+      histograms_.ExpectTotalCount(
+          base::StrCat({"Sharing.WebRtc.TimingEvents.Receiver.", name}), 1);
+    }
+  }
+
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_{
       base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
   rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> webrtc_pc_factory_;
+  base::HistogramTester histograms_;
 };
 
 TEST_F(SharingWebRtcConnectionIntegrationTest, SendMessage_Success) {
@@ -205,6 +226,17 @@
 
   send_run_loop.Run();
   receive_run_loop.Run();
+
+  ExpectTimingHistogram("OfferCreated", Role::kSender);
+  ExpectTimingHistogram("AnswerReceived", Role::kSender);
+  ExpectTimingHistogram("QueuingMessage", Role::kSender);
+  ExpectTimingHistogram("SendingMessage", Role::kSender);
+  ExpectTimingHistogram("OfferReceived", Role::kReceiver);
+  ExpectTimingHistogram("AnswerCreated", Role::kReceiver);
+  ExpectTimingHistogram("MessageReceived", Role::kReceiver);
+  ExpectTimingHistogram("SignalingStable", Role::kSenderAndReceiver);
+  ExpectTimingHistogram("IceCandidateReceived", Role::kSenderAndReceiver);
+  ExpectTimingHistogram("DataChannelOpen", Role::kSenderAndReceiver);
 }
 
 }  // namespace sharing
diff --git a/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.cc b/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.cc
index 7d00c6b..d457f86b 100644
--- a/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.cc
+++ b/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.cc
@@ -51,7 +51,8 @@
   if (!to_iter.second)
     return;
 
-  LogWebRtcTimingEvent(event, to_iter.first->second - from_iter->second);
+  base::TimeDelta delay = to_iter.first->second - from_iter->second;
+  LogWebRtcTimingEvent(event, delay, is_sender_);
 }
 
 }  // namespace sharing
diff --git a/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.h b/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.h
index a254df1..d52be05 100644
--- a/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.h
+++ b/chrome/services/sharing/webrtc/sharing_webrtc_timing_recorder.h
@@ -7,6 +7,7 @@
 
 #include <map>
 
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "chrome/services/sharing/public/cpp/sharing_webrtc_metrics.h"
 
@@ -25,9 +26,16 @@
   // is determined by the |event_start_map| in GetEventStart.
   void LogEvent(WebRtcTimingEvent event);
 
+  // Sets whether we should log events with the sender or receiver suffix in
+  // addition to the non-suffixed version. Only the non-suffixed version is
+  // logged by default.
+  void set_is_sender(bool is_sender) { is_sender_ = is_sender; }
+
  private:
   // Map of the timing event to the first timestamp it got logged.
   std::map<WebRtcTimingEvent, base::TimeTicks> timings_;
+  // Whether we should log events with the sender or receiver suffix.
+  base::Optional<bool> is_sender_;
 };
 
 }  // namespace sharing
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6087c40..8be16486 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2231,6 +2231,7 @@
         "../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/packaged_license_screen_browsertest.cc",
         "../browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.cc",
         "../browser/chromeos/login/screens/recommend_apps/scoped_test_recommend_apps_fetcher_factory.h",
         "../browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc",
@@ -3666,6 +3667,7 @@
     "//components/optimization_guide",
     "//components/optimization_guide:test_support",
     "//components/os_crypt:test_support",
+    "//components/page_info",
     "//components/page_load_metrics/browser",
     "//components/page_load_metrics/browser:test_support",
     "//components/page_load_metrics/common:test_support",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
index aa996c2..299a9fdc 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/NewTabPageTestUtils.java
@@ -19,8 +19,8 @@
 import org.chromium.chrome.browser.suggestions.tile.TileSource;
 import org.chromium.chrome.browser.suggestions.tile.TileTitleSource;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
 import org.chromium.content_public.browser.test.util.Criteria;
@@ -98,7 +98,7 @@
         FakeAccountManagerDelegate fakeAccountManager = new FakeAccountManagerDelegate(
                 FakeAccountManagerDelegate.ENABLE_PROFILE_DATA_SOURCE);
         AccountManagerFacadeProvider.overrideAccountManagerFacadeForTests(fakeAccountManager);
-        Account account = AccountManagerFacade.createAccountFromName("test@gmail.com");
+        Account account = AccountUtils.createAccountFromName("test@gmail.com");
         fakeAccountManager.addAccountHolderExplicitly(new AccountHolder.Builder(account).build());
         assertFalse(AccountManagerFacadeProvider.getInstance().isUpdatePending().get());
         assertFalse(SharedPreferencesManager.getInstance().readBoolean(
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
index 2752717..cf3ed7f 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/signin/SigninTestUtil.java
@@ -18,8 +18,8 @@
 import org.chromium.chrome.browser.signin.SigninManager;
 import org.chromium.chrome.browser.signin.SigninPreferencesManager;
 import org.chromium.chrome.browser.sync.ProfileSyncService;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
@@ -96,7 +96,7 @@
      * Add an account with a given name.
      */
     public static Account addTestAccount(String name) {
-        Account account = AccountManagerFacade.createAccountFromName(name);
+        Account account = AccountUtils.createAccountFromName(name);
         AccountHolder accountHolder = AccountHolder.builder(account).alwaysAccept(true).build();
         sAccountManager.addAccountHolderBlocking(accountHolder);
         sAddedAccounts.add(accountHolder);
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index b83ce8d..d40ecc84b 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -215,7 +215,7 @@
       log_types = ['client', 'driver', 'browser', 'server', 'performance',
         'devtools']
       log_levels = ['ALL', 'DEBUG', 'INFO', 'WARNING', 'SEVERE', 'OFF']
-      for log_type, log_level in logging_prefs.iteritems():
+      for log_type, log_level in logging_prefs.items():
         assert log_type in log_types
         assert log_level in log_levels
     else:
diff --git a/chrome/test/chromedriver/cpp_source.py b/chrome/test/chromedriver/cpp_source.py
index b0f8159..4c094d9 100644
--- a/chrome/test/chromedriver/cpp_source.py
+++ b/chrome/test/chromedriver/cpp_source.py
@@ -34,7 +34,7 @@
 
   # Write header file.
   externs = []
-  for name in global_string_map.iterkeys():
+  for name in global_string_map.keys():
     externs += ['extern const char %s[];' % name]
 
   temp = '_'.join(dir_from_src.split('/') + [base_name])
@@ -58,7 +58,7 @@
     return line.replace('\\', '\\\\').replace('"', '\\"')
 
   definitions = []
-  for name, contents in global_string_map.iteritems():
+  for name, contents in global_string_map.items():
     lines = []
     if '\n' not in contents:
       lines = ['    "%s"' % EscapeLine(contents)]
diff --git a/chrome/test/chromedriver/log_replay/client_replay.py b/chrome/test/chromedriver/log_replay/client_replay.py
index c4d2a04..687742b 100755
--- a/chrome/test/chromedriver/log_replay/client_replay.py
+++ b/chrome/test/chromedriver/log_replay/client_replay.py
@@ -308,7 +308,7 @@
     id_map: mapping from old to new IDs that should be replaced.
   """
   if isinstance(payload, dict):
-    for key, value in payload.iteritems():
+    for key, value in payload.items():
       if isinstance(value, basestring) and value in id_map:
         payload[key] = id_map[value]
       else:
@@ -831,7 +831,7 @@
 
     response = [
         {u"id": key, u"capabilities": val["response"].GetPayloadPrimitive()}
-        for key, val in command_response_pairs.iteritems()
+        for key, val in command_response_pairs.items()
     ]
     self._last_response = _GetSessionsResponseEntry(response)
 
diff --git a/chrome/test/chromedriver/test/webserver.py b/chrome/test/chromedriver/test/webserver.py
index 9a5d76a..f2b93563 100644
--- a/chrome/test/chromedriver/test/webserver.py
+++ b/chrome/test/chromedriver/test/webserver.py
@@ -29,7 +29,7 @@
   def SendHeaders(self, headers={}, content_length=None):
     """Sends headers for OK response."""
     self._handler.send_response(200)
-    for field, value in headers.iteritems():
+    for field, value in headers.items():
       self._handler.send_header(field, value)
     if content_length:
       self._handler.send_header('Content-Length', content_length)
diff --git a/chrome/test/data/extensions/api_test/debugger_inspect_worker/background.js b/chrome/test/data/extensions/api_test/debugger_inspect_worker/background.js
new file mode 100644
index 0000000..c104c77
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/debugger_inspect_worker/background.js
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const protocolVersion = '1.3';
+
+chrome.test.getConfig(config => chrome.test.runTests([
+  async function testInspectWorkerForbidden() {
+    const tab = await new Promise(resolve =>
+        chrome.tabs.create({url: config.customArg}, resolve));
+    const debuggee = {tabId: tab.id};
+    await new Promise(resolve =>
+        chrome.debugger.attach(debuggee, protocolVersion, resolve));
+    chrome.debugger.sendCommand(debuggee, 'ServiceWorker.enable', null);
+    let workerReadyCallback;
+    chrome.debugger.onEvent.addListener((source, method, params) => {
+      if (method !== 'ServiceWorker.workerVersionUpdated')
+        return;
+      const versions = params.versions;
+      if (!versions.length || versions[0].runningStatus !== 'running')
+        return;
+      workerReadyCallback(versions[0].versionId);
+    });
+    const versionId = await new Promise(resolve =>
+        workerReadyCallback = resolve);
+    await new Promise(resolve =>
+        chrome.debugger.sendCommand(debuggee, 'ServiceWorker.inspectWorker',
+            {versionId}, resolve))
+    chrome.test.assertTrue(!!chrome.runtime.lastError,
+                           'Expected ServiceWorker.inspectWorker to fail');
+    chrome.test.assertEq('Permission denied',
+                         JSON.parse(chrome.runtime.lastError.message).message);
+    chrome.test.succeed();
+  }
+]));
diff --git a/chrome/test/data/extensions/api_test/debugger_inspect_worker/inspected_page.html b/chrome/test/data/extensions/api_test/debugger_inspect_worker/inspected_page.html
new file mode 100644
index 0000000..c50ef83
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/debugger_inspect_worker/inspected_page.html
@@ -0,0 +1,5 @@
+<html>
+<script>
+navigator.serviceWorker.register("./service_worker.js");
+</script>
+</html>
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/debugger_inspect_worker/manifest.json b/chrome/test/data/extensions/api_test/debugger_inspect_worker/manifest.json
new file mode 100644
index 0000000..6ca4cde
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/debugger_inspect_worker/manifest.json
@@ -0,0 +1,11 @@
+{
+  "name": "Debugger API test for inpectWorker CDP command",
+  "version": "1.0",
+  "manifest_version": 2,
+  "background": {
+    "scripts": ["background.js"]
+  },
+  "permissions": [
+    "debugger"
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/debugger_inspect_worker/service_worker.js b/chrome/test/data/extensions/api_test/debugger_inspect_worker/service_worker.js
new file mode 100644
index 0000000..12bc989f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/debugger_inspect_worker/service_worker.js
@@ -0,0 +1,5 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// It's empty, yes.
diff --git a/chrome/test/data/webui/media/media_feeds_webui_browsertest.js b/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
index 65e2a405a..72c293b 100644
--- a/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
+++ b/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
@@ -6,10 +6,6 @@
  * @fileoverview Test suite for the Media Feeds WebUI.
  */
 
-const EXAMPLE_URL_1 = 'http://example.com/feed.json';
-
-GEN('#include "base/run_loop.h"');
-GEN('#include "chrome/browser/media/history/media_history_keyed_service.h"');
 GEN('#include "chrome/browser/ui/browser.h"');
 GEN('#include "media/base/media_switches.h"');
 
@@ -24,97 +20,6 @@
 
   isAsync: true,
 
-  testGenPreamble: function() {
-    GEN('auto* service =');
-    GEN('  media_history::MediaHistoryKeyedService::Get(');
-    GEN('    browser()->profile());');
-    GEN('service->DiscoverMediaFeed(GURL("' + EXAMPLE_URL_1 + '"));');
-    GEN('auto items = std::vector<media_feeds::mojom::MediaFeedItemPtr>();');
-    GEN('auto item = media_feeds::mojom::MediaFeedItem::New();');
-    GEN('item->name = base::ASCIIToUTF16("The Movie");');
-    GEN('item->type = media_feeds::mojom::MediaFeedItemType::kMovie;');
-    GEN('item->date_published = base::Time::FromDeltaSinceWindowsEpoch(');
-    GEN('  base::TimeDelta::FromMinutes(10));');
-    GEN('item->is_family_friendly = true;');
-    GEN('item->action_status =');
-    GEN('  media_feeds::mojom::MediaFeedItemActionStatus::kPotential;');
-    GEN('item->genre = base::ASCIIToUTF16("test");');
-    GEN('item->duration = base::TimeDelta::FromSeconds(30);');
-    GEN('item->live = media_feeds::mojom::LiveDetails::New();');
-    GEN('item->live->start_time = base::Time::FromDeltaSinceWindowsEpoch(');
-    GEN('  base::TimeDelta::FromMinutes(20));');
-    GEN('item->live->end_time = base::Time::FromDeltaSinceWindowsEpoch(');
-    GEN('  base::TimeDelta::FromMinutes(30));');
-    GEN('item->shown_count = 3;');
-    GEN('item->clicked = true;');
-    GEN('item->author = media_feeds::mojom::Author::New();');
-    GEN('item->author->name = "Media Site";');
-    GEN('item->author->url = GURL("https://www.example.com");');
-    GEN('item->action = media_feeds::mojom::Action::New();');
-    GEN('item->action->start_time = base::TimeDelta::FromSeconds(3);');
-    GEN('item->action->url = GURL("https://www.example.com");');
-    GEN('item->interaction_counters.emplace(');
-    GEN('  media_feeds::mojom::InteractionCounterType::kLike, 10000);');
-    GEN('item->interaction_counters.emplace(');
-    GEN('  media_feeds::mojom::InteractionCounterType::kDislike, 20000);');
-    GEN('item->interaction_counters.emplace(');
-    GEN('  media_feeds::mojom::InteractionCounterType::kWatch, 30000);');
-    GEN('item->content_ratings.push_back(');
-    GEN('  media_feeds::mojom::ContentRating::New("MPAA", "PG-13"));');
-    GEN('item->content_ratings.push_back(');
-    GEN('  media_feeds::mojom::ContentRating::New("agency", "TEST2"));');
-    GEN('item->identifiers.push_back(');
-    GEN('  media_feeds::mojom::Identifier::New(');
-    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST1"));');
-    GEN('item->identifiers.push_back(');
-    GEN('  media_feeds::mojom::Identifier::New(');
-    GEN('    media_feeds::mojom::Identifier::Type::kTMSId, "TEST2"));');
-    GEN('item->tv_episode = media_feeds::mojom::TVEpisode::New();');
-    GEN('item->tv_episode->name = "TV Episode Name";');
-    GEN('item->tv_episode->season_number = 1;');
-    GEN('item->tv_episode->episode_number = 2;');
-    GEN('item->tv_episode->identifiers.push_back(');
-    GEN('  media_feeds::mojom::Identifier::New(');
-    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST3"));');
-    GEN('item->play_next_candidate = ');
-    GEN('    media_feeds::mojom::PlayNextCandidate::New();');
-    GEN('item->play_next_candidate->name = "Next TV Episode Name";');
-    GEN('item->play_next_candidate->season_number = 1;');
-    GEN('item->play_next_candidate->episode_number = 3;');
-    GEN('item->play_next_candidate->duration =');
-    GEN('    base::TimeDelta::FromSeconds(10);');
-    GEN('item->play_next_candidate->action = ');
-    GEN('    media_feeds::mojom::Action::New();');
-    GEN('item->play_next_candidate->action->start_time =');
-    GEN('    base::TimeDelta::FromSeconds(3);');
-    GEN('item->play_next_candidate->action->url = ');
-    GEN('    GURL("https://www.example.com");');
-    GEN('item->play_next_candidate->identifiers.push_back(');
-    GEN('  media_feeds::mojom::Identifier::New(');
-    GEN('    media_feeds::mojom::Identifier::Type::kPartnerId, "TEST4"));');
-    GEN('media_session::MediaImage image1;');
-    GEN('image1.src = GURL("https://www.example.org/image1.png");');
-    GEN('item->images.push_back(image1);');
-    GEN('media_session::MediaImage image2;');
-    GEN('image2.src = GURL("https://www.example.org/image2.png");');
-    GEN('item->images.push_back(image2);');
-    GEN('items.push_back(std::move(item));');
-    GEN('std::vector<media_session::MediaImage> logos;');
-    GEN('media_session::MediaImage logo1;');
-    GEN('logo1.src = GURL("https://www.example.org/logo1.png");');
-    GEN('logos.push_back(logo1);');
-    GEN('media_session::MediaImage logo2;');
-    GEN('logo2.src = GURL("https://www.example.org/logo2.png");');
-    GEN('logos.push_back(logo2);');
-    GEN('service->StoreMediaFeedFetchResult(');
-    GEN('  1, std::move(items), media_feeds::mojom::FetchResult::kSuccess,');
-    GEN('  base::Time::FromDeltaSinceWindowsEpoch(');
-    GEN('  base::TimeDelta::FromMinutes(40)), logos, "Test Feed");');
-    GEN('base::RunLoop run_loop;');
-    GEN('service->PostTaskToDBForTest(run_loop.QuitClosure());');
-    GEN('run_loop.Run();');
-  },
-
   extraLibraries: [
     '//third_party/mocha/mocha.js',
     '//chrome/test/data/webui/mocha_adapter.js',
@@ -127,7 +32,7 @@
   });
 
   test('check feeds table is loaded', function() {
-    const feedsHeaders =
+    let feedHeaders =
         Array.from(document.querySelector('#feed-table-header').children);
 
     assertDeepEquals(
@@ -135,93 +40,9 @@
           'ID', 'Url', 'Display Name', 'Last Discovery Time', 'Last Fetch Time',
           'User Status', 'Last Fetch Result', 'Fetch Failed Count',
           'Cache Expiry Time', 'Last Fetch Item Count',
-          'Last Fetch Play Next Count', 'Last Fetch Content Types', 'Logos',
-          'Actions'
+          'Last Fetch Play Next Count', 'Last Fetch Content Types', 'Logos'
         ],
-        feedsHeaders.map(x => x.textContent.trim()));
-
-    const feedsContents =
-        document.querySelector('#feed-table-body').childNodes[0];
-
-    assertEquals('1', feedsContents.childNodes[0].textContent.trim());
-    assertEquals(EXAMPLE_URL_1, feedsContents.childNodes[1].textContent.trim());
-    assertEquals('Test Feed', feedsContents.childNodes[2].textContent.trim());
-    assertEquals('Auto', feedsContents.childNodes[5].textContent.trim());
-    assertEquals('Success', feedsContents.childNodes[6].textContent.trim());
-    assertEquals('0', feedsContents.childNodes[7].textContent.trim());
-    assertNotEquals('', feedsContents.childNodes[8].textContent.trim());
-    assertEquals('1', feedsContents.childNodes[9].textContent.trim());
-    assertEquals('1', feedsContents.childNodes[10].textContent.trim());
-    assertEquals('Movie', feedsContents.childNodes[11].textContent.trim());
-    assertEquals(
-        'https://www.example.org/logo1.pnghttps://www.example.org/logo2.png',
-        feedsContents.childNodes[12].textContent.trim());
-    assertEquals(
-        'Show Contents', feedsContents.childNodes[13].textContent.trim());
-
-    // Click on the show contents button.
-    feedsContents.childNodes[13].firstChild.click();
-
-    return whenFeedTableIsPopulatedForTest().then(() => {
-      assertEquals(
-          EXAMPLE_URL_1, document.querySelector('#current-feed').textContent);
-
-      const feedItemsHeaders = Array.from(
-          document.querySelector('#feed-items-table thead tr').children);
-
-      assertDeepEquals(
-          [
-            'Type', 'Name', 'Author', 'Date Published', 'Family Friendly',
-            'Action Status', 'Action URL', 'Action Start Time (secs)',
-            'Interaction Counters', 'Content Ratings', 'Genre', 'Live Details',
-            'TV Episode', 'Play Next Candidate', 'Identifiers', 'Shown Count',
-            'Clicked', 'Images'
-          ],
-          feedItemsHeaders.map(x => x.textContent.trim()));
-
-      const feedItemsContents =
-          document.querySelector('#feed-items-table tbody').childNodes[0];
-
-      assertEquals('Movie', feedItemsContents.childNodes[0].textContent.trim());
-      assertEquals(
-          'The Movie', feedItemsContents.childNodes[1].textContent.trim());
-      assertEquals(
-          'Media Site', feedItemsContents.childNodes[2].textContent.trim());
-      assertEquals(
-          '12/31/1600, 4:17:02 PM',
-          feedItemsContents.childNodes[3].textContent.trim());
-      assertEquals('Yes', feedItemsContents.childNodes[4].textContent.trim());
-      assertEquals(
-          'Potential', feedItemsContents.childNodes[5].textContent.trim());
-      assertEquals(
-          'https://www.example.com/',
-          feedItemsContents.childNodes[6].textContent.trim());
-      assertEquals('3', feedItemsContents.childNodes[7].textContent.trim());
-      assertEquals(
-          'Watch=30000 Like=10000 Dislike=20000',
-          feedItemsContents.childNodes[8].textContent.trim());
-      assertEquals(
-          'MPAA PG-13, agency TEST2',
-          feedItemsContents.childNodes[9].textContent.trim());
-      assertEquals('test', feedItemsContents.childNodes[10].textContent.trim());
-      assertEquals(
-          'Live StartTime=12/31/1600, 4:27:02 PM EndTime=12/31/1600, 4:37:02 PM',
-          feedItemsContents.childNodes[11].textContent.trim());
-      assertEquals(
-          'TV Episode Name EpisodeNumber=2 SeasonNumber=1 PartnerId=TEST3',
-          feedItemsContents.childNodes[12].textContent.trim());
-      assertEquals(
-          'Next TV Episode Name EpisodeNumber=3 SeasonNumber=1 PartnerId=TEST4 ActionURL=https://www.example.com/ ActionStartTimeSecs=3 DurationSecs=10',
-          feedItemsContents.childNodes[13].textContent.trim());
-      assertEquals(
-          'PartnerId=TEST1 TMSId=TEST2',
-          feedItemsContents.childNodes[14].textContent.trim());
-      assertEquals('3', feedItemsContents.childNodes[15].textContent.trim());
-      assertEquals('Yes', feedItemsContents.childNodes[16].textContent.trim());
-      assertEquals(
-          'https://www.example.org/image1.pnghttps://www.example.org/image2.png',
-          feedItemsContents.childNodes[17].textContent.trim());
-    });
+        feedHeaders.map(x => x.textContent.trim()));
   });
 
   mocha.run();
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
index 84a2abc..5c891a4 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
@@ -155,6 +155,6 @@
   }
 };
 
-TEST_F('NewTabPageLogoTest', 'All', function() {
+TEST_F('NewTabPageLogoTest', 'DISABLE_All', function() {
   mocha.run();
 });
diff --git a/chrome/test/data/webui/settings/password_check_test.js b/chrome/test/data/webui/settings/password_check_test.js
index a1dabc7c..ac710c11 100644
--- a/chrome/test/data/webui/settings/password_check_test.js
+++ b/chrome/test/data/webui/settings/password_check_test.js
@@ -999,7 +999,7 @@
       checkPasswordSection.$.menuEditPassword.click();
       // Since we did not specify a plaintext password above, this request
       // should fail.
-      await passwordManager.whenCalled('getPlainttextCompromisedPassword');
+      await passwordManager.whenCalled('getPlaintextCompromisedPassword');
       // Verify that the edit dialog has not become visible.
       Polymer.dom.flush();
       expectFalse(isElementVisible(
@@ -1027,7 +1027,7 @@
       node.$.more.click();
       checkPasswordSection.$.menuEditPassword.click();
       const {credential, reason} =
-          await passwordManager.whenCalled('getPlainttextCompromisedPassword');
+          await passwordManager.whenCalled('getPlaintextCompromisedPassword');
       expectEquals(passwordManager.data.leakedCredentials[0], credential);
       expectEquals(chrome.passwordsPrivate.PlaintextReason.EDIT, reason);
 
@@ -1085,6 +1085,70 @@
           PasswordManagerProxy.PasswordCheckInteraction
               .START_CHECK_AUTOMATICALLY,
           interaction);
+      settings.Router.getInstance().resetRouteForTesting();
+    });
+
+    // Verify clicking show password in menu reveal password.
+    test('showHidePasswordMenuItemSuccess', async function() {
+      passwordManager.data.leakedCredentials =
+          [autofill_test_util.makeCompromisedCredential(
+              'google.com', 'jdoerrie', 'LEAKED')];
+      passwordManager.plaintextPassword_ = 'test4';
+      const checkPasswordSection = createCheckPasswordSection();
+
+      await passwordManager.whenCalled('getCompromisedCredentials');
+
+      Polymer.dom.flush();
+      const listElements = checkPasswordSection.$.leakedPasswordList;
+      const node = listElements.children[1];
+      assertEquals('password', node.$.leakedPassword.type);
+      assertNotEquals('test4', node.$.leakedPassword.value);
+
+      // Open the more actions menu and click 'Show Password'.
+      node.$.more.click();
+      checkPasswordSection.$.menuShowPassword.click();
+
+      const interaction =
+          await passwordManager.whenCalled('recordPasswordCheckInteraction');
+
+      assertEquals(
+          PasswordManagerProxy.PasswordCheckInteraction.SHOW_PASSWORD,
+          interaction);
+      const {reason} =
+          await passwordManager.whenCalled('getPlaintextCompromisedPassword');
+      expectEquals(chrome.passwordsPrivate.PlaintextReason.VIEW, reason);
+      assertEquals('text', node.$.leakedPassword.type);
+      assertEquals('test4', node.$.leakedPassword.value);
+
+      // Open the more actions menu and click 'Hide Password'.
+      node.$.more.click();
+      checkPasswordSection.$.menuShowPassword.click();
+
+      assertEquals('password', node.$.leakedPassword.type);
+      assertNotEquals('test4', node.$.leakedPassword.value);
+    });
+
+    // Verify if getPlaintext fails password will not be shown
+    test('showHidePasswordMenuItemFail', async function() {
+      passwordManager.data.leakedCredentials =
+          [autofill_test_util.makeCompromisedCredential(
+              'google.com', 'jdoerrie', 'LEAKED')];
+      const checkPasswordSection = createCheckPasswordSection();
+      await passwordManager.whenCalled('getCompromisedCredentials');
+
+      Polymer.dom.flush();
+      const listElements = checkPasswordSection.$.leakedPasswordList;
+      const node = listElements.children[1];
+      assertEquals('password', node.$.leakedPassword.type);
+      assertNotEquals('test4', node.$.leakedPassword.value);
+
+      // Open the more actions menu and click 'Show Password'.
+      node.$.more.click();
+      checkPasswordSection.$.menuShowPassword.click();
+      await passwordManager.whenCalled('getPlaintextCompromisedPassword');
+      // Verify that password field didn't change
+      assertEquals('password', node.$.leakedPassword.type);
+      assertNotEquals('test4', node.$.leakedPassword.value);
     });
   });
   // #cr_define_end
diff --git a/chrome/test/data/webui/settings/test_password_manager_proxy.js b/chrome/test/data/webui/settings/test_password_manager_proxy.js
index da2169f..d79d1b7d 100644
--- a/chrome/test/data/webui/settings/test_password_manager_proxy.js
+++ b/chrome/test/data/webui/settings/test_password_manager_proxy.js
@@ -22,7 +22,7 @@
       'stopBulkPasswordCheck',
       'getCompromisedCredentials',
       'getPasswordCheckStatus',
-      'getPlainttextCompromisedPassword',
+      'getPlaintextCompromisedPassword',
       'changeCompromisedCredential',
       'removeCompromisedCredential',
       'recordPasswordCheckInteraction',
@@ -199,7 +199,7 @@
 
   /** @override */
   getPlaintextCompromisedPassword(credential, reason) {
-    this.methodCalled('getPlainttextCompromisedPassword', {credential, reason});
+    this.methodCalled('getPlaintextCompromisedPassword', {credential, reason});
     if (!this.plaintextPassword_) {
       return Promise.reject('Could not obtain plaintext password');
     }
diff --git a/components/captive_portal/OWNERS b/components/captive_portal/OWNERS
index 0ece4fb..7e5ec6e 100644
--- a/components/captive_portal/OWNERS
+++ b/components/captive_portal/OWNERS
@@ -1,4 +1,3 @@
-mmenke@chromium.org
-
+file://net/OWNERS
 # COMPONENT: Internals>Network
 # TEAM: net-dev@chromium.org
diff --git a/components/components_settings_strings.grdp b/components/components_settings_strings.grdp
index 59d3fec..6b26a09f 100644
--- a/components/components_settings_strings.grdp
+++ b/components/components_settings_strings.grdp
@@ -15,4 +15,7 @@
   <message name="IDS_OPTIONS_PROXIES_CONFIGURE_BUTTON" desc="The label of the 'Configure proxy settings' button">
     Change proxy settings...
   </message>
+  <message name="IDS_AUTOMATIC_DOWNLOADS_TAB_LABEL" desc="Header for multiple automatic downloads page on Content Settings dialog">
+    Automatic Downloads
+  </message>
 </grit-part>
diff --git a/components/page_info/BUILD.gn b/components/page_info/BUILD.gn
new file mode 100644
index 0000000..8fecfec4
--- /dev/null
+++ b/components/page_info/BUILD.gn
@@ -0,0 +1,54 @@
+if (is_android) {
+  import("//build/config/android/rules.gni")
+}
+
+static_library("page_info") {
+  sources = [
+    "page_info.cc",
+    "page_info.h",
+    "page_info_delegate.h",
+    "page_info_ui.cc",
+    "page_info_ui.h",
+    "page_info_ui_delegate.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/browsing_data/content",
+    "//components/content_settings/core/browser",
+    "//components/password_manager/core/browser",
+    "//components/permissions",
+    "//components/safe_browsing:buildflags",
+    "//components/safe_browsing/content/password_protection",
+    "//components/safe_browsing/content/password_protection:password_protection_metrics_util",
+    "//components/safe_browsing/core:csd_proto",
+    "//components/security_interstitials/content:security_interstitial_page",
+    "//components/security_interstitials/core",
+    "//components/security_state/core",
+    "//components/signin/public/identity_manager",
+    "//components/ssl_errors:ssl_errors",
+    "//components/strings:components_chromium_strings_grit",
+    "//components/strings:components_strings_grit",
+    "//components/subresource_filter/core/browser",
+    "//components/ukm/content",
+    "//components/url_formatter",
+    "//components/vector_icons:vector_icons",
+    "//content/public/browser:browser",
+    "//services/device/public/cpp:device_features",
+    "//services/metrics/public/cpp:ukm_builders",
+  ]
+
+  if (is_android) {
+    deps += [
+      "//components/browser_ui/util/android",
+      "//components/password_manager/core/browser:password_manager_java_enums_srcjar",
+      "//components/resources:android_resources",
+    ]
+  }
+}
+
+if (is_android) {
+  java_cpp_enum("page_info_action_javagen") {
+    sources = [ "page_info.h" ]
+  }
+}
diff --git a/components/page_info/DEPS b/components/page_info/DEPS
new file mode 100644
index 0000000..840067c
--- /dev/null
+++ b/components/page_info/DEPS
@@ -0,0 +1,33 @@
+include_rules = [
+  "+components/browser_ui/util/android/url_constants.h",
+  "+components/browsing_data/content/local_storage_helper.h",
+  "+components/content_settings/core",
+  "+components/password_manager/core/browser/password_manager_metrics_util.h",
+  "+components/permissions",
+  "+components/prefs/pref_service.h",
+  "+components/resources/android",
+  "+components/safe_browsing",
+  "+components/security_interstitials/content/stateful_ssl_host_state_delegate.h",
+  "+components/security_interstitials/core/common_string_util.h",
+  "+components/security_state/core",
+  "+components/signin/public/identity_manager/account_info.h",
+  "+components/ssl_errors/error_info.h",
+  "+components/strings/grit",
+  "+components/subresource_filter/core/browser/subresource_filter_features.h",
+  "+components/ukm/content/source_url_recorder.h",
+  "+components/url_formatter/elide_url.h",
+  "+components/vector_icons/vector_icons.h",
+  "+content/public/browser",
+  "+content/public/common",
+  "+media/base/media_switches.h",
+  "+net/cert/cert_status_flags.h",
+  "+net/cert/x509_certificate.h",
+  "+net/ssl/ssl_cipher_suite_names.h",
+  "+net/ssl/ssl_connection_status_flags.h",
+  "+ppapi/buildflags/buildflags.h",
+  "+services/device/public/cpp/device_features.h",
+  "+services/metrics/public/cpp",
+  "+third_party/boringssl/src/include/openssl/ssl.h",
+  "+ui/base/l10n/l10n_util.h",
+  "+ui/gfx",
+]
diff --git a/components/page_info/android/BUILD.gn b/components/page_info/android/BUILD.gn
index d2ca446..dfbb1ae 100644
--- a/components/page_info/android/BUILD.gn
+++ b/components/page_info/android/BUILD.gn
@@ -27,6 +27,21 @@
 
 android_resources("java_resources") {
   sources = [
+    "java/res/drawable-hdpi/pageinfo_bad.png",
+    "java/res/drawable-hdpi/pageinfo_good.png",
+    "java/res/drawable-hdpi/pageinfo_warning.png",
+    "java/res/drawable-mdpi/pageinfo_bad.png",
+    "java/res/drawable-mdpi/pageinfo_good.png",
+    "java/res/drawable-mdpi/pageinfo_warning.png",
+    "java/res/drawable-xhdpi/pageinfo_bad.png",
+    "java/res/drawable-xhdpi/pageinfo_good.png",
+    "java/res/drawable-xhdpi/pageinfo_warning.png",
+    "java/res/drawable-xxhdpi/pageinfo_bad.png",
+    "java/res/drawable-xxhdpi/pageinfo_good.png",
+    "java/res/drawable-xxhdpi/pageinfo_warning.png",
+    "java/res/drawable-xxxhdpi/pageinfo_bad.png",
+    "java/res/drawable-xxxhdpi/pageinfo_good.png",
+    "java/res/drawable-xxxhdpi/pageinfo_warning.png",
     "java/res/layout/cookie_controls_view.xml",
     "java/res/layout/page_info.xml",
     "java/res/layout/page_info_permission_row.xml",
diff --git a/chrome/android/java/res/drawable-hdpi/pageinfo_bad.png b/components/page_info/android/java/res/drawable-hdpi/pageinfo_bad.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/pageinfo_bad.png
rename to components/page_info/android/java/res/drawable-hdpi/pageinfo_bad.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-hdpi/pageinfo_good.png b/components/page_info/android/java/res/drawable-hdpi/pageinfo_good.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/pageinfo_good.png
rename to components/page_info/android/java/res/drawable-hdpi/pageinfo_good.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-hdpi/pageinfo_warning.png b/components/page_info/android/java/res/drawable-hdpi/pageinfo_warning.png
similarity index 100%
rename from chrome/android/java/res/drawable-hdpi/pageinfo_warning.png
rename to components/page_info/android/java/res/drawable-hdpi/pageinfo_warning.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/pageinfo_bad.png b/components/page_info/android/java/res/drawable-mdpi/pageinfo_bad.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/pageinfo_bad.png
rename to components/page_info/android/java/res/drawable-mdpi/pageinfo_bad.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/pageinfo_good.png b/components/page_info/android/java/res/drawable-mdpi/pageinfo_good.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/pageinfo_good.png
rename to components/page_info/android/java/res/drawable-mdpi/pageinfo_good.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/pageinfo_warning.png b/components/page_info/android/java/res/drawable-mdpi/pageinfo_warning.png
similarity index 100%
rename from chrome/android/java/res/drawable-mdpi/pageinfo_warning.png
rename to components/page_info/android/java/res/drawable-mdpi/pageinfo_warning.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/pageinfo_bad.png b/components/page_info/android/java/res/drawable-xhdpi/pageinfo_bad.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/pageinfo_bad.png
rename to components/page_info/android/java/res/drawable-xhdpi/pageinfo_bad.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/pageinfo_good.png b/components/page_info/android/java/res/drawable-xhdpi/pageinfo_good.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/pageinfo_good.png
rename to components/page_info/android/java/res/drawable-xhdpi/pageinfo_good.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/pageinfo_warning.png b/components/page_info/android/java/res/drawable-xhdpi/pageinfo_warning.png
similarity index 100%
rename from chrome/android/java/res/drawable-xhdpi/pageinfo_warning.png
rename to components/page_info/android/java/res/drawable-xhdpi/pageinfo_warning.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/pageinfo_bad.png b/components/page_info/android/java/res/drawable-xxhdpi/pageinfo_bad.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/pageinfo_bad.png
rename to components/page_info/android/java/res/drawable-xxhdpi/pageinfo_bad.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/pageinfo_good.png b/components/page_info/android/java/res/drawable-xxhdpi/pageinfo_good.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/pageinfo_good.png
rename to components/page_info/android/java/res/drawable-xxhdpi/pageinfo_good.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/pageinfo_warning.png b/components/page_info/android/java/res/drawable-xxhdpi/pageinfo_warning.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxhdpi/pageinfo_warning.png
rename to components/page_info/android/java/res/drawable-xxhdpi/pageinfo_warning.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/pageinfo_bad.png b/components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_bad.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/pageinfo_bad.png
rename to components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_bad.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/pageinfo_good.png b/components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_good.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/pageinfo_good.png
rename to components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_good.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/pageinfo_warning.png b/components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_warning.png
similarity index 100%
rename from chrome/android/java/res/drawable-xxxhdpi/pageinfo_warning.png
rename to components/page_info/android/java/res/drawable-xxxhdpi/pageinfo_warning.png
Binary files differ
diff --git a/chrome/browser/ui/page_info/page_info.cc b/components/page_info/page_info.cc
similarity index 97%
rename from chrome/browser/ui/page_info/page_info.cc
rename to components/page_info/page_info.cc
index 8965158c..8c34cb80 100644
--- a/chrome/browser/ui/page_info/page_info.cc
+++ b/components/page_info/page_info.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/page_info/page_info.h"
+#include "components/page_info/page_info.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -23,9 +23,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/browser/ui/page_info/page_info_delegate.h"
-#include "chrome/browser/ui/page_info/page_info_ui.h"
-#include "chrome/grit/theme_resources.h"
 #include "components/browser_ui/util/android/url_constants.h"
 #include "components/browsing_data/content/local_storage_helper.h"
 #include "components/content_settings/core/browser/content_settings_registry.h"
@@ -34,6 +31,8 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/content_settings/core/common/content_settings_utils.h"
+#include "components/page_info/page_info_delegate.h"
+#include "components/page_info/page_info_ui.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/permissions/chooser_context_base.h"
 #include "components/permissions/permission_decision_auto_blocker.h"
@@ -41,6 +40,9 @@
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permission_util.h"
+#if defined(OS_ANDROID)
+#include "components/resources/android/theme_resources.h"
+#endif
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/content/password_protection/metrics_util.h"
 #include "components/safe_browsing/content/password_protection/password_protection_service.h"
@@ -70,15 +72,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "url/origin.h"
 
-#if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/policy/policy_cert_service.h"
-#include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
-#endif
-
-#if !defined(OS_ANDROID)
-#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
-#endif
-
 using base::ASCIIToUTF16;
 using base::UTF16ToUTF8;
 using base::UTF8ToUTF16;
@@ -345,11 +338,11 @@
 
   // Record the total time the Page Info UI was open for all opens as well as
   // split between whether any action was taken.
-  base::UmaHistogramCustomTimes(
-      security_state::GetSecurityLevelHistogramName(
-          kPageInfoTimePrefix, security_level_),
-      base::TimeTicks::Now() - start_time_,
-      base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100);
+  base::UmaHistogramCustomTimes(security_state::GetSecurityLevelHistogramName(
+                                    kPageInfoTimePrefix, security_level_),
+                                base::TimeTicks::Now() - start_time_,
+                                base::TimeDelta::FromMilliseconds(1),
+                                base::TimeDelta::FromHours(1), 100);
   base::UmaHistogramCustomTimes(
       security_state::GetSafetyTipHistogramName(kPageInfoTimePrefix,
                                                 safety_tip_info_.status),
@@ -363,8 +356,8 @@
 
   if (did_perform_action_) {
     base::UmaHistogramCustomTimes(
-        security_state::GetSecurityLevelHistogramName(
-            kPageInfoTimeActionPrefix, security_level_),
+        security_state::GetSecurityLevelHistogramName(kPageInfoTimeActionPrefix,
+                                                      security_level_),
         base::TimeTicks::Now() - start_time_,
         base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1),
         100);
diff --git a/chrome/browser/ui/page_info/page_info.h b/components/page_info/page_info.h
similarity index 98%
rename from chrome/browser/ui/page_info/page_info.h
rename to components/page_info/page_info.h
index 1e2309d..3275d422 100644
--- a/chrome/browser/ui/page_info/page_info.h
+++ b/components/page_info/page_info.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_H_
-#define CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_H_
+#ifndef COMPONENTS_PAGE_INFO_PAGE_INFO_H_
+#define COMPONENTS_PAGE_INFO_PAGE_INFO_H_
 
 #include <vector>
 
@@ -342,4 +342,4 @@
   DISALLOW_COPY_AND_ASSIGN(PageInfo);
 };
 
-#endif  // CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_H_
+#endif  // COMPONENTS_PAGE_INFO_PAGE_INFO_H_
diff --git a/chrome/browser/ui/page_info/page_info_delegate.h b/components/page_info/page_info_delegate.h
similarity index 94%
rename from chrome/browser/ui/page_info/page_info_delegate.h
rename to components/page_info/page_info_delegate.h
index e9d7394..bc4d6ed 100644
--- a/chrome/browser/ui/page_info/page_info_delegate.h
+++ b/components/page_info/page_info_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_DELEGATE_H_
-#define CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_DELEGATE_H_
+#ifndef COMPONENTS_PAGE_INFO_PAGE_INFO_DELEGATE_H_
+#define COMPONENTS_PAGE_INFO_PAGE_INFO_DELEGATE_H_
 
 #include "base/strings/string16.h"
 #include "build/build_config.h"
@@ -88,4 +88,4 @@
   virtual bool IsContentDisplayedInVrHeadset() = 0;
 };
 
-#endif  // CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_DELEGATE_H_
+#endif  // COMPONENTS_PAGE_INFO_PAGE_INFO_DELEGATE_H_
diff --git a/chrome/browser/ui/page_info/page_info_ui.cc b/components/page_info/page_info_ui.cc
similarity index 96%
rename from chrome/browser/ui/page_info/page_info_ui.cc
rename to components/page_info/page_info_ui.cc
index 51585bc..7cf58fb8 100644
--- a/chrome/browser/ui/page_info/page_info_ui.cc
+++ b/components/page_info/page_info_ui.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/page_info/page_info_ui.h"
+#include "components/page_info/page_info_ui.h"
 
 #include <utility>
 
@@ -12,11 +12,11 @@
 #include "base/stl_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "chrome/browser/ui/page_info/page_info_ui_delegate.h"
-#include "chrome/grit/generated_resources.h"
+#include "components/page_info/page_info_ui_delegate.h"
 #include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_util.h"
+#include "components/prefs/pref_service.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/security_interstitials/core/common_string_util.h"
 #include "components/strings/grit/components_chromium_strings.h"
@@ -26,12 +26,9 @@
 #include "services/device/public/cpp/device_features.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
-
 #if defined(OS_ANDROID)
-#include "chrome/browser/android/android_theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 #else
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "components/prefs/pref_service.h"
 #include "media/base/media_switches.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
@@ -554,13 +551,13 @@
       icon = &vector_icons::kCookieIcon;
       break;
     case ContentSettingsType::IMAGES:
-      icon = &kPhotoIcon;
+      icon = &vector_icons::kPhotoIcon;
       break;
     case ContentSettingsType::JAVASCRIPT:
-      icon = &kCodeIcon;
+      icon = &vector_icons::kCodeIcon;
       break;
     case ContentSettingsType::POPUPS:
-      icon = &kLaunchIcon;
+      icon = &vector_icons::kLaunchIcon;
       break;
 #if BUILDFLAG(ENABLE_PLUGINS)
     case ContentSettingsType::PLUGINS:
@@ -580,30 +577,30 @@
       icon = &vector_icons::kVideocamIcon;
       break;
     case ContentSettingsType::AUTOMATIC_DOWNLOADS:
-      icon = &kFileDownloadIcon;
+      icon = &vector_icons::kFileDownloadIcon;
       break;
 #if defined(OS_CHROMEOS)
     case ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER:
-      icon = &kProtectedContentIcon;
+      icon = &vector_icons::kProtectedContentIcon;
       break;
 #endif
     case ContentSettingsType::MIDI_SYSEX:
       icon = &vector_icons::kMidiIcon;
       break;
     case ContentSettingsType::BACKGROUND_SYNC:
-      icon = &kSyncIcon;
+      icon = &vector_icons::kSyncIcon;
       break;
     case ContentSettingsType::ADS:
-      icon = &kAdsIcon;
+      icon = &vector_icons::kAdsIcon;
       break;
     case ContentSettingsType::SOUND:
-      icon = &kVolumeUpIcon;
+      icon = &vector_icons::kVolumeUpIcon;
       break;
     case ContentSettingsType::CLIPBOARD_READ_WRITE:
-      icon = &kPageInfoContentPasteIcon;
+      icon = &vector_icons::kPageInfoContentPasteIcon;
       break;
     case ContentSettingsType::SENSORS:
-      icon = &kSensorsIcon;
+      icon = &vector_icons::kSensorsIcon;
       break;
     case ContentSettingsType::USB_GUARD:
       icon = &vector_icons::kUsbIcon;
@@ -618,7 +615,7 @@
       icon = &vector_icons::kBluetoothScanningIcon;
       break;
     case ContentSettingsType::NATIVE_FILE_SYSTEM_WRITE_GUARD:
-      icon = &kSaveOriginalFileIcon;
+      icon = &vector_icons::kSaveOriginalFileIcon;
       break;
     case ContentSettingsType::VR:
     case ContentSettingsType::AR:
@@ -638,7 +635,7 @@
     return gfx::CreateVectorIconWithBadge(
         *icon, kVectorIconSize,
         color_utils::DeriveDefaultIconColor(related_text_color),
-        kBlockedBadgeIcon);
+        vector_icons::kBlockedBadgeIcon);
   }
   return gfx::CreateVectorIcon(
       *icon, kVectorIconSize,
@@ -676,7 +673,7 @@
     return gfx::CreateVectorIconWithBadge(
         *icon, kVectorIconSize,
         color_utils::DeriveDefaultIconColor(related_text_color),
-        kBlockedBadgeIcon);
+        vector_icons::kBlockedBadgeIcon);
   }
   return gfx::CreateVectorIcon(
       *icon, kVectorIconSize,
@@ -687,7 +684,7 @@
 const gfx::ImageSkia PageInfoUI::GetCertificateIcon(
     const SkColor related_text_color) {
   return gfx::CreateVectorIcon(
-      kCertificateIcon, kVectorIconSize,
+      vector_icons::kCertificateIcon, kVectorIconSize,
       color_utils::DeriveDefaultIconColor(related_text_color));
 }
 
diff --git a/chrome/browser/ui/page_info/page_info_ui.h b/components/page_info/page_info_ui.h
similarity index 97%
rename from chrome/browser/ui/page_info/page_info_ui.h
rename to components/page_info/page_info_ui.h
index bd354944..07907da 100644
--- a/chrome/browser/ui/page_info/page_info_ui.h
+++ b/components/page_info/page_info_ui.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_H_
-#define CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_H_
+#ifndef COMPONENTS_PAGE_INFO_PAGE_INFO_UI_H_
+#define COMPONENTS_PAGE_INFO_PAGE_INFO_UI_H_
 
 #include <memory>
 #include <string>
@@ -11,9 +11,9 @@
 
 #include "base/strings/string16.h"
 #include "build/build_config.h"
-#include "chrome/browser/ui/page_info/page_info.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
+#include "components/page_info/page_info.h"
 #include "components/permissions/chooser_context_base.h"
 #include "components/safe_browsing/buildflags.h"
 #include "ui/gfx/native_widget_types.h"
@@ -261,4 +261,4 @@
 typedef PageInfoUI::PermissionInfoList PermissionInfoList;
 typedef PageInfoUI::ChosenObjectInfoList ChosenObjectInfoList;
 
-#endif  // CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_H_
+#endif  // COMPONENTS_PAGE_INFO_PAGE_INFO_UI_H_
diff --git a/chrome/browser/ui/page_info/page_info_ui_delegate.h b/components/page_info/page_info_ui_delegate.h
similarity index 76%
rename from chrome/browser/ui/page_info/page_info_ui_delegate.h
rename to components/page_info/page_info_ui_delegate.h
index 7ce69098..e5865512 100644
--- a/chrome/browser/ui/page_info/page_info_ui_delegate.h
+++ b/components/page_info/page_info_ui_delegate.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
-#define CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
+#ifndef COMPONENTS_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
+#define COMPONENTS_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
 
 #include "build/build_config.h"
 #include "components/content_settings/core/common/content_settings_types.h"
@@ -21,4 +21,4 @@
       const GURL& url) = 0;
 };
 
-#endif  // CHROME_BROWSER_UI_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
+#endif  // COMPONENTS_PAGE_INFO_PAGE_INFO_UI_DELEGATE_H_
diff --git a/components/password_manager/core/browser/multi_store_password_save_manager.cc b/components/password_manager/core/browser/multi_store_password_save_manager.cc
index 5370ec53..46d4f39 100644
--- a/components/password_manager/core/browser/multi_store_password_save_manager.cc
+++ b/components/password_manager/core/browser/multi_store_password_save_manager.cc
@@ -9,7 +9,6 @@
 #include "components/password_manager/core/browser/form_saver_impl.h"
 #include "components/password_manager/core/browser/password_feature_manager_impl.h"
 #include "components/password_manager/core/browser/password_form_metrics_recorder.h"
-#include "components/password_manager/core/browser/password_generation_manager.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 
 using autofill::PasswordForm;
@@ -52,6 +51,50 @@
   return false;
 }
 
+PendingCredentialsState ResolvePendingCredentialsStates(
+    PendingCredentialsState profile_state,
+    PendingCredentialsState account_state) {
+  // Resolve the two states to a single canonical one, according to the
+  // following hierarchy:
+  // AUTOMATIC_SAVE > EQUAL_TO_SAVED_MATCH > UPDATE > NEW_LOGIN
+  // Note that UPDATE and NEW_LOGIN will result in an Update or Save bubble to
+  // be shown, while AUTOMATIC_SAVE and EQUAL_TO_SAVED_MATCH will result in a
+  // silent save/update.
+  // Some interesting cases:
+  // NEW_LOGIN means that store doesn't know about the credential yet. If the
+  // other store knows anything at all, then that always wins.
+  // EQUAL_TO_SAVED_MATCH vs UPDATE: This means one store had a match, the other
+  // had a mismatch (same username but different password). We want to silently
+  // update the mismatch, which EQUAL achieves (since it'll still result in an
+  // update to date_last_used, and updates always go to both stores).
+  // TODO(crbug.com/1012203): AUTOMATIC_SAVE vs EQUAL: We should still perform
+  // the silent update for EQUAL (so last_use_date gets updated).
+  // TODO(crbug.com/1012203): AUTOMATIC_SAVE vs UPDATE: What's the expected
+  // outcome? Currently we'll auto-save the PSL match and ignore the update
+  // (which isn't too bad, since on the next submission the update will become
+  // a silent update through EQUAL_TO_SAVED_MATCH).
+  // TODO(crbug.com/1012203): AUTOMATIC_SAVE vs AUTOMATIC_SAVE: Somehow make
+  // sure that the save goes to both stores.
+  if (profile_state == PendingCredentialsState::AUTOMATIC_SAVE ||
+      account_state == PendingCredentialsState::AUTOMATIC_SAVE) {
+    return PendingCredentialsState::AUTOMATIC_SAVE;
+  }
+  if (profile_state == PendingCredentialsState::EQUAL_TO_SAVED_MATCH ||
+      account_state == PendingCredentialsState::EQUAL_TO_SAVED_MATCH) {
+    return PendingCredentialsState::EQUAL_TO_SAVED_MATCH;
+  }
+  if (profile_state == PendingCredentialsState::UPDATE ||
+      account_state == PendingCredentialsState::UPDATE) {
+    return PendingCredentialsState::UPDATE;
+  }
+  if (profile_state == PendingCredentialsState::NEW_LOGIN ||
+      account_state == PendingCredentialsState::NEW_LOGIN) {
+    return PendingCredentialsState::NEW_LOGIN;
+  }
+  NOTREACHED();
+  return PendingCredentialsState::NONE;
+}
+
 }  // namespace
 
 MultiStorePasswordSaveManager::MultiStorePasswordSaveManager(
@@ -62,12 +105,6 @@
 
 MultiStorePasswordSaveManager::~MultiStorePasswordSaveManager() = default;
 
-FormSaver* MultiStorePasswordSaveManager::GetFormSaverForGeneration() {
-  return IsAccountStoreEnabled() && account_store_form_saver_
-             ? account_store_form_saver_.get()
-             : form_saver_.get();
-}
-
 void MultiStorePasswordSaveManager::SaveInternal(
     const std::vector<const PasswordForm*>& matches,
     const base::string16& old_password) {
@@ -141,10 +178,6 @@
   return result;
 }
 
-bool MultiStorePasswordSaveManager::IsAccountStoreEnabled() {
-  return client_->GetPasswordFeatureManager()->IsOptedInForAccountStorage();
-}
-
 void MultiStorePasswordSaveManager::MoveCredentialsToAccountStore() {
   // TODO(crbug.com/1032992): Moving credentials upon an update. FormFetch will
   // have an outdated credentials. Fix it if this turns out to be a product
@@ -183,4 +216,51 @@
   }
 }
 
+std::pair<const autofill::PasswordForm*, PendingCredentialsState>
+MultiStorePasswordSaveManager::FindSimilarSavedFormAndComputeState(
+    const PasswordForm& parsed_submitted_form) const {
+  const std::vector<const PasswordForm*> matches =
+      form_fetcher_->GetBestMatches();
+  const PasswordForm* similar_saved_form_from_profile_store =
+      password_manager_util::GetMatchForUpdating(parsed_submitted_form,
+                                                 ProfileStoreMatches(matches));
+  const PasswordForm* similar_saved_form_from_account_store =
+      password_manager_util::GetMatchForUpdating(parsed_submitted_form,
+                                                 AccountStoreMatches(matches));
+
+  // Compute the PendingCredentialsState (i.e. what to do - save, update, silent
+  // update) separately for the two stores.
+  PendingCredentialsState profile_state = ComputePendingCredentialsState(
+      parsed_submitted_form, similar_saved_form_from_profile_store);
+  PendingCredentialsState account_state = ComputePendingCredentialsState(
+      parsed_submitted_form, similar_saved_form_from_account_store);
+
+  // Resolve the two states to a single canonical one.
+  PendingCredentialsState state =
+      ResolvePendingCredentialsStates(profile_state, account_state);
+
+  // Choose which of the saved forms (if any) to use as the base for updating,
+  // based on which of the two states won the resolution.
+  // Note that if we got the same state for both stores, then it doesn't really
+  // matter which one we pick for updating, since the result will be the same
+  // anyway.
+  const PasswordForm* similar_saved_form = nullptr;
+  if (state == profile_state)
+    similar_saved_form = similar_saved_form_from_profile_store;
+  else if (state == account_state)
+    similar_saved_form = similar_saved_form_from_account_store;
+
+  return std::make_pair(similar_saved_form, state);
+}
+
+FormSaver* MultiStorePasswordSaveManager::GetFormSaverForGeneration() {
+  return IsAccountStoreEnabled() && account_store_form_saver_
+             ? account_store_form_saver_.get()
+             : form_saver_.get();
+}
+
+bool MultiStorePasswordSaveManager::IsAccountStoreEnabled() {
+  return client_->GetPasswordFeatureManager()->IsOptedInForAccountStorage();
+}
+
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/multi_store_password_save_manager.h b/components/password_manager/core/browser/multi_store_password_save_manager.h
index a7bceef..2582472 100644
--- a/components/password_manager/core/browser/multi_store_password_save_manager.h
+++ b/components/password_manager/core/browser/multi_store_password_save_manager.h
@@ -42,6 +42,9 @@
   void MoveCredentialsToAccountStore() override;
 
  protected:
+  std::pair<const autofill::PasswordForm*, PendingCredentialsState>
+  FindSimilarSavedFormAndComputeState(
+      const autofill::PasswordForm& parsed_submitted_form) const override;
   FormSaver* GetFormSaverForGeneration() override;
 
  private:
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index bb7f3f8..5403015 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -383,52 +383,31 @@
   if (!submitted_form.federation_origin.opaque())
     return nullptr;
 
-  // Find any form(s) that match by username or by username+password.
-  const PasswordForm* username_password_match = nullptr;
-  const PasswordForm* username_match = nullptr;
-  const base::string16& password_to_save =
-      submitted_form.new_password_value.empty()
-          ? submitted_form.password_value
-          : submitted_form.new_password_value;
-  for (const PasswordForm* form : credentials) {
-    if (!username_password_match &&
-        form->username_value == submitted_form.username_value &&
-        form->password_value == password_to_save) {
-      username_password_match = form;
-    }
-    if (!username_match &&
-        form->username_value == submitted_form.username_value) {
-      username_match = form;
-    }
-  }
+  // Try to return form with matching |username_value|.
+  const PasswordForm* username_match =
+      FindFormByUsername(credentials, submitted_form.username_value);
+  if (username_match) {
+    if (!username_match->is_public_suffix_match)
+      return username_match;
 
-  // First, try to return a non-PSL match. Prefer if the password also matches.
-  if (username_password_match &&
-      !username_password_match->is_public_suffix_match) {
-    return username_password_match;
+    const auto& password_to_save = submitted_form.new_password_value.empty()
+                                       ? submitted_form.password_value
+                                       : submitted_form.new_password_value;
+    // Normally, the copy of the PSL matched credentials, adapted for the
+    // current domain, is saved automatically without asking the user, because
+    // the copy likely represents the same account, i.e., the one for which
+    // the user already agreed to store a password.
+    //
+    // However, if the user changes the suggested password, it might indicate
+    // that the autofilled credentials and |submitted_password_form|
+    // actually correspond to two different accounts (see
+    // http://crbug.com/385619).
+    return password_to_save == username_match->password_value ? username_match
+                                                              : nullptr;
   }
-  if (username_match && !username_match->is_public_suffix_match)
-    return username_match;
-
-  // All matches (if there are any) are PSL matches. We only want to return
-  // PSL matches if the password also matches:
-  //
-  // Normally, the copy of the PSL matched credentials, adapted for the
-  // current domain, is saved automatically without asking the user, because
-  // the copy likely represents the same account, i.e., the one for which
-  // the user already agreed to store a password.
-  //
-  // However, if the user changes the suggested password, it might indicate
-  // that the autofilled credentials and |submitted_password_form|
-  // actually correspond to two different accounts (see
-  // http://crbug.com/385619).
-  if (username_password_match)
-    return username_password_match;
 
   // Next attempt is to find a match by password value. It should not be tried
   // when the username was actually detected.
-  if (username_match)
-    return nullptr;
   if (submitted_form.type == PasswordForm::Type::kApi ||
       !submitted_form.username_value.empty()) {
     return nullptr;
diff --git a/components/password_manager/core/browser/password_manager_util_unittest.cc b/components/password_manager/core/browser/password_manager_util_unittest.cc
index f5dc0af..3928c582 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -392,24 +392,6 @@
             GetMatchForUpdating(parsed, {&stored3, &stored2, &stored1}));
 }
 
-TEST(PasswordManagerUtil,
-     GetMatchForUpdating_DuplicateUsernamePickMatchingPassword) {
-  // Two credentials with a matching username are stored. One has the same
-  // password as well, the other has a different password. This can happen when
-  // one credential comes from the profile store, the other from the password
-  // store.
-  autofill::PasswordForm parsed = GetTestCredential();
-  autofill::PasswordForm stored_samepw = parsed;
-  autofill::PasswordForm stored_diffpw = parsed;
-  stored_diffpw.password_value = base::ASCIIToUTF16("different");
-
-  // The order of the two stored credentials shouldn't matter.
-  EXPECT_EQ(&stored_samepw,
-            GetMatchForUpdating(parsed, {&stored_diffpw, &stored_samepw}));
-  EXPECT_EQ(&stored_samepw,
-            GetMatchForUpdating(parsed, {&stored_samepw, &stored_diffpw}));
-}
-
 TEST(PasswordManagerUtil, MakeNormalizedBlacklistedForm_Android) {
   autofill::PasswordForm blacklisted_credential = MakeNormalizedBlacklistedForm(
       password_manager::PasswordStore::FormDigest(GetTestAndroidCredential()));
diff --git a/components/password_manager/core/browser/password_save_manager_impl.cc b/components/password_manager/core/browser/password_save_manager_impl.cc
index b2856654..7f1dc904 100644
--- a/components/password_manager/core/browser/password_save_manager_impl.cc
+++ b/components/password_manager/core/browser/password_save_manager_impl.cc
@@ -151,104 +151,6 @@
   votes_uploader_ = votes_uploader;
 }
 
-// static
-PendingCredentialsState PasswordSaveManagerImpl::ComputePendingCredentialsState(
-    const PasswordForm& parsed_submitted_form,
-    const PasswordForm* similar_saved_form) {
-  ValueElementPair password_to_save(PasswordToSave(parsed_submitted_form));
-  // Check if there are previously saved credentials (that were available to
-  // autofilling) matching the actually submitted credentials.
-  if (!similar_saved_form)
-    return PendingCredentialsState::NEW_LOGIN;
-
-  // A similar credential exists in the store already.
-  if (similar_saved_form->password_value != password_to_save.first)
-    return PendingCredentialsState::UPDATE;
-
-  // If the autofilled credentials were a PSL match, store a copy with the
-  // current origin and signon realm. This ensures that on the next visit, a
-  // precise match is found.
-  if (similar_saved_form->is_public_suffix_match)
-    return PendingCredentialsState::AUTOMATIC_SAVE;
-
-  return PendingCredentialsState::EQUAL_TO_SAVED_MATCH;
-}
-
-// static
-PasswordForm PasswordSaveManagerImpl::BuildPendingCredentials(
-    PendingCredentialsState pending_credentials_state,
-    const PasswordForm& parsed_submitted_form,
-    const FormData& observed_form,
-    const FormData& submitted_form,
-    const base::Optional<base::string16>& generated_password,
-    bool is_http_auth,
-    bool is_credential_api_save,
-    const PasswordForm* similar_saved_form) {
-  PasswordForm pending_credentials;
-
-  ValueElementPair password_to_save(PasswordToSave(parsed_submitted_form));
-
-  switch (pending_credentials_state) {
-    case PendingCredentialsState::NEW_LOGIN:
-      // No stored credentials can be matched to the submitted form. Offer to
-      // save new credentials.
-      pending_credentials = PendingCredentialsForNewCredentials(
-          parsed_submitted_form, observed_form, password_to_save.second,
-          is_http_auth, is_credential_api_save);
-      break;
-    case PendingCredentialsState::EQUAL_TO_SAVED_MATCH:
-    case PendingCredentialsState::UPDATE:
-      pending_credentials = *similar_saved_form;
-      break;
-    case PendingCredentialsState::AUTOMATIC_SAVE:
-      pending_credentials = *similar_saved_form;
-
-      // Update credential to reflect that it has been used for submission.
-      // If this isn't updated, then password generation uploads are off for
-      // sites where PSL matching is required to fill the login form, as two
-      // PASSWORD votes are uploaded per saved password instead of one.
-      password_manager_util::UpdateMetadataForUsage(&pending_credentials);
-
-      // Update |pending_credentials| in order to be able correctly save it.
-      pending_credentials.origin = parsed_submitted_form.origin;
-      pending_credentials.signon_realm = parsed_submitted_form.signon_realm;
-      pending_credentials.action = parsed_submitted_form.action;
-      break;
-    case PendingCredentialsState::NONE:
-      NOTREACHED();
-      break;
-  }
-
-  pending_credentials.password_value =
-      generated_password.value_or(password_to_save.first);
-  pending_credentials.date_last_used = base::Time::Now();
-  pending_credentials.form_has_autofilled_value =
-      parsed_submitted_form.form_has_autofilled_value;
-  pending_credentials.all_possible_passwords =
-      parsed_submitted_form.all_possible_passwords;
-  CopyFieldPropertiesMasks(submitted_form, &pending_credentials.form_data);
-
-  // If we're dealing with an API-driven provisionally saved form, then take
-  // the server provided values. We don't do this for non-API forms, as
-  // those will never have those members set.
-  if (parsed_submitted_form.type == PasswordForm::Type::kApi) {
-    pending_credentials.skip_zero_click = parsed_submitted_form.skip_zero_click;
-    pending_credentials.display_name = parsed_submitted_form.display_name;
-    pending_credentials.federation_origin =
-        parsed_submitted_form.federation_origin;
-    pending_credentials.icon_url = parsed_submitted_form.icon_url;
-    // It's important to override |signon_realm| for federated credentials
-    // because it has format "federation://" + origin_host + "/" +
-    // federation_host
-    pending_credentials.signon_realm = parsed_submitted_form.signon_realm;
-  }
-
-  if (generated_password.has_value())
-    pending_credentials.type = PasswordForm::Type::kGenerated;
-
-  return pending_credentials;
-}
-
 void PasswordSaveManagerImpl::CreatePendingCredentials(
     const PasswordForm& parsed_submitted_form,
     const FormData& observed_form,
@@ -257,17 +159,9 @@
     bool is_credential_api_save) {
   DCHECK(votes_uploader_);
 
-  // This function might be called multiple times so set variables that are
-  // changed in this function to initial states.
-  pending_credentials_ = PasswordForm();
-  votes_uploader_->set_password_overridden(false);
-
-  const PasswordForm* similar_saved_form =
-      password_manager_util::GetMatchForUpdating(
-          parsed_submitted_form, form_fetcher_->GetBestMatches());
-
-  pending_credentials_state_ =
-      ComputePendingCredentialsState(parsed_submitted_form, similar_saved_form);
+  const PasswordForm* similar_saved_form;
+  std::tie(similar_saved_form, pending_credentials_state_) =
+      FindSimilarSavedFormAndComputeState(parsed_submitted_form);
 
   base::Optional<base::string16> generated_password;
   if (HasGeneratedPassword())
@@ -278,6 +172,7 @@
       submitted_form, generated_password, is_http_auth, is_credential_api_save,
       similar_saved_form);
 
+  votes_uploader_->set_password_overridden(false);
   switch (pending_credentials_state_) {
     case PendingCredentialsState::NEW_LOGIN: {
       // Generate username correction votes.
@@ -445,17 +340,127 @@
   return result;
 }
 
+// static
+PendingCredentialsState PasswordSaveManagerImpl::ComputePendingCredentialsState(
+    const PasswordForm& parsed_submitted_form,
+    const PasswordForm* similar_saved_form) {
+  ValueElementPair password_to_save(PasswordToSave(parsed_submitted_form));
+  // Check if there are previously saved credentials (that were available to
+  // autofilling) matching the actually submitted credentials.
+  if (!similar_saved_form)
+    return PendingCredentialsState::NEW_LOGIN;
+
+  // A similar credential exists in the store already.
+  if (similar_saved_form->password_value != password_to_save.first)
+    return PendingCredentialsState::UPDATE;
+
+  // If the autofilled credentials were a PSL match, store a copy with the
+  // current origin and signon realm. This ensures that on the next visit, a
+  // precise match is found.
+  if (similar_saved_form->is_public_suffix_match)
+    return PendingCredentialsState::AUTOMATIC_SAVE;
+
+  return PendingCredentialsState::EQUAL_TO_SAVED_MATCH;
+}
+
+// static
+PasswordForm PasswordSaveManagerImpl::BuildPendingCredentials(
+    PendingCredentialsState pending_credentials_state,
+    const PasswordForm& parsed_submitted_form,
+    const FormData& observed_form,
+    const FormData& submitted_form,
+    const base::Optional<base::string16>& generated_password,
+    bool is_http_auth,
+    bool is_credential_api_save,
+    const PasswordForm* similar_saved_form) {
+  PasswordForm pending_credentials;
+
+  ValueElementPair password_to_save(PasswordToSave(parsed_submitted_form));
+
+  switch (pending_credentials_state) {
+    case PendingCredentialsState::NEW_LOGIN:
+      // No stored credentials can be matched to the submitted form. Offer to
+      // save new credentials.
+      pending_credentials = PendingCredentialsForNewCredentials(
+          parsed_submitted_form, observed_form, password_to_save.second,
+          is_http_auth, is_credential_api_save);
+      break;
+    case PendingCredentialsState::EQUAL_TO_SAVED_MATCH:
+    case PendingCredentialsState::UPDATE:
+      pending_credentials = *similar_saved_form;
+      break;
+    case PendingCredentialsState::AUTOMATIC_SAVE:
+      pending_credentials = *similar_saved_form;
+
+      // Update credential to reflect that it has been used for submission.
+      // If this isn't updated, then password generation uploads are off for
+      // sites where PSL matching is required to fill the login form, as two
+      // PASSWORD votes are uploaded per saved password instead of one.
+      password_manager_util::UpdateMetadataForUsage(&pending_credentials);
+
+      // Update |pending_credentials| in order to be able correctly save it.
+      pending_credentials.origin = parsed_submitted_form.origin;
+      pending_credentials.signon_realm = parsed_submitted_form.signon_realm;
+      pending_credentials.action = parsed_submitted_form.action;
+      break;
+    case PendingCredentialsState::NONE:
+      NOTREACHED();
+      break;
+  }
+
+  pending_credentials.password_value =
+      generated_password.value_or(password_to_save.first);
+  pending_credentials.date_last_used = base::Time::Now();
+  pending_credentials.form_has_autofilled_value =
+      parsed_submitted_form.form_has_autofilled_value;
+  pending_credentials.all_possible_passwords =
+      parsed_submitted_form.all_possible_passwords;
+  CopyFieldPropertiesMasks(submitted_form, &pending_credentials.form_data);
+
+  // If we're dealing with an API-driven provisionally saved form, then take
+  // the server provided values. We don't do this for non-API forms, as
+  // those will never have those members set.
+  if (parsed_submitted_form.type == PasswordForm::Type::kApi) {
+    pending_credentials.skip_zero_click = parsed_submitted_form.skip_zero_click;
+    pending_credentials.display_name = parsed_submitted_form.display_name;
+    pending_credentials.federation_origin =
+        parsed_submitted_form.federation_origin;
+    pending_credentials.icon_url = parsed_submitted_form.icon_url;
+    // It's important to override |signon_realm| for federated credentials
+    // because it has format "federation://" + origin_host + "/" +
+    // federation_host
+    pending_credentials.signon_realm = parsed_submitted_form.signon_realm;
+  }
+
+  if (generated_password.has_value())
+    pending_credentials.type = PasswordForm::Type::kGenerated;
+
+  return pending_credentials;
+}
+
+std::pair<const autofill::PasswordForm*, PendingCredentialsState>
+PasswordSaveManagerImpl::FindSimilarSavedFormAndComputeState(
+    const PasswordForm& parsed_submitted_form) const {
+  const PasswordForm* similar_saved_form =
+      password_manager_util::GetMatchForUpdating(
+          parsed_submitted_form, form_fetcher_->GetBestMatches());
+  return std::make_pair(similar_saved_form,
+                        ComputePendingCredentialsState(parsed_submitted_form,
+                                                       similar_saved_form));
+}
+
 void PasswordSaveManagerImpl::SavePendingToStore(
     const PasswordForm& parsed_submitted_form,
     bool update) {
-  const PasswordForm* saved_form = password_manager_util::GetMatchForUpdating(
-      parsed_submitted_form, form_fetcher_->GetBestMatches());
+  const PasswordForm* similar_saved_form =
+      FindSimilarSavedFormAndComputeState(parsed_submitted_form).first;
   if ((update || IsPasswordUpdate()) &&
       !pending_credentials_.IsFederatedCredential()) {
-    DCHECK(saved_form);
+    DCHECK(similar_saved_form);
   }
-  base::string16 old_password =
-      saved_form ? saved_form->password_value : base::string16();
+  base::string16 old_password = similar_saved_form
+                                    ? similar_saved_form->password_value
+                                    : base::string16();
   if (HasGeneratedPassword()) {
     generation_manager_->CommitGeneratedPassword(
         pending_credentials_, form_fetcher_->GetAllRelevantMatches(),
diff --git a/components/password_manager/core/browser/password_save_manager_impl.h b/components/password_manager/core/browser/password_save_manager_impl.h
index b7f6986..328d13e 100644
--- a/components/password_manager/core/browser/password_save_manager_impl.h
+++ b/components/password_manager/core/browser/password_save_manager_impl.h
@@ -97,6 +97,10 @@
       bool is_credential_api_save,
       const autofill::PasswordForm* similar_saved_form);
 
+  virtual std::pair<const autofill::PasswordForm*, PendingCredentialsState>
+  FindSimilarSavedFormAndComputeState(
+      const autofill::PasswordForm& parsed_submitted_form) const;
+
   // Returns the form_saver to be used for generated passwords. Subclasses will
   // override this method to provide different logic for get the form saver.
   virtual FormSaver* GetFormSaverForGeneration();
diff --git a/components/permissions/BUILD.gn b/components/permissions/BUILD.gn
index d70ee05f..2a4eb7e 100644
--- a/components/permissions/BUILD.gn
+++ b/components/permissions/BUILD.gn
@@ -61,8 +61,6 @@
       "android/permission_dialog_delegate.h",
       "android/permission_prompt_android.cc",
       "android/permission_prompt_android.h",
-      "android/resource_id.h",
-      "android/theme_resources.h",
       "contexts/geolocation_permission_context_android.cc",
       "contexts/geolocation_permission_context_android.h",
     ]
@@ -72,6 +70,7 @@
       "//components/location/android:settings",
       "//components/permissions/android:jni_headers",
       "//components/prefs",
+      "//components/resources:android_resources",
       "//ui/android",
     ]
   } else {
@@ -107,6 +106,9 @@
     "//ui/gfx",
     "//url",
   ]
+  if (is_android) {
+    deps += [ "//components/resources:android_resources" ]
+  }
 }
 
 source_set("unit_tests") {
diff --git a/components/permissions/DEPS b/components/permissions/DEPS
index 701faa8..24f11b1f 100644
--- a/components/permissions/DEPS
+++ b/components/permissions/DEPS
@@ -2,6 +2,7 @@
   "+components/content_settings/core",
   "+components/keyed_service/content",
   "+components/keyed_service/core",
+  "+components/resources/android",
   "+components/strings",
   "+components/sync_preferences",
   "+components/ukm/content",
diff --git a/components/permissions/android/permission_prompt_android.cc b/components/permissions/android/permission_prompt_android.cc
index 57730f9..852b148 100644
--- a/components/permissions/android/permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt_android.cc
@@ -9,9 +9,9 @@
 #include "components/infobars/core/infobar.h"
 #include "components/infobars/core/infobar_manager.h"
 #include "components/permissions/android/permission_dialog_delegate.h"
-#include "components/permissions/android/theme_resources.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permissions_client.h"
+#include "components/resources/android/theme_resources.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/url_formatter/elide_url.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/components/permissions/permission_request_impl.cc b/components/permissions/permission_request_impl.cc
index 54960201..e740cb7 100644
--- a/components/permissions/permission_request_impl.cc
+++ b/components/permissions/permission_request_impl.cc
@@ -13,7 +13,7 @@
 #include "ui/base/l10n/l10n_util.h"
 
 #if defined(OS_ANDROID)
-#include "components/permissions/android/theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 #include "media/base/android/media_drm_bridge.h"
 #else
 #include "components/vector_icons/vector_icons.h"
diff --git a/components/permissions/quota_permission_context_impl.cc b/components/permissions/quota_permission_context_impl.cc
index d145631b..fff8d90 100644
--- a/components/permissions/quota_permission_context_impl.cc
+++ b/components/permissions/quota_permission_context_impl.cc
@@ -27,7 +27,7 @@
 #include "url/gurl.h"
 
 #if defined(OS_ANDROID)
-#include "components/permissions/android/theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 #else
 #include "components/vector_icons/vector_icons.h"
 #endif
diff --git a/components/permissions/test/mock_permission_request.cc b/components/permissions/test/mock_permission_request.cc
index 7144503d..0e62cbb 100644
--- a/components/permissions/test/mock_permission_request.cc
+++ b/components/permissions/test/mock_permission_request.cc
@@ -8,7 +8,7 @@
 #include "base/strings/utf_string_conversions.h"
 
 #if defined(OS_ANDROID)
-#include "components/permissions/android/theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 #else
 #include "components/vector_icons/vector_icons.h"
 #endif
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn
index db5699e..49a0480 100644
--- a/components/resources/BUILD.gn
+++ b/components/resources/BUILD.gn
@@ -7,6 +7,10 @@
 import("//printing/buildflags/buildflags.gni")
 import("//tools/grit/grit_rule.gni")
 
+if (is_android) {
+  import("//build/config/android/rules.gni")
+}
+
 about_credits_file = "$target_gen_dir/about_credits.html"
 
 group("resources") {
@@ -100,3 +104,13 @@
     rebase_path(about_credits_file, root_build_dir),
   ]
 }
+
+if (is_android) {
+  source_set("android_resources") {
+    sources = [
+      "android/page_info_resource_id.h",
+      "android/permissions_resource_id.h",
+      "android/theme_resources.h",
+    ]
+  }
+}
diff --git a/components/resources/android/page_info_resource_id.h b/components/resources/android/page_info_resource_id.h
new file mode 100644
index 0000000..f1612bd
--- /dev/null
+++ b/components/resources/android/page_info_resource_id.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file maps resource IDs to Android resource IDs.
+
+// Presence of regular include guards is checked by:
+// 1. cpplint
+// 2. a custom presubmit in src/PRESUBMIT.py
+// 3. clang (but it only checks the guard is correct if present)
+// Disable the first two with these magic comments:
+// NOLINT(build/header_guard)
+// no-include-guard-because-multiply-included
+
+// LINK_RESOURCE_ID is used for IDs that come from a .grd file.
+#ifndef LINK_RESOURCE_ID
+#error "LINK_RESOURCE_ID should be defined before including this file"
+#endif
+// DECLARE_RESOURCE_ID is used for IDs that don't have .grd entries, and
+// are only declared in this file.
+#ifndef DECLARE_RESOURCE_ID
+#error "DECLARE_RESOURCE_ID should be defined before including this file"
+#endif
+
+// PageInfoUI images, used in ConnectionInfoPopup
+// Good:
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_GOOD, R.drawable.pageinfo_good)
+// Warnings:
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_WARNING_MINOR, R.drawable.pageinfo_warning)
+// Bad:
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_BAD, R.drawable.pageinfo_bad)
+// Should never occur, use warning just in case:
+// Enterprise managed: ChromeOS only.
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_ENTERPRISE_MANAGED,
+                    R.drawable.pageinfo_warning)
+// Info: Only shown on chrome:// urls, which don't show the connection info
+// popup.
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_INFO, R.drawable.pageinfo_warning)
+// Major warning: Used on insecure pages, which don't show the connection info
+// popup.
+DECLARE_RESOURCE_ID(IDR_PAGEINFO_WARNING_MAJOR, R.drawable.pageinfo_warning)
diff --git a/components/permissions/android/resource_id.h b/components/resources/android/permissions_resource_id.h
similarity index 100%
rename from components/permissions/android/resource_id.h
rename to components/resources/android/permissions_resource_id.h
diff --git a/components/permissions/android/theme_resources.h b/components/resources/android/theme_resources.h
similarity index 66%
rename from components/permissions/android/theme_resources.h
rename to components/resources/android/theme_resources.h
index 1d73e9f2..a84712d 100644
--- a/components/permissions/android/theme_resources.h
+++ b/components/resources/android/theme_resources.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_PERMISSIONS_ANDROID_THEME_RESOURCES_H_
-#define COMPONENTS_PERMISSIONS_ANDROID_THEME_RESOURCES_H_
+#ifndef COMPONENTS_RESOURCES_ANDROID_THEME_RESOURCES_H_
+#define COMPONENTS_RESOURCES_ANDROID_THEME_RESOURCES_H_
 
 // LINK_RESOURCE_ID will use an ID defined by grit, so no-op.
 #define LINK_RESOURCE_ID(c_id, java_id)
@@ -11,16 +11,15 @@
 #define DECLARE_RESOURCE_ID(c_id, java_id) c_id,
 
 enum {
-  // TODO: If more components need to use resource ID mapping, move this to a
-  // common location.
   // Not used; just provides a starting value for the enum. These must
   // not conflict with IDR_* values, which top out at 2^16 - 1.
   ANDROID_COMPONENTS_RESOURCE_ID_NONE = 1 << 16,
-#include "components/permissions/android/resource_id.h"
+#include "components/resources/android/page_info_resource_id.h"
+#include "components/resources/android/permissions_resource_id.h"
   ANDROID_COMPONENTS_RESOURCE_ID_MAX,
 };
 
 #undef LINK_RESOURCE_ID
 #undef DECLARE_RESOURCE_ID
 
-#endif  // COMPONENTS_PERMISSIONS_ANDROID_THEME_RESOURCES_H_
+#endif  // COMPONENTS_RESOURCES_ANDROID_THEME_RESOURCES_H_
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
index 710127c..15a166a3 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java
@@ -46,7 +46,6 @@
  */
 public class AccountManagerFacade {
     private static final String TAG = "Sync_Signin";
-    public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
 
     /**
      * An account feature (corresponding to a Gaia service flag) that specifies whether the account
@@ -129,15 +128,6 @@
     }
 
     /**
-     * Creates an Account object for the given name.
-     * TODO(https://crbug.com/1064877): Move this method to AccountUtils
-     */
-    @AnyThread
-    public static Account createAccountFromName(String name) {
-        return new Account(name, GOOGLE_ACCOUNT_TYPE);
-    }
-
-    /**
      * Runs a callback after the account list cache is populated. In the callback
      * {@link #getGoogleAccounts()} and similar methods are guaranteed to return instantly (without
      * blocking and waiting for the cache to be populated). If the cache has already been populated,
@@ -296,7 +286,7 @@
     public boolean hasGoogleAccountAuthenticator() {
         AuthenticatorDescription[] descs = mDelegate.getAuthenticatorTypes();
         for (AuthenticatorDescription desc : descs) {
-            if (GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
+            if (AccountUtils.GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
         }
         return false;
     }
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountUtils.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountUtils.java
index 7007a48..7723c75 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountUtils.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountUtils.java
@@ -4,17 +4,31 @@
 
 package org.chromium.components.signin;
 
+import android.accounts.Account;
+
+import androidx.annotation.VisibleForTesting;
+
 import java.util.Locale;
 import java.util.regex.Pattern;
 
 /**
  * AccountUtils groups some static util methods for account.
  */
-class AccountUtils {
+public class AccountUtils {
     private static final Pattern AT_SYMBOL = Pattern.compile("@");
     private static final String GMAIL_COM = "gmail.com";
     private static final String GOOGLEMAIL_COM = "googlemail.com";
 
+    @VisibleForTesting
+    public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
+
+    /**
+     * Creates an Account object for the given name.
+     */
+    public static Account createAccountFromName(String name) {
+        return new Account(name, GOOGLE_ACCOUNT_TYPE);
+    }
+
     /**
      * Canonicalizes the account name.
      */
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
index fbbab80..730e5a8 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java
@@ -40,7 +40,7 @@
             long nativeAccountFetcherService, String accountId, String accountName) {
         mNativeAccountFetcherService = nativeAccountFetcherService;
         mAccountId = accountId;
-        mAccount = AccountManagerFacade.createAccountFromName(accountName);
+        mAccount = AccountUtils.createAccountFromName(accountName);
 
         // Register for notifications about flag changes in the future.
         mAccountFlagsChangedReceiver = new BroadcastReceiver() {
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChromeSigninController.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChromeSigninController.java
index 4327b1bd..f12d1ad 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChromeSigninController.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/ChromeSigninController.java
@@ -42,7 +42,7 @@
         if (syncAccountName == null) {
             return null;
         }
-        return AccountManagerFacade.createAccountFromName(syncAccountName);
+        return AccountUtils.createAccountFromName(syncAccountName);
     }
 
     public boolean isSignedIn() {
diff --git a/components/signin/core/browser/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java b/components/signin/core/browser/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
index 3763244e..4fb1f3d8 100644
--- a/components/signin/core/browser/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
+++ b/components/signin/core/browser/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
@@ -136,7 +136,7 @@
     @Override
     public String getAuthToken(Account account, String authTokenScope) throws AuthException {
         assert !ThreadUtils.runningOnUiThread();
-        assert AccountManagerFacade.GOOGLE_ACCOUNT_TYPE.equals(account.type);
+        assert AccountUtils.GOOGLE_ACCOUNT_TYPE.equals(account.type);
         try {
             return GoogleAuthUtil.getTokenWithNotification(
                     ContextUtils.getApplicationContext(), account, authTokenScope, null);
diff --git a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
index 331152a..bf5b7f52 100644
--- a/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
+++ b/components/signin/core/browser/android/javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
@@ -19,8 +19,8 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.components.signin.AccountManagerDelegate;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.AccountsChangeObserver;
 import org.chromium.components.signin.AuthException;
 import org.chromium.components.signin.ProfileDataSource;
@@ -287,8 +287,8 @@
 
     @Override
     public AuthenticatorDescription[] getAuthenticatorTypes() {
-        AuthenticatorDescription googleAuthenticator = new AuthenticatorDescription(
-                AccountManagerFacade.GOOGLE_ACCOUNT_TYPE, "p1", 0, 0, 0, 0);
+        AuthenticatorDescription googleAuthenticator =
+                new AuthenticatorDescription(AccountUtils.GOOGLE_ACCOUNT_TYPE, "p1", 0, 0, 0, 0);
 
         return new AuthenticatorDescription[] {googleAuthenticator};
     }
diff --git a/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java b/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
index 15f7d6a9..8c474e3 100644
--- a/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
+++ b/components/signin/core/browser/android/junit/src/org/chromium/components/signin/test/AccountManagerFacadeRobolectricTest.java
@@ -29,6 +29,7 @@
 import org.chromium.components.signin.AccountManagerDelegateException;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChildAccountStatus;
 import org.chromium.components.signin.ProfileDataSource;
 import org.chromium.components.signin.test.util.AccountHolder;
@@ -241,7 +242,7 @@
     }
 
     private Account addTestAccount(String accountName, String... features) {
-        Account account = AccountManagerFacade.createAccountFromName(accountName);
+        Account account = AccountUtils.createAccountFromName(accountName);
         AccountHolder holder = AccountHolder.builder(account)
                                        .alwaysAccept(true)
                                        .featureSet(new HashSet<>(Arrays.asList(features)))
diff --git a/components/signin/public/android/DEPS b/components/signin/public/android/DEPS
index a373d81..93b627e 100644
--- a/components/signin/public/android/DEPS
+++ b/components/signin/public/android/DEPS
@@ -1,6 +1,6 @@
 specific_include_rules = {
   "CoreAccountInfo.java": [
-    "+components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java",
+    "+components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountUtils.java",
   ],
   "IdentityManager.java": [
     "+components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java",
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/base/CoreAccountInfo.java b/components/signin/public/android/java/src/org/chromium/components/signin/base/CoreAccountInfo.java
index d8b06f87..8d1cce8 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/base/CoreAccountInfo.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/base/CoreAccountInfo.java
@@ -10,7 +10,7 @@
 import androidx.annotation.Nullable;
 
 import org.chromium.base.annotations.CalledByNative;
-import org.chromium.components.signin.AccountManagerFacade;
+import org.chromium.components.signin.AccountUtils;
 
 /**
  * Structure storing the core information about a Google account that is always known. The {@link
@@ -91,9 +91,8 @@
      * @return {@link Account} for the argument if it is not null, null otherwise.
      */
     public static @Nullable Account getAndroidAccountFrom(@Nullable CoreAccountInfo accountInfo) {
-        return accountInfo == null
-                ? null
-                : AccountManagerFacade.createAccountFromName(accountInfo.getEmail());
+        return accountInfo == null ? null
+                                   : AccountUtils.createAccountFromName(accountInfo.getEmail());
     }
 
     /**
diff --git a/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java b/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
index 383ab6b4..6741359 100644
--- a/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
+++ b/components/sync/android/javatests/src/org/chromium/components/sync/AndroidSyncSettingsTest.java
@@ -20,8 +20,8 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
-import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
@@ -146,7 +146,7 @@
     }
 
     private Account addTestAccount(String name) {
-        Account account = AccountManagerFacade.createAccountFromName(name);
+        Account account = AccountUtils.createAccountFromName(name);
         AccountHolder holder = AccountHolder.builder(account).alwaysAccept(true).build();
         mAccountManager.addAccountHolderBlocking(holder);
         return account;
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index d6ac30d..1c74f0b 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -9,15 +9,19 @@
 
   icons = [
     "accessibility.icon",
+    "ads.icon",
     "back_arrow.icon",
+    "blocked_badge.icon",
     "bluetooth.icon",
     "bluetooth_connected.icon",
     "bluetooth_scanning.icon",
     "business.icon",
     "call.icon",
+    "certificate.icon",
     "check_circle.icon",
     "close.icon",
     "close_rounded.icon",
+    "code.icon",
     "content_paste.icon",
     "cookie.icon",
     "devices.icon",
@@ -25,6 +29,7 @@
     "error.icon",
     "ethernet.icon",
     "extension.icon",
+    "file_download.icon",
     "folder.icon",
     "folder_managed.icon",
     "folder_managed_touch.icon",
@@ -36,6 +41,7 @@
     "help_outline.icon",
     "info_outline.icon",
     "insert_drive_file_outline.icon",
+    "launch.icon",
     "lightbulb_outline.icon",
     "location_on.icon",
     "lock.icon",
@@ -54,18 +60,25 @@
     "notifications.icon",
     "notifications_off.icon",
     "open_in_new.icon",
+    "page_info_content_paste.icon",
     "pause.icon",
+    "photo.icon",
     "play_arrow.icon",
+    "protected_content.icon",
     "protocol_handler.icon",
     "reload.icon",
     "replay.icon",
+    "save_original_file.icon",
     "screen_share.icon",
     "search.icon",
+    "sensors.icon",
     "serial_port.icon",
     "settings.icon",
+    "sync.icon",
     "usb.icon",
     "videocam.icon",
     "videogame_asset.icon",
+    "volume_up.icon",
     "vr_headset.icon",
     "warning.icon",
     "wifi_add.icon",
diff --git a/chrome/app/vector_icons/ads.icon b/components/vector_icons/ads.icon
similarity index 100%
rename from chrome/app/vector_icons/ads.icon
rename to components/vector_icons/ads.icon
diff --git a/chrome/app/vector_icons/blocked_badge.icon b/components/vector_icons/blocked_badge.icon
similarity index 100%
rename from chrome/app/vector_icons/blocked_badge.icon
rename to components/vector_icons/blocked_badge.icon
diff --git a/chrome/app/vector_icons/certificate.icon b/components/vector_icons/certificate.icon
similarity index 100%
rename from chrome/app/vector_icons/certificate.icon
rename to components/vector_icons/certificate.icon
diff --git a/chrome/app/vector_icons/code.icon b/components/vector_icons/code.icon
similarity index 100%
rename from chrome/app/vector_icons/code.icon
rename to components/vector_icons/code.icon
diff --git a/chrome/app/vector_icons/file_download.icon b/components/vector_icons/file_download.icon
similarity index 100%
rename from chrome/app/vector_icons/file_download.icon
rename to components/vector_icons/file_download.icon
diff --git a/chrome/app/vector_icons/launch.icon b/components/vector_icons/launch.icon
similarity index 100%
rename from chrome/app/vector_icons/launch.icon
rename to components/vector_icons/launch.icon
diff --git a/chrome/app/vector_icons/page_info_content_paste.icon b/components/vector_icons/page_info_content_paste.icon
similarity index 100%
rename from chrome/app/vector_icons/page_info_content_paste.icon
rename to components/vector_icons/page_info_content_paste.icon
diff --git a/chrome/app/vector_icons/photo.icon b/components/vector_icons/photo.icon
similarity index 100%
rename from chrome/app/vector_icons/photo.icon
rename to components/vector_icons/photo.icon
diff --git a/chrome/app/vector_icons/protected_content.icon b/components/vector_icons/protected_content.icon
similarity index 100%
rename from chrome/app/vector_icons/protected_content.icon
rename to components/vector_icons/protected_content.icon
diff --git a/chrome/app/vector_icons/save_original_file.icon b/components/vector_icons/save_original_file.icon
similarity index 100%
rename from chrome/app/vector_icons/save_original_file.icon
rename to components/vector_icons/save_original_file.icon
diff --git a/chrome/app/vector_icons/sensors.icon b/components/vector_icons/sensors.icon
similarity index 100%
rename from chrome/app/vector_icons/sensors.icon
rename to components/vector_icons/sensors.icon
diff --git a/chrome/app/vector_icons/sync.icon b/components/vector_icons/sync.icon
similarity index 100%
rename from chrome/app/vector_icons/sync.icon
rename to components/vector_icons/sync.icon
diff --git a/chrome/app/vector_icons/volume_up.icon b/components/vector_icons/volume_up.icon
similarity index 100%
rename from chrome/app/vector_icons/volume_up.icon
rename to components/vector_icons/volume_up.icon
diff --git a/components/viz/service/display_embedder/software_output_device_ozone_unittest.cc b/components/viz/service/display_embedder/software_output_device_ozone_unittest.cc
index de120cc2..f5c8d98 100644
--- a/components/viz/service/display_embedder/software_output_device_ozone_unittest.cc
+++ b/components/viz/service/display_embedder/software_output_device_ozone_unittest.cc
@@ -6,9 +6,9 @@
 
 #include <memory>
 
-#include "base/macros.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "ui/compositor/compositor.h"
@@ -29,32 +29,25 @@
 
 namespace {
 
-class TestPlatformWindowDelegate : public ui::PlatformWindowDelegate {
+class TestSurfaceOzoneCanvas : public ui::SurfaceOzoneCanvas {
  public:
-  TestPlatformWindowDelegate() : widget_(gfx::kNullAcceleratedWidget) {}
-  ~TestPlatformWindowDelegate() override {}
+  TestSurfaceOzoneCanvas() = default;
+  ~TestSurfaceOzoneCanvas() override = default;
 
-  gfx::AcceleratedWidget GetAcceleratedWidget() const { return widget_; }
-
-  // ui::PlatformWindowDelegate:
-  void OnBoundsChanged(const gfx::Rect& new_bounds) override {}
-  void OnDamageRect(const gfx::Rect& damaged_region) override {}
-  void DispatchEvent(ui::Event* event) override {}
-  void OnCloseRequest() override {}
-  void OnClosed() override {}
-  void OnWindowStateChanged(ui::PlatformWindowState new_state) override {}
-  void OnLostCapture() override {}
-  void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {
-    widget_ = widget;
+  // ui::SurfaceOzoneCanvas override:
+  SkCanvas* GetCanvas() override { return surface_->getCanvas(); }
+  void ResizeCanvas(const gfx::Size& viewport_size) override {
+    surface_ = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(
+        viewport_size.width(), viewport_size.height()));
   }
-  void OnAcceleratedWidgetDestroyed() override {}
-  void OnActivationChanged(bool active) override {}
-  void OnMouseEnter() override {}
+  std::unique_ptr<gfx::VSyncProvider> CreateVSyncProvider() override {
+    return nullptr;
+  }
+
+  MOCK_METHOD1(PresentCanvas, void(const gfx::Rect& damage));
 
  private:
-  gfx::AcceleratedWidget widget_;
-
-  DISALLOW_COPY_AND_ASSIGN(TestPlatformWindowDelegate);
+  sk_sp<SkSurface> surface_;
 };
 
 }  // namespace
@@ -63,6 +56,9 @@
  public:
   SoftwareOutputDeviceOzoneTest();
   ~SoftwareOutputDeviceOzoneTest() override;
+  SoftwareOutputDeviceOzoneTest(const SoftwareOutputDeviceOzoneTest&) = delete;
+  SoftwareOutputDeviceOzoneTest& operator=(
+      const SoftwareOutputDeviceOzoneTest&) = delete;
 
   void SetUp() override;
   void TearDown() override;
@@ -71,95 +67,49 @@
   std::unique_ptr<SoftwareOutputDeviceOzone> output_device_;
   bool enable_pixel_output_ = false;
 
- private:
-  std::unique_ptr<ui::TestContextFactories> context_factories_;
-  std::unique_ptr<ui::Compositor> compositor_;
-  TestPlatformWindowDelegate window_delegate_;
-
-  DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDeviceOzoneTest);
+  TestSurfaceOzoneCanvas* surface_ozone_ = nullptr;
 };
 
 SoftwareOutputDeviceOzoneTest::SoftwareOutputDeviceOzoneTest() = default;
 SoftwareOutputDeviceOzoneTest::~SoftwareOutputDeviceOzoneTest() = default;
 
 void SoftwareOutputDeviceOzoneTest::SetUp() {
-  ui::OzonePlatform::InitParams params;
-  params.single_process = true;
-  ui::OzonePlatform::InitializeForUI(params);
-  ui::OzonePlatform::InitializeForGPU(params);
-
-  ui::PlatformWindowInitProperties properties;
-  properties.bounds = gfx::Rect(800, 600, 100, 100);
-  auto platform_window = ui::OzonePlatform::GetInstance()->CreatePlatformWindow(
-      &window_delegate_, std::move(properties));
-  platform_window->Show();
-
-  context_factories_ =
-      std::make_unique<ui::TestContextFactories>(enable_pixel_output_);
-
-  const gfx::Size size(500, 400);
-  compositor_ = std::make_unique<ui::Compositor>(
-      FrameSinkId(1, 1), context_factories_->GetContextFactory(),
-      base::ThreadTaskRunnerHandle::Get(), false /* enable_pixel_canvas */);
-  compositor_->SetAcceleratedWidget(window_delegate_.GetAcceleratedWidget());
-  compositor_->SetScaleAndSize(1.0f, size, LocalSurfaceIdAllocation());
-
-  ui::SurfaceFactoryOzone* factory =
-      ui::OzonePlatform::GetInstance()->GetSurfaceFactoryOzone();
-  std::unique_ptr<ui::PlatformWindowSurface> platform_window_surface =
-      factory->CreatePlatformWindowSurface(compositor_->widget());
-  std::unique_ptr<ui::SurfaceOzoneCanvas> surface_ozone =
-      factory->CreateCanvasForWidget(compositor_->widget(), nullptr);
-  if (!surface_ozone) {
-    LOG(ERROR) << "SurfaceOzoneCanvas not constructible on this platform";
-  } else {
-    output_device_ = std::make_unique<SoftwareOutputDeviceOzone>(
-        std::move(platform_window_surface), std::move(surface_ozone));
-  }
-  if (output_device_)
-    output_device_->Resize(size, 1.f);
+  std::unique_ptr<TestSurfaceOzoneCanvas> surface_ozone =
+      std::make_unique<TestSurfaceOzoneCanvas>();
+  surface_ozone_ = surface_ozone.get();
+  output_device_ = std::make_unique<SoftwareOutputDeviceOzone>(
+      nullptr, std::move(surface_ozone));
 }
 
 void SoftwareOutputDeviceOzoneTest::TearDown() {
   output_device_.reset();
-  compositor_.reset();
-  context_factories_.reset();
-}
-
-class SoftwareOutputDeviceOzonePixelTest
-    : public SoftwareOutputDeviceOzoneTest {
- protected:
-  void SetUp() override;
-};
-
-void SoftwareOutputDeviceOzonePixelTest::SetUp() {
-  enable_pixel_output_ = true;
-  SoftwareOutputDeviceOzoneTest::SetUp();
 }
 
 TEST_F(SoftwareOutputDeviceOzoneTest, CheckCorrectResizeBehavior) {
-  // Check if software rendering mode is not supported.
-  if (!output_device_)
-    return;
-
-  gfx::Rect damage(0, 0, 100, 100);
-  gfx::Size size(200, 100);
+  constexpr gfx::Size size(200, 100);
   // Reduce size.
   output_device_->Resize(size, 1.f);
 
-  SkCanvas* canvas = output_device_->BeginPaint(damage);
+  constexpr gfx::Rect damage1(0, 0, 100, 100);
+  SkCanvas* canvas = output_device_->BeginPaint(damage1);
+  ASSERT_TRUE(canvas);
   gfx::Size canvas_size(canvas->getBaseLayerSize().width(),
                         canvas->getBaseLayerSize().height());
-  EXPECT_EQ(size.ToString(), canvas_size.ToString());
+  EXPECT_EQ(size, canvas_size);
+  EXPECT_CALL(*surface_ozone_, PresentCanvas(damage1)).Times(1);
+  output_device_->EndPaint();
 
-  size.SetSize(1000, 500);
+  constexpr gfx::Size size2(1000, 500);
   // Increase size.
-  output_device_->Resize(size, 1.f);
+  output_device_->Resize(size2, 1.f);
 
-  canvas = output_device_->BeginPaint(damage);
+  constexpr gfx::Rect damage2(0, 0, 50, 60);
+  canvas = output_device_->BeginPaint(damage2);
   canvas_size.SetSize(canvas->getBaseLayerSize().width(),
                       canvas->getBaseLayerSize().height());
-  EXPECT_EQ(size.ToString(), canvas_size.ToString());
+  EXPECT_EQ(size2, canvas_size);
+  EXPECT_CALL(*surface_ozone_, PresentCanvas(damage2)).Times(1);
+  output_device_->EndPaint();
 }
 
 }  // namespace viz
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 40f7ad4..9355cd6 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -328,6 +328,10 @@
   RunCSSTest(FILE_PATH_LITERAL("table-data-display-other.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSOverflow) {
+  RunCSSTest(FILE_PATH_LITERAL("overflow.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSTransform) {
   RunCSSTest(FILE_PATH_LITERAL("transform.html"));
 }
diff --git a/content/browser/devtools/protocol/service_worker_handler.cc b/content/browser/devtools/protocol/service_worker_handler.cc
index 2d0ca48..b209ce3 100644
--- a/content/browser/devtools/protocol/service_worker_handler.cc
+++ b/content/browser/devtools/protocol/service_worker_handler.cc
@@ -179,8 +179,9 @@
 
 }  // namespace
 
-ServiceWorkerHandler::ServiceWorkerHandler()
+ServiceWorkerHandler::ServiceWorkerHandler(bool allow_inspect_worker)
     : DevToolsDomainHandler(ServiceWorker::Metainfo::domainName),
+      allow_inspect_worker_(allow_inspect_worker),
       enabled_(false),
       browser_context_(nullptr),
       storage_partition_(nullptr) {}
@@ -313,7 +314,8 @@
     return CreateDomainNotEnabledErrorResponse();
   if (!context_)
     return CreateContextErrorResponse();
-
+  if (!allow_inspect_worker_)
+    return Response::ServerError("Permission denied");
   int64_t id = blink::mojom::kInvalidServiceWorkerVersionId;
   if (!base::StringToInt64(version_id, &id))
     return CreateInvalidVersionIdErrorResponse();
diff --git a/content/browser/devtools/protocol/service_worker_handler.h b/content/browser/devtools/protocol/service_worker_handler.h
index 848540dc..2139fa30 100644
--- a/content/browser/devtools/protocol/service_worker_handler.h
+++ b/content/browser/devtools/protocol/service_worker_handler.h
@@ -31,7 +31,7 @@
 class ServiceWorkerHandler : public DevToolsDomainHandler,
                              public ServiceWorker::Backend {
  public:
-  ServiceWorkerHandler();
+  explicit ServiceWorkerHandler(bool allow_inspect_worker);
   ~ServiceWorkerHandler() override;
 
   void Wire(UberDispatcher* dispatcher) override;
@@ -72,6 +72,7 @@
   void OpenNewDevToolsWindow(int process_id, int devtools_agent_route_id);
   void ClearForceUpdate();
 
+  const bool allow_inspect_worker_;
   scoped_refptr<ServiceWorkerContextWrapper> context_;
   std::unique_ptr<ServiceWorker::Frontend> frontend_;
   bool enabled_;
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 5382734..01e6767 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -318,10 +318,12 @@
                           },
                           base::Unretained(this))));
   session->AddHandler(std::make_unique<protocol::SchemaHandler>());
-  session->AddHandler(std::make_unique<protocol::ServiceWorkerHandler>());
+  const bool may_attach_to_brower = session->GetClient()->MayAttachToBrowser();
+  session->AddHandler(std::make_unique<protocol::ServiceWorkerHandler>(
+      /* allow_inspect_worker= */ may_attach_to_brower));
   session->AddHandler(std::make_unique<protocol::StorageHandler>());
   session->AddHandler(std::make_unique<protocol::TargetHandler>(
-      session->GetClient()->MayAttachToBrowser()
+      may_attach_to_brower
           ? protocol::TargetHandler::AccessMode::kRegular
           : protocol::TargetHandler::AccessMode::kAutoAttachOnly,
       GetId(), GetRendererChannel(), session->GetRootSession()));
diff --git a/content/browser/frame_host/mixed_content_navigation_throttle.cc b/content/browser/frame_host/mixed_content_navigation_throttle.cc
index 608f464..b401e44 100644
--- a/content/browser/frame_host/mixed_content_navigation_throttle.cc
+++ b/content/browser/frame_host/mixed_content_navigation_throttle.cc
@@ -75,6 +75,7 @@
   params.main_resource_url = mixed_content_url;
   params.mixed_content_url = navigation_request->GetURL();
   params.request_context_type = navigation_request->request_context_type();
+  params.request_destination = navigation_request->request_destination();
   params.was_allowed = was_allowed;
   params.had_redirect = for_redirect;
   params.source_location =
diff --git a/content/browser/frame_host/mixed_content_navigation_throttle.h b/content/browser/frame_host/mixed_content_navigation_throttle.h
index b0b5977..f2131465 100644
--- a/content/browser/frame_host/mixed_content_navigation_throttle.h
+++ b/content/browser/frame_host/mixed_content_navigation_throttle.h
@@ -12,6 +12,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle.h"
+#include "services/network/public/mojom/fetch_api.mojom.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 #include "third_party/blink/public/mojom/web_feature/web_feature.mojom.h"
 #include "third_party/blink/public/platform/web_mixed_content_context_type.h"
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index dbfc243..797feb48 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -99,6 +99,7 @@
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/mojom/fetch_api.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/blob/blob_utils.h"
 #include "third_party/blink/public/common/frame/sandbox_flags.h"
@@ -685,6 +686,7 @@
   auto navigation_params = mojom::BeginNavigationParams::New(
       extra_headers, net::LOAD_NORMAL, false /* skip_service_worker */,
       blink::mojom::RequestContextType::LOCATION,
+      network::mojom::RequestDestination::kDocument,
       blink::WebMixedContentContextType::kBlockable, is_form_submission,
       false /* was_initiated_by_link_click */, GURL() /* searchable_form_url */,
       std::string() /* searchable_form_encoding */,
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index da2c6b0..7364dd3 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -508,6 +508,10 @@
     return begin_params_->request_context_type;
   }
 
+  network::mojom::RequestDestination request_destination() const {
+    return begin_params_->request_destination;
+  }
+
   blink::WebMixedContentContextType mixed_content_context_type() const {
     return begin_params_->mixed_content_context_type;
   }
diff --git a/content/browser/loader/navigation_url_loader_impl_unittest.cc b/content/browser/loader/navigation_url_loader_impl_unittest.cc
index c3e41901..588d0cd0 100644
--- a/content/browser/loader/navigation_url_loader_impl_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_impl_unittest.cc
@@ -172,6 +172,7 @@
         mojom::BeginNavigationParams::New(
             headers, net::LOAD_NORMAL, false /* skip_service_worker */,
             blink::mojom::RequestContextType::LOCATION,
+            network::mojom::RequestDestination::kDocument,
             blink::WebMixedContentContextType::kBlockable,
             false /* is_form_submission */,
             false /* was_initiated_by_link_click */,
diff --git a/content/browser/loader/navigation_url_loader_unittest.cc b/content/browser/loader/navigation_url_loader_unittest.cc
index a2c85587..186beac9 100644
--- a/content/browser/loader/navigation_url_loader_unittest.cc
+++ b/content/browser/loader/navigation_url_loader_unittest.cc
@@ -60,6 +60,7 @@
             std::string() /* headers */, net::LOAD_NORMAL,
             false /* skip_service_worker */,
             blink::mojom::RequestContextType::LOCATION,
+            network::mojom::RequestDestination::kDocument,
             blink::WebMixedContentContextType::kBlockable,
             false /* is_form_submission */,
             false /* was_initiated_by_link_click */,
diff --git a/content/browser/media/audio_stream_monitor_unittest.cc b/content/browser/media/audio_stream_monitor_unittest.cc
index d6d1c8b..2604c053 100644
--- a/content/browser/media/audio_stream_monitor_unittest.cc
+++ b/content/browser/media/audio_stream_monitor_unittest.cc
@@ -16,7 +16,7 @@
 #include "content/public/browser/invalidate_type.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/test/test_renderer_host.h"
-#include "media/audio/audio_power_monitor.h"
+#include "media/base/audio_power_monitor.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index e802bfe0..43d4b85 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -1519,6 +1519,7 @@
           std::string() /* headers */, net::LOAD_NORMAL,
           false /* skip_service_worker */,
           blink::mojom::RequestContextType::LOCATION,
+          network::mojom::RequestDestination::kDocument,
           blink::WebMixedContentContextType::kBlockable,
           false /* is_form_submission */,
           false /* was_initiated_by_link_click */,
diff --git a/content/browser/service_worker/service_worker_registration.cc b/content/browser/service_worker/service_worker_registration.cc
index 54fec83..b8ace915 100644
--- a/content/browser/service_worker/service_worker_registration.cc
+++ b/content/browser/service_worker/service_worker_registration.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/debug/alias.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_container_host.h"
@@ -39,6 +40,48 @@
   return version->GetInfo();
 }
 
+// TODO(crbug.com/1015692): Remove this and CrashDueToControllee() once we
+// identified the cause of crash.
+struct ControlleeInfo {
+  std::string client_uuid;
+  ServiceWorkerVersion* controller;
+  ServiceWorkerRegistration* controller_registration;
+  bool is_context_secure_for_service_worker;
+
+  explicit ControlleeInfo(const ServiceWorkerContainerHost& container_host)
+      : client_uuid(container_host.client_uuid()),
+        controller(container_host.controller()),
+        controller_registration(container_host.controller_registration()),
+        is_context_secure_for_service_worker(
+            container_host.IsContextSecureForServiceWorker()) {}
+};
+
+void CrashDueToControllee(
+    ServiceWorkerRegistration* registration,
+    scoped_refptr<ServiceWorkerVersion> exiting_version,
+    scoped_refptr<ServiceWorkerVersion> activating_version) {
+  std::vector<ControlleeInfo> controllees_in_exiting;
+  for (const auto& it : exiting_version->controllee_map()) {
+    ControlleeInfo info(*it.second);
+    controllees_in_exiting.push_back(info);
+  }
+  std::vector<ControlleeInfo> controllees_in_activating;
+  for (const auto& it : activating_version->controllee_map()) {
+    ControlleeInfo info(*it.second);
+    controllees_in_activating.push_back(info);
+  }
+
+  bool exiting_version_skip_waiting = exiting_version->skip_waiting();
+  bool activating_version_skip_waiting = activating_version->skip_waiting();
+
+  base::debug::Alias(&registration);
+  base::debug::Alias(&controllees_in_exiting);
+  base::debug::Alias(&controllees_in_activating);
+  base::debug::Alias(&exiting_version_skip_waiting);
+  base::debug::Alias(&activating_version_skip_waiting);
+  CHECK(false);
+}
+
 }  // namespace
 
 ServiceWorkerRegistration::ServiceWorkerRegistration(
@@ -505,9 +548,9 @@
       observer.OnSkippedWaiting(this);
   }
 
-  // TODO(crbug.com/951571): Remove this once we identified the cause of crash.
-  if (exiting_version) {
-    CHECK(!exiting_version->HasControllee());
+  // TODO(crbug.com/1015692): Remove this once we identified the cause of crash.
+  if (exiting_version && exiting_version->HasControllee()) {
+    CrashDueToControllee(this, exiting_version, activating_version);
   }
 
   // "10. Queue a task to fire an event named activate..."
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 7419cc6..0224341 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -448,6 +448,7 @@
   IPC_STRUCT_MEMBER(GURL, main_resource_url)
   IPC_STRUCT_MEMBER(GURL, mixed_content_url)
   IPC_STRUCT_MEMBER(blink::mojom::RequestContextType, request_context_type)
+  IPC_STRUCT_MEMBER(network::mojom::RequestDestination, request_destination)
   IPC_STRUCT_MEMBER(bool, was_allowed)
   IPC_STRUCT_MEMBER(bool, had_redirect)
   IPC_STRUCT_MEMBER(network::mojom::SourceLocation, source_location)
diff --git a/content/common/navigation_params.mojom b/content/common/navigation_params.mojom
index 1a77477..f904ff6 100644
--- a/content/common/navigation_params.mojom
+++ b/content/common/navigation_params.mojom
@@ -10,6 +10,7 @@
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "mojo/public/mojom/base/values.mojom";
 import "services/network/public/mojom/content_security_policy.mojom";
+import "services/network/public/mojom/fetch_api.mojom";
 import "services/network/public/mojom/ip_address_space.mojom";
 import "services/network/public/mojom/source_location.mojom";
 import "services/network/public/mojom/url_loader.mojom";
@@ -101,6 +102,9 @@
   // Indicates the request context type.
   blink.mojom.RequestContextType request_context_type;
 
+  // Indicates the request destination.
+  network.mojom.RequestDestination request_destination;
+
   // The mixed content context type for potential mixed content checks.
   MixedContentContextType mixed_content_context_type;
 
diff --git a/content/renderer/loader/web_url_request_util.cc b/content/renderer/loader/web_url_request_util.cc
index 2226987..828286e 100644
--- a/content/renderer/loader/web_url_request_util.cc
+++ b/content/renderer/loader/web_url_request_util.cc
@@ -254,6 +254,12 @@
       request.GetRequestContext());
 }
 
+network::mojom::RequestDestination GetRequestDestinationForWebURLRequest(
+    const WebURLRequest& request) {
+  return static_cast<network::mojom::RequestDestination>(
+      request.GetRequestDestination());
+}
+
 blink::WebMixedContentContextType GetMixedContentContextTypeForWebURLRequest(
     const WebURLRequest& request) {
   return blink::WebMixedContent::ContextTypeFromRequestContext(
diff --git a/content/renderer/loader/web_url_request_util.h b/content/renderer/loader/web_url_request_util.h
index 3434e58..540daa4 100644
--- a/content/renderer/loader/web_url_request_util.h
+++ b/content/renderer/loader/web_url_request_util.h
@@ -48,6 +48,8 @@
     const blink::WebURLRequest& request);
 blink::mojom::RequestContextType GetRequestContextTypeForWebURLRequest(
     const blink::WebURLRequest& request);
+network::mojom::RequestDestination GetRequestDestinationForWebURLRequest(
+    const blink::WebURLRequest& request);
 blink::WebMixedContentContextType GetMixedContentContextTypeForWebURLRequest(
     const blink::WebURLRequest& request);
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 1d37c33..ebb5745 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -6284,6 +6284,7 @@
           GetWebURLRequestHeadersAsString(info->url_request), load_flags,
           info->url_request.GetSkipServiceWorker(),
           GetRequestContextTypeForWebURLRequest(info->url_request),
+          GetRequestDestinationForWebURLRequest(info->url_request),
           GetMixedContentContextTypeForWebURLRequest(info->url_request),
           is_form_submission, was_initiated_by_link_click, searchable_form_url,
           searchable_form_encoding, client_side_redirect_url,
diff --git a/content/test/data/accessibility/aria/aria-focusable-subwidget-not-editable-expected-win.txt b/content/test/data/accessibility/aria/aria-focusable-subwidget-not-editable-expected-win.txt
index f3c560d..c49088d 100644
--- a/content/test/data/accessibility/aria/aria-focusable-subwidget-not-editable-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-focusable-subwidget-not-editable-expected-win.txt
@@ -1,6 +1,6 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
 ++IA2_ROLE_SECTION value='cats<newline>dogs' FOCUSABLE IA2_STATE_EDITABLE IA2_STATE_MULTI_LINE
-++++ROLE_SYSTEM_OUTLINE FOCUSABLE IA2_STATE_EDITABLE
+++++ROLE_SYSTEM_OUTLINE IA2_STATE_EDITABLE
 ++++++ROLE_SYSTEM_OUTLINEITEM name='cats' FOCUSABLE
 ++++++++ROLE_SYSTEM_STATICTEXT name='cats' IA2_STATE_EDITABLE
 ++++++ROLE_SYSTEM_OUTLINEITEM name='dogs' FOCUSABLE
diff --git a/content/test/data/accessibility/html/portal-expected-blink.txt b/content/test/data/accessibility/html/portal-expected-blink.txt
index ed825c8e..efabb3c1 100644
--- a/content/test/data/accessibility/html/portal-expected-blink.txt
+++ b/content/test/data/accessibility/html/portal-expected-blink.txt
@@ -1,9 +1,9 @@
-rootWebArea
+rootWebArea focusable
 ++genericContainer ignored
 ++++paragraph
 ++++++staticText name='Before portal'
 ++++++++inlineTextBox name='Before portal'
-++++portal name='portal'
+++++portal focusable name='portal'
 ++++++rootWebArea name='Text in iframe'
 ++++paragraph
 ++++++staticText name='After portal'
diff --git a/content/test/data/accessibility/html/portal.html b/content/test/data/accessibility/html/portal.html
index 4fc4d69..40db31b 100644
--- a/content/test/data/accessibility/html/portal.html
+++ b/content/test/data/accessibility/html/portal.html
@@ -1,3 +1,6 @@
+<!--
+@BLINK-ALLOW:focusable
+-->
 <!DOCTYPE html>
 <html>
 <body>
diff --git a/content/test/gpu/gpu_tests/gpu_helper.py b/content/test/gpu/gpu_tests/gpu_helper.py
index 724d536..dd651ac 100644
--- a/content/test/gpu/gpu_tests/gpu_helper.py
+++ b/content/test/gpu/gpu_tests/gpu_helper.py
@@ -185,7 +185,8 @@
   return DRIVER_TAG_MATCHER.match(tag.lower())
 
 
-def EvaluateVersionComparison(version1, operation, version2):
+def EvaluateVersionComparison(version, operation, ref_version,
+                              os_name=None, driver_vendor=None):
   def parse_version(ver):
     if ver.isdigit():
       return int(ver), ''
@@ -193,8 +194,32 @@
       if not ver[i].isdigit():
         return int(ver[:i]) if i > 0 else 0, ver[i:]
 
-  ver_list1 = version1.split('.')
-  ver_list2 = version2.split('.')
+  def is_old_intel_driver(ver_list):
+    assert len(ver_list) == 4
+    num, suffix = parse_version(ver_list[2])
+    assert not suffix
+    return num < 100
+
+  ver_list1 = version.split('.')
+  ver_list2 = ref_version.split('.')
+  # On Windows, if the driver vendor is Intel, the driver version should be
+  # compared based on the Intel graphics driver version schema.
+  # https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
+  if os_name == 'win' and driver_vendor == 'intel':
+    # If either of the two versions doesn't match the Intel driver version
+    # schema, or they belong to different generation of version schema, they
+    # should not be compared.
+    if len(ver_list1) != 4 or len(ver_list2) != 4:
+      return operation == 'ne'
+    if is_old_intel_driver(ver_list1) != is_old_intel_driver(ver_list2):
+      return operation == 'ne'
+    if is_old_intel_driver(ver_list1):
+      ver_list1 = ver_list1[3:]
+      ver_list2 = ver_list2[3:]
+    else:
+      ver_list1 = ver_list1[2:]
+      ver_list2 = ver_list2[2:]
+
   for i in range(0, max(len(ver_list1), len(ver_list2))):
     ver1 = ver_list1[i] if i < len(ver_list1) else '0'
     ver2 = ver_list2[i] if i < len(ver_list2) else '0'
diff --git a/content/test/gpu/gpu_tests/test_expectations_unittest.py b/content/test/gpu/gpu_tests/test_expectations_unittest.py
index 6d6040c..7d4234e 100644
--- a/content/test/gpu/gpu_tests/test_expectations_unittest.py
+++ b/content/test/gpu/gpu_tests/test_expectations_unittest.py
@@ -45,6 +45,39 @@
 ResultType = json_results.ResultType
 
 
+INTEL_DRIVER_VERSION_SCHEMA = '''
+The version format of Intel graphics driver is AA.BB.CCC.DDDD.
+DDDD(old schema) or CCC.DDDD(new schema) is the build number. That is,
+indicates the actual driver number. The comparison between old schema
+and new schema is NOT valid. In such a condition the only comparison
+operator that returns true is "not equal".
+
+AA.BB: You are free to specify the real number here, but they are meaningless
+when comparing two version numbers. Usually it's okay to leave it to "0.0".
+
+CCC: It's necessary for new schema. Regarding to old schema, you can specify
+the real number or any number less than 100 in order to differentiate from
+new schema.
+
+DDDD: It's always meaningful. It must not be "0" under old schema.
+
+Legal: "24.20.100.7000", "0.0.100.7000", "0.0.0.7000", "0.0.100.0"
+Illegal: "24.0.0.0", "24.20.0.0", "0.0.99.0"
+'''
+
+
+def check_intel_driver_version(version):
+  ver_list = version.split('.')
+  if len(ver_list) != 4:
+    return False
+  for ver in ver_list:
+    if not ver.isdigit():
+      return False
+  if int(ver_list[2]) < 100 and ver_list[3] == '0':
+    return False
+  return True
+
+
 def _MapGpuDevicesToVendors(tag_sets):
   for tag_set in tag_sets:
     if any(gpu in tag_set for gpu in GPU_CONDITIONS):
@@ -297,7 +330,12 @@
         for tag_set in parser.tag_sets:
           if gpu_helper.MatchDriverTag(list(tag_set)[0]):
             for tag in tag_set:
-              assert gpu_helper.MatchDriverTag(tag)
+              match = gpu_helper.MatchDriverTag(tag)
+              assert match
+              if match.group(1) == 'intel':
+                if not check_intel_driver_version(match.group(3)):
+                  assert False, INTEL_DRIVER_VERSION_SCHEMA
+
             assert not driver_tag_set
             driver_tag_set = tag_set
           else:
@@ -416,3 +454,37 @@
         self.assertIn('3: Expectation with pattern \'a/b/d\' does not match'
                       ' any tests in the GpuIntegrationTest test suite',
                       str(context.exception))
+
+def testDriverVersionComparision(self):
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'eq', '24.20.100.7000'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100', 'ne', '24.20.100.7000'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'gt', '24.20.100'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000a', 'gt', '24.20.100.7000'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'lt', '24.20.100.7001'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'lt', '24.20.200.6000'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'lt', '25.30.100.6000', 'linux', 'intel'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'gt', '25.30.100.6000', 'win', 'intel'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.101.6000', 'gt', '25.30.100.7000', 'win', 'intel'))
+  self.assertFalse(gpu_helper.EvaluateVersionComparison(
+      '24.20.99.7000', 'gt', '24.20.100.7000', 'win', 'intel'))
+  self.assertFalse(gpu_helper.EvaluateVersionComparison(
+      '24.20.99.7000', 'lt', '24.20.100.7000', 'win', 'intel'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.99.7000', 'ne', '24.20.100.7000', 'win', 'intel'))
+  self.assertFalse(gpu_helper.EvaluateVersionComparison(
+      '24.20.100', 'lt', '24.20.100.7000', 'win', 'intel'))
+  self.assertFalse(gpu_helper.EvaluateVersionComparison(
+      '24.20.100', 'gt', '24.20.100.7000', 'win', 'intel'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100', 'ne', '24.20.100.7000', 'win', 'intel'))
+  self.assertTrue(gpu_helper.EvaluateVersionComparison(
+      '24.20.100.7000', 'eq', '25.20.100.7000', 'win', 'intel'))
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py b/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
index 5fbd02b..07a0aa1f 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_integration_test.py
@@ -499,7 +499,8 @@
             assert match
             if (driver_vendor == match.group(1) and
                 gpu_helper.EvaluateVersionComparison(
-                    driver_version, match.group(2), match.group(3))):
+                    driver_version, match.group(2), match.group(3),
+                    browser.platform.GetOSName(), driver_vendor)):
               tags.append(tag)
     return tags
 
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index d404f30..bb63405 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1128,6 +1128,7 @@
           std::string() /* headers */, net::LOAD_NORMAL,
           false /* skip_service_worker */,
           blink::mojom::RequestContextType::HYPERLINK,
+          network::mojom::RequestDestination::kDocument,
           blink::WebMixedContentContextType::kBlockable, is_form_submission_,
           was_initiated_by_link_click_, GURL() /* searchable_form_url */,
           std::string() /* searchable_form_encoding */,
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index 2b78e77b..718f48f 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -327,6 +327,7 @@
           std::string() /* headers */, net::LOAD_NORMAL,
           false /* skip_service_worker */,
           blink::mojom::RequestContextType::HYPERLINK,
+          network::mojom::RequestDestination::kDocument,
           blink::WebMixedContentContextType::kBlockable,
           false /* is_form_submission */,
           false /* was_initiated_by_link_click */,
diff --git a/gpu/config/gpu_control_list.cc b/gpu/config/gpu_control_list.cc
index eda38b5..b4ab820 100644
--- a/gpu/config/gpu_control_list.cc
+++ b/gpu/config/gpu_control_list.cc
@@ -96,6 +96,14 @@
   return 0;
 }
 
+bool isOldIntelDriver(const std::vector<std::string>& version) {
+  DCHECK_EQ(4u, version.size());
+  unsigned value = 0;
+  bool valid = base::StringToUint(version[2], &value);
+  DCHECK(valid);
+  return value < 100;
+}
+
 // A mismatch is identified only if both |input| and |pattern| are not empty.
 bool StringMismatch(const std::string& input, const std::string& pattern) {
   if (input.empty() || pattern.empty())
@@ -234,14 +242,59 @@
                                 control_list_logging_name.c_str());
 }
 
-bool GpuControlList::DriverInfo::Contains(const GPUInfo& gpu_info) const {
+bool GpuControlList::DriverInfo::Contains(const GPUInfo& gpu_info,
+                                          VersionSchema version_schema) const {
   const GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
   if (StringMismatch(active_gpu.driver_vendor, driver_vendor)) {
     return false;
   }
-  if (driver_version.IsSpecified() && !active_gpu.driver_version.empty() &&
-      !driver_version.Contains(active_gpu.driver_version)) {
-    return false;
+  if (driver_version.IsSpecified() && !active_gpu.driver_version.empty()) {
+    if (version_schema == kCommon) {
+      if (!driver_version.Contains(active_gpu.driver_version))
+        return false;
+    } else if (version_schema == kIntelDriver) {
+      std::vector<std::string> version;
+      if (!ProcessVersionString(active_gpu.driver_version, '.', &version))
+        return false;
+      std::vector<std::string> ref_version, ref_version2;
+      bool valid = ProcessVersionString(driver_version.value1, '.',
+                                        &ref_version);
+      DCHECK(valid);
+      if (driver_version.value2) {
+        valid = ProcessVersionString(driver_version.value2, '.', &ref_version2);
+        DCHECK(valid);
+      }
+      // If either of the two versions doesn't match the Intel driver version                                                                                                                                     +      // schema, or they belong to different generation of version schema, they
+      // should not be compared.
+      if (version.size() != 4 || ref_version.size() != 4)
+        return false;
+      if (isOldIntelDriver(version) != isOldIntelDriver(ref_version))
+        return false;
+      if (!ref_version2.empty()) {
+        if (ref_version2.size() != 4
+            || isOldIntelDriver(ref_version) != isOldIntelDriver(ref_version2))
+          return false;
+      }
+
+      std::string build_num, ref_build_num, ref_build_num2;
+      if (isOldIntelDriver(version)) {
+        build_num = version[3];
+        ref_build_num = ref_version[3];
+        if (!ref_version2.empty())
+          ref_build_num2 = ref_version2[3];
+      } else {
+        build_num = version[2] + "." + version[3];
+        ref_build_num = ref_version[2] + "." + ref_version[3];
+        if (!ref_version2.empty())
+          ref_build_num2 = ref_version2[2] + "." + ref_version2[3];
+      }
+      Version ref_driver_version(driver_version);
+      ref_driver_version.value1 = ref_build_num.c_str();
+      if (!ref_build_num2.empty())
+        ref_driver_version.value2 = ref_build_num2.c_str();
+      if (!ref_driver_version.Contains(build_num))
+        return false;
+    }
   }
   return true;
 }
@@ -448,8 +501,30 @@
     case kMultiGpuStyleNone:
       break;
   }
-  if (driver_info && !driver_info->Contains(gpu_info)) {
-    return false;
+
+  if (driver_info) {
+    // On Windows, if either current gpu or the gpu condition is from Intel,
+    // the driver version should be compared based on the Intel graphics driver
+    // version schema.
+    // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html
+    VersionSchema version_schema = kCommon;
+    if (target_os_type == kOsWin) {
+      if (vendor_id == 0x8086
+          || intel_gpu_series_list_size > 0
+          || intel_gpu_generation.IsSpecified()
+          || (driver_info->driver_vendor
+              && std::string(driver_info->driver_vendor).find("Intel")
+                 != std::string::npos)) {
+        version_schema = kIntelDriver;
+      } else {
+        const GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
+        if (active_gpu.vendor_id == 0x8086
+            || active_gpu.driver_vendor.find("Intel") != std::string::npos)
+          version_schema = kIntelDriver;
+      }
+    }
+    if (!driver_info->Contains(gpu_info, version_schema))
+      return false;
   }
   if (gl_strings && !gl_strings->Contains(gpu_info)) {
     return false;
diff --git a/gpu/config/gpu_control_list.h b/gpu/config/gpu_control_list.h
index 2cadf67..063da31a 100644
--- a/gpu/config/gpu_control_list.h
+++ b/gpu/config/gpu_control_list.h
@@ -85,6 +85,15 @@
     kVersionStyleUnknown
   };
 
+  enum VersionSchema {
+    // All digits are meaningful when distinguishing versions.
+    kCommon,
+    // The version format of Intel graphics driver is AA.BB.CCC.DDDD.
+    // DDDD(old schema) or CCC.DDDD(new schema) is the build number.
+    // That is, indicates the actual driver number.
+    kIntelDriver,
+  };
+
   enum SupportedOrNot {
     kSupported,
     kUnsupported,
@@ -124,7 +133,8 @@
     const char* driver_vendor;
     Version driver_version;
 
-    bool Contains(const GPUInfo& gpu_info) const;
+    bool Contains(const GPUInfo& gpu_info,
+                  VersionSchema version_schema = kCommon) const;
   };
 
   struct GPU_EXPORT GLStrings {
diff --git a/gpu/config/gpu_control_list_entry_unittest.cc b/gpu/config/gpu_control_list_entry_unittest.cc
index 046690d..03f97c1 100644
--- a/gpu/config/gpu_control_list_entry_unittest.cc
+++ b/gpu/config/gpu_control_list_entry_unittest.cc
@@ -1111,4 +1111,59 @@
   EXPECT_TRUE(entry.Contains(kOsChromeOS, "10.0", gpu_info));
 }
 
+TEST_F(GpuControlListEntryTest, IntelDriverVendorEntry) {
+  const Entry& entry =
+      GetEntry(kGpuControlListEntryTest_IntelDriverVendorEntry);
+  GPUInfo gpu_info;
+  gpu_info.gpu.driver_vendor = "Intel(R) UHD Graphics 630";
+  gpu_info.gpu.driver_version = "25.20.100.5000";
+  EXPECT_TRUE(entry.Contains(kOsLinux, "", gpu_info));
+  gpu_info.gpu.driver_version = "23.20.100.6500";
+  EXPECT_FALSE(entry.Contains(kOsLinux, "", gpu_info));
+
+  gpu_info.gpu.driver_version = "25.20.100.5000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "23.20.100.6500";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+}
+
+TEST_F(GpuControlListEntryTest, IntelDriverVersionEntry) {
+  const Entry& entry =
+      GetEntry(kGpuControlListEntryTest_IntelDriverVersionEntry);
+  GPUInfo gpu_info;
+  gpu_info.gpu.vendor_id = 0x8086;
+  gpu_info.gpu.driver_version = "23.20.100.8000";
+  EXPECT_TRUE(entry.Contains(kOsLinux, "", gpu_info));
+  gpu_info.gpu.driver_version = "25.20.100.6000";
+  EXPECT_FALSE(entry.Contains(kOsLinux, "", gpu_info));
+
+  gpu_info.gpu.driver_version = "23.20.100.8000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "25.20.100.6000";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "24.20.99.6000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "24.20.101.6000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "25.20.100.7000";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+}
+
+TEST_F(GpuControlListEntryTest, IntelOldDriverVersionEntry) {
+  const Entry& entry =
+      GetEntry(kGpuControlListEntryTest_IntelOldDriverVersionEntry);
+  GPUInfo gpu_info;
+  gpu_info.gpu.vendor_id = 0x8086;
+  gpu_info.gpu.driver_version = "23.20.10.8000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "25.20.10.6000";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "24.20.100.6000";
+  EXPECT_FALSE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "24.20.11.6000";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+  gpu_info.gpu.driver_version = "25.20.9.7000";
+  EXPECT_TRUE(entry.Contains(kOsWin, "", gpu_info));
+}
+
 }  // namespace gpu
diff --git a/gpu/config/gpu_control_list_testing.json b/gpu/config/gpu_control_list_testing.json
index 9054a606..84505407 100644
--- a/gpu/config/gpu_control_list_testing.json
+++ b/gpu/config/gpu_control_list_testing.json
@@ -224,7 +224,7 @@
       "vendor_id": "0x8086",
       "driver_version": {
         "op": "<",
-        "value": "10.7"
+        "value": "24.20.100.7000"
       },
       "features": [
         "test_feature_1"
@@ -899,6 +899,43 @@
       "features": [
         "test_feature_0"
       ]
+    },
+    {
+      "id": 75,
+      "description": "GpuControlListEntryTest.IntelDriverVendorEntry",
+      "driver_vendor": "Intel.*",
+      "driver_version": {
+        "op": "between",
+        "value": "24.20.100.6000",
+        "value2": "26.20.100.7000"
+      },
+      "features": [
+        "test_feature_0"
+      ]
+    },
+    {
+      "id": 76,
+      "description": "GpuControlListEntryTest.IntelDriverVersionEntry",
+      "vendor_id": "0x8086",
+      "driver_version": {
+        "op": "<=",
+        "value": "24.20.100.7000"
+      },
+      "features": [
+        "test_feature_0"
+      ]
+    },
+    {
+      "id": 77,
+      "description": "GpuControlListEntryTest.IntelOldDriverVersionEntry",
+      "vendor_id": "0x8086",
+      "driver_version": {
+        "op": "<=",
+        "value": "24.20.10.7000"
+      },
+      "features": [
+        "test_feature_0"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_control_list_testing_arrays_and_structs_autogen.h b/gpu/config/gpu_control_list_testing_arrays_and_structs_autogen.h
index ab6171f7..167430b 100644
--- a/gpu/config/gpu_control_list_testing_arrays_and_structs_autogen.h
+++ b/gpu/config/gpu_control_list_testing_arrays_and_structs_autogen.h
@@ -525,8 +525,8 @@
 
 const GpuControlList::DriverInfo kDriverInfoForGpuControlTestingEntry20 = {
     nullptr,  // driver_vendor
-    {GpuControlList::kLT, GpuControlList::kVersionStyleNumerical, "10.7",
-     nullptr},  // driver_version
+    {GpuControlList::kLT, GpuControlList::kVersionStyleNumerical,
+     "24.20.100.7000", nullptr},  // driver_version
 };
 
 const GpuControlList::More kMoreForEntry20_1440601243 = {
@@ -2061,6 +2061,87 @@
     GpuControlList::kDontCare,  // subpixel_font_rendering
 };
 
+const int kFeatureListForGpuControlTestingEntry75[1] = {
+    TEST_FEATURE_0,
+};
+
+const GpuControlList::DriverInfo kDriverInfoForGpuControlTestingEntry75 = {
+    "Intel.*",  // driver_vendor
+    {GpuControlList::kBetween, GpuControlList::kVersionStyleNumerical,
+     "24.20.100.6000", "26.20.100.7000"},  // driver_version
+};
+
+const GpuControlList::More kMoreForEntry75_1440601243 = {
+    GpuControlList::kGLTypeNone,  // gl_type
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // gl_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // pixel_shader_version
+    false,      // in_process_gpu
+    0,          // gl_reset_notification_strategy
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // direct_rendering_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},                  // gpu_count
+    GpuControlList::kDontCare,  // hardware_overlay
+    0,                          // test_group
+    GpuControlList::kDontCare,  // subpixel_font_rendering
+};
+
+const int kFeatureListForGpuControlTestingEntry76[1] = {
+    TEST_FEATURE_0,
+};
+
+const GpuControlList::DriverInfo kDriverInfoForGpuControlTestingEntry76 = {
+    nullptr,  // driver_vendor
+    {GpuControlList::kLE, GpuControlList::kVersionStyleNumerical,
+     "24.20.100.7000", nullptr},  // driver_version
+};
+
+const GpuControlList::More kMoreForEntry76_1440601243 = {
+    GpuControlList::kGLTypeNone,  // gl_type
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // gl_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // pixel_shader_version
+    false,      // in_process_gpu
+    0,          // gl_reset_notification_strategy
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // direct_rendering_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},                  // gpu_count
+    GpuControlList::kDontCare,  // hardware_overlay
+    0,                          // test_group
+    GpuControlList::kDontCare,  // subpixel_font_rendering
+};
+
+const int kFeatureListForGpuControlTestingEntry77[1] = {
+    TEST_FEATURE_0,
+};
+
+const GpuControlList::DriverInfo kDriverInfoForGpuControlTestingEntry77 = {
+    nullptr,  // driver_vendor
+    {GpuControlList::kLE, GpuControlList::kVersionStyleNumerical,
+     "24.20.10.7000", nullptr},  // driver_version
+};
+
+const GpuControlList::More kMoreForEntry77_1440601243 = {
+    GpuControlList::kGLTypeNone,  // gl_type
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // gl_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // pixel_shader_version
+    false,      // in_process_gpu
+    0,          // gl_reset_notification_strategy
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},  // direct_rendering_version
+    {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical, nullptr,
+     nullptr},                  // gpu_count
+    GpuControlList::kDontCare,  // hardware_overlay
+    0,                          // test_group
+    GpuControlList::kDontCare,  // subpixel_font_rendering
+};
+
 }  // namespace gpu
 
 #endif  // GPU_CONFIG_GPU_CONTROL_LIST_TESTING_ARRAYS_AND_STRUCTS_AUTOGEN_H_
diff --git a/gpu/config/gpu_control_list_testing_autogen.cc b/gpu/config/gpu_control_list_testing_autogen.cc
index 78a175fd..3245838 100644
--- a/gpu/config/gpu_control_list_testing_autogen.cc
+++ b/gpu/config/gpu_control_list_testing_autogen.cc
@@ -2394,6 +2394,102 @@
         0,        // exceptions count
         nullptr,  // exceptions
     },
+    {
+        75,  // id
+        "GpuControlListEntryTest.IntelDriverVendorEntry",
+        base::size(kFeatureListForGpuControlTestingEntry75),  // features size
+        kFeatureListForGpuControlTestingEntry75,              // features
+        0,        // DisabledExtensions size
+        nullptr,  // DisabledExtensions
+        0,        // DisabledWebGLExtensions size
+        nullptr,  // DisabledWebGLExtensions
+        0,        // CrBugs size
+        nullptr,  // CrBugs
+        {
+            GpuControlList::kOsAny,  // os_type
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},                       // os_version
+            0x00,                                     // vendor_id
+            0,                                        // DeviceIDs size
+            nullptr,                                  // DeviceIDs
+            GpuControlList::kMultiGpuCategoryNone,    // multi_gpu_category
+            GpuControlList::kMultiGpuStyleNone,       // multi_gpu_style
+            &kDriverInfoForGpuControlTestingEntry75,  // driver info
+            nullptr,                                  // GL strings
+            nullptr,                                  // machine model info
+            0,                                        // intel_gpu_series size
+            nullptr,                                  // intel_gpu_series
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},           // intel_gpu_generation
+            &kMoreForEntry75_1440601243,  // more data
+        },
+        0,        // exceptions count
+        nullptr,  // exceptions
+    },
+    {
+        76,  // id
+        "GpuControlListEntryTest.IntelDriverVersionEntry",
+        base::size(kFeatureListForGpuControlTestingEntry76),  // features size
+        kFeatureListForGpuControlTestingEntry76,              // features
+        0,        // DisabledExtensions size
+        nullptr,  // DisabledExtensions
+        0,        // DisabledWebGLExtensions size
+        nullptr,  // DisabledWebGLExtensions
+        0,        // CrBugs size
+        nullptr,  // CrBugs
+        {
+            GpuControlList::kOsAny,  // os_type
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},                       // os_version
+            0x8086,                                   // vendor_id
+            0,                                        // DeviceIDs size
+            nullptr,                                  // DeviceIDs
+            GpuControlList::kMultiGpuCategoryNone,    // multi_gpu_category
+            GpuControlList::kMultiGpuStyleNone,       // multi_gpu_style
+            &kDriverInfoForGpuControlTestingEntry76,  // driver info
+            nullptr,                                  // GL strings
+            nullptr,                                  // machine model info
+            0,                                        // intel_gpu_series size
+            nullptr,                                  // intel_gpu_series
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},           // intel_gpu_generation
+            &kMoreForEntry76_1440601243,  // more data
+        },
+        0,        // exceptions count
+        nullptr,  // exceptions
+    },
+    {
+        77,  // id
+        "GpuControlListEntryTest.IntelOldDriverVersionEntry",
+        base::size(kFeatureListForGpuControlTestingEntry77),  // features size
+        kFeatureListForGpuControlTestingEntry77,              // features
+        0,        // DisabledExtensions size
+        nullptr,  // DisabledExtensions
+        0,        // DisabledWebGLExtensions size
+        nullptr,  // DisabledWebGLExtensions
+        0,        // CrBugs size
+        nullptr,  // CrBugs
+        {
+            GpuControlList::kOsAny,  // os_type
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},                       // os_version
+            0x8086,                                   // vendor_id
+            0,                                        // DeviceIDs size
+            nullptr,                                  // DeviceIDs
+            GpuControlList::kMultiGpuCategoryNone,    // multi_gpu_category
+            GpuControlList::kMultiGpuStyleNone,       // multi_gpu_style
+            &kDriverInfoForGpuControlTestingEntry77,  // driver info
+            nullptr,                                  // GL strings
+            nullptr,                                  // machine model info
+            0,                                        // intel_gpu_series size
+            nullptr,                                  // intel_gpu_series
+            {GpuControlList::kUnknown, GpuControlList::kVersionStyleNumerical,
+             nullptr, nullptr},           // intel_gpu_generation
+            &kMoreForEntry77_1440601243,  // more data
+        },
+        0,        // exceptions count
+        nullptr,  // exceptions
+    },
 };
-const size_t kGpuControlListTestingEntryCount = 74;
+const size_t kGpuControlListTestingEntryCount = 77;
 }  // namespace gpu
diff --git a/gpu/config/gpu_control_list_testing_entry_enums_autogen.h b/gpu/config/gpu_control_list_testing_entry_enums_autogen.h
index f70d8b3..fc12d78 100644
--- a/gpu/config/gpu_control_list_testing_entry_enums_autogen.h
+++ b/gpu/config/gpu_control_list_testing_entry_enums_autogen.h
@@ -87,6 +87,9 @@
   kGpuControlListEntryTest_GpuGenerationSecondary = 71,
   kGpuControlListEntryTest_SubpixelFontRendering = 72,
   kGpuControlListEntryTest_SubpixelFontRenderingDontCare = 73,
+  kGpuControlListEntryTest_IntelDriverVendorEntry = 74,
+  kGpuControlListEntryTest_IntelDriverVersionEntry = 75,
+  kGpuControlListEntryTest_IntelOldDriverVersionEntry = 76,
 };
 }  // namespace gpu
 
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index aa54cf59..7fee20a 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -682,8 +682,8 @@
       },
       "vendor_id": "0x8086",
       "driver_version": {
-        "op": "<=",
-        "value": "9.18.0.0"
+        "op": "<",
+        "value": "0.0.0.3000"
       },
       "features": [
         "disable_d3d11"
@@ -3002,8 +3002,8 @@
         "type": "win"
       },
       "driver_version": {
-        "op": ">=",
-        "value": "24"
+        "op": ">",
+        "value": "0.0.100.0"
       },
       "hardware_overlay": "unsupported",
       "features": [
@@ -3184,7 +3184,7 @@
       "vendor_id": "0x8086",
       "driver_version": {
         "op": "<",
-        "value": "24.0.0.0"
+        "value": "0.0.0.9999"
       },
       "features": [
         "disable_nv12_dynamic_textures"
diff --git a/gpu/config/process_json.py b/gpu/config/process_json.py
index dc052fe..08c56a2 100755
--- a/gpu/config/process_json.py
+++ b/gpu/config/process_json.py
@@ -36,6 +36,38 @@
     '': 'kOsAny',
   }
 
+INTEL_DRIVER_VERSION_SCHEMA = '''
+The version format of Intel graphics driver is AA.BB.CCC.DDDD.
+DDDD(old schema) or CCC.DDDD(new schema) is the build number. That is,
+indicates the actual driver number. The comparison between old schema
+and new schema is NOT valid. In such a condition the only comparison
+operator that returns true is "not equal".
+
+AA.BB: You are free to specify the real number here, but they are meaningless
+when comparing two version numbers. Usually it's okay to leave it to "0.0".
+
+CCC: It's necessary for new schema. Regarding to old schema, you can speicy
+the real number or any number less than 100 in order to differentiate from
+new schema.
+
+DDDD: It's always meaningful. It must not be "0" under old schema.
+
+Legal: "24.20.100.7000", "0.0.100.7000", "0.0.0.7000", "0.0.100.0"
+Illegal: "24.0.0.0", "24.20.0.0", "0.0.99.0"
+'''
+
+
+def check_intel_driver_version(version):
+  ver_list = version.split('.')
+  if len(ver_list) != 4:
+    return False
+  for ver in ver_list:
+    if not ver.isdigit():
+      return False
+  if int(ver_list[2]) < 100 and ver_list[3] == '0':
+    return False
+  return True
+
 
 def load_software_rendering_list_features(feature_type_filename):
   header_file = open(feature_type_filename, 'r')
@@ -485,6 +517,15 @@
   write_multi_gpu_style(multi_gpu_style, data_file)
   # group driver info
   if driver_vendor != '' or driver_version != None:
+    if driver_version and os_type == 'win':
+      if (format(vendor_id, '#04x') == '0x8086' or intel_gpu_series_list
+          or intel_gpu_generation or 'Intel' in driver_vendor):
+        if not check_intel_driver_version(driver_version['value']):
+          assert False, INTEL_DRIVER_VERSION_SCHEMA
+        if driver_version.has_key('value2'):
+          if not check_intel_driver_version(driver_version['value2']):
+            assert False, INTEL_DRIVER_VERSION_SCHEMA
+
     write_driver_info(entry_id, is_exception, exception_id, driver_vendor,
                       driver_version, unique_symbol_id,
                       data_file, data_helper_file)
diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json
index 7eefd99..68bed9e 100644
--- a/gpu/config/software_rendering_list.json
+++ b/gpu/config/software_rendering_list.json
@@ -1443,9 +1443,8 @@
       },
       "vendor_id": "0x8086",
       "driver_version": {
-        "comment": "INF_version: 8.16.0.0",
         "op": "<",
-        "value": "8.16.0.0"
+        "value": "0.0.0.3000"
       },
       "features": [
         "accelerated_webgl2"
diff --git a/gpu/vulkan/BUILD.gn b/gpu/vulkan/BUILD.gn
index 26e22a3c..32e981584 100644
--- a/gpu/vulkan/BUILD.gn
+++ b/gpu/vulkan/BUILD.gn
@@ -126,11 +126,14 @@
       deps += [ "//ui/gfx/x" ]
       configs += [ "//build/config/linux:x11" ]
     }
+    if (is_win) {
+      sources += [ "tests/native_window_win.cc" ]
+    }
   }
 
   # TODO(penghuang): support more platforms
   # https://crbug.com/1065499
-  if (is_android || use_x11) {
+  if (is_android || use_x11 || is_win) {
     test("vulkan_tests") {
       sources = [
         "tests/basic_vulkan_test.cc",
diff --git a/gpu/vulkan/generate_bindings.py b/gpu/vulkan/generate_bindings.py
index 0b9ab4c..cd10c516 100755
--- a/gpu/vulkan/generate_bindings.py
+++ b/gpu/vulkan/generate_bindings.py
@@ -67,6 +67,14 @@
     ]
   },
   {
+    'ifdef': 'defined(OS_WIN)',
+    'extension': 'VK_KHR_WIN32_SURFACE_EXTENSION_NAME',
+    'functions': [
+      'vkCreateWin32SurfaceKHR',
+      'vkGetPhysicalDeviceWin32PresentationSupportKHR',
+    ]
+  },
+  {
     'ifdef': 'defined(OS_ANDROID)',
     'extension': 'VK_KHR_ANDROID_SURFACE_EXTENSION_NAME',
     'functions': [
@@ -329,6 +337,10 @@
 #include <vulkan/vulkan_xlib.h>
 #endif
 
+#if defined(OS_WIN)
+#include <vulkan/vulkan_win32.h>
+#endif
+
 namespace gpu {
 
 struct VulkanFunctionPointers;
diff --git a/gpu/vulkan/init/vulkan_factory.cc b/gpu/vulkan/init/vulkan_factory.cc
index 5bfcb40..74791620 100644
--- a/gpu/vulkan/init/vulkan_factory.cc
+++ b/gpu/vulkan/init/vulkan_factory.cc
@@ -30,7 +30,7 @@
     bool use_swiftshader,
     bool allow_protected_memory,
     bool enforce_protected_memory) {
-#if !defined(USE_X11)
+#if !defined(USE_X11) && !defined(OS_WIN)
   // TODO(samans): Support Swiftshader on more platforms.
   // https://crbug.com/963988
   DCHECK(!use_swiftshader)
@@ -50,7 +50,7 @@
       ->CreateVulkanImplementation(allow_protected_memory,
                                    enforce_protected_memory);
 #elif defined(OS_WIN)
-  return std::make_unique<VulkanImplementationWin32>();
+  return std::make_unique<VulkanImplementationWin32>(use_swiftshader);
 #else
 #error Unsupported Vulkan Platform.
 #endif
diff --git a/gpu/vulkan/tests/basic_vulkan_test.cc b/gpu/vulkan/tests/basic_vulkan_test.cc
index 5724b56..6968ebd 100644
--- a/gpu/vulkan/tests/basic_vulkan_test.cc
+++ b/gpu/vulkan/tests/basic_vulkan_test.cc
@@ -4,6 +4,7 @@
 
 #include "gpu/vulkan/tests/basic_vulkan_test.h"
 
+#include "base/command_line.h"
 #include "gpu/vulkan/init/vulkan_factory.h"
 #include "gpu/vulkan/tests/native_window.h"
 #include "gpu/vulkan/vulkan_surface.h"
@@ -22,17 +23,21 @@
 
 void BasicVulkanTest::SetUp() {
   platform_event_source_ = ui::PlatformEventSource::CreateDefault();
-#if defined(USE_X11)
+#if defined(USE_X11) || defined(OS_WIN)
+  bool use_swiftshader =
+      base::CommandLine::ForCurrentProcess()->HasSwitch("use-swiftshader");
   const gfx::Rect kDefaultBounds(10, 10, 100, 100);
   window_ = CreateNativeWindow(kDefaultBounds);
 #elif defined(OS_ANDROID)
+  // Vulkan swiftshader is not supported on Android.
+  bool use_swiftshader = false;
   // TODO(penghuang): Not depend on gl.
   uint texture = 0;
   surface_texture_ = gl::SurfaceTexture::Create(texture);
   window_ = surface_texture_->CreateSurface();
   ASSERT_TRUE(window_ != gfx::kNullAcceleratedWidget);
 #endif
-  vulkan_implementation_ = CreateVulkanImplementation();
+  vulkan_implementation_ = CreateVulkanImplementation(use_swiftshader);
   ASSERT_TRUE(vulkan_implementation_);
   ASSERT_TRUE(vulkan_implementation_->InitializeVulkanInstance());
   device_queue_ = gpu::CreateVulkanDeviceQueue(
@@ -43,7 +48,7 @@
 }
 
 void BasicVulkanTest::TearDown() {
-#if defined(USE_X11)
+#if defined(USE_X11) || defined(OS_WIN)
   DestroyNativeWindow(window_);
 #elif defined(OS_ANDROID)
   ANativeWindow_release(window_);
diff --git a/gpu/vulkan/tests/native_window_win.cc b/gpu/vulkan/tests/native_window_win.cc
new file mode 100644
index 0000000..64f6094
--- /dev/null
+++ b/gpu/vulkan/tests/native_window_win.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Chromium 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 "gpu/vulkan/tests/native_window.h"
+
+#include <windows.h>
+
+#include <memory>
+
+#include "base/containers/flat_map.h"
+#include "ui/gfx/win/window_impl.h"
+
+namespace gpu {
+
+class Window;
+
+base::flat_map<gfx::AcceleratedWidget, std::unique_ptr<Window>> g_windows_;
+
+class Window : public gfx::WindowImpl {
+ public:
+  Window() { set_window_style(WS_VISIBLE | WS_POPUP); }
+  ~Window() override = default;
+
+ private:
+  // Overridden from gfx::WindowImpl:
+  BOOL ProcessWindowMessage(HWND window,
+                            UINT message,
+                            WPARAM w_param,
+                            LPARAM l_param,
+                            LRESULT& result,
+                            DWORD msg_map_id) override {
+    return false;  // Results in DefWindowProc().
+  }
+};
+
+gfx::AcceleratedWidget CreateNativeWindow(const gfx::Rect& bounds) {
+  auto window = std::make_unique<Window>();
+  window->Init(/*parent=*/nullptr, bounds);
+  gfx::AcceleratedWidget widget = window->hwnd();
+  g_windows_[widget] = std::move(window);
+  return widget;
+}
+
+void DestroyNativeWindow(gfx::AcceleratedWidget window) {
+  auto it = g_windows_.find(window);
+  DCHECK(it != g_windows_.end());
+
+  it->second.reset();
+  g_windows_.erase(it);
+}
+
+}  // namespace gpu
diff --git a/gpu/vulkan/vulkan_fence_helper.h b/gpu/vulkan/vulkan_fence_helper.h
index 52c04552..20fdfe11 100644
--- a/gpu/vulkan/vulkan_fence_helper.h
+++ b/gpu/vulkan/vulkan_fence_helper.h
@@ -28,7 +28,7 @@
 
   // Class representing a fence registered with this system. Should be treated
   // as an opaque handle.
-  class FenceHandle {
+  class VULKAN_EXPORT FenceHandle {
    public:
     FenceHandle();
     FenceHandle(const FenceHandle& other);
diff --git a/gpu/vulkan/vulkan_function_pointers.cc b/gpu/vulkan/vulkan_function_pointers.cc
index 3012f232..77c2a79a 100644
--- a/gpu/vulkan/vulkan_function_pointers.cc
+++ b/gpu/vulkan/vulkan_function_pointers.cc
@@ -261,6 +261,29 @@
   }
 #endif  // defined(USE_VULKAN_XLIB)
 
+#if defined(OS_WIN)
+  if (gfx::HasExtension(enabled_extensions,
+                        VK_KHR_WIN32_SURFACE_EXTENSION_NAME)) {
+    vkCreateWin32SurfaceKHRFn = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(
+        vkGetInstanceProcAddr(vk_instance, "vkCreateWin32SurfaceKHR"));
+    if (!vkCreateWin32SurfaceKHRFn) {
+      DLOG(WARNING) << "Failed to bind vulkan entrypoint: "
+                    << "vkCreateWin32SurfaceKHR";
+      return false;
+    }
+
+    vkGetPhysicalDeviceWin32PresentationSupportKHRFn =
+        reinterpret_cast<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>(
+            vkGetInstanceProcAddr(
+                vk_instance, "vkGetPhysicalDeviceWin32PresentationSupportKHR"));
+    if (!vkGetPhysicalDeviceWin32PresentationSupportKHRFn) {
+      DLOG(WARNING) << "Failed to bind vulkan entrypoint: "
+                    << "vkGetPhysicalDeviceWin32PresentationSupportKHR";
+      return false;
+    }
+  }
+#endif  // defined(OS_WIN)
+
 #if defined(OS_ANDROID)
   if (gfx::HasExtension(enabled_extensions,
                         VK_KHR_ANDROID_SURFACE_EXTENSION_NAME)) {
diff --git a/gpu/vulkan/vulkan_function_pointers.h b/gpu/vulkan/vulkan_function_pointers.h
index 1c82c67..290a7ea 100644
--- a/gpu/vulkan/vulkan_function_pointers.h
+++ b/gpu/vulkan/vulkan_function_pointers.h
@@ -36,6 +36,10 @@
 #include <vulkan/vulkan_xlib.h>
 #endif
 
+#if defined(OS_WIN)
+#include <vulkan/vulkan_win32.h>
+#endif
+
 namespace gpu {
 
 struct VulkanFunctionPointers;
@@ -134,6 +138,12 @@
       vkGetPhysicalDeviceXlibPresentationSupportKHRFn;
 #endif  // defined(USE_VULKAN_XLIB)
 
+#if defined(OS_WIN)
+  VulkanFunction<PFN_vkCreateWin32SurfaceKHR> vkCreateWin32SurfaceKHRFn;
+  VulkanFunction<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>
+      vkGetPhysicalDeviceWin32PresentationSupportKHRFn;
+#endif  // defined(OS_WIN)
+
 #if defined(OS_ANDROID)
   VulkanFunction<PFN_vkCreateAndroidSurfaceKHR> vkCreateAndroidSurfaceKHRFn;
 #endif  // defined(OS_ANDROID)
@@ -317,6 +327,14 @@
       ->vkGetPhysicalDeviceXlibPresentationSupportKHRFn
 #endif  // defined(USE_VULKAN_XLIB)
 
+#if defined(OS_WIN)
+#define vkCreateWin32SurfaceKHR \
+  gpu::GetVulkanFunctionPointers()->vkCreateWin32SurfaceKHRFn
+#define vkGetPhysicalDeviceWin32PresentationSupportKHR \
+  gpu::GetVulkanFunctionPointers()                     \
+      ->vkGetPhysicalDeviceWin32PresentationSupportKHRFn
+#endif  // defined(OS_WIN)
+
 #if defined(OS_ANDROID)
 #define vkCreateAndroidSurfaceKHR \
   gpu::GetVulkanFunctionPointers()->vkCreateAndroidSurfaceKHRFn
diff --git a/gpu/vulkan/win32/vulkan_implementation_win32.cc b/gpu/vulkan/win32/vulkan_implementation_win32.cc
index bc8b38c..1961307 100644
--- a/gpu/vulkan/win32/vulkan_implementation_win32.cc
+++ b/gpu/vulkan/win32/vulkan_implementation_win32.cc
@@ -17,44 +17,32 @@
 
 namespace gpu {
 
+VulkanImplementationWin32::VulkanImplementationWin32(bool use_swiftshader)
+    : VulkanImplementation(use_swiftshader) {}
+
 VulkanImplementationWin32::~VulkanImplementationWin32() = default;
 
 bool VulkanImplementationWin32::InitializeVulkanInstance(bool using_surface) {
   DCHECK(using_surface);
   std::vector<const char*> required_extensions = {
-      VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME};
+      VK_KHR_SURFACE_EXTENSION_NAME,
+      VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
+  };
 
   VulkanFunctionPointers* vulkan_function_pointers =
       gpu::GetVulkanFunctionPointers();
 
+  base::FilePath path(use_swiftshader() ? L"vk_swiftshader.dll"
+                                        : L"vulkan-1.dll");
+
   base::NativeLibraryLoadError native_library_load_error;
-  vulkan_function_pointers->vulkan_loader_library = base::LoadNativeLibrary(
-      base::FilePath(L"vulkan-1.dll"), &native_library_load_error);
+  vulkan_function_pointers->vulkan_loader_library =
+      base::LoadNativeLibrary(path, &native_library_load_error);
   if (!vulkan_function_pointers->vulkan_loader_library)
     return false;
 
   if (!vulkan_instance_.Initialize(required_extensions, {}))
     return false;
-
-  // Initialize platform function pointers
-  vkGetPhysicalDeviceWin32PresentationSupportKHR_ =
-      reinterpret_cast<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>(
-          vkGetInstanceProcAddr(
-              vulkan_instance_.vk_instance(),
-              "vkGetPhysicalDeviceWin32PresentationSupportKHR"));
-  if (!vkGetPhysicalDeviceWin32PresentationSupportKHR_) {
-    LOG(ERROR) << "vkGetPhysicalDeviceWin32PresentationSupportKHR not found";
-    return false;
-  }
-
-  vkCreateWin32SurfaceKHR_ =
-      reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(vkGetInstanceProcAddr(
-          vulkan_instance_.vk_instance(), "vkCreateWin32SurfaceKHR"));
-  if (!vkCreateWin32SurfaceKHR_) {
-    LOG(ERROR) << "vkCreateWin32SurfaceKHR not found";
-    return false;
-  }
-
   return true;
 }
 
@@ -70,7 +58,7 @@
   surface_create_info.hinstance =
       reinterpret_cast<HINSTANCE>(GetWindowLongPtr(window, GWLP_HINSTANCE));
   surface_create_info.hwnd = window;
-  VkResult result = vkCreateWin32SurfaceKHR_(
+  VkResult result = vkCreateWin32SurfaceKHR(
       vulkan_instance_.vk_instance(), &surface_create_info, nullptr, &surface);
   if (VK_SUCCESS != result) {
     DLOG(ERROR) << "vkCreatWin32SurfaceKHR() failed: " << result;
@@ -86,13 +74,15 @@
     VkPhysicalDevice device,
     const std::vector<VkQueueFamilyProperties>& queue_family_properties,
     uint32_t queue_family_index) {
-  return vkGetPhysicalDeviceWin32PresentationSupportKHR_(device,
-                                                         queue_family_index);
+  return vkGetPhysicalDeviceWin32PresentationSupportKHR(device,
+                                                        queue_family_index);
 }
 
 std::vector<const char*>
 VulkanImplementationWin32::GetRequiredDeviceExtensions() {
-  return {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
+  return {
+      VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+  };
 }
 
 std::vector<const char*>
diff --git a/gpu/vulkan/win32/vulkan_implementation_win32.h b/gpu/vulkan/win32/vulkan_implementation_win32.h
index 45c2110c..8e6b649 100644
--- a/gpu/vulkan/win32/vulkan_implementation_win32.h
+++ b/gpu/vulkan/win32/vulkan_implementation_win32.h
@@ -5,8 +5,6 @@
 #ifndef GPU_VULKAN_WIN32_VULKAN_IMPLEMENTATION_WIN32_H_
 #define GPU_VULKAN_WIN32_VULKAN_IMPLEMENTATION_WIN32_H_
 
-#include <memory>
-
 #include "base/component_export.h"
 #include "gpu/vulkan/vulkan_implementation.h"
 #include "gpu/vulkan/vulkan_instance.h"
@@ -16,7 +14,7 @@
 class COMPONENT_EXPORT(VULKAN_WIN32) VulkanImplementationWin32
     : public VulkanImplementation {
  public:
-  VulkanImplementationWin32() = default;
+  explicit VulkanImplementationWin32(bool use_swiftshader);
   ~VulkanImplementationWin32() override;
 
   // VulkanImplementation:
@@ -51,10 +49,6 @@
  private:
   VulkanInstance vulkan_instance_;
 
-  PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR
-      vkGetPhysicalDeviceWin32PresentationSupportKHR_ = nullptr;
-  PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR_ = nullptr;
-
   DISALLOW_COPY_AND_ASSIGN(VulkanImplementationWin32);
 };
 
diff --git a/infra/config/buckets/ci.star b/infra/config/buckets/ci.star
index 8777c01..016a7236c 100644
--- a/infra/config/buckets/ci.star
+++ b/infra/config/buckets/ci.star
@@ -953,6 +953,7 @@
     name = 'mac-upload-perfetto',
     os = os.MAC_DEFAULT,
     schedule = 'with 3h interval',
+    triggered_by = [],
 )
 
 ci.fyi_builder(
@@ -960,6 +961,7 @@
     name = 'win-upload-perfetto',
     os = os.WINDOWS_DEFAULT,
     schedule = 'with 3h interval',
+    triggered_by = [],
 )
 
 ci.fyi_celab_builder(
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 09b90b8..a5ac826c 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -10481,7 +10481,6 @@
   triggers: "mac-code-coverage"
   triggers: "mac-hermetic-upgrade-rel"
   triggers: "mac-mojo-rel"
-  triggers: "mac-upload-perfetto"
   triggers: "win-annotator-rel"
   triggers: "win-archive-dbg"
   triggers: "win-archive-rel"
@@ -10493,7 +10492,6 @@
   triggers: "win-swangle-tot-swiftshader-x86"
   triggers: "win-swangle-x64"
   triggers: "win-swangle-x86"
-  triggers: "win-upload-perfetto"
   triggers: "win10-code-coverage"
   triggers: "win32-archive-dbg"
   triggers: "win32-archive-rel"
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index 1d835f5d..25c60f0c 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -57,6 +57,7 @@
     "//ios/chrome/browser/u2f",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/main",
+    "//ios/chrome/browser/ui/main:scene",
     "//ios/chrome/browser/ui/main/test",
     "//ios/chrome/browser/ui/safe_mode",
     "//ios/chrome/browser/ui/settings",
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index 0f763ae5..8bc869b 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -308,10 +308,8 @@
 
   GetApplicationContext()->OnAppEnterForeground();
 
-  [MetricsMediator
-      logLaunchMetricsWithStartupInformation:_startupInformation
-                           interfaceProvider:_browserLauncher
-                                                 .interfaceProvider];
+  [MetricsMediator logLaunchMetricsWithStartupInformation:_startupInformation
+                                          connectedScenes:self.connectedScenes];
   [memoryHelper resetForegroundMemoryWarningCount];
 
   // Use the mainBVC as the ContentSuggestions can only be started in non-OTR.
diff --git a/ios/chrome/app/application_delegate/app_state_unittest.mm b/ios/chrome/app/application_delegate/app_state_unittest.mm
index 15e895a5..8161ddc 100644
--- a/ios/chrome/app/application_delegate/app_state_unittest.mm
+++ b/ios/chrome/app/application_delegate/app_state_unittest.mm
@@ -37,6 +37,7 @@
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/ui/main/browser_interface_provider.h"
+#import "ios/chrome/browser/ui/main/test/fake_scene_state.h"
 #import "ios/chrome/browser/ui/main/test/stub_browser_interface.h"
 #import "ios/chrome/browser/ui/main/test/stub_browser_interface_provider.h"
 #import "ios/chrome/browser/ui/safe_mode/safe_mode_coordinator.h"
@@ -263,14 +264,15 @@
 
     ScopedBlockSwizzler swizzler(
         [MetricsMediator class],
-        @selector(logLaunchMetricsWithStartupInformation:interfaceProvider:),
+        @selector(logLaunchMetricsWithStartupInformation:connectedScenes:),
         swizzleBlock);
 
     [appState applicationWillEnterForeground:application
                              metricsMediator:metricsMediator
                                 memoryHelper:memoryHelper
                                    tabOpener:tabOpener];
-
+    // TODO(crbug.com/1065815): Inject scene states for multiwindow as well.
+    app_state_.mainSceneState = [[FakeSceneState alloc] init];
     initializeIncognitoBlocker(window);
 
     return appState;
@@ -282,6 +284,8 @@
           [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
                                  startupInformation:startup_information_mock_
                                 applicationDelegate:main_application_delegate_];
+      // TODO(crbug.com/1065815): Inject scene states for multiwindow as well.
+      app_state_.mainSceneState = [[FakeSceneState alloc] init];
       [app_state_ setWindow:window_];
     }
     return app_state_;
@@ -293,6 +297,8 @@
           [[AppState alloc] initWithBrowserLauncher:browser_launcher_mock_
                                  startupInformation:startup_information_mock_
                                 applicationDelegate:main_application_delegate_];
+      // TODO(crbug.com/1065815): Inject scene states for multiwindow as well.
+      app_state_.mainSceneState = [[FakeSceneState alloc] init];
       [app_state_ setWindow:window];
       [window makeKeyAndVisible];
     }
@@ -738,7 +744,7 @@
 
   ScopedBlockSwizzler swizzler(
       [MetricsMediator class],
-      @selector(logLaunchMetricsWithStartupInformation:interfaceProvider:),
+      @selector(logLaunchMetricsWithStartupInformation:connectedScenes:),
       swizzleBlock);
 
   // Actions.
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.h b/ios/chrome/app/application_delegate/metrics_mediator.h
index 81e60326..1aae453 100644
--- a/ios/chrome/app/application_delegate/metrics_mediator.h
+++ b/ios/chrome/app/application_delegate/metrics_mediator.h
@@ -7,9 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
-@protocol StartupInformation;
-
-@protocol BrowserInterfaceProvider;
+@class SceneState;
 @protocol StartupInformation;
 
 namespace metrics_mediator {
@@ -38,8 +36,7 @@
 // Logs the number of tabs open and the start type.
 + (void)logLaunchMetricsWithStartupInformation:
             (id<StartupInformation>)startupInformation
-                             interfaceProvider:(id<BrowserInterfaceProvider>)
-                                                   interfaceProvider;
+                               connectedScenes:(NSArray<SceneState*>*)scenes;
 // Logs in UserDefaults the current date with kAppEnteredBackgroundDateKey as
 // key.
 + (void)logDateInUserDefaults;
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.mm b/ios/chrome/app/application_delegate/metrics_mediator.mm
index 90b22b65..1f8a608 100644
--- a/ios/chrome/app/application_delegate/metrics_mediator.mm
+++ b/ios/chrome/app/application_delegate/metrics_mediator.mm
@@ -22,13 +22,14 @@
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #include "ios/chrome/browser/crash_report/breakpad_helper.h"
+#include "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/metrics/first_user_action_recorder.h"
 #import "ios/chrome/browser/metrics/previous_session_info.h"
 #import "ios/chrome/browser/net/connection_type_observer_bridge.h"
 #include "ios/chrome/browser/pref_names.h"
 #include "ios/chrome/browser/system_flags.h"
-#import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/ui/main/browser_interface_provider.h"
+#import "ios/chrome/browser/ui/main/scene_state.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #include "ios/chrome/common/app_group/app_group_metrics_mainapp.h"
 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
@@ -143,10 +144,13 @@
 
 + (void)logLaunchMetricsWithStartupInformation:
             (id<StartupInformation>)startupInformation
-                             interfaceProvider:(id<BrowserInterfaceProvider>)
-                                                   interfaceProvider {
-  int numTabs =
-      static_cast<int>(interfaceProvider.mainInterface.tabModel.count);
+                               connectedScenes:(NSArray<SceneState*>*)scenes {
+  int numTabs = 0;
+  for (SceneState* scene in scenes) {
+    numTabs += scene.interfaceProvider.mainInterface.browser->GetWebStateList()
+                   ->count();
+  }
+
   if (startupInformation.isColdStart) {
     [self recordNumTabAtStartup:numTabs];
   } else {
@@ -169,15 +173,27 @@
     [startupInformation
         activateFirstUserActionRecorderWithBackgroundTime:interval];
 
-    web::WebState* currentWebState = interfaceProvider.currentInterface.tabModel
-                                         .webStateList->GetActiveWebState();
-    if (currentWebState &&
-        currentWebState->GetLastCommittedURL() == kChromeUINewTabURL) {
-      startupInformation.firstUserActionRecorder->RecordStartOnNTP();
-      [startupInformation resetFirstUserActionRecorder];
-    } else {
-      [startupInformation
-          expireFirstUserActionRecorderAfterDelay:kFirstUserActionTimeout];
+    SceneState* activeScene = nil;
+    for (SceneState* scene in scenes) {
+      if (scene.activationLevel == SceneActivationLevelForegroundActive) {
+        activeScene = scene;
+        break;
+      }
+    }
+
+    if (activeScene) {
+      web::WebState* currentWebState =
+          activeScene.interfaceProvider.currentInterface.browser
+              ->GetWebStateList()
+              ->GetActiveWebState();
+      if (currentWebState &&
+          currentWebState->GetLastCommittedURL() == kChromeUINewTabURL) {
+        startupInformation.firstUserActionRecorder->RecordStartOnNTP();
+        [startupInformation resetFirstUserActionRecorder];
+      } else {
+        [startupInformation
+            expireFirstUserActionRecorderAfterDelay:kFirstUserActionTimeout];
+      }
     }
     // Remove the value so it's not reused if the app crashes.
     [[NSUserDefaults standardUserDefaults]
diff --git a/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm b/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm
index e22adb08..a0ac288 100644
--- a/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm
+++ b/ios/chrome/app/application_delegate/metrics_mediator_unittest.mm
@@ -10,16 +10,20 @@
 #include "components/metrics/metrics_service.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
 #include "ios/chrome/browser/application_context.h"
+#import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/metrics/previous_session_info.h"
 #import "ios/chrome/browser/metrics/previous_session_info_private.h"
 #import "ios/chrome/browser/tabs/tab_model.h"
 #import "ios/chrome/browser/ui/main/browser_interface_provider.h"
-#import "ios/chrome/browser/ui/main/test/stub_browser_interface.h"
-#import "ios/chrome/browser/ui/main/test/stub_browser_interface_provider.h"
+#import "ios/chrome/browser/ui/main/scene_state.h"
+#import "ios/chrome/browser/ui/main/test/fake_scene_state.h"
 #import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_opener.h"
 #import "ios/chrome/test/ocmock/OCMockObject+BreakpadControllerTesting.h"
 #import "ios/testing/scoped_block_swizzler.h"
+#import "ios/web/public/test/fakes/test_web_state.h"
+#import "ios/web/public/test/web_task_environment.h"
 #include "net/base/network_change_notifier.h"
 #include "testing/platform_test.h"
 #import "third_party/breakpad/breakpad/src/client/ios/BreakpadController.h"
@@ -124,22 +128,9 @@
 
 class MetricsMediatorLogLaunchTest : public PlatformTest {
  protected:
-  MetricsMediatorLogLaunchTest()
-      : has_been_called_(FALSE),
-        web_state_list_(
-            std::make_unique<WebStateList>(&web_state_list_delegate_)) {}
+  MetricsMediatorLogLaunchTest() : has_been_called_(FALSE) {}
 
   void initiateMetricsMediator(BOOL coldStart, int tabCount) {
-    id mainTabModel = [OCMockObject mockForClass:[TabModel class]];
-    [[[mainTabModel stub] andReturnValue:@(tabCount)] count];
-    WebStateList* web_state_list = web_state_list_.get();
-    [[[mainTabModel stub] andReturnValue:OCMOCK_VALUE(web_state_list)]
-        webStateList];
-    StubBrowserInterfaceProvider* concreteProvider =
-        [[StubBrowserInterfaceProvider alloc] init];
-    concreteProvider.mainInterface.tabModel = mainTabModel;
-    interface_provider_ = concreteProvider;
-
     swizzle_block_ = [^(id self, int numTab) {
       has_been_called_ = YES;
       // Tests.
@@ -158,17 +149,12 @@
 
   void verifySwizzleHasBeenCalled() { EXPECT_TRUE(has_been_called_); }
 
-  id<BrowserInterfaceProvider> getInterfaceProvider() {
-    return interface_provider_;
-  }
-
- private:
-  id<BrowserInterfaceProvider> interface_provider_;
+  web::WebTaskEnvironment task_environment_;
+  NSArray<FakeSceneState*>* connected_scenes_;
   __block BOOL has_been_called_;
   logLaunchMetricsBlock swizzle_block_;
   std::unique_ptr<ScopedBlockSwizzler> uma_histogram_swizzler_;
-  std::unique_ptr<WebStateList> web_state_list_;
-  FakeWebStateListDelegate web_state_list_delegate_;
+  std::set<std::unique_ptr<TestBrowser>> browsers_;
 };
 
 // Verifies that the log of the number of open tabs is sent and verifies
@@ -177,6 +163,13 @@
   // Setup.
   BOOL coldStart = YES;
   initiateMetricsMediator(coldStart, 23);
+  // 23 tabs across three scenes.
+  connected_scenes_ = [FakeSceneState sceneArrayWithCount:3];
+  [connected_scenes_[0] appendWebStatesWithURL:GURL() count:9];
+  [connected_scenes_[1] appendWebStatesWithURL:GURL() count:9];
+  [connected_scenes_[2] appendWebStatesWithURL:GURL() count:5];
+  // Mark one of the scenes as active.
+  connected_scenes_[0].activationLevel = SceneActivationLevelForegroundActive;
 
   const NSTimeInterval kFirstUserActionTimeout = 30.0;
 
@@ -191,9 +184,8 @@
          forKey:metrics_mediator::kAppEnteredBackgroundDateKey];
 
   // Action.
-  [MetricsMediator
-      logLaunchMetricsWithStartupInformation:startupInformation
-                           interfaceProvider:getInterfaceProvider()];
+  [MetricsMediator logLaunchMetricsWithStartupInformation:startupInformation
+                                          connectedScenes:connected_scenes_];
 
   // Tests.
   NSDate* dateStored = [[NSUserDefaults standardUserDefaults]
@@ -209,6 +201,13 @@
   // Setup.
   BOOL coldStart = NO;
   initiateMetricsMediator(coldStart, 32);
+  // 32 tabs across five scenes.
+  connected_scenes_ = [FakeSceneState sceneArrayWithCount:5];
+  [connected_scenes_[0] appendWebStatesWithURL:GURL() count:8];
+  [connected_scenes_[1] appendWebStatesWithURL:GURL() count:8];
+  // Scene 2 has zero tabs.
+  [connected_scenes_[3] appendWebStatesWithURL:GURL() count:8];
+  [connected_scenes_[4] appendWebStatesWithURL:GURL() count:8];
 
   id startupInformation =
       [OCMockObject mockForProtocol:@protocol(StartupInformation)];
@@ -218,10 +217,8 @@
       removeObjectForKey:metrics_mediator::kAppEnteredBackgroundDateKey];
 
   // Action.
-  [MetricsMediator
-      logLaunchMetricsWithStartupInformation:startupInformation
-                           interfaceProvider:getInterfaceProvider()];
-
+  [MetricsMediator logLaunchMetricsWithStartupInformation:startupInformation
+                                          connectedScenes:connected_scenes_];
   // Tests.
   verifySwizzleHasBeenCalled();
 }
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index 3d6f65b..8d4c441 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -540,7 +540,7 @@
   [self scheduleStartupCleanupTasks];
   [MetricsMediator
       logLaunchMetricsWithStartupInformation:self
-                           interfaceProvider:self.interfaceProvider];
+                             connectedScenes:self.appState.connectedScenes];
   if (self.isColdStart) {
     [ContentSuggestionsSchedulerNotifications
         notifyColdStart:self.mainBrowserState];
diff --git a/ios/chrome/browser/signin/gaia_auth_fetcher_ios_ns_url_session_bridge_unittests.mm b/ios/chrome/browser/signin/gaia_auth_fetcher_ios_ns_url_session_bridge_unittests.mm
index 304c750..14e5931 100644
--- a/ios/chrome/browser/signin/gaia_auth_fetcher_ios_ns_url_session_bridge_unittests.mm
+++ b/ios/chrome/browser/signin/gaia_auth_fetcher_ios_ns_url_session_bridge_unittests.mm
@@ -323,7 +323,8 @@
 // Tests to send a request with no cookies set in the cookie store and receive
 // multiples cookies from the request.
 // TODO(crbug.com/1065349): this test is flaky.
-TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest, FetchWithEmptyCookieStore) {
+TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest,
+       DISABLED_FetchWithEmptyCookieStore) {
   ns_url_session_bridge_->Fetch(GetFetchGURL(), "", "", false);
   OCMExpect([http_cookie_storage_mock_
       storeCookies:@[]
@@ -345,7 +346,8 @@
 // Tests to send a request with one cookie set in the cookie store and receive
 // another cookies from the request.
 // TODO(crbug.com/1065349): this test is flaky.
-TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest, FLAKY_FetchWithCookieStore) {
+TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest,
+       DISABLED_FetchWithCookieStore) {
   NSArray* cookies_to_send = @[ GetCookie1() ];
   AddCookiesToCookieManager(cookies_to_send);
   ns_url_session_bridge_->Fetch(GetFetchGURL(), "", "", false);
@@ -368,7 +370,7 @@
 // Tests to a request with a redirect. One cookie is received by the first
 // request, and a second one by the redirected request.
 // TODO(crbug.com/1065349): this test is flaky.
-TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest, FLAKY_FetchWithRedirect) {
+TEST_F(GaiaAuthFetcherIOSNSURLSessionBridgeTest, DISABLED_FetchWithRedirect) {
   ns_url_session_bridge_->Fetch(GetFetchGURL(), "", "", false);
   OCMExpect([http_cookie_storage_mock_
       storeCookies:@[]
diff --git a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
index d6db722..443c263c 100644
--- a/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
+++ b/ios/chrome/browser/test/perf_test_with_bvc_ios.mm
@@ -123,8 +123,6 @@
   bvc_ = [[BrowserViewController alloc]
                      initWithBrowser:browser_.get()
                    dependencyFactory:bvc_factory_
-          applicationCommandEndpoint:nil
-         browsingDataCommandEndpoint:nil
       browserContainerViewController:[[BrowserContainerViewController alloc]
                                          init]];
   [bvc_ setActive:YES];
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.h b/ios/chrome/browser/ui/browser_view/browser_coordinator.h
index 28666d45..100eaf9 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.h
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.h
@@ -8,8 +8,6 @@
 #include "base/ios/block_types.h"
 #import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
 
-@protocol ApplicationCommands;
-@protocol BrowsingDataCommands;
 @class BrowserViewController;
 
 class AppUrlLoadingService;
@@ -27,11 +25,6 @@
 // The main view controller.
 @property(nonatomic, strong, readonly) BrowserViewController* viewController;
 
-// Command handler for ApplicationCommands.
-@property(nonatomic, weak) id<ApplicationCommands> applicationCommandHandler;
-// Command handler for BrowsingDataCommands.
-@property(nonatomic, weak) id<BrowsingDataCommands> browsingDataCommandHandler;
-
 // The application level component for url loading. Should be used only by
 // browser state level UrlLoadingService instances.
 @property(nonatomic, assign) AppUrlLoadingService* appURLLoadingService;
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index ff65788..3342827c 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -296,8 +296,6 @@
   _viewController = [[BrowserViewController alloc]
                      initWithBrowser:self.browser
                    dependencyFactory:factory
-          applicationCommandEndpoint:self.applicationCommandHandler
-         browsingDataCommandEndpoint:self.browsingDataCommandHandler
       browserContainerViewController:self.browserContainerCoordinator
                                          .viewController];
 }
@@ -726,17 +724,17 @@
 #pragma mark - FormInputAccessoryCoordinatorNavigator
 
 - (void)openPasswordSettings {
-  [self.applicationCommandHandler
+  [HandlerForProtocol(self.dispatcher, ApplicationCommands)
       showSavedPasswordsSettingsFromViewController:self.viewController];
 }
 
 - (void)openAddressSettings {
-  [self.applicationCommandHandler
+  [HandlerForProtocol(self.dispatcher, ApplicationCommands)
       showProfileSettingsFromViewController:self.viewController];
 }
 
 - (void)openCreditCardSettings {
-  [self.applicationCommandHandler
+  [HandlerForProtocol(self.dispatcher, ApplicationCommands)
       showCreditCardSettingsFromViewController:self.viewController];
 }
 
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.h b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
index d9ec30b..c3be42a 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
@@ -49,10 +49,6 @@
 - (instancetype)initWithBrowser:(Browser*)browser
                  dependencyFactory:
                      (BrowserViewControllerDependencyFactory*)factory
-        applicationCommandEndpoint:
-            (id<ApplicationCommands>)applicationCommandEndpoint
-       browsingDataCommandEndpoint:
-           (id<BrowsingDataCommands>)browsingDataCommandEndpoint
     browserContainerViewController:
         (BrowserContainerViewController*)browserContainerViewController
     NS_DESIGNATED_INITIALIZER;
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index b16b3dd..6bf41ca8 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -713,10 +713,6 @@
 - (instancetype)initWithBrowser:(Browser*)browser
                  dependencyFactory:
                      (BrowserViewControllerDependencyFactory*)factory
-        applicationCommandEndpoint:
-            (id<ApplicationCommands>)applicationCommandEndpoint
-       browsingDataCommandEndpoint:
-           (id<BrowsingDataCommands>)browsingDataCommandEndpoint
     browserContainerViewController:
         (BrowserContainerViewController*)browserContainerViewController {
   self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
@@ -731,22 +727,7 @@
     [self.commandDispatcher
         startDispatchingToTarget:self
                      forProtocol:@protocol(BrowserCommands)];
-    [self.commandDispatcher
-        startDispatchingToTarget:applicationCommandEndpoint
-                     forProtocol:@protocol(ApplicationCommands)];
-    // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
-    // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
-    // dispatched to the endpoint as well. Since this is potentially
-    // fragile, DCHECK that it should still work (if the endpoint is nonnull).
-    DCHECK(!applicationCommandEndpoint ||
-           [applicationCommandEndpoint
-               conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
-    [self.commandDispatcher
-        startDispatchingToTarget:applicationCommandEndpoint
-                     forProtocol:@protocol(ApplicationSettingsCommands)];
-    [self.commandDispatcher
-        startDispatchingToTarget:browsingDataCommandEndpoint
-                     forProtocol:@protocol(BrowsingDataCommands)];
+
     _toolbarCoordinatorAdaptor =
         [[ToolbarCoordinatorAdaptor alloc] initWithDispatcher:self.dispatcher];
     self.toolbarInterface = _toolbarCoordinatorAdaptor;
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 8778f054..775754b 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -129,6 +129,20 @@
         startDispatchingToTarget:mockPageInfoCommandHandler
                      forProtocol:@protocol(PageInfoCommands)];
 
+    // Set up ApplicationCommands mock. Because ApplicationCommands conforms
+    // to ApplicationSettingsCommands, that needs to be mocked and dispatched
+    // as well.
+    id mockApplicationCommandHandler =
+        OCMProtocolMock(@protocol(ApplicationCommands));
+    id mockApplicationSettingsCommandHandler =
+        OCMProtocolMock(@protocol(ApplicationSettingsCommands));
+    [browser_->GetCommandDispatcher()
+        startDispatchingToTarget:mockApplicationCommandHandler
+                     forProtocol:@protocol(ApplicationCommands)];
+    [browser_->GetCommandDispatcher()
+        startDispatchingToTarget:mockApplicationSettingsCommandHandler
+                     forProtocol:@protocol(ApplicationSettingsCommands)];
+
     // Create three web states.
     for (int i = 0; i < 3; i++) {
       web::WebState::CreateParams params(chrome_browser_state_.get());
@@ -145,15 +159,9 @@
             chrome_browser_state_.get());
     template_url_service->Load();
 
-    // Instantiate the BVC.
-    id mockApplicationCommandHandler =
-        OCMProtocolMock(@protocol(ApplicationCommands));
-
     bvc_ = [[BrowserViewController alloc]
                        initWithBrowser:browser_.get()
                      dependencyFactory:factory
-            applicationCommandEndpoint:mockApplicationCommandHandler
-           browsingDataCommandEndpoint:nil
         browserContainerViewController:[[BrowserContainerViewController alloc]
                                            init]];
 
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index f95489ab..f014a856 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -22,6 +22,9 @@
 #import "ios/chrome/browser/ui/browser_view/browser_coordinator.h"
 #import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
+#import "ios/chrome/browser/ui/commands/application_commands.h"
+#import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
+#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/url_loading/app_url_loading_service.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -170,6 +173,7 @@
   BrowserList* browserList =
       BrowserListFactory::GetForBrowserState(_mainBrowser->GetBrowserState());
   browserList->AddBrowser(_mainBrowser.get());
+  [self dispatchToEndpointsForBrowser:_mainBrowser.get()];
   [self restoreSessionToBrowser:_mainBrowser.get()];
   [self addObserversToWebStateList:_mainBrowser->GetWebStateList()];
 
@@ -381,6 +385,7 @@
   BrowserList* browserList =
       BrowserListFactory::GetForBrowserState(browser->GetBrowserState());
   browserList->AddIncognitoBrowser(browser.get());
+  [self dispatchToEndpointsForBrowser:browser.get()];
   if (restorePersistedState)
     [self restoreSessionToBrowser:browser.get()];
 
@@ -393,12 +398,29 @@
   BrowserCoordinator* coordinator =
       [[BrowserCoordinator alloc] initWithBaseViewController:nil
                                                      browser:browser];
-  coordinator.applicationCommandHandler = _applicationCommandEndpoint;
-  coordinator.browsingDataCommandHandler = _browsingDataCommandEndpoint;
   coordinator.appURLLoadingService = _appURLLoadingService;
   return coordinator;
 }
 
+- (void)dispatchToEndpointsForBrowser:(Browser*)browser {
+  [browser->GetCommandDispatcher()
+      startDispatchingToTarget:_applicationCommandEndpoint
+                   forProtocol:@protocol(ApplicationCommands)];
+  // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
+  // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
+  // dispatched to the endpoint as well. Since this is potentially
+  // fragile, DCHECK that it should still work (if the endpoint is non-nil).
+  DCHECK(!_applicationCommandEndpoint ||
+         [_applicationCommandEndpoint
+             conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
+  [browser->GetCommandDispatcher()
+      startDispatchingToTarget:_applicationCommandEndpoint
+                   forProtocol:@protocol(ApplicationSettingsCommands)];
+  [browser->GetCommandDispatcher()
+      startDispatchingToTarget:_browsingDataCommandEndpoint
+                   forProtocol:@protocol(BrowsingDataCommands)];
+}
+
 - (void)restoreSessionToBrowser:(Browser*)browser {
   SessionWindowIOS* sessionWindow = nil;
   NSString* statePath = base::SysUTF8ToNSString(
diff --git a/ios/chrome/browser/ui/main/test/BUILD.gn b/ios/chrome/browser/ui/main/test/BUILD.gn
index 9d3709a..a2e9ab2 100644
--- a/ios/chrome/browser/ui/main/test/BUILD.gn
+++ b/ios/chrome/browser/ui/main/test/BUILD.gn
@@ -4,11 +4,21 @@
 
 source_set("test") {
   configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
   sources = [
+    "fake_scene_state.h",
+    "fake_scene_state.mm",
     "stub_browser_interface.h",
     "stub_browser_interface.mm",
     "stub_browser_interface_provider.h",
     "stub_browser_interface_provider.mm",
   ]
-  deps = [ "//ios/chrome/browser/ui/main" ]
+  deps = [
+    "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/main:test_support",
+    "//ios/chrome/browser/ui/main",
+    "//ios/chrome/browser/ui/main:scene",
+    "//ios/chrome/browser/web_state_list",
+    "//ios/web/public/test/fakes",
+  ]
 }
diff --git a/ios/chrome/browser/ui/main/test/fake_scene_state.h b/ios/chrome/browser/ui/main/test/fake_scene_state.h
new file mode 100644
index 0000000..113cc80
--- /dev/null
+++ b/ios/chrome/browser/ui/main/test/fake_scene_state.h
@@ -0,0 +1,27 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_MAIN_TEST_FAKE_SCENE_STATE_H_
+#define IOS_CHROME_BROWSER_UI_MAIN_TEST_FAKE_SCENE_STATE_H_
+
+#import "ios/chrome/browser/ui/main/scene_state.h"
+#import "url/gurl.h"
+
+// Test double for SceneState, created with appropriate interface objects backed
+// by a browser. No incognito interface is created by default.
+// Any test using objects of this class must include a TaskEnvironment member
+// because of the embedded test browser state.
+@interface FakeSceneState : SceneState
+
+// Creates an array of |count| instances.
++ (NSArray<FakeSceneState*>*)sceneArrayWithCount:(int)count;
+
+// Append a suitable web state test double to the receiver's main interface.
+- (void)appendWebStateWithURL:(const GURL)URL;
+// Append |count| web states, all with |url| as the current URL, to the
+- (void)appendWebStatesWithURL:(const GURL)URL count:(int)count;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_MAIN_TEST_FAKE_SCENE_STATE_H_
diff --git a/ios/chrome/browser/ui/main/test/fake_scene_state.mm b/ios/chrome/browser/ui/main/test/fake_scene_state.mm
new file mode 100644
index 0000000..90d1a91
--- /dev/null
+++ b/ios/chrome/browser/ui/main/test/fake_scene_state.mm
@@ -0,0 +1,68 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/main/test/fake_scene_state.h"
+
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/main/test_browser.h"
+#import "ios/chrome/browser/ui/main/test/stub_browser_interface.h"
+#import "ios/chrome/browser/ui/main/test/stub_browser_interface_provider.h"
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_opener.h"
+#import "ios/web/public/test/fakes/test_web_state.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface FakeSceneState ()
+// Redeclare interface provider readwrite.
+@property(nonatomic, strong, readwrite) id<BrowserInterfaceProvider>
+    interfaceProvider;
+@end
+
+@implementation FakeSceneState {
+  // Owning pointer for the browser that backs the interface provider.
+  std::unique_ptr<TestBrowser> _browser;
+}
+
+@synthesize interfaceProvider = _interfaceProvider;
+
+- (instancetype)init {
+  if (self = [super init]) {
+    self.activationLevel = SceneActivationLevelForegroundInactive;
+    self.interfaceProvider = [[StubBrowserInterfaceProvider alloc] init];
+    StubBrowserInterface* mainInterface = static_cast<StubBrowserInterface*>(
+        self.interfaceProvider.mainInterface);
+    _browser = std::make_unique<TestBrowser>();
+    mainInterface.browser = _browser.get();
+  }
+  return self;
+}
+
++ (NSArray<FakeSceneState*>*)sceneArrayWithCount:(int)count {
+  NSMutableArray<SceneState*>* scenes = [NSMutableArray array];
+  for (int i = 0; i < count; i++) {
+    [scenes addObject:[[self alloc] init]];
+  }
+  return [scenes copy];
+}
+
+- (void)appendWebStateWithURL:(const GURL)URL {
+  auto test_web_state = std::make_unique<web::TestWebState>();
+  test_web_state->SetCurrentURL(URL);
+  WebStateList* web_state_list =
+      self.interfaceProvider.mainInterface.browser->GetWebStateList();
+  web_state_list->InsertWebState(
+      WebStateList::kInvalidIndex, std::move(test_web_state),
+      WebStateList::INSERT_NO_FLAGS, WebStateOpener());
+}
+
+- (void)appendWebStatesWithURL:(const GURL)URL count:(int)count {
+  for (int i = 0; i < count; i++) {
+    [self appendWebStateWithURL:URL];
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index a0de7b03..8d348c9 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -151,6 +151,7 @@
   ]
   deps = [
     ":constants",
+    "//base/test:test_support",
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
@@ -174,6 +175,7 @@
   ]
   deps = [
     "//base",
+    "//base/test:test_support",
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui/popup_menu:constants",
diff --git a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
index 7e3260436..2b0bbac 100644
--- a/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
+++ b/ios/chrome/browser/ui/popup_menu/request_desktop_mobile_site_egtest.mm
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/strings/sys_string_conversions.h"
+#import "base/test/ios/wait_util.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -31,6 +32,17 @@
 const char kDesktopSiteLabel[] = "Desktop";
 const char kDesktopPlatformLabel[] = "MacIntel";
 
+// URL to be used when the page needs to be reloaded on back/forward
+// navigations.
+const char kPurgeURL[] = "url-purge.com";
+// JavaScript used to reload the page on back/forward navigations.
+const char kJavaScriptReload[] =
+    "<script>window.onpageshow = function(event) {"
+    "    if (event.persisted) {"
+    "       window.location.href = window.location.href + \"?reloaded\""
+    "    }"
+    "};</script>";
+
 // Custom timeout used when waiting for a web state after requesting desktop
 // or mobile mode.
 const NSTimeInterval kWaitForUserAgentChangeTimeout = 15.0;
@@ -79,15 +91,20 @@
       return;
     }
 
+    std::string purge_additions = "";
+    if (request.url.path().find(kPurgeURL) != std::string::npos) {
+      purge_additions = kJavaScriptReload;
+    }
+
     *headers = web::ResponseProvider::GetDefaultResponseHeaders();
     std::string userAgent;
     std::string desktop_user_agent =
         web::BuildUserAgentFromProduct(web::UserAgentType::DESKTOP, "");
     if (request.headers.GetHeader("User-Agent", &userAgent) &&
         userAgent == desktop_user_agent) {
-      response_body->assign("Desktop");
+      response_body->assign(std::string("Desktop\n") + purge_additions);
     } else {
-      response_body->assign("Mobile");
+      response_body->assign(std::string("Mobile\n") + purge_additions);
     }
   }
 };
@@ -168,6 +185,45 @@
   [ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel];
 }
 
+// Tests that when requesting desktop on another page and coming back to a page
+// that has been purged from memory, we still display the mobile page.
+- (void)testRequestDesktopSiteGoBackToMobilePurged {
+  if (@available(iOS 13, *)) {
+  } else {
+    EARL_GREY_TEST_DISABLED(@"On iOS 12, the User Agent can be wrong when "
+                            @"doing back/forward navigations");
+  }
+
+  std::unique_ptr<web::DataResponseProvider> provider(
+      new UserAgentResponseProvider());
+  web::test::SetUpHttpServer(std::move(provider));
+
+  [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(
+                              "http://" + std::string(kPurgeURL))];
+  // Verify initial reception of the mobile site.
+  [ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel];
+
+  [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl("http://2.com")];
+
+  // Request and verify reception of the desktop site.
+  [ChromeEarlGreyUI openToolsMenu];
+  [RequestDesktopButton() performAction:grey_tap()];
+  [ChromeEarlGrey waitForWebStateContainingText:kDesktopSiteLabel
+                                        timeout:kWaitForUserAgentChangeTimeout];
+
+  // Verify that going back returns to the mobile site.
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
+      performAction:grey_tap()];
+  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
+                 base::test::ios::kWaitForPageLoadTimeout,
+                 ^bool {
+                   return [ChromeEarlGrey webStateVisibleURL].query() ==
+                          "reloaded";
+                 }),
+             @"Page did not reload");
+  [ChromeEarlGrey waitForWebStateContainingText:kMobileSiteLabel];
+}
+
 // Tests that requesting mobile site of a page works and the user agent
 // propagates to the next navigations in the same tab.
 - (void)testRequestMobileSitePropagatesToNextNavigations {
diff --git a/ios/chrome/browser/ui/settings/BUILD.gn b/ios/chrome/browser/ui/settings/BUILD.gn
index c774202..6dc9484f 100644
--- a/ios/chrome/browser/ui/settings/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/BUILD.gn
@@ -54,12 +54,8 @@
     "content_settings_table_view_controller.mm",
     "dataplan_usage_table_view_controller.h",
     "dataplan_usage_table_view_controller.mm",
-    "handoff_table_view_controller.h",
-    "handoff_table_view_controller.mm",
     "import_data_table_view_controller.h",
     "import_data_table_view_controller.mm",
-    "privacy_table_view_controller.h",
-    "privacy_table_view_controller.mm",
     "search_engine_table_view_controller.h",
     "search_engine_table_view_controller.mm",
     "settings_navigation_controller.mm",
@@ -105,7 +101,6 @@
     "//components/content_settings/core/browser",
     "//components/content_settings/core/common",
     "//components/feature_engagement",
-    "//components/handoff",
     "//components/history/core/browser",
     "//components/image_fetcher/ios",
     "//components/keyed_service/core",
@@ -160,6 +155,7 @@
     "//ios/chrome/browser/ui/settings/language:language",
     "//ios/chrome/browser/ui/settings/language:language_ui",
     "//ios/chrome/browser/ui/settings/password",
+    "//ios/chrome/browser/ui/settings/privacy",
     "//ios/chrome/browser/ui/settings/sync",
     "//ios/chrome/browser/ui/settings/sync/utils",
     "//ios/chrome/browser/ui/settings/utils",
@@ -255,7 +251,6 @@
     "content_settings_table_view_controller_unittest.mm",
     "dataplan_usage_table_view_controller_unittest.mm",
     "import_data_table_view_controller_unittest.mm",
-    "privacy_table_view_controller_unittest.mm",
     "search_engine_table_view_controller_unittest.mm",
     "settings_navigation_controller_unittest.mm",
     "settings_root_table_view_controller_unittest.mm",
diff --git a/ios/chrome/browser/ui/settings/privacy/BUILD.gn b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
new file mode 100644
index 0000000..ba0895b
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/privacy/BUILD.gn
@@ -0,0 +1,65 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("privacy") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "handoff_table_view_controller.h",
+    "handoff_table_view_controller.mm",
+    "privacy_table_view_controller.h",
+    "privacy_table_view_controller.mm",
+  ]
+  deps = [
+    "//base",
+    "//components/handoff",
+    "//components/prefs",
+    "//components/prefs/ios",
+    "//components/strings",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/browsing_data:feature_flags",
+    "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/ui:feature_flags",
+    "//ios/chrome/browser/ui/colors",
+    "//ios/chrome/browser/ui/commands",
+    "//ios/chrome/browser/ui/settings:constants",
+    "//ios/chrome/browser/ui/settings:settings_root",
+    "//ios/chrome/browser/ui/settings/cells",
+    "//ios/chrome/browser/ui/settings/clear_browsing_data:clear_browsing_data",
+    "//ios/chrome/browser/ui/settings/sync/utils",
+    "//ios/chrome/browser/ui/settings/utils",
+    "//ios/chrome/browser/ui/table_view",
+    "//ios/chrome/browser/ui/table_view/cells",
+    "//ios/chrome/browser/ui/table_view/cells:cells_constants",
+    "//ui/base",
+  ]
+}
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [ "privacy_table_view_controller_unittest.mm" ]
+  deps = [
+    "//base/test:test_support",
+    "//components/handoff",
+    "//components/prefs",
+    "//components/prefs/ios",
+    "//components/strings",
+    "//components/sync_preferences",
+    "//components/sync_preferences:test_support",
+    "//ios/chrome/app/strings",
+    "//ios/chrome/browser",
+    "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/browsing_data:feature_flags",
+    "//ios/chrome/browser/main:test_support",
+    "//ios/chrome/browser/prefs:browser_prefs",
+    "//ios/chrome/browser/ui/settings/privacy",
+    "//ios/chrome/browser/ui/table_view:test_support",
+    "//ios/chrome/test:test_support",
+    "//ios/web/public/test",
+    "//testing/gtest",
+    "//ui/base",
+  ]
+}
diff --git a/ios/chrome/browser/ui/settings/handoff_table_view_controller.h b/ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h
similarity index 73%
rename from ios/chrome/browser/ui/settings/handoff_table_view_controller.h
rename to ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h
index e60017d..7007123 100644
--- a/ios/chrome/browser/ui/settings/handoff_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_HANDOFF_TABLE_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_SETTINGS_HANDOFF_TABLE_VIEW_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_HANDOFF_TABLE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_HANDOFF_TABLE_VIEW_CONTROLLER_H_
 
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
@@ -20,4 +20,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_HANDOFF_TABLE_VIEW_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_HANDOFF_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/handoff_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.mm
similarity index 94%
rename from ios/chrome/browser/ui/settings/handoff_table_view_controller.mm
rename to ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.mm
index 351c4b3..89b4545 100644
--- a/ios/chrome/browser/ui/settings/handoff_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/settings/handoff_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h"
 
 #import "base/mac/foundation_util.h"
 #include "components/handoff/pref_names_ios.h"
@@ -93,8 +93,8 @@
 
 - (UITableViewCell*)tableView:(UITableView*)tableView
         cellForRowAtIndexPath:(NSIndexPath*)indexPath {
-  UITableViewCell* cell =
-      [super tableView:tableView cellForRowAtIndexPath:indexPath];
+  UITableViewCell* cell = [super tableView:tableView
+                     cellForRowAtIndexPath:indexPath];
 
   ItemType itemType = static_cast<ItemType>(
       [self.tableViewModel itemTypeForIndexPath:indexPath]);
diff --git a/ios/chrome/browser/ui/settings/privacy_table_view_controller.h b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h
similarity index 72%
rename from ios/chrome/browser/ui/settings/privacy_table_view_controller.h
rename to ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h
index f12369e..a82822d0 100644
--- a/ios/chrome/browser/ui/settings/privacy_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_TABLE_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_TABLE_VIEW_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_PRIVACY_TABLE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_PRIVACY_TABLE_VIEW_CONTROLLER_H_
 
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
@@ -21,4 +21,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_TABLE_VIEW_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRIVACY_PRIVACY_TABLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
similarity index 97%
rename from ios/chrome/browser/ui/settings/privacy_table_view_controller.mm
rename to ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
index 9772d45..10a6777 100644
--- a/ios/chrome/browser/ui/settings/privacy_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/settings/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
 
 #include "base/logging.h"
 #import "base/mac/foundation_util.h"
@@ -20,7 +20,7 @@
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_delegate.h"
-#import "ios/chrome/browser/ui/settings/handoff_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/handoff_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
@@ -189,8 +189,8 @@
 
 - (UIView*)tableView:(UITableView*)tableView
     viewForFooterInSection:(NSInteger)section {
-  UIView* footerView =
-      [super tableView:tableView viewForFooterInSection:section];
+  UIView* footerView = [super tableView:tableView
+                 viewForFooterInSection:section];
   TableViewLinkHeaderFooterView* footer =
       base::mac::ObjCCast<TableViewLinkHeaderFooterView>(footerView);
   if (footer) {
diff --git a/ios/chrome/browser/ui/settings/privacy_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
similarity index 97%
rename from ios/chrome/browser/ui/settings/privacy_table_view_controller_unittest.mm
rename to ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
index 495738f..061a656 100644
--- a/ios/chrome/browser/ui/settings/privacy_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller_unittest.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/ui/settings/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
 
 #include <memory>
 
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 4b8ce94..1048ce93 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -57,7 +57,7 @@
 #import "ios/chrome/browser/ui/settings/language/language_settings_mediator.h"
 #import "ios/chrome/browser/ui/settings/language/language_settings_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/password/passwords_table_view_controller.h"
-#import "ios/chrome/browser/ui/settings/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/search_engine_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 5bd02b3..3127073 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -292,6 +292,7 @@
     "//ios/chrome/browser/ui/settings/credit_card_scanner:unit_tests",
     "//ios/chrome/browser/ui/settings/language:unit_tests",
     "//ios/chrome/browser/ui/settings/password:unit_tests",
+    "//ios/chrome/browser/ui/settings/privacy:unit_tests",
     "//ios/chrome/browser/ui/settings/sync:unit_tests",
     "//ios/chrome/browser/ui/side_swipe:unit_tests",
     "//ios/chrome/browser/ui/tab_grid:unit_tests",
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 392f4a3b..018be6a7 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -299,6 +299,7 @@
     "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/browser/ui/settings/password:eg_test_support",
     "//ios/chrome/browser/ui/settings/password:password_constants",
+    "//ios/chrome/browser/ui/settings/privacy",
     "//ios/chrome/browser/ui/settings/sync",
     "//ios/chrome/browser/ui/tab_grid:tab_grid_ui_constants",
     "//ios/chrome/browser/ui/tab_grid/grid:grid_ui_constants",
@@ -458,6 +459,7 @@
     "//ios/chrome/browser/ui/settings/language:eg_app_support+eg2",
     "//ios/chrome/browser/ui/settings/password:eg_app_support+eg2",
     "//ios/chrome/browser/ui/settings/password:password_constants",
+    "//ios/chrome/browser/ui/settings/privacy",
     "//ios/chrome/browser/ui/settings/sync",
     "//ios/chrome/browser/ui/signin_interaction:eg_app_support+eg2",
     "//ios/chrome/browser/ui/tab_grid:tab_grid_ui_constants",
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 1968237..280ea852 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -42,7 +42,7 @@
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/import_data_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
-#import "ios/chrome/browser/ui/settings/privacy_table_view_controller.h"
+#import "ios/chrome/browser/ui/settings/privacy/privacy_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/settings_root_table_constants.h"
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 6c22128..710f56c 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -181,6 +181,21 @@
     }
   }
 
+  if (item && userAgentType == web::UserAgentType::NONE &&
+      web::GetWebClient()->IsAppSpecificURL(item->GetVirtualURL())) {
+    // In case the URL to be loaded is a WebUI URL and the user agent is nil,
+    // get the mobile user agent.
+    userAgentType = web::UserAgentType::MOBILE;
+  }
+
+  if (userAgentType != web::UserAgentType::NONE) {
+    NSString* userAgentString = base::SysUTF8ToNSString(
+        web::GetWebClient()->GetUserAgent(userAgentType));
+    if (![webView.customUserAgent isEqualToString:userAgentString]) {
+      webView.customUserAgent = userAgentString;
+    }
+  }
+
   WKContentMode contentMode = userAgentType == web::UserAgentType::DESKTOP
                                   ? WKContentModeDesktop
                                   : WKContentModeMobile;
diff --git a/ios/web/web_state/ui/crw_web_request_controller.mm b/ios/web/web_state/ui/crw_web_request_controller.mm
index aed49085..8622250 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.mm
+++ b/ios/web/web_state/ui/crw_web_request_controller.mm
@@ -573,11 +573,18 @@
     itemUserAgentType = web::UserAgentType::MOBILE;
   }
 
-  if (itemUserAgentType != web::UserAgentType::NONE) {
-    NSString* userAgentString = base::SysUTF8ToNSString(
-        web::GetWebClient()->GetUserAgent(itemUserAgentType));
-    if (![self.webView.customUserAgent isEqualToString:userAgentString]) {
-      self.webView.customUserAgent = userAgentString;
+  if (@available(iOS 13, *)) {
+  } else {
+    // On iOS 13, this is done in
+    // webView:decidePolicyForNavigationAction:preferences:decisionHandler:. As
+    // the method only exists for iOS 13, this check still need to be there for
+    // iOS 12.
+    if (itemUserAgentType != web::UserAgentType::NONE) {
+      NSString* userAgentString = base::SysUTF8ToNSString(
+          web::GetWebClient()->GetUserAgent(itemUserAgentType));
+      if (![self.webView.customUserAgent isEqualToString:userAgentString]) {
+        self.webView.customUserAgent = userAgentString;
+      }
     }
   }
 
diff --git a/ios/web_view/public/cwv_ui_delegate.h b/ios/web_view/public/cwv_ui_delegate.h
index 809fa07..fe54339a 100644
--- a/ios/web_view/public/cwv_ui_delegate.h
+++ b/ios/web_view/public/cwv_ui_delegate.h
@@ -68,7 +68,7 @@
                               defaultText:(NSString*)defaultText
                                   pageURL:(NSURL*)URL
                         completionHandler:
-                            (void (^)(NSString*))completionHandler;
+                            (void (^)(NSString* _Nullable))completionHandler;
 
 // Determines whether the given link with |linkURL| should show a preview on
 // force touch. Return value NO is assumed if the method is not implemented.
@@ -98,7 +98,7 @@
 - (void)webView:(CWVWebView*)webView
     contextMenuConfigurationForLinkWithURL:(NSURL*)linkURL
                          completionHandler:
-                             (void (^)(UIContextMenuConfiguration*))
+                             (void (^)(UIContextMenuConfiguration* _Nullable))
                                  completionHandler API_AVAILABLE(ios(13.0));
 
 // Equivalent of -[WKUIDelegate
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index a86080cf..5acdb01 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -103,8 +103,6 @@
     "audio_output_resampler.h",
     "audio_output_stream_sink.cc",
     "audio_output_stream_sink.h",
-    "audio_power_monitor.cc",
-    "audio_power_monitor.h",
     "audio_sink_parameters.cc",
     "audio_sink_parameters.h",
     "audio_source_diverter.h",
@@ -377,7 +375,6 @@
     "audio_output_device_unittest.cc",
     "audio_output_proxy_unittest.cc",
     "audio_output_unittest.cc",
-    "audio_power_monitor_unittest.cc",
     "audio_system_impl_unittest.cc",
     "audio_thread_hang_monitor_unittest.cc",
     "power_observer_helper_unittest.cc",
diff --git a/media/audio/audio_output_device.cc b/media/audio/audio_output_device.cc
index 75ad621..86239ef1 100644
--- a/media/audio/audio_output_device.cc
+++ b/media/audio/audio_output_device.cc
@@ -68,7 +68,7 @@
 AudioOutputDevice::~AudioOutputDevice() {
   {
     // Abort any pending callbacks. Technically we don't need to acquire the
-    // lock here since ther eshould be no other calls outstanding, but because
+    // lock here since there should be no other calls outstanding, but because
     // we've used the GUARDED_BY compiler syntax, we'll get an error without it.
     base::AutoLock auto_lock(device_info_lock_);
     if (pending_device_info_cb_) {
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index b1791a0a..60b6abb 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -60,6 +60,8 @@
     "audio_fifo.h",
     "audio_hash.cc",
     "audio_hash.h",
+    "audio_power_monitor.cc",
+    "audio_power_monitor.h",
     "audio_processing.cc",
     "audio_processing.h",
     "audio_pull_fifo.cc",
@@ -518,6 +520,7 @@
     "audio_latency_unittest.cc",
     "audio_parameters_unittest.cc",
     "audio_point_unittest.cc",
+    "audio_power_monitor_unittest.cc",
     "audio_pull_fifo_unittest.cc",
     "audio_push_fifo_unittest.cc",
     "audio_renderer_mixer_input_unittest.cc",
diff --git a/media/audio/audio_power_monitor.cc b/media/base/audio_power_monitor.cc
similarity index 85%
rename from media/audio/audio_power_monitor.cc
rename to media/base/audio_power_monitor.cc
index 77a848e..70d9afd 100644
--- a/media/audio/audio_power_monitor.cc
+++ b/media/base/audio_power_monitor.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/audio/audio_power_monitor.h"
+#include "media/base/audio_power_monitor.h"
 
 #include <algorithm>
 #include <cmath>
@@ -15,10 +15,10 @@
 
 namespace media {
 
-AudioPowerMonitor::AudioPowerMonitor(
-    int sample_rate, const base::TimeDelta& time_constant)
-    : sample_weight_(
-          1.0f - expf(-1.0f / (sample_rate * time_constant.InSecondsF()))) {
+AudioPowerMonitor::AudioPowerMonitor(int sample_rate,
+                                     base::TimeDelta time_constant)
+    : sample_weight_(1.0f -
+                     expf(-1.0f / (sample_rate * time_constant.InSecondsF()))) {
   Reset();
 }
 
@@ -80,8 +80,9 @@
   // Convert power level to dBFS units, and pin it down to zero if it is
   // insignificantly small.
   const float kInsignificantPower = 1.0e-10f;  // -100 dBFS
-  const float power_dbfs = power_reading_ < kInsignificantPower ? zero_power() :
-      10.0f * log10f(power_reading_);
+  const float power_dbfs = power_reading_ < kInsignificantPower
+                               ? zero_power()
+                               : 10.0f * log10f(power_reading_);
 
   const bool clipped = clipped_reading_;
   clipped_reading_ = false;
diff --git a/media/audio/audio_power_monitor.h b/media/base/audio_power_monitor.h
similarity index 93%
rename from media/audio/audio_power_monitor.h
rename to media/base/audio_power_monitor.h
index 1703d24..d95b2836 100644
--- a/media/audio/audio_power_monitor.h
+++ b/media/base/audio_power_monitor.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_
-#define MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_
+#ifndef MEDIA_BASE_AUDIO_POWER_MONITOR_H_
+#define MEDIA_BASE_AUDIO_POWER_MONITOR_H_
 
 #include <limits>
 #include <utility>
@@ -40,7 +40,7 @@
   // characterizes how samples are averaged over time to determine the power
   // level; and is the amount of time it takes a zero power level to increase to
   // ~63.2% of maximum given a step input signal.
-  AudioPowerMonitor(int sample_rate, const base::TimeDelta& time_constant);
+  AudioPowerMonitor(int sample_rate, base::TimeDelta time_constant);
 
   ~AudioPowerMonitor();
 
@@ -85,4 +85,4 @@
 
 }  // namespace media
 
-#endif  // MEDIA_AUDIO_AUDIO_POWER_MONITOR_H_
+#endif  // MEDIA_BASE_AUDIO_POWER_MONITOR_H_
diff --git a/media/audio/audio_power_monitor_unittest.cc b/media/base/audio_power_monitor_unittest.cc
similarity index 78%
rename from media/audio/audio_power_monitor_unittest.cc
rename to media/base/audio_power_monitor_unittest.cc
index 8d8eed0..7ce16e0 100644
--- a/media/audio/audio_power_monitor_unittest.cc
+++ b/media/base/audio_power_monitor_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/audio/audio_power_monitor.h"
+#include "media/base/audio_power_monitor.h"
 
 #include <limits>
 #include <memory>
@@ -24,8 +24,11 @@
 // Container for each parameterized test's data (input and expected results).
 class TestScenario {
  public:
-  TestScenario(const float* data, int num_channels, int num_frames,
-               float expected_power, bool expected_clipped)
+  TestScenario(const float* data,
+               int num_channels,
+               int num_frames,
+               float expected_power,
+               bool expected_clipped)
       : expected_power_(expected_power), expected_clipped_(expected_clipped) {
     CreatePopulatedBuffer(data, num_channels, num_frames);
   }
@@ -48,23 +51,18 @@
     return result;
   }
 
-  const AudioBus& data() const {
-    return *bus_;
-  }
+  const AudioBus& data() const { return *bus_; }
 
-  float expected_power() const {
-    return expected_power_;
-  }
+  float expected_power() const { return expected_power_; }
 
-  bool expected_clipped() const {
-    return expected_clipped_;
-  }
+  bool expected_clipped() const { return expected_clipped_; }
 
  private:
   // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of
   // data.  The given test |data| is repeated to fill the buffer.
-  void CreatePopulatedBuffer(
-      const float* data, int num_channels, int num_frames) {
+  void CreatePopulatedBuffer(const float* data,
+                             int num_channels,
+                             int num_frames) {
     bus_ = AudioBus::Create(num_channels, kFramesPerBuffer);
     for (int ch = 0; ch < num_channels; ++ch) {
       for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) {
@@ -85,8 +83,7 @@
 ::std::ostream& operator<<(::std::ostream& os, const TestScenario& ts) {
   return os << "{" << ts.data().channels() << "-channel signal} --> {"
             << ts.expected_power() << " dBFS, "
-            << (ts.expected_clipped() ? "clipped" : "not clipped")
-            << "}";
+            << (ts.expected_clipped() ? "clipped" : "not clipped") << "}";
 }
 
 // An observer that receives power measurements.  Each power measurement should
@@ -99,17 +96,11 @@
         last_power_measurement_(AudioPowerMonitor::zero_power()),
         last_clipped_(false) {}
 
-  int measurement_count() const {
-    return measurement_count_;
-  }
+  int measurement_count() const { return measurement_count_; }
 
-  float last_power_measurement() const {
-    return last_power_measurement_;
-  }
+  float last_power_measurement() const { return last_power_measurement_; }
 
-  bool last_clipped() const {
-    return last_clipped_;
-  }
+  bool last_clipped() const { return last_clipped_; }
 
   void OnPowerMeasured(float cur_power_measurement, bool clipped) {
     if (measurement_count_ == 0) {
@@ -159,8 +150,9 @@
                        base::TimeDelta::FromMilliseconds(kTimeConstantMillis)) {
   }
 
-  void FeedAndCheckExpectedPowerIsMeasured(
-      const AudioBus& bus, float power, bool clipped) {
+  void FeedAndCheckExpectedPowerIsMeasured(const AudioBus& bus,
+                                           float power,
+                                           bool clipped) {
     // Feed the AudioPowerMonitor, read measurements from it, and record them in
     // MeasurementObserver.
     static const int kNumFeedIters = 100;
@@ -195,83 +187,70 @@
   // Send a "zero power" audio signal, then this scenario's audio signal, then
   // the "zero power" audio signal again; testing that the power monitor
   // measurements match expected values.
-  FeedAndCheckExpectedPowerIsMeasured(
-      *zeroed_bus, AudioPowerMonitor::zero_power(), false);
+  FeedAndCheckExpectedPowerIsMeasured(*zeroed_bus,
+                                      AudioPowerMonitor::zero_power(), false);
   FeedAndCheckExpectedPowerIsMeasured(
       scenario.data(), scenario.expected_power(), scenario.expected_clipped());
-  FeedAndCheckExpectedPowerIsMeasured(
-      *zeroed_bus, AudioPowerMonitor::zero_power(), false);
+  FeedAndCheckExpectedPowerIsMeasured(*zeroed_bus,
+                                      AudioPowerMonitor::zero_power(), false);
 }
 
-static const float kMonoSilentNoise[] = {
-  0.01f, -0.01f
-};
+static const float kMonoSilentNoise[] = {0.01f, -0.01f};
 
-static const float kMonoMaxAmplitude[] = {
-  1.0f
-};
+static const float kMonoMaxAmplitude[] = {1.0f};
 
-static const float kMonoMaxAmplitude2[] = {
-  -1.0f, 1.0f
-};
+static const float kMonoMaxAmplitude2[] = {-1.0f, 1.0f};
 
-static const float kMonoHalfMaxAmplitude[] = {
-  0.5f, -0.5f, 0.5f, -0.5f
-};
+static const float kMonoHalfMaxAmplitude[] = {0.5f, -0.5f, 0.5f, -0.5f};
 
-static const float kMonoAmplitudeClipped[] = {
-  2.0f, -2.0f
-};
+static const float kMonoAmplitudeClipped[] = {2.0f, -2.0f};
 
-static const float kMonoMaxAmplitudeWithClip[] = {
-  2.0f, 0.0, 0.0f, 0.0f
-};
+static const float kMonoMaxAmplitudeWithClip[] = {2.0f, 0.0, 0.0f, 0.0f};
 
-static const float kMonoMaxAmplitudeWithClip2[] = {
-  4.0f, 0.0, 0.0f, 0.0f
-};
+static const float kMonoMaxAmplitudeWithClip2[] = {4.0f, 0.0, 0.0f, 0.0f};
 
 static const float kStereoSilentNoise[] = {
-  // left channel
-  0.005f, -0.005f,
-  // right channel
-  0.005f, -0.005f
-};
+    // left channel
+    0.005f, -0.005f,
+    // right channel
+    0.005f, -0.005f};
 
 static const float kStereoMaxAmplitude[] = {
-  // left channel
-  1.0f, -1.0f,
-  // right channel
-  -1.0f, 1.0f
-};
+    // left channel
+    1.0f, -1.0f,
+    // right channel
+    -1.0f, 1.0f};
 
 static const float kRightChannelMaxAmplitude[] = {
-  // left channel
-  0.0f, 0.0f, 0.0f, 0.0f,
-  // right channel
-  -1.0f, 1.0f, -1.0f, 1.0f
-};
+    // left channel
+    0.0f, 0.0f, 0.0f, 0.0f,
+    // right channel
+    -1.0f, 1.0f, -1.0f, 1.0f};
 
 static const float kLeftChannelHalfMaxAmplitude[] = {
-  // left channel
-  0.5f, -0.5f, 0.5f, -0.5f,
-  // right channel
-  0.0f, 0.0f, 0.0f, 0.0f,
+    // left channel
+    0.5f,
+    -0.5f,
+    0.5f,
+    -0.5f,
+    // right channel
+    0.0f,
+    0.0f,
+    0.0f,
+    0.0f,
 };
 
 static const float kStereoMixed[] = {
-  // left channel
-  0.5f, -0.5f, 0.5f, -0.5f,
-  // right channel
-  -1.0f, 1.0f, -1.0f, 1.0f
-};
+    // left channel
+    0.5f, -0.5f, 0.5f, -0.5f,
+    // right channel
+    -1.0f, 1.0f, -1.0f, 1.0f};
 
 static const float kStereoMixed2[] = {
-  // left channel
-  1.0f, -1.0f, 0.75f, -0.75f, 0.5f, -0.5f, 0.25f, -0.25f,
-  // right channel
-  0.25f, -0.25f, 0.5f, -0.5f, 0.75f, -0.75f, 1.0f, -1.0f
-};
+    // left channel
+    1.0f, -1.0f, 0.75f, -0.75f, 0.5f, -0.5f, 0.25f, -0.25f,
+    // right channel
+    0.25f, -0.25f, 0.5f, -0.5f, 0.75f, -0.75f, 1.0f, -1.0f};
 
 INSTANTIATE_TEST_SUITE_P(
     Scenarios,
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index 3df95d9a..8eeaa0a 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -620,13 +620,17 @@
           protected_video_type = gfx::ProtectedVideoType::kSoftwareProtected;
       }
 
+      const gfx::Vector2dF offset(
+          static_cast<float>(visible_rect.x()) / coded_size.width(),
+          static_cast<float>(visible_rect.y()) / coded_size.height());
+
       auto* texture_quad =
           render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
-      texture_quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect,
-                           needs_blending, frame_resources_[0].id,
-                           premultiplied_alpha, uv_top_left, uv_bottom_right,
-                           SK_ColorTRANSPARENT, opacity, flipped,
-                           nearest_neighbor, false, protected_video_type);
+      texture_quad->SetNew(
+          shared_quad_state, quad_rect, visible_quad_rect, needs_blending,
+          frame_resources_[0].id, premultiplied_alpha, uv_top_left + offset,
+          uv_bottom_right + offset, SK_ColorTRANSPARENT, opacity, flipped,
+          nearest_neighbor, false, protected_video_type);
       texture_quad->set_resource_size_in_pixels(coded_size);
       for (viz::ResourceId resource_id : texture_quad->resources) {
         resource_provider_->ValidateResource(resource_id);
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index 5c2bc6ef..202206cd 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -362,13 +362,13 @@
     true)
 
 // If true, QuicSession\'s various write methods will set transmission type.
-QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_write_with_transmission, false)
+QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_write_with_transmission, true)
 
 // If true, fix a bug in QUIC BBR where bandwidth estimate becomes 0 after a
 // loss only event.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_bbr_fix_zero_bw_on_loss_only_event,
-          false)
+          true)
 
 // If true, trigger QUIC_BUG in two ShouldCreateIncomingStream() overrides when
 // called with locally initiated stream ID.
diff --git a/services/audio/output_controller.cc b/services/audio/output_controller.cc
index 9307774..862f90b 100644
--- a/services/audio/output_controller.cc
+++ b/services/audio/output_controller.cc
@@ -31,7 +31,8 @@
 namespace {
 
 // Time in seconds between two successive measurements of audio power levels.
-constexpr int kPowerMonitorLogIntervalSeconds = 15;
+constexpr base::TimeDelta kPowerMonitorLogInterval =
+    base::TimeDelta::FromSeconds(15);
 
 // Used to log the result of rendering startup.
 // Elements in this enum should not be deleted or rearranged; the only
@@ -485,8 +486,7 @@
     power_monitor_.Scan(*dest, frames);
 
     const auto now = base::TimeTicks::Now();
-    if ((now - last_audio_level_log_time_).InSeconds() >
-        kPowerMonitorLogIntervalSeconds) {
+    if ((now - last_audio_level_log_time_) > kPowerMonitorLogInterval) {
       LogAudioPowerLevel(__func__);
       last_audio_level_log_time_ = now;
     }
diff --git a/services/audio/output_controller.h b/services/audio/output_controller.h
index 8550d37f..4cc74781 100644
--- a/services/audio/output_controller.h
+++ b/services/audio/output_controller.h
@@ -25,8 +25,8 @@
 #include "build/build_config.h"
 #include "media/audio/audio_io.h"
 #include "media/audio/audio_manager.h"
-#include "media/audio/audio_power_monitor.h"
 #include "media/audio/audio_source_diverter.h"
+#include "media/base/audio_power_monitor.h"
 #include "services/audio/loopback_group_member.h"
 #include "services/audio/stream_monitor_coordinator.h"
 
diff --git a/services/network/origin_policy/origin_policy_parsed_header.cc b/services/network/origin_policy/origin_policy_parsed_header.cc
index 93d37a8..90c99b07 100644
--- a/services/network/origin_policy/origin_policy_parsed_header.cc
+++ b/services/network/origin_policy/origin_policy_parsed_header.cc
@@ -32,19 +32,17 @@
     return base::nullopt;
   }
 
-  // TODO(domenic): when https://crbug.com/1061139 gets fixed we can
-  // make this a const reference.
-  sh::Dictionary& parsed_header = *parsed_header_opt;
+  const sh::Dictionary& parsed_header = *parsed_header_opt;
 
   std::vector<OriginPolicyAllowedValue> allowed;
 
   if (parsed_header.contains("allowed")) {
-    if (!parsed_header["allowed"].member_is_inner_list) {
+    if (!parsed_header.at("allowed").member_is_inner_list) {
       return base::nullopt;
     }
 
     const std::vector<sh::ParameterizedItem>& raw_allowed_list =
-        parsed_header["allowed"].member;
+        parsed_header.at("allowed").member;
     for (const auto& parameterized_item : raw_allowed_list) {
       base::Optional<OriginPolicyAllowedValue> result;
 
@@ -74,11 +72,11 @@
 
   base::Optional<OriginPolicyPreferredValue> preferred;
   if (parsed_header.contains("preferred")) {
-    if (parsed_header["preferred"].member_is_inner_list) {
+    if (parsed_header.at("preferred").member_is_inner_list) {
       return base::nullopt;
     }
 
-    const sh::Item& item = parsed_header["preferred"].member[0].item;
+    const sh::Item& item = parsed_header.at("preferred").member[0].item;
     if (item.is_string()) {
       const std::string& string = item.GetString();
       if (string.empty()) {
diff --git a/testing/buildbot/chromium.ci.json b/testing/buildbot/chromium.ci.json
index 33e99d3..f70e2ee 100644
--- a/testing/buildbot/chromium.ci.json
+++ b/testing/buildbot/chromium.ci.json
@@ -226837,11 +226837,17 @@
     ]
   },
   "linux-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
@@ -231836,11 +231842,17 @@
     ]
   },
   "mac-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
@@ -235021,11 +235033,17 @@
     ]
   },
   "win-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c085ee02..7007faf 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -38582,11 +38582,17 @@
     ]
   },
   "linux-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
@@ -41942,11 +41948,17 @@
     ]
   },
   "mac-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
@@ -42182,11 +42194,17 @@
     ]
   },
   "win-upload-perfetto": {
-    "scripts": [
+    "gtest_tests": [
       {
-        "name": "upload_perfetto_script",
-        "script": "//tools/perf/core/perfetto_binary_roller/upload_trace_processor",
-        "swarming": {}
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": false
+        },
+        "test": "upload_trace_processor",
+        "test_target": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor"
       }
     ]
   },
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index f0deb0ad..8f415f44 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1724,6 +1724,10 @@
     "label": "//chrome/updater:updater_tests",
     "type": "console_test_launcher",
   },
+  "upload_trace_processor": {
+    "label": "//tools/perf/core/perfetto_binary_roller:upload_trace_processor",
+    "type": "raw",
+  },
   "url_unittests": {
     "label": "//url:url_unittests",
     "type": "console_test_launcher",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index f148b79..1914923 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3593,10 +3593,7 @@
     },
 
     'upload_perfetto': {
-      'upload_perfetto_script': {
-        'script':
-            '//tools/perf/core/perfetto_binary_roller/upload_trace_processor',
-      },
+      'upload_trace_processor': {},
     },
 
     'vr_platform_specific_chromium_gtests': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index d7414315d..7267d5ed 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2088,8 +2088,9 @@
       },
       'linux-upload-perfetto': {
         'test_suites': {
-          'scripts': 'upload_perfetto',
+          'gtest_tests': 'upload_perfetto',
         },
+        'use_swarming': False,
       },
       'linux-wpt-fyi-rel': {
         'mixins': [
@@ -2133,8 +2134,9 @@
       },
       'mac-upload-perfetto': {
         'test_suites': {
-          'scripts': 'upload_perfetto',
+          'gtest_tests': 'upload_perfetto',
         },
+        'use_swarming': False,
       },
       'mac10.10-blink-rel-dummy': {
         'test_suites': {
@@ -2244,8 +2246,9 @@
       },
       'win-upload-perfetto': {
         'test_suites': {
-          'scripts': 'upload_perfetto',
+          'gtest_tests': 'upload_perfetto',
         },
+        'use_swarming': False,
       },
       'win10-blink-rel-dummy': {
         'swarming': {
diff --git a/third_party/blink/common/feature_policy/feature_policy.cc b/third_party/blink/common/feature_policy/feature_policy.cc
index d3b99496..94bf3a9 100644
--- a/third_party/blink/common/feature_policy/feature_policy.cc
+++ b/third_party/blink/common/feature_policy/feature_policy.cc
@@ -595,7 +595,7 @@
        {mojom::FeaturePolicyFeature::kVerticalScroll,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
                             mojom::PolicyValueType::kBool)},
-       {mojom::FeaturePolicyFeature::kWakeLock,
+       {mojom::FeaturePolicyFeature::kScreenWakeLock,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
                             mojom::PolicyValueType::kBool)},
        {mojom::FeaturePolicyFeature::kWebVr,
diff --git a/third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom b/third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom
index fd03c34e..d231671 100644
--- a/third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom
+++ b/third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom
@@ -64,8 +64,8 @@
   kLazyLoad = 29,
   // Restricts the usage of layout-causing animations in a document.
   kLayoutAnimations = 30,
-  // Controls access to WakeLock
-  kWakeLock = 31,
+  // Controls access to Screen Wake Lock
+  kScreenWakeLock = 31,
 
   // These are the defined sandbox features implemented as policy-controlled
   // features.
diff --git a/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h b/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
index 6da9a04..0d76a982 100644
--- a/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
+++ b/third_party/blink/public/web/modules/mediastream/webmediaplayer_ms.h
@@ -277,6 +277,8 @@
   // Callback used to fulfill video.requestAnimationFrame() requests.
   void OnNewFramePresentedCallback();
 
+  void SendLogMessage(const WTF::String& message) const;
+
   std::unique_ptr<MediaStreamInternalFrameWrapper> internal_frame_;
 
   WebMediaPlayer::NetworkState network_state_;
diff --git a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
index 81457a2..c7eb05b 100644
--- a/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
+++ b/third_party/blink/renderer/bindings/core/v8/local_window_proxy.cc
@@ -176,7 +176,9 @@
   if (evaluate_csp_for_eval) {
     ContentSecurityPolicy* csp =
         GetFrame()->GetDocument()->GetContentSecurityPolicyForWorld();
-    context->AllowCodeGenerationFromStrings(!csp->ShouldCheckEval());
+    // Configure V8 to check Blink for every 'eval' attempts.
+    // See CodeGenerationCheckCallbackInMainThread.
+    context->AllowCodeGenerationFromStrings(false);
     context->SetErrorMessageForCodeGenerationFromStrings(
         V8String(GetIsolate(), csp->EvalDisabledErrorMessage()));
   }
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
index 696a6170..e514c85 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc
@@ -423,9 +423,29 @@
   return {true, V8String(context->GetIsolate(), stringified_source)};
 }
 
+namespace {
+
+// Check whether a given |context| have some CSP or TrustedType policies to be
+// checked before evaluating a string.
+bool ShouldCheckEval(v8::Local<v8::Context> v8_context) {
+  ExecutionContext* context = ToExecutionContext(v8_context);
+  if (!context)
+    return false;
+
+  ContentSecurityPolicy* policy = context->GetContentSecurityPolicyForWorld();
+  if (!policy)
+    return false;
+
+  return policy->ShouldCheckEval();
+}
+}  // namespace
+
 static v8::ModifyCodeGenerationFromStringsResult
 CodeGenerationCheckCallbackInMainThread(v8::Local<v8::Context> context,
                                         v8::Local<v8::Value> source) {
+  if (!ShouldCheckEval(context))
+    return {/* allowed */ true, /* modified source */ {}};
+
   // With Trusted Types, we always run the TT check first because of reporting,
   // and because a default policy might want to stringify or modify the original
   // source. When TT enforcement is disabled, codegen is always allowed, and we
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py b/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
index dbcbc660..cda6ef7 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/blink_v8_bridge.py
@@ -166,9 +166,19 @@
 
     if (real_type.is_sequence or real_type.is_frozen_array
             or real_type.is_variadic):
-        element_type = blink_type_info(real_type.element_type)
+        element_type = real_type.element_type
+        element_type_info = blink_type_info(real_type.element_type)
+        if element_type.type_definition_object is not None:
+            # In order to support recursive IDL data structures, we have to
+            # avoid recursive C++ header inclusions and utilize C++ forward
+            # declarations.  Since |VectorOf| requires complete type
+            # definition, |HeapVector<Member<T>>| is preferred as it
+            # requires only forward declaration.
+            vector_fmt = "HeapVector<Member<{}>>"
+        else:
+            vector_fmt = "VectorOf<{}>"
         return TypeInfo(
-            "VectorOf<{}>".format(element_type.typename),
+            vector_fmt.format(element_type_info.typename),
             ref_fmt="{}&",
             const_ref_fmt="const {}&")
 
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
index af5a7b28..2e29538 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/code_node.py
@@ -316,7 +316,7 @@
         for node in outers:
             if node.own_template_vars is None:
                 continue
-            for name, value in node.own_template_vars.iteritems():
+            for name, value in node.own_template_vars.items():
                 assert name not in bindings, (
                     "Duplicated template variable binding: {}".format(name))
                 bindings[name] = value
@@ -341,7 +341,7 @@
 
     def add_template_vars(self, template_vars):
         assert isinstance(template_vars, dict)
-        for name, value in template_vars.iteritems():
+        for name, value in template_vars.items():
             self.add_template_var(name, value)
 
     @property
@@ -357,7 +357,7 @@
 
     def set_base_template_vars(self, template_vars):
         assert isinstance(template_vars, dict)
-        for name, value in template_vars.iteritems():
+        for name, value in template_vars.items():
             assert isinstance(name, str)
             assert not isinstance(value, CodeNode)
         assert self._base_template_vars is None
@@ -507,7 +507,7 @@
             gensym = CodeNode.gensym()
             gensym_args.append("${{{}}}".format(gensym))
             template_vars[gensym] = arg
-        for key, value in kwargs.iteritems():
+        for key, value in kwargs.items():
             assert isinstance(key, (int, long, str))
             assert isinstance(value, (CodeNode, int, long, str))
             gensym = CodeNode.gensym()
@@ -732,7 +732,7 @@
                     scope = scope_chain[0]
                     scope_to_likeliness[scope] = max(
                         likeliness, scope_to_likeliness.get(scope, likeliness))
-            for likeliness in scope_to_likeliness.itervalues():
+            for likeliness in scope_to_likeliness.values():
                 counts[DIRECT_CHILD_SCOPES] += 1
                 counts[likeliness] += 1
             return counts
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_context.py b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_context.py
index 6ffc5e5..0350727a 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_context.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/codegen_context.py
@@ -26,6 +26,23 @@
     NON_MAIN_WORLDS = "other"
     ALL_WORLDS = "all"
 
+    # "v8_callback_type" attribute values
+    #
+    # void (*)(const v8::FunctionCallbackInfo<v8::Value>&)
+    V8_FUNCTION_CALLBACK = "v8::FunctionCallback"
+    # void (*)(v8::Local<v8::Name>,
+    #          const v8::PropertyCallbackInfo<v8::Value>&)
+    V8_ACCESSOR_NAME_GETTER_CALLBACK = "v8::AccessorNameGetterCallback"
+    # void (*)(v8::Local<v8::Name>, v8::Local<v8::Value>,
+    #          const v8::PropertyCallbackInfo<void>&)
+    V8_ACCESSOR_NAME_SETTER_CALLBACK = "v8::AccessorNameSetterCallback"
+    # void (*)(v8::Local<v8::Name>, v8::Local<v8::Value>,
+    #          const v8::PropertyCallbackInfo<v8::Value>&)
+    V8_GENERIC_NAMED_PROPERTY_SETTER_CALLBACK = (
+        "v8::GenericNamedPropertySetterCallback")
+    # Others
+    V8_OTHER_CALLBACK = "other callback type"
+
     @classmethod
     def init(cls):
         """Initialize the class.  Must be called exactly once."""
@@ -70,7 +87,13 @@
             "class_name": None,
 
             # Main world or all worlds
+            # Used via [PerWorldBindings] to optimize the code path of the main
+            # world.
             "for_world": cls.ALL_WORLDS,
+
+            # Type of V8 callback function which implements IDL attribute,
+            # IDL operation, etc.
+            "v8_callback_type": cls.V8_FUNCTION_CALLBACK
         }
 
         # List of computational attribute names
@@ -89,7 +112,7 @@
         )
 
         # Define public readonly properties of this class.
-        for attr in cls._context_attrs.iterkeys():
+        for attr in cls._context_attrs.keys():
 
             def make_get():
                 _attr = cls._internal_attr(attr)
@@ -108,11 +131,11 @@
     def __init__(self, **kwargs):
         assert CodeGenContext._was_initialized
 
-        for arg in kwargs.iterkeys():
+        for arg in kwargs.keys():
             assert arg in self._context_attrs, "Unknown argument: {}".format(
                 arg)
 
-        for attr, default_value in self._context_attrs.iteritems():
+        for attr, default_value in self._context_attrs.items():
             value = kwargs[attr] if attr in kwargs else default_value
             assert (default_value is None
                     or type(value) is type(default_value)), (
@@ -124,13 +147,13 @@
         Returns a copy of this context applying the updates given as the
         arguments.
         """
-        for arg in kwargs.iterkeys():
+        for arg in kwargs.keys():
             assert arg in self._context_attrs, "Unknown argument: {}".format(
                 arg)
 
         new_object = copy.copy(self)
 
-        for attr, new_value in kwargs.iteritems():
+        for attr, new_value in kwargs.items():
             old_value = getattr(self, attr)
             assert old_value is None or type(new_value) is type(old_value), (
                 "Type mismatch at argument: {}".format(attr))
@@ -147,7 +170,7 @@
         """
         bindings = {}
 
-        for attr in self._context_attrs.iterkeys():
+        for attr in self._context_attrs.keys():
             value = getattr(self, attr)
             if value is None:
                 value = NonRenderable(attr)
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index 3424b6b..3629f02 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -90,7 +90,9 @@
     return name
 
 
-def callback_function_name(cg_context, overload_index=None):
+def callback_function_name(cg_context,
+                           overload_index=None,
+                           for_cross_origin=False):
     assert isinstance(cg_context, CodeGenContext)
 
     def _cxx_name(name):
@@ -133,10 +135,12 @@
     elif cg_context.stringifier:
         kind = "Operation"
 
-    if overload_index is None:
-        callback_suffix = "Callback"
+    if for_cross_origin:
+        suffix = "CrossOrigin"
+    elif overload_index is not None:
+        suffix = "Overload{}".format(overload_index + 1)
     else:
-        callback_suffix = "Overload{}".format(overload_index + 1)
+        suffix = "Callback"
 
     if cg_context.for_world == CodeGenContext.MAIN_WORLD:
         world_suffix = "ForMainWorld"
@@ -145,7 +149,7 @@
     elif cg_context.for_world == CodeGenContext.ALL_WORLDS:
         world_suffix = ""
 
-    return name_style.func(property_name, kind, callback_suffix, world_suffix)
+    return name_style.func(property_name, kind, suffix, world_suffix)
 
 
 def constant_name(cg_context):
@@ -195,7 +199,7 @@
 
     if cg_context.attribute_set:
         name = "arg1_value"
-        v8_value = "${info}[0]"
+        v8_value = "${v8_property_value}"
         code_node.register_code_symbol(
             make_v8_to_blink_value(name, v8_value,
                                    cg_context.attribute.idl_type))
@@ -342,6 +346,15 @@
         text = _format(pattern, _1=_1)
     local_vars.append(S("blink_receiver", text))
 
+    # v8_property_value
+    if cg_context.v8_callback_type == CodeGenContext.V8_FUNCTION_CALLBACK:
+        # In case of V8_ACCESSOR_NAME_SETTER_CALLBACK, |v8_property_value| is
+        # defined as an argument.  In case of V8_FUNCTION_CALLBACK (of IDL
+        # attribute set function), |info[0]| is the value to be set.
+        local_vars.append(
+            S("v8_property_value",
+              "v8::Local<v8::Value> ${v8_property_value} = ${info}[0];"))
+
     code_node.register_code_symbols(local_vars)
     code_node.add_template_vars(template_vars)
 
@@ -663,6 +676,9 @@
     T = TextNode
     F = lambda *args, **kwargs: T(_format(*args, **kwargs))
 
+    if cg_context.v8_callback_type != CodeGenContext.V8_FUNCTION_CALLBACK:
+        return None
+
     if cg_context.attribute_get:
         num_of_required_args = 0
     elif cg_context.attribute_set:
@@ -672,8 +688,8 @@
                 "TreatNonObjectAsNull" in idl_type.unwrap().extended_attributes
                 or "PutForwards" in cg_context.attribute.extended_attributes
                 or "Replaceable" in cg_context.attribute.extended_attributes):
-            # ES undefined in ${info}[0] will cause a TypeError anyway, so omit
-            # the check against the number of arguments.
+            # ES undefined in ${v8_property_value} will cause a TypeError
+            # anyway, so omit the check against the number of arguments.
             return None
         num_of_required_args = 1
     elif cg_context.function_like:
@@ -826,7 +842,7 @@
         _4 = ""
     elif cg_context.attribute_set:
         _1 = "LogSetter"
-        _4 = ", ${info}[0]"
+        _4 = ", ${v8_property_value}"
     elif cg_context.operation_group:
         _1 = "LogMethod"
         _4 = ", ${info}"
@@ -1218,8 +1234,9 @@
                         "(${v8_receiver}, ${info}.GetReturnValue().Get());")
 
 
-def make_runtime_call_timer_scope(cg_context):
+def make_runtime_call_timer_scope(cg_context, overriding_name=None):
     assert isinstance(cg_context, CodeGenContext)
+    assert _is_none_or_str(overriding_name)
 
     target = cg_context.member_like or cg_context.property_
 
@@ -1230,16 +1247,6 @@
         suffix = "_Setter"
     elif cg_context.exposed_construct:
         suffix = "_ConstructorGetterCallback"
-    elif cg_context.indexed_property_getter:
-        suffix = "_IndexedPropertyGetter"
-    elif cg_context.indexed_property_setter:
-        suffix = "_IndexedPropertySetter"
-    elif cg_context.named_property_getter:
-        suffix = "_NamedPropertyGetter"
-    elif cg_context.named_property_setter:
-        suffix = "_NamedPropertySetter"
-    elif cg_context.named_property_deleter:
-        suffix = "_NamedPropertyDeleter"
 
     counter = (target and
                target.extended_attributes.value_of("RuntimeCallStatsCounter"))
@@ -1250,8 +1257,8 @@
     else:
         macro_name = "RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT"
         counter_name = "\"Blink_{}_{}{}\"".format(
-            blink_class_name(cg_context.class_like),
-            target.identifier if target else "", suffix)
+            blink_class_name(cg_context.class_like), overriding_name
+            or target.identifier, suffix)
 
     return TextNode(
         _format(
@@ -1321,7 +1328,7 @@
         T("if (!target.As<v8::Object>()->Set(${current_context}, "
           "V8AtomicString(${isolate}, "
           "\"${attribute.extended_attributes.value_of(\"PutForwards\")}\""
-          "), ${info}[0]).To(&did_set)) {{\n"
+          "), ${v8_property_value}).To(&did_set)) {{\n"
           "  return;\n"
           "}}"),
     ])
@@ -1337,7 +1344,7 @@
         T("bool did_create;"),
         T("if (!${v8_receiver}->CreateDataProperty(${current_context}, "
           "V8AtomicString(${isolate}, ${property_name}), "
-          "${info}[0]).To(&did_create)) {\n"
+          "${v8_property_value}).To(&did_create)) {\n"
           "  return;\n"
           "}"),
     ])
@@ -1411,19 +1418,44 @@
              "ToV8(${return_value}, ${creation_context_object}, ${isolate}));")
 
 
-def _make_empty_callback_def(cg_context, function_name, arg_decls=None):
+def _make_empty_callback_def(cg_context, function_name):
     assert isinstance(cg_context, CodeGenContext)
     assert isinstance(function_name, str)
 
-    if arg_decls is None:
+    if cg_context.v8_callback_type == CodeGenContext.V8_FUNCTION_CALLBACK:
         arg_decls = ["const v8::FunctionCallbackInfo<v8::Value>& info"]
+        arg_names = ["info"]
+    elif (cg_context.v8_callback_type == CodeGenContext.
+          V8_ACCESSOR_NAME_GETTER_CALLBACK):
+        arg_decls = [
+            "v8::Local<v8::Name> v8_property_name",
+            "const v8::PropertyCallbackInfo<v8::Value>& info",
+        ]
+        arg_names = ["v8_property_name", "info"]
+    elif (cg_context.v8_callback_type == CodeGenContext.
+          V8_ACCESSOR_NAME_SETTER_CALLBACK):
+        arg_decls = [
+            "v8::Local<v8::Name> v8_property_name",
+            "v8::Local<v8::Value> v8_property_value",
+            "const v8::PropertyCallbackInfo<void>& info",
+        ]
+        arg_names = ["v8_property_name", "v8_property_value", "info"]
+    elif (cg_context.v8_callback_type == CodeGenContext.
+          V8_GENERIC_NAMED_PROPERTY_SETTER_CALLBACK):
+        arg_decls = [
+            "v8::Local<v8::Name> v8_property_name",
+            "v8::Local<v8::Value> v8_property_value",
+            "const v8::PropertyCallbackInfo<v8::Value>& info",
+        ]
+        arg_names = ["v8_property_name", "v8_property_value", "info"]
 
     func_def = CxxFuncDefNode(
         name=function_name, arg_decls=arg_decls, return_type="void")
     func_def.set_base_template_vars(cg_context.template_bindings())
-
     body = func_def.body
-    body.add_template_var("info", "info")
+
+    for arg_name in arg_names:
+        body.add_template_var(arg_name, arg_name)
 
     bind_callback_local_vars(body, cg_context)
     if cg_context.attribute or cg_context.function_like:
@@ -1496,7 +1528,7 @@
 
     if "Setter" in cg_context.property_.extended_attributes.values_of(
             "Custom"):
-        text = _format("${class_name}::{}(${info}[0], ${info});",
+        text = _format("${class_name}::{}(${v8_property_value}, ${info});",
                        custom_function_name(cg_context))
         body.append(TextNode(text))
         return func_def
@@ -1536,13 +1568,7 @@
     if not logging_nodes:
         return None
 
-    func_def = _make_empty_callback_def(
-        cg_context,
-        function_name,
-        arg_decls=[
-            "v8::Local<v8::Name> property",
-            "const v8::PropertyCallbackInfo<v8::Value>& info",
-        ])
+    func_def = _make_empty_callback_def(cg_context, function_name)
     body = func_def.body
 
     v8_set_return_value = _format(
@@ -1672,13 +1698,7 @@
     assert isinstance(cg_context, CodeGenContext)
     assert isinstance(function_name, str)
 
-    func_def = _make_empty_callback_def(
-        cg_context,
-        function_name,
-        arg_decls=[
-            "v8::Local<v8::Name> property",
-            "const v8::PropertyCallbackInfo<v8::Value>& info",
-        ])
+    func_def = _make_empty_callback_def(cg_context, function_name)
     body = func_def.body
 
     v8_set_return_value = _format(
@@ -1800,7 +1820,7 @@
     bind_return_value(body, cg_context, overriding_args=["${index}"])
 
     body.extend([
-        make_runtime_call_timer_scope(cg_context),
+        make_runtime_call_timer_scope(cg_context, "IndexedPropertyGetter"),
         EmptyNode(),
         make_v8_set_return_value(cg_context),
     ])
@@ -1849,7 +1869,7 @@
             argument_index=2))
 
     body.extend([
-        make_runtime_call_timer_scope(cg_context),
+        make_runtime_call_timer_scope(cg_context, "IndexedPropertySetter"),
         EmptyNode(),
         make_steps_of_ce_reactions(cg_context),
         EmptyNode(),
@@ -2052,7 +2072,7 @@
         body, cg_context, overriding_args=["${blink_property_name}"])
 
     body.extend([
-        make_runtime_call_timer_scope(cg_context),
+        make_runtime_call_timer_scope(cg_context, "NamedPropertyGetter"),
         EmptyNode(),
     ])
 
@@ -2075,7 +2095,7 @@
 
     arg_decls = [
         "v8::Local<v8::Name> v8_property_name",
-        "v8::Local<v8::Value> v8_value",
+        "v8::Local<v8::Value> v8_property_value",
         "const v8::PropertyCallbackInfo<v8::Value>& info",
     ]
     return_type = "void"
@@ -2094,7 +2114,7 @@
     func_def.set_base_template_vars(cg_context.template_bindings())
     body = func_def.body
     body.add_template_var("v8_property_name", "v8_property_name")
-    body.add_template_var("v8_value", "v8_value")
+    body.add_template_var("v8_property_value", "v8_property_value")
     body.add_template_var("info", "info")
     bind_callback_local_vars(body, cg_context)
     bind_return_value(
@@ -2104,19 +2124,19 @@
     body.register_code_symbol(
         make_v8_to_blink_value(
             "blink_value",
-            "${v8_value}",
+            "${v8_property_value}",
             cg_context.named_property_setter.arguments[1].idl_type,
             argument_index=2))
 
     body.extend([
-        make_runtime_call_timer_scope(cg_context),
+        make_runtime_call_timer_scope(cg_context, "NamedPropertySetter"),
         EmptyNode(),
     ])
 
     if "Custom" in cg_context.named_property_setter.extended_attributes:
         text = _format(
             "${class_name}::{}"
-            "(${blink_property_name}, ${v8_value}, ${info});",
+            "(${blink_property_name}, ${v8_property_value}, ${info});",
             custom_function_name(cg_context))
         body.append(TextNode(text))
     else:
@@ -2351,12 +2371,12 @@
         TextNode("""\
 Vector<String> blink_property_names;
 ${blink_receiver}->NamedPropertyEnumerator(
-  blink_property_names, ${exception_state});
+    blink_property_names, ${exception_state});
 if (${exception_state}.HadException())
   return;
 bindings::V8SetReturnValue(
-  ${info},
-  ToV8(blink_property_names, ${creation_context_object}, ${isolate}));
+    ${info},
+    ToV8(blink_property_names, ${creation_context_object}, ${isolate}));
 """))
 
     return func_decl, func_def
@@ -2379,6 +2399,295 @@
 
 
 # ----------------------------------------------------------------------------
+# Callback functions of cross origin properties
+# ----------------------------------------------------------------------------
+
+
+def make_cross_origin_access_check_callback(cg_context, function_name):
+    assert isinstance(cg_context, CodeGenContext)
+    assert isinstance(function_name, str)
+
+    func_def = CxxFuncDefNode(
+        name=function_name,
+        arg_decls=[
+            "v8::Local<v8::Context> accessing_context",
+            "v8::Local<v8::Object> accessed_object",
+            "v8::Local<v8::Value> unused_data",
+        ],
+        return_type="bool")
+    func_def.set_base_template_vars(cg_context.template_bindings())
+    body = func_def.body
+    body.add_template_var("accessing_context", "accessing_context")
+    body.add_template_var("accessed_object", "accessed_object")
+    bind_callback_local_vars(body, cg_context)
+
+    if cg_context.interface.identifier == "Window":
+        blink_class = "DOMWindow"
+    else:
+        blink_class = blink_class_name(cg_context.interface)
+    body.extend([
+        TextNode(
+            _format(
+                "{blink_class}* blink_accessed_object = "
+                "${class_name}::ToWrappableUnsafe(${accessed_object});",
+                blink_class=blink_class)),
+        TextNode("return BindingSecurity::ShouldAllowAccessTo("
+                 "ToLocalDOMWindow(${accessing_context}), "
+                 "blink_accessed_object, "
+                 "BindingSecurity::ErrorReportOption::kDoNotReport);"),
+    ])
+
+    return func_def
+
+
+def make_cross_origin_property_getter_callback(cg_context, function_name):
+    assert isinstance(cg_context, CodeGenContext)
+    assert isinstance(function_name, str)
+
+    func_def = CxxFuncDefNode(
+        name=function_name,
+        arg_decls=[
+            "v8::Local<v8::Name> v8_property_name",
+            "const v8::PropertyCallbackInfo<v8::Value>& info",
+        ],
+        return_type="void")
+    func_def.set_base_template_vars(cg_context.template_bindings())
+    body = func_def.body
+    body.add_template_var("v8_property_name", "v8_property_name")
+    body.add_template_var("info", "info")
+    bind_callback_local_vars(body, cg_context)
+
+    string_case_body = []
+    string_case_body.append(
+        TextNode("""\
+for (const auto& attribute : kCrossOriginAttributeTable) {
+  if (${blink_property_name} != attribute.name)
+    continue;
+  if (UNLIKELY(!attribute.get_value)) {
+    BindingSecurity::FailedAccessCheckFor(
+        ${info}.GetIsolate(),
+        ${class_name}::GetWrapperTypeInfo(),
+        ${info}.This());
+    return;
+  }
+  return attribute.get_value(${v8_property_name}, ${info});
+}
+for (const auto& operation : kCrossOriginOperationTable) {
+  if (${blink_property_name} != operation.name)
+    continue;
+  v8::Local<v8::Function> function;
+  if (bindings::GetCrossOriginFunction(
+          ${info}.GetIsolate(), operation.callback, operation.func_length,
+          ${class_name}::GetWrapperTypeInfo())
+          .ToLocal(&function)) {
+    bindings::V8SetReturnValue(${info}, function);
+  }
+  return;
+}
+% if interface.identifier == "Window":
+
+// Window object's document-tree child browsing context name property set
+//
+// TODO(yukishiino): Update the following hard-coded call to an appropriate
+// one.
+V8Window::NamedPropertyGetterCustom(${blink_property_name}, ${info});
+if (!${info}.GetReturnValue().Get()->IsUndefined())
+  return;
+% endif"""))
+
+    body.extend([
+        make_runtime_call_timer_scope(cg_context, "CrossOriginPropertyGetter"),
+        EmptyNode(),
+        CxxLikelyIfNode(
+            cond="${v8_property_name}->IsString()", body=string_case_body),
+        EmptyNode(),
+        TextNode("""\
+// 7.2.3.2 CrossOriginPropertyFallback ( P )
+// https://html.spec.whatwg.org/C/#crossoriginpropertyfallback-(-p-)
+if (bindings::IsSupportedInCrossOriginPropertyFallback(
+        ${info}.GetIsolate(), ${v8_property_name})) {
+  return ${info}.GetReturnValue().SetUndefined();
+}
+BindingSecurity::FailedAccessCheckFor(
+    ${info}.GetIsolate(),
+    ${class_name}::GetWrapperTypeInfo(),
+    ${info}.This());"""),
+    ])
+
+    return func_def
+
+
+def make_cross_origin_property_setter_callback(cg_context, function_name):
+    assert isinstance(cg_context, CodeGenContext)
+    assert isinstance(function_name, str)
+
+    func_def = CxxFuncDefNode(
+        name=function_name,
+        arg_decls=[
+            "v8::Local<v8::Name> v8_property_name",
+            "v8::Local<v8::Value> v8_property_value",
+            "const v8::PropertyCallbackInfo<v8::Value>& info",
+        ],
+        return_type="void")
+    func_def.set_base_template_vars(cg_context.template_bindings())
+    body = func_def.body
+    body.add_template_var("v8_property_name", "v8_property_name")
+    body.add_template_var("v8_property_value", "v8_property_value")
+    body.add_template_var("info", "info")
+    bind_callback_local_vars(body, cg_context)
+
+    string_case_body = []
+    string_case_body.append(
+        TextNode("""\
+for (const auto& attribute : kCrossOriginAttributeTable) {
+  if (${blink_property_name} == attribute.name && attribute.set_value) {
+    return attribute.set_value(
+        ${v8_property_name}, ${v8_property_value}, ${info});
+  }
+}"""))
+
+    body.extend([
+        make_runtime_call_timer_scope(cg_context, "CrossOriginPropertySetter"),
+        EmptyNode(),
+        CxxLikelyIfNode(
+            cond="${v8_property_name}->IsString()", body=string_case_body),
+        EmptyNode(),
+        TextNode("""\
+BindingSecurity::FailedAccessCheckFor(
+    ${info}.GetIsolate(),
+    ${class_name}::GetWrapperTypeInfo(),
+    ${info}.This());"""),
+    ])
+
+    return func_def
+
+
+def make_cross_origin_property_descriptor_callback(cg_context, function_name):
+    assert isinstance(cg_context, CodeGenContext)
+    assert isinstance(function_name, str)
+
+    func_def = CxxFuncDefNode(
+        name=function_name,
+        arg_decls=[
+            "v8::Local<v8::Name> v8_property_name",
+            "const v8::PropertyCallbackInfo<v8::Value>& info",
+        ],
+        return_type="void")
+    func_def.set_base_template_vars(cg_context.template_bindings())
+    body = func_def.body
+    body.add_template_var("v8_property_name", "v8_property_name")
+    body.add_template_var("info", "info")
+    bind_callback_local_vars(body, cg_context)
+
+    string_case_body = []
+    string_case_body.append(
+        TextNode("""\
+// 7.2.3.4 CrossOriginGetOwnPropertyHelper ( O, P )
+// https://html.spec.whatwg.org/C/#crossorigingetownpropertyhelper-(-o,-p-)
+for (const auto& attribute : kCrossOriginAttributeTable) {
+  if (${blink_property_name} != attribute.name)
+    continue;
+  v8::Local<v8::Value> get;
+  v8::Local<v8::Value> set;
+  if (!bindings::GetCrossOriginFunctionOrUndefined(
+           ${info}.GetIsolate(), attribute.get_callback, 0,
+           ${class_name}::GetWrapperTypeInfo())
+           .ToLocal(&get) ||
+      !bindings::GetCrossOriginFunctionOrUndefined(
+           ${info}.GetIsolate(), attribute.set_callback, 1,
+           ${class_name}::GetWrapperTypeInfo())
+           .ToLocal(&set)) {
+    return;
+  }
+  v8::PropertyDescriptor desc(get, set);
+  desc.set_enumerable(false);
+  desc.set_configurable(true);
+  return;
+}
+for (const auto& operation : kCrossOriginOperationTable) {
+  if (${blink_property_name} != operation.name)
+    continue;
+  v8::Local<v8::Function> function;
+  if (!bindings::GetCrossOriginFunction(
+           ${info}.GetIsolate(), operation.callback, operation.func_length,
+           ${class_name}::GetWrapperTypeInfo())
+           .ToLocal(&function)) {
+    return;
+  }
+  v8::PropertyDescriptor desc(function, /*writable=*/false);
+  desc.set_enumerable(false);
+  desc.set_configurable(true);
+  bindings::V8SetReturnValue(${info}, desc);
+  return;
+}
+% if interface.identifier == "Window":
+
+// Window object's document-tree child browsing context name property set
+//
+// TODO(yukishiino): Update the following hard-coded call to an appropriate
+// one.
+V8Window::NamedPropertyGetterCustom(${blink_property_name}, ${info});
+if (!${info}.GetReturnValue().Get()->IsUndefined()) {
+  v8::PropertyDescriptor desc(${info}.GetReturnValue().Get(),
+                              /*writable=*/false);
+  desc.set_enumerable(false);
+  desc.set_configurable(true);
+  bindings::V8SetReturnValue(${info}, desc);
+  return;
+}
+% endif"""))
+
+    body.extend([
+        CxxLikelyIfNode(
+            cond="${v8_property_name}->IsString()", body=string_case_body),
+        EmptyNode(),
+        TextNode("""\
+// 7.2.3.2 CrossOriginPropertyFallback ( P )
+// https://html.spec.whatwg.org/C/#crossoriginpropertyfallback-(-p-)
+if (bindings::IsSupportedInCrossOriginPropertyFallback(
+        ${info}.GetIsolate(), ${v8_property_name})) {
+  v8::PropertyDescriptor desc(v8::Undefined(${info}.GetIsolate()),
+                              /*writable=*/false);
+  desc.set_enumerable(false);
+  desc.set_configurable(true);
+  bindings::V8SetReturnValue(${info}, desc);
+  return;
+}
+BindingSecurity::FailedAccessCheckFor(
+    ${info}.GetIsolate(),
+    ${class_name}::GetWrapperTypeInfo(),
+    ${info}.This());"""),
+    ])
+
+    return func_def
+
+
+def make_cross_origin_property_enumerator_callback(cg_context, function_name):
+    assert isinstance(cg_context, CodeGenContext)
+    assert isinstance(function_name, str)
+
+    func_def = CxxFuncDefNode(
+        name=function_name,
+        arg_decls=["const v8::PropertyCallbackInfo<v8::Array>& info"],
+        return_type="void")
+    func_def.set_base_template_vars(cg_context.template_bindings())
+    body = func_def.body
+    body.add_template_var("info", "info")
+    bind_callback_local_vars(body, cg_context)
+
+    body.append(
+        TextNode("""\
+bindings::V8SetReturnValue(
+    ${info},
+    bindings::EnumerateCrossOriginProperties(
+        ${isolate},
+        kCrossOriginAttributeTable,
+        kCrossOriginOperationTable));"""))
+
+    return func_def
+
+
+# ----------------------------------------------------------------------------
 # Installer functions
 # ----------------------------------------------------------------------------
 
@@ -2868,7 +3177,10 @@
 
     def process_constant(constant, is_context_dependent, exposure_conditional,
                          world):
-        cgc = cg_context.make_copy(constant=constant, for_world=world)
+        cgc = cg_context.make_copy(
+            constant=constant,
+            for_world=world,
+            v8_callback_type=CodeGenContext.V8_ACCESSOR_NAME_GETTER_CALLBACK)
         const_callback_name = callback_function_name(cgc)
         const_callback_node = make_constant_callback_def(
             cgc, const_callback_name)
@@ -2917,7 +3229,9 @@
     def process_exposed_construct(exposed_construct, is_context_dependent,
                                   exposure_conditional, world):
         cgc = cg_context.make_copy(
-            exposed_construct=exposed_construct, for_world=world)
+            exposed_construct=exposed_construct,
+            for_world=world,
+            v8_callback_type=CodeGenContext.V8_ACCESSOR_NAME_GETTER_CALLBACK)
         prop_callback_name = callback_function_name(cgc)
         prop_callback_node = make_exposed_construct_callback_def(
             cgc, prop_callback_name)
@@ -2988,11 +3302,11 @@
     return callback_def_nodes
 
 
-def make_install_interface_template(cg_context, function_name, class_name,
-                                    trampoline_var_name, constructor_entries,
-                                    indexed_and_named_property_install_nodes,
-                                    install_unconditional_func_name,
-                                    install_context_independent_func_name):
+def make_install_interface_template(
+        cg_context, function_name, class_name, trampoline_var_name,
+        constructor_entries, indexed_and_named_property_install_nodes,
+        cross_origin_property_install_nodes, install_unconditional_func_name,
+        install_context_independent_func_name):
     """
     Returns:
         A triplet of CodeNode of:
@@ -3009,6 +3323,8 @@
     assert all(
         isinstance(entry, _PropEntryConstructorGroup)
         for entry in constructor_entries)
+    assert isinstance(indexed_and_named_property_install_nodes, SequenceNode)
+    assert isinstance(cross_origin_property_install_nodes, SequenceNode)
     assert _is_none_or_str(install_unconditional_func_name)
     assert _is_none_or_str(install_context_independent_func_name)
 
@@ -3084,6 +3400,37 @@
             assert False
     body.append(EmptyNode())
 
+    if cross_origin_property_install_nodes:
+        body.extend([
+            cross_origin_property_install_nodes,
+            EmptyNode(),
+        ])
+
+    if cg_context.class_like.identifier == "Location":
+        body.append(
+            T("""\
+// https://html.spec.whatwg.org/C/#the-location-interface
+// To create a Location object, run these steps:
+// step 3. Let valueOf be location's relevant
+//   Realm.[[Intrinsics]].[[%ObjProto_valueOf%]].
+// step 3. Perform ! location.[[DefineOwnProperty]]("valueOf",
+//   { [[Value]]: valueOf, [[Writable]]: false, [[Enumerable]]: false,
+//     [[Configurable]]: false }).
+${instance_template}->SetIntrinsicDataProperty(
+    V8AtomicString(${isolate}, "valueOf"),
+    v8::kObjProto_valueOf,
+    static_cast<v8::PropertyAttribute>(
+        int(v8::ReadOnly) | int(v8::DontEnum) | int(v8::DontDelete)));
+// step 4. Perform ! location.[[DefineOwnProperty]](@@toPrimitive,
+//   { [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false,
+//     [[Configurable]]: false }).
+${instance_template}->Set(
+    v8::Symbol::GetToPrimitive(${isolate}),
+    v8::Undefined(${isolate}),
+    static_cast<v8::PropertyAttribute>(
+        int(v8::ReadOnly) | int(v8::DontEnum) | int(v8::DontDelete)));
+"""))
+
     if indexed_and_named_property_install_nodes:
         body.extend([
             indexed_and_named_property_install_nodes,
@@ -3265,7 +3612,7 @@
                     TextNode(installer_call_text),
                 ]))
             body.append(EmptyNode())
-        for conditional, entries in conditional_to_entries.iteritems():
+        for conditional, entries in conditional_to_entries.items():
             body.append(
                 CxxUnlikelyIfNode(
                     cond=conditional,
@@ -3361,7 +3708,7 @@
     assert isinstance(cg_context, CodeGenContext)
 
     func_decls = ListNode()
-    func_defs = SequenceNode()
+    func_defs = ListNode()
     install_nodes = SequenceNode()
 
     interface = cg_context.interface
@@ -3379,21 +3726,20 @@
         key = lambda interface: len(interface.inclusive_inherited_interfaces)
         return sorted(filter(None, interfaces), key=key)[-1]
 
+    cg_context = cg_context.make_copy(
+        v8_callback_type=CodeGenContext.V8_OTHER_CALLBACK)
+
     if props.own_indexed_getter or props.own_indexed_setter:
         add_callback(*make_indexed_property_getter_callback(
             cg_context.make_copy(indexed_property_getter=props.indexed_getter),
             "IndexedPropertyGetterCallback"))
-
         add_callback(*make_indexed_property_setter_callback(
             cg_context.make_copy(indexed_property_setter=props.indexed_setter),
             "IndexedPropertySetterCallback"))
-
         add_callback(*make_indexed_property_definer_callback(
             cg_context, "IndexedPropertyDefinerCallback"))
-
         add_callback(*make_indexed_property_descriptor_callback(
             cg_context, "IndexedPropertyDescriptorCallback"))
-
         add_callback(*make_indexed_property_enumerator_callback(
             cg_context, "IndexedPropertyEnumeratorCallback"))
 
@@ -3413,19 +3759,19 @@
         pattern = """\
 // Indexed properties
 ${instance_template}->SetHandler(
-  v8::IndexedPropertyHandlerConfiguration(
-    {impl_bridge}::IndexedPropertyGetterCallback,
+    v8::IndexedPropertyHandlerConfiguration(
+        {impl_bridge}::IndexedPropertyGetterCallback,
 % if interface.indexed_and_named_properties.indexed_setter:
-    {impl_bridge}::IndexedPropertySetterCallback,
+        {impl_bridge}::IndexedPropertySetterCallback,
 % else:
-    nullptr,  // setter
+        nullptr,  // setter
 % endif
-    {impl_bridge}::IndexedPropertyDescriptorCallback,
-    nullptr,  // deleter
-    {impl_bridge}::IndexedPropertyEnumeratorCallback,
-    {impl_bridge}::IndexedPropertyDefinerCallback,
-    v8::Local<v8::Value>(),
-    {property_handler_flags}));"""
+        {impl_bridge}::IndexedPropertyDescriptorCallback,
+        nullptr,  // deleter
+        {impl_bridge}::IndexedPropertyEnumeratorCallback,
+        {impl_bridge}::IndexedPropertyDefinerCallback,
+        v8::Local<v8::Value>(),
+        {property_handler_flags}));"""
         install_nodes.append(
             TextNode(
                 _format(
@@ -3438,21 +3784,16 @@
         add_callback(*make_named_property_getter_callback(
             cg_context.make_copy(named_property_getter=props.named_getter),
             "NamedPropertyGetterCallback"))
-
         add_callback(*make_named_property_setter_callback(
             cg_context.make_copy(named_property_setter=props.named_setter),
             "NamedPropertySetterCallback"))
-
         add_callback(*make_named_property_deleter_callback(
             cg_context.make_copy(named_property_deleter=props.named_deleter),
             "NamedPropertyDeleterCallback"))
-
         add_callback(*make_named_property_definer_callback(
             cg_context, "NamedPropertyDefinerCallback"))
-
         add_callback(*make_named_property_descriptor_callback(
             cg_context, "NamedPropertyDescriptorCallback"))
-
         add_callback(*make_named_property_enumerator_callback(
             cg_context, "NamedPropertyEnumeratorCallback"))
 
@@ -3475,27 +3816,27 @@
         pattern = """\
 // Named properties
 ${instance_template}->SetHandler(
-  v8::NamedPropertyHandlerConfiguration(
-    {impl_bridge}::NamedPropertyGetterCallback,
+    v8::NamedPropertyHandlerConfiguration(
+        {impl_bridge}::NamedPropertyGetterCallback,
 % if interface.indexed_and_named_properties.named_setter:
-    {impl_bridge}::NamedPropertySetterCallback,
+        {impl_bridge}::NamedPropertySetterCallback,
 % else:
-    nullptr,  // setter
+        nullptr,  // setter
 % endif
-    {impl_bridge}::NamedPropertyDescriptorCallback,
+        {impl_bridge}::NamedPropertyDescriptorCallback,
 % if interface.indexed_and_named_properties.named_deleter:
-    {impl_bridge}::NamedPropertyDeleterCallback,
+        {impl_bridge}::NamedPropertyDeleterCallback,
 % else:
-    nullptr,  // deleter
+        nullptr,  // deleter
 % endif
 % if interface.indexed_and_named_properties.is_named_property_enumerable:
-    {impl_bridge}::NamedPropertyEnumeratorCallback,
+        {impl_bridge}::NamedPropertyEnumeratorCallback,
 % else:
-    nullptr,  // enumerator
+        nullptr,  // enumerator
 % endif
-    {impl_bridge}::NamedPropertyDefinerCallback,
-    v8::Local<v8::Value>(),
-    {property_handler_flags}));"""
+        {impl_bridge}::NamedPropertyDefinerCallback,
+        v8::Local<v8::Value>(),
+        {property_handler_flags}));"""
         install_nodes.append(
             TextNode(
                 _format(
@@ -3506,6 +3847,157 @@
     return func_decls, func_defs, install_nodes
 
 
+def make_cross_origin_property_callbacks_and_install_nodes(
+        cg_context, attribute_entries, operation_entries):
+    assert isinstance(cg_context, CodeGenContext)
+
+    callback_defs = []
+    install_nodes = SequenceNode()
+
+    CROSS_ORIGIN_INTERFACES = ("Window", "Location")
+    if cg_context.interface.identifier not in CROSS_ORIGIN_INTERFACES:
+        return callback_defs, install_nodes
+
+    entry_nodes = []
+    for entry in attribute_entries:
+        attribute = entry.property_
+        if "CrossOrigin" not in attribute.extended_attributes:
+            continue
+        assert entry.world == CodeGenContext.ALL_WORLDS
+        values = attribute.extended_attributes.values_of("CrossOrigin")
+        get_func = "nullptr"
+        set_func = "nullptr"
+        get_value = "nullptr"
+        set_value = "nullptr"
+        if not values or "Getter" in values:
+            get_func = entry.attr_get_callback_name
+            cgc = cg_context.make_copy(
+                attribute=attribute,
+                attribute_get=True,
+                v8_callback_type=(
+                    CodeGenContext.V8_ACCESSOR_NAME_GETTER_CALLBACK))
+            get_value = callback_function_name(cgc, for_cross_origin=True)
+            func_def = make_attribute_get_callback_def(cgc, get_value)
+            callback_defs.extend([func_def, EmptyNode()])
+        if (not values or "Setter" in values) and entry.attr_set_callback_name:
+            set_func = entry.attr_set_callback_name
+            cgc = cg_context.make_copy(
+                attribute=attribute,
+                attribute_set=True,
+                v8_callback_type=(
+                    CodeGenContext.V8_GENERIC_NAMED_PROPERTY_SETTER_CALLBACK))
+            set_value = callback_function_name(cgc, for_cross_origin=True)
+            func_def = make_attribute_set_callback_def(cgc, set_value)
+            callback_defs.extend([func_def, EmptyNode()])
+        pattern = ("{{\"{property_name}\", "
+                   "{get_func}, {set_func}, {get_value}, {set_value}}},")
+        entry_nodes.append(
+            TextNode(
+                _format(
+                    pattern,
+                    property_name=attribute.identifier,
+                    get_func=get_func,
+                    set_func=set_func,
+                    get_value=get_value,
+                    set_value=set_value)))
+    callback_defs.append(
+        ListNode([
+            TextNode("constexpr bindings::CrossOriginAttributeTableEntry "
+                     "kCrossOriginAttributeTable[] = {"),
+            ListNode(entry_nodes),
+            TextNode("};"),
+            EmptyNode(),
+        ]))
+
+    entry_nodes = []
+    for entry in operation_entries:
+        operation_group = entry.property_
+        if "CrossOrigin" not in operation_group.extended_attributes:
+            continue
+        assert entry.world == CodeGenContext.ALL_WORLDS
+        entry_nodes.append(
+            TextNode(
+                _format(
+                    "{{\"{property_name}\", {op_callback}, {op_func_length}}},",
+                    property_name=operation_group.identifier,
+                    op_callback=entry.op_callback_name,
+                    op_func_length=entry.op_func_length)))
+    callback_defs.append(
+        ListNode([
+            TextNode("constexpr bindings::CrossOriginOperationTableEntry "
+                     "kCrossOriginOperationTable[] = {"),
+            ListNode(entry_nodes),
+            TextNode("};"),
+            EmptyNode(),
+        ]))
+
+    cg_context = cg_context.make_copy(
+        v8_callback_type=CodeGenContext.V8_OTHER_CALLBACK)
+
+    callback_defs.extend([
+        make_cross_origin_access_check_callback(
+            cg_context, "CrossOriginAccessCheckCallback"),
+        EmptyNode(),
+        make_cross_origin_property_getter_callback(
+            cg_context, "CrossOriginPropertyGetterCallback"),
+        EmptyNode(),
+        make_cross_origin_property_setter_callback(
+            cg_context, "CrossOriginPropertySetterCallback"),
+        EmptyNode(),
+        make_cross_origin_property_descriptor_callback(
+            cg_context, "CrossOriginPropertyDescriptorCallback"),
+        EmptyNode(),
+        make_cross_origin_property_enumerator_callback(
+            cg_context, "CrossOriginPropertyEnumeratorCallback"),
+    ])
+
+    text = """\
+// Cross origin properties
+${instance_template}->SetAccessCheckCallbackAndHandler(
+    CrossOriginAccessCheckCallback,
+    v8::NamedPropertyHandlerConfiguration(
+        CrossOriginPropertyGetterCallback,
+        CrossOriginPropertySetterCallback,
+        CrossOriginPropertyDescriptorCallback,
+        nullptr,  // deleter
+        CrossOriginPropertyEnumeratorCallback,
+        nullptr,  // definer,
+        v8::Local<v8::Value>(),
+        v8::PropertyHandlerFlags::kNone),
+% if interface.indexed_and_named_properties and \
+     interface.indexed_and_named_properties.has_indexed_properties:
+    // Reuse non-cross origin indexed property callbacks.
+    v8::IndexedPropertyHandlerConfiguration(
+        ${class_name}::IndexedPropertyGetterCallback,
+        nullptr,  // setter
+        ${class_name}::IndexedPropertyDescriptorCallback,
+        nullptr,  // deleter
+        ${class_name}::IndexedPropertyEnumeratorCallback,
+        nullptr,  // definer
+        v8::Local<v8::Value>(),
+        v8::PropertyHandlerFlags::kNone),
+% else:
+    v8::IndexedPropertyHandlerConfiguration(
+        nullptr,  // getter
+        nullptr,  // setter
+        nullptr,  // descriptor
+        nullptr,  // deleter
+        nullptr,  // enumerator
+        nullptr,  // definer
+        v8::Local<v8::Value>(),
+        v8::PropertyHandlerFlags::kNone),
+% endif
+    v8::Local<v8::Value>());"""
+    install_nodes.append(TextNode(text))
+    install_nodes.accumulate(
+        CodeGenAccumulator.require_include_headers([
+            "third_party/blink/renderer/bindings/core/v8/binding_security.h",
+            "third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.h",
+        ]))
+
+    return callback_defs, install_nodes
+
+
 def make_cross_component_init(
         cg_context, function_name, class_name, has_unconditional_props,
         has_context_independent_props, has_context_dependent_props):
@@ -3869,11 +4361,20 @@
             arg_decls=["const v8::FunctionCallbackInfo<v8::Value>&"])
     for attribute in interface.attributes:
         custom_values = attribute.extended_attributes.values_of("Custom")
+        is_cross_origin = "CrossOrigin" in attribute.extended_attributes
+        cross_origin_values = attribute.extended_attributes.values_of(
+            "CrossOrigin")
         if "Getter" in custom_values:
             add_custom_callback_impl_decl(
                 attribute=attribute,
                 attribute_get=True,
                 arg_decls=["const v8::FunctionCallbackInfo<v8::Value>&"])
+            if is_cross_origin and (not cross_origin_values
+                                    or "Getter" in cross_origin_values):
+                add_custom_callback_impl_decl(
+                    attribute=attribute,
+                    attribute_get=True,
+                    arg_decls=["const v8::PropertyCallbackInfo<v8::Value>&"])
         if "Setter" in custom_values:
             add_custom_callback_impl_decl(
                 attribute=attribute,
@@ -3882,6 +4383,15 @@
                     "v8::Local<v8::Value>",
                     "const v8::FunctionCallbackInfo<v8::Value>&",
                 ])
+            if is_cross_origin and (not cross_origin_values
+                                    or "Setter" in cross_origin_values):
+                add_custom_callback_impl_decl(
+                    attribute=attribute,
+                    attribute_set=True,
+                    arg_decls=[
+                        "v8::Local<v8::Value>",
+                        "const v8::PropertyCallbackInfo<v8::Value>&",
+                    ])
     for operation_group in interface.operation_groups:
         if "Custom" in operation_group.extended_attributes:
             add_custom_callback_impl_decl(
@@ -3903,7 +4413,7 @@
                 named_property_setter=operation,
                 arg_decls=[
                     "const AtomicString& property_name",
-                    "v8::Local<v8::Value> v8_value",
+                    "v8::Local<v8::Value> v8_property_value",
                     "const v8::PropertyCallbackInfo<v8::Value>&",
                 ])
         operation = props.own_named_deleter
@@ -3949,6 +4459,13 @@
          make_indexed_and_named_property_callbacks_and_install_nodes(
              cg_context))
 
+    # Cross origin properties
+    (cross_origin_property_callback_defs,
+     cross_origin_property_install_nodes) = (
+         make_cross_origin_property_callbacks_and_install_nodes(
+             cg_context, attribute_entries, operation_entries))
+    callback_defs.extend(cross_origin_property_callback_defs)
+
     # Installer functions
     is_unconditional = lambda entry: entry.exposure_conditional.is_always_true
     is_context_dependent = lambda entry: entry.is_context_dependent
@@ -4000,6 +4517,8 @@
          constructor_entries=constructor_entries,
          indexed_and_named_property_install_nodes=(
              indexed_and_named_property_install_nodes),
+         cross_origin_property_install_nodes=(
+             cross_origin_property_install_nodes),
          install_unconditional_func_name=(install_unconditional_props_def
                                           and FN_INSTALL_UNCONDITIONAL_PROPS),
          install_context_independent_func_name=(
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py b/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
index e03ae3f6..b4c7055 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/mako_renderer.py
@@ -166,7 +166,7 @@
     """Returns the best-guessed name of |caller|."""
     try:
         # Outer CodeNode may have a binding to the caller.
-        for name, value in caller.outer.template_vars.iteritems():
+        for name, value in caller.outer.template_vars.items():
             if value is caller:
                 return name
         try:
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/name_style.py b/third_party/blink/renderer/bindings/scripts/bind_gen/name_style.py
index a5c0f21b..36a858ce 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/name_style.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/name_style.py
@@ -140,7 +140,7 @@
     assert isinstance(format_string, str)
 
     args = map(style_func, map(_tokenize, args))
-    for key, value in kwargs.iteritems():
+    for key, value in kwargs.items():
         kwargs[key] = style_func(_tokenize(value))
     return format_string.format(*args, **kwargs)
 
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/path_manager.py b/third_party/blink/renderer/bindings/scripts/bind_gen/path_manager.py
index 42fd7d2..47d6c08b 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/path_manager.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/path_manager.py
@@ -58,7 +58,7 @@
         cls._root_gen_dir = os.path.abspath(root_gen_dir)
         cls._component_reldirs = {
             component: posixpath.normpath(rel_dir)
-            for component, rel_dir in component_reldirs.iteritems()
+            for component, rel_dir in component_reldirs.items()
         }
         cls._is_initialized = True
 
diff --git a/third_party/blink/renderer/bindings/scripts/code_generator_v8.py b/third_party/blink/renderer/bindings/scripts/code_generator_v8.py
index a5973457..f296d48 100644
--- a/third_party/blink/renderer/bindings/scripts/code_generator_v8.py
+++ b/third_party/blink/renderer/bindings/scripts/code_generator_v8.py
@@ -95,7 +95,7 @@
     def resolve(self, definitions, definition_name):
         """Traverse definitions and resolves typedefs with the actual types."""
         self.typedefs = {}
-        for name, typedef in self.info_provider.typedefs.iteritems():
+        for name, typedef in self.info_provider.typedefs.items():
             self.typedefs[name] = typedef.idl_type
         self.additional_header_includes = set()
         definitions.accept(self)
@@ -338,7 +338,7 @@
         # idl_definitions.py. What we do instead is to resolve typedefs in
         # _generate_container_code() whenever a new union file is generated.
         self.typedefs = {}
-        for name, typedef in self.info_provider.typedefs.iteritems():
+        for name, typedef in self.info_provider.typedefs.items():
             self.typedefs[name] = typedef.idl_type
 
     def _generate_container_code(self, union_type):
@@ -441,7 +441,7 @@
         if not callback_functions:
             return ()
         outputs = set()
-        for callback_function_dict in callback_functions.itervalues():
+        for callback_function_dict in callback_functions.values():
             if callback_function_dict['component_dir'] != self.target_component:
                 continue
             callback_function = callback_function_dict['callback_function']
diff --git a/third_party/blink/renderer/bindings/scripts/compute_global_objects.py b/third_party/blink/renderer/bindings/scripts/compute_global_objects.py
index 81dc51f..7fa1c022 100755
--- a/third_party/blink/renderer/bindings/scripts/compute_global_objects.py
+++ b/third_party/blink/renderer/bindings/scripts/compute_global_objects.py
@@ -48,7 +48,7 @@
 
 
 def dict_union(dicts):
-    return dict((k, v) for d in dicts for k, v in d.iteritems())
+    return dict((k, v) for d in dicts for k, v in d.items())
 
 
 def idl_file_to_global_names(idl_filename):
diff --git a/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_individual.py b/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_individual.py
index 06b88aa..01c23ba 100755
--- a/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_individual.py
+++ b/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_individual.py
@@ -238,7 +238,7 @@
         self.union_types.update(this_union_types)
         self.typedefs.update(definitions.typedefs)
         for callback_function_name, callback_function in \
-                definitions.callback_functions.iteritems():
+                definitions.callback_functions.items():
             # Set 'component_dir' to specify a directory that callback function files belong to
             self.callback_functions[callback_function_name] = {
                 'callback_function': callback_function,
@@ -253,7 +253,7 @@
         self.enumerations.update(definitions.enumerations)
 
         if definitions.interfaces:
-            definition = next(definitions.interfaces.itervalues())
+            definition = next(iter(definitions.interfaces.values()))
             interface_info = {
                 'is_callback_interface':
                 definition.is_callback,
@@ -270,7 +270,7 @@
                 get_put_forward_interfaces_from_definition(definition),
             }
         elif definitions.dictionaries:
-            definition = next(definitions.dictionaries.itervalues())
+            definition = next(iter(definitions.dictionaries.values()))
             interface_info = {
                 'is_callback_interface': False,
                 'is_dictionary': True,
diff --git a/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_overall.py b/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_overall.py
index 98f0a5f..235cf0f 100755
--- a/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_overall.py
+++ b/third_party/blink/renderer/bindings/scripts/compute_interfaces_info_overall.py
@@ -127,12 +127,12 @@
 
     Needed for merging partial_interface_files across components.
     """
-    for key, value in other.iteritems():
+    for key, value in other.items():
         if key not in existing:
             existing[key] = value
             continue
         existing_value = existing[key]
-        for inner_key, inner_value in value.iteritems():
+        for inner_key, inner_value in value.items():
             existing_value[inner_key].extend(inner_value)
 
 
@@ -178,7 +178,7 @@
     garbage_collected_interfaces = set()
     callback_interfaces = set()
 
-    for interface_name, interface_info in interfaces_info.iteritems():
+    for interface_name, interface_info in interfaces_info.items():
         component_dirs[interface_name] = idl_filename_to_component(
             interface_info['full_path'])
 
@@ -220,10 +220,10 @@
             partial_interface_files, info['partial_interface_files'])
 
     # Record inheritance information individually
-    for interface_name, interface_info in interfaces_info.iteritems():
+    for interface_name, interface_info in interfaces_info.items():
         extended_attributes = interface_info['extended_attributes']
         inherited_extended_attributes_by_interface[interface_name] = dict(
-            (key, value) for key, value in extended_attributes.iteritems()
+            (key, value) for key, value in extended_attributes.items()
             if key in INHERITED_EXTENDED_ATTRIBUTES)
         parent = interface_info['parent']
         if parent:
@@ -241,7 +241,7 @@
     # 'includes').
     # Note that moving an 'includes' statement between files does not change the
     # info itself (or hence cause a rebuild)!
-    for mixin_name, interface_info in interfaces_info.iteritems():
+    for mixin_name, interface_info in interfaces_info.items():
         for interface_name in interface_info['included_by_interfaces']:
             interfaces_info[interface_name]['including_mixins'].append(
                 mixin_name)
@@ -249,7 +249,7 @@
 
     # An IDL file's dependencies are partial interface files that extend it,
     # and files for other interfaces that this interfaces include.
-    for interface_name, interface_info in interfaces_info.iteritems():
+    for interface_name, interface_info in interfaces_info.items():
         partial_interface_paths = partial_interface_files[interface_name]
         partial_interfaces_full_paths = partial_interface_paths['full_paths']
         # Partial interface definitions each need an include, as they are
@@ -311,7 +311,7 @@
         })
 
     # Clean up temporary private information
-    for interface_info in interfaces_info.itervalues():
+    for interface_info in interfaces_info.values():
         del interface_info['extended_attributes']
         del interface_info['union_types']
         del interface_info['is_legacy_treat_as_partial_interface']
diff --git a/third_party/blink/renderer/bindings/scripts/generate_global_constructors.py b/third_party/blink/renderer/bindings/scripts/generate_global_constructors.py
index 6cc7bf0..987f737c 100755
--- a/third_party/blink/renderer/bindings/scripts/generate_global_constructors.py
+++ b/third_party/blink/renderer/bindings/scripts/generate_global_constructors.py
@@ -120,7 +120,7 @@
     extended_attributes_list = [
         name + (('=' + extended_attributes[name])
                 if extended_attributes[name] else '')
-        for name in 'RuntimeEnabled', 'ContextEnabled', 'SecureContext'
+        for name in ['RuntimeEnabled', 'ContextEnabled', 'SecureContext']
         if name in extended_attributes
     ]
     if extended_attributes_list:
diff --git a/third_party/blink/renderer/bindings/scripts/idl_definitions.py b/third_party/blink/renderer/bindings/scripts/idl_definitions.py
index f42bc78..11f6846 100644
--- a/third_party/blink/renderer/bindings/scripts/idl_definitions.py
+++ b/third_party/blink/renderer/bindings/scripts/idl_definitions.py
@@ -137,22 +137,22 @@
 
     def accept(self, visitor):
         visitor.visit_definitions(self)
-        for interface in self.interfaces.itervalues():
+        for interface in self.interfaces.values():
             interface.accept(visitor)
-        for callback_function in self.callback_functions.itervalues():
+        for callback_function in self.callback_functions.values():
             callback_function.accept(visitor)
-        for dictionary in self.dictionaries.itervalues():
+        for dictionary in self.dictionaries.values():
             dictionary.accept(visitor)
-        for enumeration in self.enumerations.itervalues():
+        for enumeration in self.enumerations.values():
             enumeration.accept(visitor)
         for include in self.includes:
             include.accept(visitor)
-        for typedef in self.typedefs.itervalues():
+        for typedef in self.typedefs.values():
             typedef.accept(visitor)
 
     def update(self, other):
         """Update with additional IdlDefinitions."""
-        for interface_name, new_interface in other.interfaces.iteritems():
+        for interface_name, new_interface in other.interfaces.items():
             if not new_interface.is_partial:
                 # Add as new interface
                 self.interfaces[interface_name] = new_interface
diff --git a/third_party/blink/renderer/bindings/scripts/idl_types.py b/third_party/blink/renderer/bindings/scripts/idl_types.py
index 0618f3c7..cd4f0c35 100644
--- a/third_party/blink/renderer/bindings/scripts/idl_types.py
+++ b/third_party/blink/renderer/bindings/scripts/idl_types.py
@@ -642,7 +642,7 @@
     def __str__(self):
         annotation = ', '.join(
             (key + ('' if val is None else '=' + val))
-            for key, val in self.extended_attributes.iteritems())
+            for key, val in self.extended_attributes.items())
         return '[%s] %s' % (annotation, str(self.inner_type))
 
     def __getattr__(self, name):
@@ -670,7 +670,7 @@
     def name(self):
         annotation = ''.join(
             (key + ('' if val is None else val))
-            for key, val in sorted(self.extended_attributes.iteritems()))
+            for key, val in sorted(self.extended_attributes.items()))
         return self.inner_type.name + annotation
 
     def resolve_typedefs(self, typedefs):
diff --git a/third_party/blink/renderer/bindings/scripts/idl_validator.py b/third_party/blink/renderer/bindings/scripts/idl_validator.py
index 59eb8e1..64e84ef7d 100644
--- a/third_party/blink/renderer/bindings/scripts/idl_validator.py
+++ b/third_party/blink/renderer/bindings/scripts/idl_validator.py
@@ -51,7 +51,7 @@
 
     def validate_extended_attributes(self, definitions):
         # FIXME: this should be done when parsing the file, rather than after.
-        for interface in definitions.interfaces.itervalues():
+        for interface in definitions.interfaces.values():
             self.validate_extended_attributes_node(interface)
             for attribute in interface.attributes:
                 self.validate_extended_attributes_node(attribute)
@@ -59,17 +59,17 @@
                 self.validate_extended_attributes_node(operation)
                 for argument in operation.arguments:
                     self.validate_extended_attributes_node(argument)
-        for dictionary in definitions.dictionaries.itervalues():
+        for dictionary in definitions.dictionaries.values():
             self.validate_extended_attributes_node(dictionary)
             for member in dictionary.members:
                 self.validate_extended_attributes_node(member)
-        for callback_function in definitions.callback_functions.itervalues():
+        for callback_function in definitions.callback_functions.values():
             self.validate_extended_attributes_node(callback_function)
             for argument in callback_function.arguments:
                 self.validate_extended_attributes_node(argument)
 
     def validate_extended_attributes_node(self, node):
-        for name, values_string in node.extended_attributes.iteritems():
+        for name, values_string in node.extended_attributes.items():
             self.validate_name_values_string(name, values_string)
 
     def validate_name_values_string(self, name, values_string):
diff --git a/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py b/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
index 1a6b9bc..030ca1c9 100644
--- a/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
+++ b/third_party/blink/renderer/bindings/scripts/interface_dependency_resolver.py
@@ -101,7 +101,7 @@
                             'this definition: %s, because this should '
                             'have a dictionary.' % definitions.idl_name)
 
-        target_interface = next(definitions.interfaces.itervalues())
+        target_interface = next(iter(definitions.interfaces.values()))
         interface_name = target_interface.name
         interface_info = self.interfaces_info[interface_name]
 
@@ -163,7 +163,7 @@
             dependency_idl_filename)
 
         dependency_interface = next(
-            dependency_definitions.interfaces.itervalues())
+            iter(dependency_definitions.interfaces.values()))
 
         transfer_extended_attributes(dependency_interface,
                                      dependency_idl_filename)
@@ -361,8 +361,8 @@
             interface.get('cpp_includes', {}).get(component, {}))
         return unforgeable_attributes, referenced_interfaces, cpp_includes
 
-    for component, definitions in resolved_definitions.iteritems():
-        for interface_name, interface in definitions.interfaces.iteritems():
+    for component, definitions in resolved_definitions.items():
+        for interface_name, interface in definitions.interfaces.items():
             interface_info = interfaces_info[interface_name]
             inherited_unforgeable_attributes, referenced_interfaces, cpp_includes = \
                 collect_unforgeable_attributes_in_ancestors(
diff --git a/third_party/blink/renderer/bindings/scripts/overload_set_algorithm.py b/third_party/blink/renderer/bindings/scripts/overload_set_algorithm.py
index baaf0e73..309de69 100644
--- a/third_party/blink/renderer/bindings/scripts/overload_set_algorithm.py
+++ b/third_party/blink/renderer/bindings/scripts/overload_set_algorithm.py
@@ -183,7 +183,7 @@
     # Filter to only methods that are actually overloaded
     method_counts = Counter(method['name'] for method in methods)
     overloaded_method_names = set(
-        name for name, count in method_counts.iteritems() if count > 1)
+        name for name, count in method_counts.items() if count > 1)
     overloaded_methods = [
         method for method in methods
         if method['name'] in overloaded_method_names
diff --git a/third_party/blink/renderer/bindings/scripts/utilities.py b/third_party/blink/renderer/bindings/scripts/utilities.py
index 8529b5a..e52de46c 100644
--- a/third_party/blink/renderer/bindings/scripts/utilities.py
+++ b/third_party/blink/renderer/bindings/scripts/utilities.py
@@ -219,7 +219,7 @@
     |target| will be updated with |diff|.  Part of |diff| may be re-used in
     |target|.
     """
-    for key, value in diff.iteritems():
+    for key, value in diff.items():
         if key not in target:
             target[key] = value
         elif type(value) == dict:
@@ -324,7 +324,7 @@
 
 
 def read_pickle_file(pickle_filename):
-    with open(pickle_filename) as pickle_file:
+    with open(pickle_filename, 'rb') as pickle_file:
         return pickle.load(pickle_file)
 
 
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/database.py b/third_party/blink/renderer/bindings/scripts/web_idl/database.py
index 33ac805..c92cf48e 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/database.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/database.py
@@ -48,17 +48,17 @@
         )
 
         @classmethod
-        def itervalues(cls):
+        def values(cls):
             return cls._ALL_ENTRIES.__iter__()
 
     def __init__(self):
         self._defs = {}
-        for kind in DatabaseBody.Kind.itervalues():
+        for kind in DatabaseBody.Kind.values():
             self._defs[kind] = {}
 
     def register(self, kind, user_defined_type):
         assert isinstance(user_defined_type, (Typedef, Union, UserDefinedType))
-        assert kind in DatabaseBody.Kind.itervalues()
+        assert kind in DatabaseBody.Kind.values()
         try:
             self.find_by_identifier(user_defined_type.identifier)
             assert False, user_defined_type.identifier
@@ -67,7 +67,7 @@
         self._defs[kind][user_defined_type.identifier] = user_defined_type
 
     def find_by_identifier(self, identifier):
-        for defs_per_kind in self._defs.itervalues():
+        for defs_per_kind in self._defs.values():
             if identifier in defs_per_kind:
                 return defs_per_kind[identifier]
         raise KeyError(identifier)
@@ -156,4 +156,4 @@
         return self._view_by_kind(Database._Kind.UNION)
 
     def _view_by_kind(self, kind):
-        return self._impl.find_by_kind(kind).viewvalues()
+        return self._impl.find_by_kind(kind).values()
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py b/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
index 52c04173..36eec862 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
@@ -190,7 +190,7 @@
     def _on_ext_attrs_updated(self):
         self._keys = tuple(sorted(self._ext_attrs.keys()))
         self._length = 0
-        for ext_attrs in self._ext_attrs.itervalues():
+        for ext_attrs in self._ext_attrs.values():
             self._length += len(ext_attrs)
 
     @classmethod
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py b/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
index 46096964..9ad0852 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
@@ -299,7 +299,7 @@
 
         self._ir_map.move_to_new_phase()
 
-        for identifier, old_dictionary in old_dictionaries.iteritems():
+        for identifier, old_dictionary in old_dictionaries.items():
             new_dictionary = make_copy(old_dictionary)
             self._ir_map.add(new_dictionary)
             for partial_dictionary in old_partial_dictionaries.get(
@@ -331,7 +331,7 @@
         ir_sets_to_merge = [(interface, [
             mixins[include.mixin_identifier]
             for include in includes.get(identifier, [])
-        ]) for identifier, interface in interfaces.iteritems()]
+        ]) for identifier, interface in interfaces.items()]
 
         self._ir_map.move_to_new_phase()
 
@@ -380,7 +380,7 @@
 
         self._ir_map.move_to_new_phase()
 
-        for old_interface in old_interfaces.itervalues():
+        for old_interface in old_interfaces.values():
             new_interface = make_copy(old_interface)
             self._ir_map.add(new_interface)
             inheritance_chain = create_inheritance_chain(
@@ -644,13 +644,13 @@
 
         grouped_typedefs = {}  # {unique key: list of typedefs to the union}
         all_typedefs = self._db.find_by_kind(DatabaseBody.Kind.TYPEDEF)
-        for typedef in all_typedefs.itervalues():
+        for typedef in all_typedefs.values():
             if not typedef.idl_type.is_union:
                 continue
             key = unique_key(typedef.idl_type)
             grouped_typedefs.setdefault(key, []).append(typedef)
 
-        for key, union_types in grouped_unions.iteritems():
+        for key, union_types in grouped_unions.items():
             self._db.register(
                 DatabaseBody.Kind.UNION,
                 Union(
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/idl_type.py b/third_party/blink/renderer/bindings/scripts/web_idl/idl_type.py
index 4184196..1d7ae80 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/idl_type.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/idl_type.py
@@ -265,14 +265,14 @@
         }
 
         value_counts = {None: 0, False: 0, True: 0}
-        for value in switches.itervalues():
+        for value in switches.values():
             assert value is None or isinstance(value, bool)
             value_counts[value] += 1
         assert value_counts[False] == 0 or value_counts[True] == 0, (
             "Specify only True or False arguments.  Unspecified arguments are "
             "automatically set to the opposite value.")
         default = value_counts[True] == 0
-        for arg, value in switches.iteritems():
+        for arg, value in switches.items():
             if value is None:
                 switches[arg] = default
 
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/idl_type_test.py b/third_party/blink/renderer/bindings/scripts/web_idl/idl_type_test.py
index 1f44473..b3d097a7 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/idl_type_test.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/idl_type_test.py
@@ -83,7 +83,7 @@
             'void': 'Void',
             'symbol': 'Symbol',
         }
-        for name, expect in type_names.iteritems():
+        for name, expect in type_names.items():
             self.assertEqual(expect, factory.simple_type(name).type_name)
 
         short_type = factory.simple_type('short')
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/ir_map.py b/third_party/blink/renderer/bindings/scripts/web_idl/ir_map.py
index f95cbd9..91806523 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/ir_map.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/ir_map.py
@@ -190,11 +190,11 @@
         """Returns a flattened list of IRs of the given kind."""
         if IRMap.IR.Kind.does_support_multiple_defs(kind):
             accumulated = []
-            for irs in self.find_by_kind(kind).itervalues():
+            for irs in self.find_by_kind(kind).values():
                 accumulated.extend(irs)
             return accumulated
         else:
-            return list(self.find_by_kind(kind).itervalues())
+            return list(self.find_by_kind(kind).values())
 
     def irs_of_kinds(self, *kinds):
         """
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/make_copy.py b/third_party/blink/renderer/bindings/scripts/web_idl/make_copy.py
index b9a22eb..a7a2b11 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/make_copy.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/make_copy.py
@@ -48,11 +48,11 @@
     if isinstance(obj, dict):
         return memoize(
             cls([(make_copy(key, memo), make_copy(value, memo))
-                 for key, value in obj.iteritems()]))
+                 for key, value in obj.items()]))
 
     if hasattr(obj, '__dict__'):
         copy = memoize(cls.__new__(cls))
-        for name, value in obj.__dict__.iteritems():
+        for name, value in obj.__dict__.items():
             setattr(copy, name, make_copy(value, memo))
         return copy
 
diff --git a/third_party/blink/renderer/build/scripts/blinkbuild/PRESUBMIT.py b/third_party/blink/renderer/build/scripts/blinkbuild/PRESUBMIT.py
index e0735b1..75dda00 100644
--- a/third_party/blink/renderer/build/scripts/blinkbuild/PRESUBMIT.py
+++ b/third_party/blink/renderer/build/scripts/blinkbuild/PRESUBMIT.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 
 def _RunBindingsTests(input_api, output_api):
     pardir = input_api.os_path.pardir
diff --git a/third_party/blink/renderer/build/scripts/gperf.py b/third_party/blink/renderer/build/scripts/gperf.py
index 73dd62ad..5ee49056 100644
--- a/third_party/blink/renderer/build/scripts/gperf.py
+++ b/third_party/blink/renderer/build/scripts/gperf.py
@@ -71,7 +71,7 @@
                 gperf_args.extend(gperf_extra_args)
             return generate_gperf(gperf_path, gperf_input, gperf_args)
 
-        generator_internal.func_name = generator.func_name
+        generator_internal.__name__ = generator.__name__
         return generator_internal
 
     return wrapper
diff --git a/third_party/blink/renderer/build/scripts/in_file.py b/third_party/blink/renderer/build/scripts/in_file.py
index 9d1f7c3..28adc05 100644
--- a/third_party/blink/renderer/build/scripts/in_file.py
+++ b/third_party/blink/renderer/build/scripts/in_file.py
@@ -26,6 +26,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+from __future__ import print_function
+
 import copy
 import os
 
@@ -175,5 +177,5 @@
 
     def _fatal(self, message):
         # FIXME: This should probably raise instead of exit(1)
-        print message
+        print(message)
         exit(1)
diff --git a/third_party/blink/renderer/build/scripts/in_generator.py b/third_party/blink/renderer/build/scripts/in_generator.py
index f66347b2..e46740a 100644
--- a/third_party/blink/renderer/build/scripts/in_generator.py
+++ b/third_party/blink/renderer/build/scripts/in_generator.py
@@ -26,6 +26,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+from __future__ import print_function
+
 import os
 import os.path
 import shlex
@@ -94,7 +96,7 @@
         script_name = os.path.basename(argv[0])
         args = argv[1:]
         if len(args) < 1:
-            print "USAGE: %s INPUT_FILES" % script_name
+            print("USAGE: %s INPUT_FILES" % script_name)
             exit(1)
 
         parser = optparse.OptionParser()
diff --git a/third_party/blink/renderer/build/scripts/make_event_factory.py b/third_party/blink/renderer/build/scripts/make_event_factory.py
index 0050b386..a7ceb451 100755
--- a/third_party/blink/renderer/build/scripts/make_event_factory.py
+++ b/third_party/blink/renderer/build/scripts/make_event_factory.py
@@ -27,6 +27,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+from __future__ import print_function
+
 import os.path
 import sys
 
@@ -103,7 +105,7 @@
         }
 
     def _fatal(self, message):
-        print 'FATAL ERROR: ' + message
+        print('FATAL ERROR: ' + message)
         exit(1)
 
     def _headers_header_include_path(self, entry):
diff --git a/third_party/blink/renderer/build/scripts/make_names.py b/third_party/blink/renderer/build/scripts/make_names.py
index 39c5db7..3f7520a7 100755
--- a/third_party/blink/renderer/build/scripts/make_names.py
+++ b/third_party/blink/renderer/build/scripts/make_names.py
@@ -84,6 +84,7 @@
             entries = json5_generator.remove_duplicates(entries)
         else:
             json5_generator.reject_duplicates(entries)
+        entries.sort(key=lambda x: _symbol(x))
 
         basename, _ = os.path.splitext(os.path.basename(json5_file_path[0]))
         self._outputs = {
diff --git a/third_party/blink/renderer/build/scripts/make_origin_trials.py b/third_party/blink/renderer/build/scripts/make_origin_trials.py
index 672f9d7..db5b93a9 100755
--- a/third_party/blink/renderer/build/scripts/make_origin_trials.py
+++ b/third_party/blink/renderer/build/scripts/make_origin_trials.py
@@ -48,8 +48,7 @@
         self._implied_mappings = self._make_implied_mappings()
         self._trial_to_features_map = self._make_trial_to_features_map()
         self._max_features_per_trial = max(
-            len(features)
-            for features in self._trial_to_features_map.itervalues())
+            len(features) for features in self._trial_to_features_map.values())
         self._set_trial_types()
 
     @property
diff --git a/third_party/blink/renderer/build/scripts/templates/make_names.h.tmpl b/third_party/blink/renderer/build/scripts/templates/make_names.h.tmpl
index 6b62497..f6dd475 100644
--- a/third_party/blink/renderer/build/scripts/templates/make_names.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/make_names.h.tmpl
@@ -19,7 +19,7 @@
 namespace blink {
 namespace {{namespace}} {
 
-{% for entry in entries|sort %}
+{% for entry in entries %}
 {{symbol_export}}extern const WTF::AtomicString& {{entry|symbol}};
 {% endfor %}
 
diff --git a/third_party/blink/renderer/core/animation/BUILD.gn b/third_party/blink/renderer/core/animation/BUILD.gn
index 12ceb359..d2a6c965 100644
--- a/third_party/blink/renderer/core/animation/BUILD.gn
+++ b/third_party/blink/renderer/core/animation/BUILD.gn
@@ -92,6 +92,8 @@
     "css_image_list_interpolation_type.h",
     "css_image_slice_interpolation_type.cc",
     "css_image_slice_interpolation_type.h",
+    "css_interpolation_environment.cc",
+    "css_interpolation_environment.h",
     "css_interpolation_type.cc",
     "css_interpolation_type.h",
     "css_interpolation_types_map.cc",
diff --git a/third_party/blink/renderer/core/animation/animation_test_helper.cc b/third_party/blink/renderer/core/animation/animation_test_helper.cc
index 4bafa54..88cc4e0 100644
--- a/third_party/blink/renderer/core/animation/animation_test_helper.cc
+++ b/third_party/blink/renderer/core/animation/animation_test_helper.cc
@@ -8,7 +8,6 @@
 #include "third_party/blink/renderer/core/animation/css_interpolation_environment.h"
 #include "third_party/blink/renderer/core/animation/css_interpolation_types_map.h"
 #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
-#include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h"
 #include "third_party/blink/renderer/core/css/resolver/style_cascade.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -55,13 +54,8 @@
     ActiveInterpolationsMap map;
     map.Set(PropertyHandle("--unused"), interpolations);
 
-    using Entry = CascadeInterpolations::Entry;
-    CascadeInterpolations cascade_interpolations(Vector<Entry, 4>{
-        Entry{&map, CascadeOrigin::kAnimation},
-    });
-
-    cascade.Analyze(cascade_interpolations, CascadeFilter());
-    cascade.Apply(cascade_interpolations, CascadeFilter());
+    cascade.AddInterpolations(&map, CascadeOrigin::kAnimation);
+    cascade.Apply();
   } else {
     CSSInterpolationTypesMap map(state.GetDocument().GetPropertyRegistry(),
                                  state.GetDocument());
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_environment.cc b/third_party/blink/renderer/core/animation/css_interpolation_environment.cc
new file mode 100644
index 0000000..bb8bb38f
--- /dev/null
+++ b/third_party/blink/renderer/core/animation/css_interpolation_environment.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/animation/css_interpolation_environment.h"
+
+#include "third_party/blink/renderer/core/animation/property_handle.h"
+#include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h"
+#include "third_party/blink/renderer/core/css/resolver/style_cascade.h"
+
+namespace blink {
+
+const CSSValue* CSSInterpolationEnvironment::Resolve(
+    const PropertyHandle& property,
+    const CSSValue* value) const {
+  DCHECK(RuntimeEnabledFeatures::CSSCascadeEnabled());
+  DCHECK(cascade_);
+  DCHECK(cascade_resolver_);
+  if (!value)
+    return value;
+  return cascade_->Resolve(property.GetCSSPropertyName(), *value,
+                           *cascade_resolver_);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/css_interpolation_environment.h b/third_party/blink/renderer/core/animation/css_interpolation_environment.h
index 6a68781..7e15b15f 100644
--- a/third_party/blink/renderer/core/animation/css_interpolation_environment.h
+++ b/third_party/blink/renderer/core/animation/css_interpolation_environment.h
@@ -6,13 +6,14 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_CSS_INTERPOLATION_ENVIRONMENT_H_
 
 #include "third_party/blink/renderer/core/animation/interpolation_environment.h"
-#include "third_party/blink/renderer/core/css/resolver/style_cascade.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
 
 namespace blink {
 
+class CascadeResolver;
 class ComputedStyle;
 class CSSVariableResolver;
+class StyleCascade;
 
 class CSSInterpolationEnvironment : public InterpolationEnvironment {
  public:
@@ -27,7 +28,7 @@
   explicit CSSInterpolationEnvironment(const InterpolationTypesMap& map,
                                        StyleResolverState& state,
                                        StyleCascade* cascade,
-                                       StyleCascade::Resolver* cascade_resolver)
+                                       CascadeResolver* cascade_resolver)
       : InterpolationEnvironment(map),
         state_(&state),
         style_(state.Style()),
@@ -67,23 +68,14 @@
   }
 
   // TODO(crbug.com/985023): This effective violates const.
-  const CSSValue* Resolve(const PropertyHandle& property,
-                          const CSSValue* value) const {
-    DCHECK(RuntimeEnabledFeatures::CSSCascadeEnabled());
-    DCHECK(cascade_);
-    DCHECK(cascade_resolver_);
-    if (!value)
-      return value;
-    return cascade_->Resolve(property.GetCSSPropertyName(), *value,
-                             *cascade_resolver_);
-  }
+  const CSSValue* Resolve(const PropertyHandle&, const CSSValue*) const;
 
  private:
   StyleResolverState* state_ = nullptr;
   const ComputedStyle* style_ = nullptr;
   CSSVariableResolver* variable_resolver_ = nullptr;
   StyleCascade* cascade_ = nullptr;
-  StyleCascade::Resolver* cascade_resolver_ = nullptr;
+  CascadeResolver* cascade_resolver_ = nullptr;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/css/BUILD.gn b/third_party/blink/renderer/core/css/BUILD.gn
index 9f1e812..183fd3f 100644
--- a/third_party/blink/renderer/core/css/BUILD.gn
+++ b/third_party/blink/renderer/core/css/BUILD.gn
@@ -475,6 +475,8 @@
     "resolver/cascade_map.h",
     "resolver/cascade_origin.h",
     "resolver/cascade_priority.h",
+    "resolver/cascade_resolver.cc",
+    "resolver/cascade_resolver.h",
     "resolver/css_property_priority.h",
     "resolver/css_to_style_map.cc",
     "resolver/css_to_style_map.h",
diff --git a/third_party/blink/renderer/core/css/page_rule_collector.cc b/third_party/blink/renderer/core/css/page_rule_collector.cc
index ff844b8..0b20fc6 100644
--- a/third_party/blink/renderer/core/css/page_rule_collector.cc
+++ b/third_party/blink/renderer/core/css/page_rule_collector.cc
@@ -64,10 +64,12 @@
 }
 
 PageRuleCollector::PageRuleCollector(const ComputedStyle* root_element_style,
-                                     int page_index)
+                                     int page_index,
+                                     MatchResult& match_result)
     : is_left_page_(IsLeftPage(root_element_style, page_index)),
       is_first_page_(IsFirstPage(page_index)),
-      page_name_(PageName(page_index)) {}
+      page_name_(PageName(page_index)),
+      result_(match_result) {}
 
 void PageRuleCollector::MatchPageRules(RuleSet* rules) {
   if (!rules)
diff --git a/third_party/blink/renderer/core/css/page_rule_collector.h b/third_party/blink/renderer/core/css/page_rule_collector.h
index 065618b..f8fe90e 100644
--- a/third_party/blink/renderer/core/css/page_rule_collector.h
+++ b/third_party/blink/renderer/core/css/page_rule_collector.h
@@ -34,7 +34,9 @@
   STACK_ALLOCATED();
 
  public:
-  PageRuleCollector(const ComputedStyle* root_element_style, int page_index);
+  PageRuleCollector(const ComputedStyle* root_element_style,
+                    int page_index,
+                    MatchResult&);
 
   void MatchPageRules(RuleSet* rules);
   const MatchResult& MatchedResult() { return result_; }
@@ -59,7 +61,7 @@
   const bool is_first_page_;
   const String page_name_;
 
-  MatchResult result_;
+  MatchResult& result_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/properties/longhand.h b/third_party/blink/renderer/core/css/properties/longhand.h
index 6bdebb7b..161366c 100644
--- a/third_party/blink/renderer/core/css/properties/longhand.h
+++ b/third_party/blink/renderer/core/css/properties/longhand.h
@@ -43,9 +43,7 @@
   }
 
  protected:
-  constexpr Longhand(CSSPropertyID id,
-                     uint16_t flags,
-                     char repetition_separator)
+  constexpr Longhand(CSSPropertyID id, Flags flags, char repetition_separator)
       : CSSProperty(id, flags | kLonghand, repetition_separator) {}
 };
 
diff --git a/third_party/blink/renderer/core/css/properties/shorthand.h b/third_party/blink/renderer/core/css/properties/shorthand.h
index a4743b27..6970ad40 100644
--- a/third_party/blink/renderer/core/css/properties/shorthand.h
+++ b/third_party/blink/renderer/core/css/properties/shorthand.h
@@ -31,9 +31,7 @@
   }
 
  protected:
-  constexpr Shorthand(CSSPropertyID id,
-                      uint16_t flags,
-                      char repetition_separator)
+  constexpr Shorthand(CSSPropertyID id, Flags flags, char repetition_separator)
       : CSSProperty(id, flags | kShorthand, repetition_separator) {}
 };
 
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_interpolations.h b/third_party/blink/renderer/core/css/resolver/cascade_interpolations.h
index 6a6e3b4b..a2a9fb2 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_interpolations.h
+++ b/third_party/blink/renderer/core/css/resolver/cascade_interpolations.h
@@ -25,9 +25,10 @@
     CascadeOrigin origin = CascadeOrigin::kNone;
   };
 
-  CascadeInterpolations() = default;
-  CascadeInterpolations(Vector<Entry, 4> entries)
-      : entries_(std::move(entries)) {}
+  void Add(const ActiveInterpolationsMap* map, CascadeOrigin origin) {
+    DCHECK(map);
+    entries_.push_back(Entry{map, origin});
+  }
 
   bool IsEmpty() const { return GetEntries().IsEmpty(); }
 
@@ -39,6 +40,8 @@
     return entries_;
   }
 
+  void Reset() { entries_.clear(); }
+
  private:
   // We need to add at most four entries (see CSSAnimationUpdate):
   //
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_interpolations_test.cc b/third_party/blink/renderer/core/css/resolver/cascade_interpolations_test.cc
index 7a6e37a6..005809d 100644
--- a/third_party/blink/renderer/core/css/resolver/cascade_interpolations_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/cascade_interpolations_test.cc
@@ -15,16 +15,32 @@
                 "Unexpected max. If the limit increased, evaluate whether it "
                 "still makes sense to run this test");
 
-  using Entry = CascadeInterpolations::Entry;
+  ActiveInterpolationsMap map;
 
-  CascadeInterpolations at_max(Vector<Entry, 4>(max + 1));
-  CascadeInterpolations above_max(Vector<Entry, 4>(max + 2));
+  CascadeInterpolations interpolations;
+  for (size_t i = 0; i <= max; ++i)
+    interpolations.Add(&map, CascadeOrigin::kAuthor);
 
-  EXPECT_EQ(max + 1, at_max.GetEntries().size());
-  EXPECT_FALSE(at_max.IsEmpty());
+  // At maximum
+  EXPECT_FALSE(interpolations.IsEmpty());
 
-  EXPECT_FALSE(above_max.GetEntries().size());
-  EXPECT_TRUE(above_max.IsEmpty());
+  interpolations.Add(&map, CascadeOrigin::kAuthor);
+
+  // Maximum + 1
+  EXPECT_TRUE(interpolations.IsEmpty());
+}
+
+TEST(CascadeInterpolationsTest, Reset) {
+  ActiveInterpolationsMap map;
+
+  CascadeInterpolations interpolations;
+  EXPECT_TRUE(interpolations.IsEmpty());
+
+  interpolations.Add(&map, CascadeOrigin::kAuthor);
+  EXPECT_FALSE(interpolations.IsEmpty());
+
+  interpolations.Reset();
+  EXPECT_TRUE(interpolations.IsEmpty());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_resolver.cc b/third_party/blink/renderer/core/css/resolver/cascade_resolver.cc
new file mode 100644
index 0000000..164a54a
--- /dev/null
+++ b/third_party/blink/renderer/core/css/resolver/cascade_resolver.cc
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h"
+
+#include "third_party/blink/renderer/core/animation/css/css_animations.h"
+#include "third_party/blink/renderer/core/css/css_variable_data.h"
+#include "third_party/blink/renderer/core/css/properties/css_property.h"
+
+namespace blink {
+
+bool CascadeResolver::IsLocked(const CSSProperty& property) const {
+  return IsLocked(property.GetCSSPropertyName());
+}
+
+bool CascadeResolver::IsLocked(const CSSPropertyName& name) const {
+  return stack_.Contains(name);
+}
+
+bool CascadeResolver::AllowSubstitution(CSSVariableData* data) const {
+  if (data && data->IsAnimationTainted() && stack_.size()) {
+    const CSSPropertyName& name = stack_.back();
+    if (name.IsCustomProperty())
+      return true;
+    const CSSProperty& property = CSSProperty::Get(name.Id());
+    return !CSSAnimations::IsAnimationAffectingProperty(property);
+  }
+  return true;
+}
+
+bool CascadeResolver::DetectCycle(const CSSProperty& property) {
+  wtf_size_t index = stack_.Find(property.GetCSSPropertyName());
+  if (index == kNotFound)
+    return false;
+  cycle_depth_ = std::min(cycle_depth_, index);
+  return true;
+}
+
+bool CascadeResolver::InCycle() const {
+  return cycle_depth_ != kNotFound;
+}
+
+CascadeResolver::AutoLock::AutoLock(const CSSProperty& property,
+                                    CascadeResolver& resolver)
+    : AutoLock(property.GetCSSPropertyName(), resolver) {}
+
+CascadeResolver::AutoLock::AutoLock(const CSSPropertyName& name,
+                                    CascadeResolver& resolver)
+    : resolver_(resolver) {
+  DCHECK(!resolver.IsLocked(name));
+  resolver_.stack_.push_back(name);
+}
+
+CascadeResolver::AutoLock::~AutoLock() {
+  resolver_.stack_.pop_back();
+  if (resolver_.stack_.size() <= resolver_.cycle_depth_)
+    resolver_.cycle_depth_ = kNotFound;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/resolver/cascade_resolver.h b/third_party/blink/renderer/core/css/resolver/cascade_resolver.h
new file mode 100644
index 0000000..8e3e204
--- /dev/null
+++ b/third_party/blink/renderer/core/css/resolver/cascade_resolver.h
@@ -0,0 +1,104 @@
+// Copyright 2020 The Chromium 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_CORE_CSS_RESOLVER_CASCADE_RESOLVER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_CASCADE_RESOLVER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/css_property_name.h"
+#include "third_party/blink/renderer/core/css/properties/css_property.h"
+#include "third_party/blink/renderer/core/css/resolver/cascade_filter.h"
+#include "third_party/blink/renderer/core/css/rule_set.h"
+
+namespace blink {
+
+class CSSVariableData;
+class CSSProperty;
+
+namespace cssvalue {
+
+class CSSPendingSubstitutionValue;
+
+}  // namespace cssvalue
+
+// CascadeResolver is an object passed on the stack during Apply. Its most
+// important job is to detect cycles during Apply (in general, keep track of
+// which properties we're currently applying).
+class CORE_EXPORT CascadeResolver {
+  STACK_ALLOCATED();
+
+ public:
+  // TODO(crbug.com/985047): Probably use a HashMap for this.
+  using NameStack = Vector<CSSPropertyName, 8>;
+
+  // A 'locked' property is a property we are in the process of applying.
+  // In other words, once a property is locked, locking it again would form
+  // a cycle, and is therefore an error.
+  bool IsLocked(const CSSProperty&) const;
+  bool IsLocked(const CSSPropertyName&) const;
+
+  // We do not allow substitution of animation-tainted values into
+  // an animation-affecting property.
+  //
+  // https://drafts.csswg.org/css-variables/#animation-tainted
+  bool AllowSubstitution(CSSVariableData*) const;
+
+  // Automatically locks and unlocks the given property. (See
+  // CascadeResolver::IsLocked).
+  class CORE_EXPORT AutoLock {
+    STACK_ALLOCATED();
+
+   public:
+    AutoLock(const CSSProperty&, CascadeResolver&);
+    AutoLock(const CSSPropertyName&, CascadeResolver&);
+    ~AutoLock();
+
+   private:
+    CascadeResolver& resolver_;
+  };
+
+ private:
+  friend class AutoLock;
+  friend class StyleCascade;
+  friend class TestCascadeResolver;
+
+  CascadeResolver(CascadeFilter filter, uint8_t generation)
+      : filter_(filter), generation_(generation) {}
+
+  // If the given property is already being applied, returns true.
+  // The return value is the same value you would get from InCycle(), and
+  // is just returned for convenience.
+  //
+  // When a cycle has been detected, the CascadeResolver will *persist the cycle
+  // state* (i.e. InCycle() will continue to return true) until we reach
+  // the start of the cycle.
+  //
+  // The cycle state is cleared by ~AutoLock, once we have moved far enough
+  // up the stack.
+  bool DetectCycle(const CSSProperty&);
+  // Returns true whenever the CascadeResolver is in a cycle state.
+  // This DOES NOT detect cycles; the caller must call DetectCycle first.
+  bool InCycle() const;
+
+  NameStack stack_;
+  wtf_size_t cycle_depth_ = kNotFound;
+  CascadeFilter filter_;
+  const uint8_t generation_ = 0;
+
+  // A very simple cache for CSSPendingSubstitutionValues. We cache only the
+  // most recently parsed CSSPendingSubstitutionValue, such that consecutive
+  // calls to ResolvePendingSubstitution with the same value don't need to
+  // do the same parsing job all over again.
+  struct {
+    STACK_ALLOCATED();
+
+   public:
+    const cssvalue::CSSPendingSubstitutionValue* value = nullptr;
+    HeapVector<CSSPropertyValue, 256> parsed_properties;
+  } shorthand_cache_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_CASCADE_RESOLVER_H_
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 7eb2e07..545faf87 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/css/property_registry.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_expansion.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h"
+#include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h"
 #include "third_party/blink/renderer/core/css/resolver/css_property_priority.h"
 #include "third_party/blink/renderer/core/css/resolver/style_builder.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
@@ -96,49 +97,22 @@
 
 }  // namespace
 
-void StyleCascade::Analyze(const MatchResult& match_result,
-                           CascadeFilter filter) {
-  for (auto e : match_result.Expansions(GetDocument(), filter)) {
-    for (; !e.AtEnd(); e.Next())
-      map_.Add(e.Name(), e.Priority());
-  }
+MatchResult& StyleCascade::MutableMatchResult() {
+  needs_match_result_analyze_ = true;
+  return match_result_;
 }
 
-void StyleCascade::Analyze(const CascadeInterpolations& interpolations,
-                           CascadeFilter filter) {
-  const auto& entries = interpolations.GetEntries();
-  for (size_t i = 0; i < entries.size(); ++i) {
-    for (const auto& active_interpolation : *entries[i].map) {
-      uint32_t position = EncodeInterpolationPosition(
-          i, active_interpolation.key.IsPresentationAttribute());
-      CascadePriority priority(entries[i].origin, false, 0, position);
-
-      auto name = active_interpolation.key.GetCSSPropertyName();
-      CSSPropertyRef ref(name, GetDocument());
-      DCHECK(ref.IsValid());
-      const CSSProperty& property = ref.GetProperty();
-
-      if (filter.Rejects(property))
-        continue;
-
-      map_.Add(name, priority);
-
-      // Since an interpolation for an unvisited property also causes an
-      // interpolation of the visited property, add the visited property to
-      // the map as well.
-      // TODO(crbug.com/1062217): Interpolate visited colors separately
-      if (const CSSProperty* visited = property.GetVisitedProperty()) {
-        if (!filter.Rejects(*visited))
-          map_.Add(visited->GetCSSPropertyName(), priority);
-      }
-    }
-  }
+void StyleCascade::AddInterpolations(const ActiveInterpolationsMap* map,
+                                     CascadeOrigin origin) {
+  DCHECK(map);
+  needs_interpolations_analyze_ = true;
+  interpolations_.Add(map, origin);
 }
 
-void StyleCascade::Apply(const MatchResult* match_result,
-                         const CascadeInterpolations* interpolations,
-                         CascadeFilter filter) {
-  Resolver resolver(filter, match_result, interpolations, ++generation_);
+void StyleCascade::Apply(CascadeFilter filter) {
+  AnalyzeIfNeeded();
+
+  CascadeResolver resolver(filter, ++generation_);
 
   // Affects the computed value of 'color', hence needs to happen before
   // high-priority properties.
@@ -152,13 +126,11 @@
 
   ApplyHighPriority(resolver);
 
-  if (match_result)
-    ApplyMatchResult(*match_result, resolver);
-
-  if (interpolations)
-    ApplyInterpolations(*interpolations, resolver);
+  ApplyMatchResult(resolver);
+  ApplyInterpolations(resolver);
 
   if (map_.Find(CSSPropertyName(CSSPropertyID::kWebkitAppearance)) &&
+      !resolver.filter_.Rejects(GetCSSPropertyWebkitAppearance()) &&
       state_.Style()->HasAppearance()) {
     state_.Style()->SetHasAuthorBackground(HasAuthorBackground());
     state_.Style()->SetHasAuthorBorder(HasAuthorBorder());
@@ -167,13 +139,15 @@
 
 void StyleCascade::Reset() {
   map_.Reset();
+  match_result_.Reset();
+  interpolations_.Reset();
   generation_ = 0;
 }
 
 StyleCascade::Surrogate StyleCascade::ResolveSurrogate(
     const CSSProperty& surrogate,
     CascadePriority priority,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   DCHECK(surrogate.IsSurrogate());
   const CSSProperty* original = surrogate.SurrogateFor(
       state_.Style()->Direction(), state_.Style()->GetWritingMode());
@@ -186,7 +160,7 @@
 
 const CSSValue* StyleCascade::Resolve(const CSSPropertyName& name,
                                       const CSSValue& value,
-                                      Resolver& resolver) {
+                                      CascadeResolver& resolver) {
   CSSPropertyRef ref(name, state_.GetDocument());
 
   const CSSValue* resolved = Resolve(ref.GetProperty(), value, resolver);
@@ -199,7 +173,50 @@
   return resolved;
 }
 
-void StyleCascade::ApplyHighPriority(Resolver& resolver) {
+void StyleCascade::AnalyzeIfNeeded() {
+  if (needs_match_result_analyze_) {
+    AnalyzeMatchResult();
+    needs_match_result_analyze_ = false;
+  }
+  if (needs_interpolations_analyze_) {
+    AnalyzeInterpolations();
+    needs_interpolations_analyze_ = true;
+  }
+}
+
+void StyleCascade::AnalyzeMatchResult() {
+  for (auto e : match_result_.Expansions(GetDocument(), CascadeFilter())) {
+    for (; !e.AtEnd(); e.Next())
+      map_.Add(e.Name(), e.Priority());
+  }
+}
+
+void StyleCascade::AnalyzeInterpolations() {
+  const auto& entries = interpolations_.GetEntries();
+  for (size_t i = 0; i < entries.size(); ++i) {
+    for (const auto& active_interpolation : *entries[i].map) {
+      uint32_t position = EncodeInterpolationPosition(
+          i, active_interpolation.key.IsPresentationAttribute());
+      CascadePriority priority(entries[i].origin, false, 0, position);
+
+      auto name = active_interpolation.key.GetCSSPropertyName();
+      CSSPropertyRef ref(name, GetDocument());
+      DCHECK(ref.IsValid());
+      const CSSProperty& property = ref.GetProperty();
+
+      map_.Add(name, priority);
+
+      // Since an interpolation for an unvisited property also causes an
+      // interpolation of the visited property, add the visited property to
+      // the map as well.
+      // TODO(crbug.com/1062217): Interpolate visited colors separately
+      if (const CSSProperty* visited = property.GetVisitedProperty())
+        map_.Add(visited->GetCSSPropertyName(), priority);
+    }
+  }
+}
+
+void StyleCascade::ApplyHighPriority(CascadeResolver& resolver) {
   uint64_t bits = map_.HighPriorityBits();
 
   if (bits) {
@@ -230,7 +247,7 @@
   }
 }
 
-void StyleCascade::ApplyWebkitBorderImage(Resolver& resolver) {
+void StyleCascade::ApplyWebkitBorderImage(CascadeResolver& resolver) {
   const CascadePriority* priority =
       map_.Find(CSSPropertyName(CSSPropertyID::kWebkitBorderImage));
   if (!priority)
@@ -256,9 +273,8 @@
   }
 }
 
-void StyleCascade::ApplyMatchResult(const MatchResult& match_result,
-                                    Resolver& resolver) {
-  for (auto e : match_result.Expansions(GetDocument(), resolver.filter_)) {
+void StyleCascade::ApplyMatchResult(CascadeResolver& resolver) {
+  for (auto e : match_result_.Expansions(GetDocument(), resolver.filter_)) {
     for (; !e.AtEnd(); e.Next()) {
       auto priority = CascadePriority(e.Priority(), resolver.generation_);
       CascadePriority* p = map_.Find(e.Name());
@@ -274,10 +290,8 @@
   }
 }
 
-void StyleCascade::ApplyInterpolations(
-    const CascadeInterpolations& interpolations,
-    Resolver& resolver) {
-  const auto& entries = interpolations.GetEntries();
+void StyleCascade::ApplyInterpolations(CascadeResolver& resolver) {
+  const auto& entries = interpolations_.GetEntries();
   for (size_t i = 0; i < entries.size(); ++i) {
     const auto& entry = entries[i];
     ApplyInterpolationMap(*entry.map, entry.origin, i, resolver);
@@ -287,7 +301,7 @@
 void StyleCascade::ApplyInterpolationMap(const ActiveInterpolationsMap& map,
                                          CascadeOrigin origin,
                                          size_t index,
-                                         Resolver& resolver) {
+                                         CascadeResolver& resolver) {
   for (const auto& entry : map) {
     auto name = entry.key.GetCSSPropertyName();
     uint32_t position =
@@ -319,7 +333,7 @@
     const CSSProperty& property,
     CascadePriority priority,
     const ActiveInterpolations& interpolations,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   const Interpolation& interpolation = *interpolations.front();
   if (IsA<InvalidatableInterpolation>(interpolation)) {
     CSSInterpolationTypesMap map(state_.GetDocument().GetPropertyRegistry(),
@@ -355,14 +369,14 @@
 }
 
 void StyleCascade::LookupAndApply(const CSSPropertyName& name,
-                                  Resolver& resolver) {
+                                  CascadeResolver& resolver) {
   CSSPropertyRef ref(name, state_.GetDocument());
   DCHECK(ref.IsValid());
   LookupAndApply(ref.GetProperty(), resolver);
 }
 
 void StyleCascade::LookupAndApply(const CSSProperty& property,
-                                  Resolver& resolver) {
+                                  CascadeResolver& resolver) {
   CSSPropertyName name = property.GetCSSPropertyName();
   DCHECK(!resolver.IsLocked(name));
 
@@ -379,23 +393,17 @@
   if (ResolveIfSurrogate(property, priority, resolver) == Surrogate::kSkip)
     return;
 
-  const MatchResult* match_result = resolver.match_result_;
-  const CascadeInterpolations* interpolations = resolver.interpolations_;
-
-  if (priority.GetOrigin() < CascadeOrigin::kAnimation && match_result) {
-    LookupAndApplyDeclaration(property, priority, *match_result, resolver);
-  } else if (priority.GetOrigin() >= CascadeOrigin::kAnimation &&
-             interpolations) {
-    LookupAndApplyInterpolation(property, priority, *interpolations, resolver);
-  }
+  if (priority.GetOrigin() < CascadeOrigin::kAnimation)
+    LookupAndApplyDeclaration(property, priority, resolver);
+  else if (priority.GetOrigin() >= CascadeOrigin::kAnimation)
+    LookupAndApplyInterpolation(property, priority, resolver);
 }
 
 void StyleCascade::LookupAndApplyDeclaration(const CSSProperty& property,
                                              CascadePriority priority,
-                                             const MatchResult& result,
-                                             Resolver& resolver) {
+                                             CascadeResolver& resolver) {
   DCHECK(priority.GetOrigin() < CascadeOrigin::kAnimation);
-  const CSSValue* value = ValueAt(result, priority.GetPosition());
+  const CSSValue* value = ValueAt(match_result_, priority.GetPosition());
   DCHECK(value);
   value = Resolve(property, *value, resolver);
   DCHECK(!value->IsVariableReferenceValue());
@@ -403,11 +411,9 @@
   StyleBuilder::ApplyProperty(property, state_, *value);
 }
 
-void StyleCascade::LookupAndApplyInterpolation(
-    const CSSProperty& property,
-    CascadePriority priority,
-    const CascadeInterpolations& interpolations,
-    Resolver& resolver) {
+void StyleCascade::LookupAndApplyInterpolation(const CSSProperty& property,
+                                               CascadePriority priority,
+                                               CascadeResolver& resolver) {
   // Interpolations for -internal-visited properties are applied via the
   // interpolation for the main (unvisited) property, so we don't need to
   // apply it twice.
@@ -416,8 +422,8 @@
     return;
   DCHECK(priority.GetOrigin() >= CascadeOrigin::kAnimation);
   size_t index = DecodeInterpolationIndex(priority.GetPosition());
-  DCHECK_LE(index, interpolations.GetEntries().size());
-  const ActiveInterpolationsMap& map = *interpolations.GetEntries()[index].map;
+  DCHECK_LE(index, interpolations_.GetEntries().size());
+  const ActiveInterpolationsMap& map = *interpolations_.GetEntries()[index].map;
   PropertyHandle handle = ToPropertyHandle(property, priority);
   const auto& entry = map.find(handle);
   DCHECK_NE(entry, map.end());
@@ -467,7 +473,7 @@
 
 const CSSValue* StyleCascade::Resolve(const CSSProperty& property,
                                       const CSSValue& value,
-                                      Resolver& resolver) {
+                                      CascadeResolver& resolver) {
   if (const auto* v = DynamicTo<CSSCustomPropertyDeclaration>(value))
     return ResolveCustomProperty(property, *v, resolver);
   if (const auto* v = DynamicTo<CSSVariableReferenceValue>(value))
@@ -480,9 +486,9 @@
 const CSSValue* StyleCascade::ResolveCustomProperty(
     const CSSProperty& property,
     const CSSCustomPropertyDeclaration& decl,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   DCHECK(!resolver.IsLocked(property));
-  AutoLock lock(property, resolver);
+  CascadeResolver::AutoLock lock(property, resolver);
 
   // TODO(andruud): Don't transport css-wide keywords in this value.
   if (!decl.Value())
@@ -512,9 +518,9 @@
 const CSSValue* StyleCascade::ResolveVariableReference(
     const CSSProperty& property,
     const CSSVariableReferenceValue& value,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   DCHECK(!resolver.IsLocked(property));
-  AutoLock lock(property, resolver);
+  CascadeResolver::AutoLock lock(property, resolver);
 
   const CSSVariableData* data = value.VariableDataValue();
   const CSSParserContext* context = GetParserContext(value);
@@ -537,9 +543,9 @@
 const CSSValue* StyleCascade::ResolvePendingSubstitution(
     const CSSProperty& property,
     const cssvalue::CSSPendingSubstitutionValue& value,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   DCHECK(!resolver.IsLocked(property));
-  AutoLock lock(property, resolver);
+  CascadeResolver::AutoLock lock(property, resolver);
 
   CascadePriority priority = map_.At(property.GetCSSPropertyName());
   DCHECK_NE(property.PropertyID(), CSSPropertyID::kVariable);
@@ -600,7 +606,7 @@
 
 scoped_refptr<CSSVariableData> StyleCascade::ResolveVariableData(
     CSSVariableData* data,
-    Resolver& resolver) {
+    CascadeResolver& resolver) {
   DCHECK(data && data->NeedsVariableResolution());
 
   TokenSequence sequence(data);
@@ -612,7 +618,7 @@
 }
 
 bool StyleCascade::ResolveTokensInto(CSSParserTokenRange range,
-                                     Resolver& resolver,
+                                     CascadeResolver& resolver,
                                      TokenSequence& out) {
   bool success = true;
   while (!range.AtEnd()) {
@@ -628,7 +634,7 @@
 }
 
 bool StyleCascade::ResolveVarInto(CSSParserTokenRange range,
-                                  Resolver& resolver,
+                                  CascadeResolver& resolver,
                                   TokenSequence& out) {
   AtomicString variable_name = ConsumeVariableName(range);
   DCHECK(range.AtEnd() || (range.Peek().GetType() == kCommaToken));
@@ -690,7 +696,7 @@
 }
 
 bool StyleCascade::ResolveEnvInto(CSSParserTokenRange range,
-                                  Resolver& resolver,
+                                  CascadeResolver& resolver,
                                   TokenSequence& out) {
   AtomicString variable_name = ConsumeVariableName(range);
   DCHECK(range.AtEnd() || (range.Peek().GetType() == kCommaToken));
@@ -771,54 +777,6 @@
     state_.Style()->SetHasVariableReferenceFromNonInheritedProperty();
 }
 
-bool StyleCascade::Resolver::IsLocked(const CSSProperty& property) const {
-  return IsLocked(property.GetCSSPropertyName());
-}
-
-bool StyleCascade::Resolver::IsLocked(const CSSPropertyName& name) const {
-  return stack_.Contains(name);
-}
-
-bool StyleCascade::Resolver::AllowSubstitution(CSSVariableData* data) const {
-  if (data && data->IsAnimationTainted() && stack_.size()) {
-    const CSSPropertyName& name = stack_.back();
-    if (name.IsCustomProperty())
-      return true;
-    const CSSProperty& property = CSSProperty::Get(name.Id());
-    return !CSSAnimations::IsAnimationAffectingProperty(property);
-  }
-  return true;
-}
-
-bool StyleCascade::Resolver::DetectCycle(const CSSProperty& property) {
-  wtf_size_t index = stack_.Find(property.GetCSSPropertyName());
-  if (index == kNotFound)
-    return false;
-  cycle_depth_ = std::min(cycle_depth_, index);
-  return true;
-}
-
-bool StyleCascade::Resolver::InCycle() const {
-  return cycle_depth_ != kNotFound;
-}
-
-StyleCascade::AutoLock::AutoLock(const CSSProperty& property,
-                                 Resolver& resolver)
-    : AutoLock(property.GetCSSPropertyName(), resolver) {}
-
-StyleCascade::AutoLock::AutoLock(const CSSPropertyName& name,
-                                 Resolver& resolver)
-    : resolver_(resolver) {
-  DCHECK(!resolver.IsLocked(name));
-  resolver_.stack_.push_back(name);
-}
-
-StyleCascade::AutoLock::~AutoLock() {
-  resolver_.stack_.pop_back();
-  if (resolver_.stack_.size() <= resolver_.cycle_depth_)
-    resolver_.cycle_depth_ = kNotFound;
-}
-
 const Document& StyleCascade::GetDocument() const {
   return state_.GetDocument();
 }
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.h b/third_party/blink/renderer/core/css/resolver/style_cascade.h
index 7de7440..22b2c859 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.h
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.h
@@ -13,15 +13,19 @@
 #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_filter.h"
+#include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_map.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_origin.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_priority.h"
+#include "third_party/blink/renderer/core/css/resolver/match_result.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 
+class CascadeInterpolations;
+class CascadeResolver;
 class CSSCustomPropertyDeclaration;
 class CSSParserContext;
 class CSSProperty;
@@ -29,9 +33,8 @@
 class CSSVariableData;
 class CSSVariableReferenceValue;
 class CustomProperty;
-class StyleResolverState;
 class MatchResult;
-class CascadeInterpolations;
+class StyleResolverState;
 
 namespace cssvalue {
 
@@ -39,19 +42,17 @@
 
 }  // namespace cssvalue
 
-// StyleCascade can analyze a MatchResult/CascadeInterpolations object to figure
-// out which declarations should be skipped (e.g. due to a subsequent
-// declaration with a higher priority), and which should be applied.
+// StyleCascade analyzes declarations provided by CSS rules and animations,
+// and figures out which declarations should be skipped, and which should be
+// applied (and in which order).
 //
 // Usage:
 //
-//   MatchResult result;
-//   AddRulesSomehow(result);
-//
 //   StyleCascade cascade(state);
-//   CascadeFilter allow_all;
-//   cascade.Analyze(result, allow_all);
-//   cascade.Apply(result, allow_all);
+//   cascade.MutableMatchResult().AddMatchedProperties(...matched rule...);
+//   cascade.MutableMatchResult().AddMatchedProperties(...another rule...);
+//   cascade.AddInterpolation(...); // Optional
+//   cascade.Apply();
 //
 // [1] https://drafts.csswg.org/css-cascade/#cascade
 class CORE_EXPORT StyleCascade {
@@ -60,133 +61,36 @@
   using CSSPendingSubstitutionValue = cssvalue::CSSPendingSubstitutionValue;
 
  public:
-  class Resolver;
-  class AutoLock;
-
   StyleCascade(StyleResolverState& state) : state_(state) {}
 
-  // The Analyze pass goes through the MatchResult (or CascadeInterpolations),
-  // and produces a CascadePriority for each declaration. Each declaration
-  // is compared against the currently stored priority for the associated
-  // property, and either added the CascadeMap, or discarded, depending on which
-  // priority is greater.
-  //
-  // Note that the MatchResult/CascadeInterpolations (and their values) are
-  // not retained by StyleCascade. The caller must provide the same object
-  // (or a compatible object) when calling Apply.
-  void Analyze(const MatchResult&, CascadeFilter);
-  void Analyze(const CascadeInterpolations&, CascadeFilter);
+  const MatchResult& GetMatchResult() { return match_result_; }
 
-  // The Apply pass goes through the MatchResult (or CascadeInterpolations),
-  // and produces a CascadePriority for each declaration. If the priority of
-  // the declaration is equal to the priority stored for the associated
-  // property, then we Apply that declaration to the ComputedStyle. Otherwise,
-  // the declaration is skipped.
-  void Apply(const MatchResult& result, CascadeFilter filter) {
-    Apply(&result, nullptr, filter);
-  }
-  void Apply(const CascadeInterpolations& i, CascadeFilter filter) {
-    Apply(nullptr, &i, filter);
-  }
-  // Applying a MatchResult and CascadeInterpolations at the same time means
-  // that dependency resolution can take place across the two "declaration
-  // sources".
+  // Access the MatchResult in order to add declarations to it.
+  // The modifications made will be taken into account during the next call to
+  // Apply.
   //
-  // For example, if there is an interpolation currently taking place on
-  // 'font-size', static declarations from the MatchResult object that contain
-  // 'em' units would be responsive to to that interpolation. This would not be
-  // the case if two are applied separately.
-  void Apply(const MatchResult*, const CascadeInterpolations*, CascadeFilter);
+  // TODO(andruud): ElementRuleCollector could emit MatchedProperties
+  // directly to the cascade.
+  MatchResult& MutableMatchResult();
+
+  // Add ActiveInterpolationsMap to the cascade. The interpolations present
+  // in the map will be taken into account during the next call to Apply.
+  //
+  // Note that it's assumed that the incoming ActiveInterpolationsMap outlives
+  // the StyleCascade object.
+  void AddInterpolations(const ActiveInterpolationsMap*, CascadeOrigin);
+
+  // Applies the current CSS declarations and animations to the
+  // StyleResolverState.
+  //
+  // It is valid to call Apply multiple times (up to 15), and each call may
+  // provide a different filter.
+  void Apply(CascadeFilter = CascadeFilter());
 
   // Resets the cascade to its initial state. Note that this does not undo
   // any changes already applied to the StyleResolverState/ComputedStyle.
   void Reset();
 
-  // Resolver is an object passed on the stack during Apply. Its most important
-  // job is to detect cycles during Apply (in general, keep track of which
-  // properties we're currently applying).
-  class CORE_EXPORT Resolver {
-    STACK_ALLOCATED();
-
-   public:
-    // TODO(crbug.com/985047): Probably use a HashMap for this.
-    using NameStack = Vector<CSSPropertyName, 8>;
-
-    // A 'locked' property is a property we are in the process of applying.
-    // In other words, once a property is locked, locking it again would form
-    // a cycle, and is therefore an error.
-    bool IsLocked(const CSSProperty&) const;
-    bool IsLocked(const CSSPropertyName&) const;
-
-    // We do not allow substitution of animation-tainted values into
-    // an animation-affecting property.
-    //
-    // https://drafts.csswg.org/css-variables/#animation-tainted
-    bool AllowSubstitution(CSSVariableData*) const;
-
-   private:
-    friend class AutoLock;
-    friend class StyleCascade;
-    friend class TestCascadeResolver;
-
-    Resolver(CascadeFilter filter,
-             const MatchResult* match_result,
-             const CascadeInterpolations* interpolations,
-             uint8_t generation)
-        : filter_(filter),
-          match_result_(match_result),
-          interpolations_(interpolations),
-          generation_(generation) {}
-
-    // If the given property is already being applied, returns true.
-    // The return value is the same value you would get from InCycle(), and
-    // is just returned for convenience.
-    //
-    // When a cycle has been detected, the Resolver will *persist the cycle
-    // state* (i.e. InCycle() will continue to return true) until we reach
-    // the start of the cycle.
-    //
-    // The cycle state is cleared by ~AutoLock, once we have moved far enough
-    // up the stack.
-    bool DetectCycle(const CSSProperty&);
-    // Returns true whenever the Resolver is in a cycle state.
-    // This DOES NOT detect cycles; the caller must call DetectCycle first.
-    bool InCycle() const;
-
-    NameStack stack_;
-    wtf_size_t cycle_depth_ = kNotFound;
-    CascadeFilter filter_;
-    const MatchResult* match_result_;
-    const CascadeInterpolations* interpolations_;
-    const uint8_t generation_ = 0;
-
-    // A very simple cache for CSSPendingSubstitutionValues. We cache only the
-    // most recently parsed CSSPendingSubstitutionValue, such that consecutive
-    // calls to ResolvePendingSubstitution with the same value don't need to
-    // do the same parsing job all over again.
-    struct {
-      STACK_ALLOCATED();
-
-     public:
-      const CSSPendingSubstitutionValue* value = nullptr;
-      HeapVector<CSSPropertyValue, 256> parsed_properties;
-    } shorthand_cache_;
-  };
-
-  // Automatically locks and unlocks the given property. (See
-  // Resolver::IsLocked).
-  class CORE_EXPORT AutoLock {
-    STACK_ALLOCATED();
-
-   public:
-    AutoLock(const CSSProperty&, Resolver&);
-    AutoLock(const CSSPropertyName&, Resolver&);
-    ~AutoLock();
-
-   private:
-    Resolver& resolver_;
-  };
-
   // Applying interpolations may involve resolving values, since we may be
   // applying a keyframe from e.g. "color: var(--x)" to "color: var(--y)".
   // Hence that code needs an entry point to the resolving process.
@@ -196,7 +100,9 @@
   // StyleCascade, however).
   //
   // See documentation the other Resolve* functions for what resolve means.
-  const CSSValue* Resolve(const CSSPropertyName&, const CSSValue&, Resolver&);
+  const CSSValue* Resolve(const CSSPropertyName&,
+                          const CSSValue&,
+                          CascadeResolver&);
 
  private:
   friend class TestCascade;
@@ -207,17 +113,28 @@
   // https://drafts.csswg.org/css-variables/#long-variables
   static const size_t kMaxSubstitutionTokens = 16384;
 
+  // Before we can Apply the cascade, the MatchResult and CascadeInterpolations
+  // must be Analyzed. This means going through all the declarations, and
+  // adding them to the CascadeMap, which gives us a complete picture of which
+  // declarations won the cascade.
+  //
+  // We analyze only if needed (i.e. if MatchResult or CascadeInterpolations)
+  // has been mutated since the last call to AnalyzeIfNeeded.
+  void AnalyzeIfNeeded();
+  void AnalyzeMatchResult();
+  void AnalyzeInterpolations();
+
   // Applies kHighPropertyPriority properties.
   //
   // In theory, it would be possible for each property/value that contains
   // em/ch/etc to dynamically apply font-size (and related properties), but
   // in practice, it is very inconvenient to detect these dependencies. Hence,
   // we apply font-affecting properties (among others) before all the others.
-  void ApplyHighPriority(Resolver&);
+  void ApplyHighPriority(CascadeResolver&);
 
   // Applies -webkit-appearance, and excludes -internal-ua-* properties if
   // we don't have an appearance.
-  void ApplyAppearance(Resolver&);
+  void ApplyAppearance(CascadeResolver&);
 
   // Applies -webkit-border-image (if present), and skips any border-image
   // longhands found with lower priority than -webkit-border-image.
@@ -225,30 +142,28 @@
   // The -webkit-border-image property is unique (in a bad way), since it's
   // a surrogate of a shorthand. Therefore it needs special treatment to
   // behave correctly.
-  void ApplyWebkitBorderImage(Resolver&);
+  void ApplyWebkitBorderImage(CascadeResolver&);
 
-  void ApplyMatchResult(const MatchResult&, Resolver&);
-  void ApplyInterpolations(const CascadeInterpolations&, Resolver&);
+  void ApplyMatchResult(CascadeResolver&);
+  void ApplyInterpolations(CascadeResolver&);
   void ApplyInterpolationMap(const ActiveInterpolationsMap&,
                              CascadeOrigin,
                              size_t index,
-                             Resolver&);
+                             CascadeResolver&);
   void ApplyInterpolation(const CSSProperty&,
                           CascadePriority,
                           const ActiveInterpolations&,
-                          Resolver&);
+                          CascadeResolver&);
 
   // Looks up a value with random access, and applies it.
-  void LookupAndApply(const CSSPropertyName&, Resolver&);
-  void LookupAndApply(const CSSProperty&, Resolver&);
+  void LookupAndApply(const CSSPropertyName&, CascadeResolver&);
+  void LookupAndApply(const CSSProperty&, CascadeResolver&);
   void LookupAndApplyDeclaration(const CSSProperty&,
                                  CascadePriority,
-                                 const MatchResult&,
-                                 Resolver&);
+                                 CascadeResolver&);
   void LookupAndApplyInterpolation(const CSSProperty&,
                                    CascadePriority,
-                                   const CascadeInterpolations&,
-                                   Resolver&);
+                                   CascadeResolver&);
 
   // Whether or not we are calculating the style for the root element.
   // We need to know this to detect cycles with 'rem' units.
@@ -336,11 +251,13 @@
   // Surrogate properties should be skipped (i.e. not applied after all) if
   // the corresponding original property has a higher priority.
   //
-  Surrogate ResolveSurrogate(const CSSProperty&, CascadePriority, Resolver&);
+  Surrogate ResolveSurrogate(const CSSProperty&,
+                             CascadePriority,
+                             CascadeResolver&);
 
   inline Surrogate ResolveIfSurrogate(const CSSProperty& property,
                                       CascadePriority priority,
-                                      Resolver& resolver) {
+                                      CascadeResolver& resolver) {
     if (!property.IsSurrogate())
       return Surrogate::kNoSurrogate;
     return ResolveSurrogate(property, priority, resolver);
@@ -363,19 +280,21 @@
   // other words, we must first Apply '--y'. Hence, resolving 'width' will
   // Apply '--y' as a side-effect. (This process would then continue to '--x').
 
-  const CSSValue* Resolve(const CSSProperty&, const CSSValue&, Resolver&);
+  const CSSValue* Resolve(const CSSProperty&,
+                          const CSSValue&,
+                          CascadeResolver&);
   const CSSValue* ResolveCustomProperty(const CSSProperty&,
                                         const CSSCustomPropertyDeclaration&,
-                                        Resolver&);
+                                        CascadeResolver&);
   const CSSValue* ResolveVariableReference(const CSSProperty&,
                                            const CSSVariableReferenceValue&,
-                                           Resolver&);
+                                           CascadeResolver&);
   const CSSValue* ResolvePendingSubstitution(const CSSProperty&,
                                              const CSSPendingSubstitutionValue&,
-                                             Resolver&);
+                                             CascadeResolver&);
 
   scoped_refptr<CSSVariableData> ResolveVariableData(CSSVariableData*,
-                                                     Resolver&);
+                                                     CascadeResolver&);
 
   // The Resolve*Into functions either resolve dependencies, append to the
   // TokenSequence accordingly, and return true; or it returns false when
@@ -385,9 +304,9 @@
   //
   // [1] https://drafts.csswg.org/css-variables/#invalid-at-computed-value-time
 
-  bool ResolveTokensInto(CSSParserTokenRange, Resolver&, TokenSequence&);
-  bool ResolveVarInto(CSSParserTokenRange, Resolver&, TokenSequence&);
-  bool ResolveEnvInto(CSSParserTokenRange, Resolver&, TokenSequence&);
+  bool ResolveTokensInto(CSSParserTokenRange, CascadeResolver&, TokenSequence&);
+  bool ResolveVarInto(CSSParserTokenRange, CascadeResolver&, TokenSequence&);
+  bool ResolveEnvInto(CSSParserTokenRange, CascadeResolver&, TokenSequence&);
 
   CSSVariableData* GetVariableData(const CustomProperty&) const;
   CSSVariableData* GetEnvironmentVariable(const AtomicString&) const;
@@ -417,6 +336,8 @@
   bool HasAuthorBackground() const;
 
   StyleResolverState& state_;
+  MatchResult match_result_;
+  CascadeInterpolations interpolations_;
   CascadeMap map_;
   // Generational Apply
   //
@@ -457,6 +378,9 @@
   //       enough for our needs.
   uint8_t generation_ = 0;
 
+  bool needs_match_result_analyze_ = false;
+  bool needs_interpolations_analyze_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(StyleCascade);
 };
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
index 37eb866e..e9384e5 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade_test.cc
@@ -30,6 +30,7 @@
 #include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_map.h"
 #include "third_party/blink/renderer/core/css/resolver/cascade_priority.h"
+#include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h"
 #include "third_party/blink/renderer/core/css/resolver/scoped_style_resolver.h"
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
@@ -67,39 +68,15 @@
     state_.StyleRef().InheritFrom(*parent);
   }
 
-  // TestCascade has two main APIs:
-
-  // 1. Direct Analyze & Apply. This is a simple wrapper for Analyze & Apply
-  //    on the inner cascade.
-
-  void Analyze(const MatchResult& result,
-               CascadeFilter filter = CascadeFilter()) {
-    cascade_.Analyze(result, filter);
-  }
-
-  void Apply(const MatchResult& result,
-             CascadeFilter filter = CascadeFilter()) {
-    cascade_.Apply(result, filter);
-  }
-
-  // 2. "Add" API. This allows the caller to build a MatchResult from strings.
-  //
-  //   TestCascade cascade(GetDocument());
-  //   cascade.Add("color:green;top:1px", CascadeOrigin::kUserAgent);
-  //   cascade.Add("color:red");
-  //   cascade.Apply();
-  //
-  //  The Add() functions will parse the declaration blocks and add the result
-  //  to an internal MatchResult object. The param-less Apply() function will
-  //  then Analyze that MatchResult and immediately Apply it.
-  //
   //  Note that because of how MatchResult works, declarations must be added
   //  in "origin order", i.e. UserAgent first, then User, then Author.
 
-  void Add(String block, CascadeOrigin origin = CascadeOrigin::kAuthor) {
+  void Add(String block,
+           CascadeOrigin origin = CascadeOrigin::kAuthor,
+           unsigned link_match_type = CSSSelector::kMatchAll) {
     CSSParserMode mode =
         origin == CascadeOrigin::kUserAgent ? kUASheetMode : kHTMLStandardMode;
-    Add(ParseDeclarationBlock(block, mode), origin, CSSSelector::kMatchAll);
+    Add(ParseDeclarationBlock(block, mode), origin, link_match_type);
   }
 
   void Add(String name, String value, CascadeOrigin origin = Origin::kAuthor) {
@@ -112,16 +89,12 @@
     DCHECK_LE(origin, CascadeOrigin::kAuthor) << "Animations not supported";
     DCHECK_LE(current_origin_, origin) << "Please add declarations in order";
     EnsureAtLeast(origin);
-    match_result_.AddMatchedProperties(set, link_match_type);
+    cascade_.MutableMatchResult().AddMatchedProperties(set, link_match_type);
   }
 
   void Apply(CascadeFilter filter = CascadeFilter()) {
     EnsureAtLeast(CascadeOrigin::kAuthor);
-    cascade_.Analyze(match_result_, filter);
-    auto interpolations = GetInterpolations();
-    cascade_.Apply(&match_result_,
-                   interpolations.IsEmpty() ? nullptr : &interpolations,
-                   filter);
+    cascade_.Apply(filter);
   }
 
   String ComputedValue(String name) const {
@@ -153,6 +126,7 @@
     CSSAnimations::CalculateTransitionUpdate(
         state_.AnimationUpdate(), CSSAnimations::PropertyPass::kStandard,
         &state_.GetElement(), *state_.Style());
+    AddTransitions();
   }
 
   void CalculateAnimationUpdate() {
@@ -160,15 +134,7 @@
         state_.AnimationUpdate(), &state_.GetElement(), state_.GetElement(),
         *state_.Style(), state_.ParentStyle(),
         &GetDocument().EnsureStyleResolver());
-  }
-
-  void AnalyzeAnimations(CascadeFilter filter = CascadeFilter()) {
-    CalculateAnimationUpdate();
-    cascade_.Analyze(GetInterpolations(), filter);
-  }
-  void AnalyzeTransitions(CascadeFilter filter = CascadeFilter()) {
-    CalculateTransitionUpdate();
-    cascade_.Analyze(GetInterpolations(), filter);
+    AddAnimations();
   }
 
   void Reset() { cascade_.Reset(); }
@@ -190,11 +156,11 @@
   void FinishOrigin() {
     switch (current_origin_) {
       case CascadeOrigin::kUserAgent:
-        match_result_.FinishAddingUARules();
+        cascade_.MutableMatchResult().FinishAddingUARules();
         current_origin_ = CascadeOrigin::kUser;
         break;
       case CascadeOrigin::kUser:
-        match_result_.FinishAddingUserRules();
+        cascade_.MutableMatchResult().FinishAddingUserRules();
         current_origin_ = CascadeOrigin::kAuthor;
         break;
       case CascadeOrigin::kAuthor:
@@ -209,25 +175,31 @@
       FinishOrigin();
   }
 
-  CascadeInterpolations GetInterpolations() {
+  void AddAnimations() {
     const auto& update = state_.AnimationUpdate();
     if (update.IsEmpty())
-      return CascadeInterpolations();
-    using Entry = CascadeInterpolations::Entry;
-    return CascadeInterpolations(Vector<Entry, 4>({
-        Entry{&update.ActiveInterpolationsForCustomAnimations(),
-              CascadeOrigin::kAnimation},
-        Entry{&update.ActiveInterpolationsForStandardAnimations(),
-              CascadeOrigin::kAnimation},
-        Entry{&update.ActiveInterpolationsForCustomTransitions(),
-              CascadeOrigin::kTransition},
-        Entry{&update.ActiveInterpolationsForStandardTransitions(),
-              CascadeOrigin::kTransition},
-    }));
+      return;
+    cascade_.AddInterpolations(
+        &update.ActiveInterpolationsForCustomAnimations(),
+        CascadeOrigin::kAnimation);
+    cascade_.AddInterpolations(
+        &update.ActiveInterpolationsForStandardAnimations(),
+        CascadeOrigin::kAnimation);
+  }
+
+  void AddTransitions() {
+    const auto& update = state_.AnimationUpdate();
+    if (update.IsEmpty())
+      return;
+    cascade_.AddInterpolations(
+        &update.ActiveInterpolationsForCustomTransitions(),
+        CascadeOrigin::kTransition);
+    cascade_.AddInterpolations(
+        &update.ActiveInterpolationsForStandardTransitions(),
+        CascadeOrigin::kTransition);
   }
 
   CascadeOrigin current_origin_ = CascadeOrigin::kUserAgent;
-  MatchResult match_result_;
   StyleResolverState state_;
   StyleCascade cascade_;
 };
@@ -237,7 +209,7 @@
 
  public:
   explicit TestCascadeResolver(Document& document)
-      : document_(document), resolver_(CascadeFilter(), nullptr, nullptr, 0) {}
+      : document_(document), resolver_(CascadeFilter(), 0) {}
   bool InCycle() const { return resolver_.InCycle(); }
   bool DetectCycle(String name) {
     CSSPropertyRef ref(name, document_);
@@ -251,7 +223,7 @@
   friend class TestCascadeAutoLock;
 
   Document& document_;
-  StyleCascade::Resolver resolver_;
+  CascadeResolver resolver_;
 };
 
 class TestCascadeAutoLock {
@@ -263,7 +235,7 @@
       : lock_(name, resolver.resolver_) {}
 
  private:
-  StyleCascade::AutoLock lock_;
+  CascadeResolver::AutoLock lock_;
 };
 
 class StyleCascadeTest : public PageTestBase, private ScopedCSSCascadeForTest {
@@ -1421,7 +1393,7 @@
   EXPECT_EQ("rgb(255, 0, 0)", cascade.ComputedValue("background-color"));
 }
 
-TEST_F(StyleCascadeTest, AnimationAnalyzeFilter) {
+TEST_F(StyleCascadeTest, AnimationApplyFilter) {
   AppendSheet(R"HTML(
      @keyframes test {
         from { color: white; background-color: white; }
@@ -1431,20 +1403,22 @@
 
   TestCascade cascade(GetDocument());
 
-  cascade.Add("animation: test 1s");
+  cascade.Add("animation: test linear 10s -5s");
+  cascade.Add("color:green");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations(CascadeFilter(CSSProperty::kInherited, true));
+  cascade.CalculateAnimationUpdate();
+  cascade.Apply(CascadeFilter(CSSProperty::kInherited, true));
 
-  EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("background-color"));
-  EXPECT_EQ(CascadeOrigin::kNone, cascade.GetOrigin("color"));
+  EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
+  EXPECT_EQ("rgb(192, 192, 192)", cascade.ComputedValue("background-color"));
 }
 
-TEST_F(StyleCascadeTest, TransitionAnalyzeFilter) {
+TEST_F(StyleCascadeTest, TransitionApplyFilter) {
   TestCascade cascade1(GetDocument());
   cascade1.Add("background-color: white");
   cascade1.Add("color: white");
-  cascade1.Add("transition: all 1s");
+  cascade1.Add("transition: all steps(2, start) 100s");
   cascade1.Apply();
 
   // Set the old style on the element, so that the transition
@@ -1455,13 +1429,14 @@
   TestCascade cascade2(GetDocument());
   cascade2.Add("background-color: gray");
   cascade2.Add("color: gray");
-  cascade2.Add("transition: all 1s");
+  cascade2.Add("transition: all steps(2, start) 100s");
   cascade2.Apply();
 
-  cascade2.AnalyzeTransitions(CascadeFilter(CSSProperty::kInherited, true));
+  cascade2.CalculateTransitionUpdate();
+  cascade2.Apply(CascadeFilter(CSSProperty::kInherited, true));
 
-  EXPECT_EQ(CascadeOrigin::kTransition, cascade2.GetOrigin("background-color"));
-  EXPECT_EQ(CascadeOrigin::kAuthor, cascade2.GetOrigin("color"));
+  EXPECT_EQ("rgb(128, 128, 128)", cascade2.ComputedValue("color"));
+  EXPECT_EQ("rgb(192, 192, 192)", cascade2.ComputedValue("background-color"));
 }
 
 TEST_F(StyleCascadeTest, PendingKeyframeAnimation) {
@@ -1480,7 +1455,8 @@
   cascade.Add("animation-duration", "1s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
+  cascade.Apply();
 
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetPriority("--x").GetOrigin());
 }
@@ -1503,10 +1479,10 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
+  cascade.Apply();
 
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetPriority("--x").GetOrigin());
-  cascade.Apply();
   EXPECT_EQ("15px", cascade.ComputedValue("--x"));
 }
 
@@ -1529,7 +1505,8 @@
   cascade2.Add("transition", "--x 1s");
   cascade2.Apply();
 
-  cascade2.AnalyzeTransitions();
+  cascade2.CalculateTransitionUpdate();
+  cascade2.Apply();
 
   EXPECT_EQ(CascadeOrigin::kTransition,
             cascade2.GetPriority("--x").GetOrigin());
@@ -1556,7 +1533,8 @@
   cascade2.Add("transition", "--x 1s, width 1s");
   cascade2.Apply();
 
-  cascade2.AnalyzeTransitions();
+  cascade2.CalculateTransitionUpdate();
+  cascade2.Apply();
 
   EXPECT_EQ(CascadeOrigin::kTransition, cascade2.GetOrigin("--x"));
   EXPECT_EQ(CascadeOrigin::kTransition, cascade2.GetOrigin("width"));
@@ -1581,7 +1559,7 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Add("--from", "10px");
   cascade.Add("--to", "20px");
   cascade.Add("--y", "var(--x)");
@@ -1607,10 +1585,10 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
-  EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("width"));
-
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
+
+  EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("width"));
   EXPECT_EQ("15px", cascade.ComputedValue("width"));
 }
 
@@ -1632,11 +1610,12 @@
   cascade.Add("height:40px !important");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
+  cascade.Apply();
+
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("width"));
   EXPECT_EQ(CascadeOrigin::kAuthor, cascade.GetOrigin("height"));
 
-  cascade.Apply();
   EXPECT_EQ("15px", cascade.ComputedValue("width"));
   EXPECT_EQ("40px", cascade.ComputedValue("height"));
 }
@@ -1660,7 +1639,7 @@
   cascade2.Add("transition:all 1s");
   cascade2.Apply();
 
-  cascade2.AnalyzeTransitions();
+  cascade2.CalculateTransitionUpdate();
   cascade2.Apply();
 
   EXPECT_EQ(CascadeOrigin::kTransition,
@@ -1687,7 +1666,7 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Add("--x", "2em");
   cascade.Add("width", "10em");
 
@@ -1714,7 +1693,7 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Add("--from", "10px");
   cascade.Add("--to", "20px");
 
@@ -1740,13 +1719,14 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
+  cascade.Apply();
+
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-top"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-right"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-bottom"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left"));
 
-  cascade.Apply();
   EXPECT_EQ("15px", cascade.ComputedValue("margin-top"));
   EXPECT_EQ("15px", cascade.ComputedValue("margin-right"));
   EXPECT_EQ("15px", cascade.ComputedValue("margin-bottom"));
@@ -1772,7 +1752,7 @@
   cascade.Add("animation-delay:-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_EQ("rgb(150, 150, 150)", cascade.ComputedValue("background-color"));
 
@@ -1800,7 +1780,7 @@
   cascade.Add("animation:test 10s -5s linear");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_EQ("rgb(150, 150, 150)", cascade.ComputedValue("color"));
 
@@ -1829,7 +1809,7 @@
   cascade.Apply();
   EXPECT_FALSE(cascade.State().HasImportantOverrides());
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_TRUE(cascade.State().HasImportantOverrides());
 }
@@ -1848,7 +1828,7 @@
   cascade.Apply();
   EXPECT_FALSE(cascade.State().HasImportantOverrides());
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_FALSE(cascade.State().HasImportantOverrides());
 }
@@ -1868,7 +1848,7 @@
   cascade.Apply();
   EXPECT_FALSE(cascade.State().HasImportantOverrides());
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_TRUE(cascade.State().HasImportantOverrides());
 }
@@ -1890,7 +1870,7 @@
   cascade.Apply();
   EXPECT_FALSE(cascade.State().HasImportantOverrides());
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Apply();
   EXPECT_TRUE(cascade.State().HasImportantOverrides());
 }
@@ -1913,16 +1893,16 @@
   cascade.Add("animation-delay", "-5s");
   cascade.Apply();
 
-  cascade.AnalyzeAnimations();
+  cascade.CalculateAnimationUpdate();
   cascade.Add("--from", "10px");
   cascade.Add("--to", "20px");
+  cascade.Apply();
 
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-top"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-right"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-bottom"));
   EXPECT_EQ(CascadeOrigin::kAnimation, cascade.GetOrigin("margin-left"));
 
-  cascade.Apply();
   EXPECT_EQ("15px", cascade.ComputedValue("margin-top"));
   EXPECT_EQ("15px", cascade.ComputedValue("margin-right"));
   EXPECT_EQ("15px", cascade.ComputedValue("margin-bottom"));
@@ -2153,18 +2133,12 @@
 }
 
 TEST_F(StyleCascadeTest, InternalVisitedColorLonghand) {
-  MatchResult result;
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("color:green"));
-  result.AddMatchedProperties(ParseDeclarationBlock("color:red"),
-                              CSSSelector::kMatchVisited);
-  result.FinishAddingAuthorRulesForTreeScope();
-
   TestCascade cascade(GetDocument());
+  cascade.Add("color:green", CascadeOrigin::kAuthor);
+  cascade.Add("color:red", CascadeOrigin::kAuthor, CSSSelector::kMatchVisited);
+
   cascade.State().Style()->SetInsideLink(EInsideLink::kInsideVisitedLink);
-  cascade.Analyze(result);
-  cascade.Apply(result);
+  cascade.Apply();
 
   EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
 
@@ -2174,21 +2148,15 @@
 }
 
 TEST_F(StyleCascadeTest, VarInInternalVisitedColorShorthand) {
-  MatchResult result;
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("--x:red"));
-  result.AddMatchedProperties(
-      ParseDeclarationBlock("outline:medium solid var(--x)"),
-      CSSSelector::kMatchVisited);
-  result.AddMatchedProperties(ParseDeclarationBlock("outline-color:green"),
-                              CSSSelector::kMatchLink);
-  result.FinishAddingAuthorRulesForTreeScope();
-
   TestCascade cascade(GetDocument());
+  cascade.Add("--x:red", CascadeOrigin::kAuthor);
+  cascade.Add("outline:medium solid var(--x)", CascadeOrigin::kAuthor,
+              CSSSelector::kMatchVisited);
+  cascade.Add("outline-color:green", CascadeOrigin::kAuthor,
+              CSSSelector::kMatchLink);
+
   cascade.State().Style()->SetInsideLink(EInsideLink::kInsideVisitedLink);
-  cascade.Analyze(result);
-  cascade.Apply(result);
+  cascade.Apply();
 
   EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("outline-color"));
 
@@ -2213,10 +2181,10 @@
 }
 
 TEST_F(StyleCascadeTest, HasAuthorBackground) {
-  Vector<String> properties = {"background-attachment", "background-blend-mode",
+  Vector<String> properties = {"background-attachment"/*, "background-blend-mode",
                                "background-clip",       "background-image",
                                "background-origin",     "background-position-x",
-                               "background-position-y", "background-size"};
+                               "background-position-y", "background-size"*/};
 
   for (String property : properties) {
     TestCascade cascade(GetDocument());
@@ -2264,23 +2232,16 @@
 }
 
 TEST_F(StyleCascadeTest, AnalyzeMatchResult) {
-  TestCascade cascade(GetDocument());
-
-  MatchResult result;
-  result.AddMatchedProperties(ParseDeclarationBlock("display:none;left:5px"));
-  result.AddMatchedProperties(
-      ParseDeclarationBlock("font-size:1px !important"));
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("display:block;color:red"));
-  result.AddMatchedProperties(ParseDeclarationBlock("font-size:3px"));
-  result.FinishAddingAuthorRulesForTreeScope();
-
-  cascade.Analyze(result);
-
   auto ua = CascadeOrigin::kUserAgent;
   auto author = CascadeOrigin::kAuthor;
 
+  TestCascade cascade(GetDocument());
+  cascade.Add("display:none;left:5px", ua);
+  cascade.Add("font-size:1px !important", ua);
+  cascade.Add("display:block;color:red", author);
+  cascade.Add("font-size:3px", author);
+  cascade.Apply();
+
   EXPECT_EQ(cascade.GetPriority("display").GetOrigin(), author);
   EXPECT_EQ(cascade.GetPriority("left").GetOrigin(), ua);
   EXPECT_EQ(cascade.GetPriority("color").GetOrigin(), author);
@@ -2288,22 +2249,15 @@
 }
 
 TEST_F(StyleCascadeTest, AnalyzeMatchResultAll) {
-  TestCascade cascade(GetDocument());
-
-  MatchResult result;
-  result.AddMatchedProperties(ParseDeclarationBlock("display:block"));
-  result.AddMatchedProperties(
-      ParseDeclarationBlock("font-size:1px !important"));
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("all:unset"));
-  result.FinishAddingAuthorRulesForTreeScope();
-
-  cascade.Analyze(result);
-
   auto ua = CascadeOrigin::kUserAgent;
   auto author = CascadeOrigin::kAuthor;
 
+  TestCascade cascade(GetDocument());
+  cascade.Add("display:block", ua);
+  cascade.Add("font-size:1px !important", ua);
+  cascade.Add("all:unset", author);
+  cascade.Apply();
+
   EXPECT_EQ(cascade.GetPriority("display").GetOrigin(), author);
   EXPECT_EQ(cascade.GetPriority("font-size").GetOrigin(), ua);
 
@@ -2312,44 +2266,33 @@
   EXPECT_EQ(cascade.GetPriority("color"), cascade.GetPriority("display"));
 }
 
-TEST_F(StyleCascadeTest, AnalyzeMatchResultFilter) {
+TEST_F(StyleCascadeTest, ApplyMatchResultFilter) {
   TestCascade cascade(GetDocument());
+  cascade.Add("display:block");
+  cascade.Add("color:green");
+  cascade.Add("font-size:3px");
+  cascade.Apply();
 
-  MatchResult result;
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("display:block"));
-  result.AddMatchedProperties(ParseDeclarationBlock("color:red"));
-  result.AddMatchedProperties(ParseDeclarationBlock("font-size:3px"));
-  result.FinishAddingAuthorRulesForTreeScope();
+  cascade.Add("display:inline");
+  cascade.Add("color:red");
+  cascade.Apply(CascadeFilter(CSSProperty::kInherited, true));
 
-  cascade.Analyze(result, CascadeFilter(CSSProperty::kInherited, true));
-
-  auto none = CascadeOrigin::kNone;
-  auto author = CascadeOrigin::kAuthor;
-
-  EXPECT_EQ(cascade.GetPriority("display").GetOrigin(), author);
-  EXPECT_EQ(cascade.GetPriority("color").GetOrigin(), none);
-  EXPECT_EQ(cascade.GetPriority("font-size").GetOrigin(), none);
+  EXPECT_EQ("inline", cascade.ComputedValue("display"));
+  EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
+  EXPECT_EQ("3px", cascade.ComputedValue("font-size"));
 }
 
-TEST_F(StyleCascadeTest, AnalyzeMatchResultAllFilter) {
+TEST_F(StyleCascadeTest, ApplyMatchResultAllFilter) {
   TestCascade cascade(GetDocument());
+  cascade.Add("color:green");
+  cascade.Add("display:block");
+  cascade.Apply();
 
-  MatchResult result;
-  result.FinishAddingUARules();
-  result.FinishAddingUserRules();
-  result.AddMatchedProperties(ParseDeclarationBlock("all:unset"));
-  result.FinishAddingAuthorRulesForTreeScope();
+  cascade.Add("all:unset");
+  cascade.Apply(CascadeFilter(CSSProperty::kInherited, true));
 
-  cascade.Analyze(result, CascadeFilter(CSSProperty::kInherited, true));
-
-  auto none = CascadeOrigin::kNone;
-  auto author = CascadeOrigin::kAuthor;
-
-  EXPECT_EQ(cascade.GetPriority("display").GetOrigin(), author);
-  EXPECT_EQ(cascade.GetPriority("color").GetOrigin(), none);
-  EXPECT_EQ(cascade.GetPriority("font-size").GetOrigin(), none);
+  EXPECT_EQ("rgb(0, 128, 0)", cascade.ComputedValue("color"));
+  EXPECT_EQ("inline", cascade.ComputedValue("display"));
 }
 
 TEST_F(StyleCascadeTest, MarkHasReferenceLonghand) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index ac4742a..366a27a 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -61,7 +61,6 @@
 #include "third_party/blink/renderer/core/css/part_names.h"
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
-#include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h"
 #include "third_party/blink/renderer/core/css/resolver/css_variable_animator.h"
 #include "third_party/blink/renderer/core/css/resolver/css_variable_resolver.h"
 #include "third_party/blink/renderer/core/css/resolver/match_result.h"
@@ -173,16 +172,14 @@
 }
 
 // When force-computing the base computed style for validation purposes,
-// we need to reset the StyleCascade/MatchResult when the base computed style
-// optimization is used. This is because we don't want the computation of
-// the base to populate the cascade/MatchResult, as they are supposed to be
-// empty when the optimization is in use. This is to match the behavior of
-// non-DCHECK builds.
-void MaybeResetCascade(StyleCascade* cascade, MatchResult& match_result) {
+// we need to reset the StyleCascade when the base computed style optimization
+// is used. This is because we don't want the computation of the base to
+// populate the cascade, as they are supposed to be empty when the optimization
+// is in use. This is to match the behavior of non-DCHECK builds.
+void MaybeResetCascade(StyleCascade* cascade) {
 #if DCHECK_IS_ON()
   if (cascade)
     cascade->Reset();
-  match_result.Reset();
 #endif  // DCHECK_IS_ON()
 }
 
@@ -831,12 +828,11 @@
   StyleCascade* cascade_ptr =
       RuntimeEnabledFeatures::CSSCascadeEnabled() ? &cascade : nullptr;
 
-  MatchResult match_result;
-  ApplyBaseComputedStyle(element, state, cascade_ptr, match_result,
-                         matching_behavior,
+  ApplyBaseComputedStyle(element, state, cascade_ptr,
+                         cascade.MutableMatchResult(), matching_behavior,
                          can_cache_animation_base_computed_style);
 
-  if (ApplyAnimatedStandardProperties(state, match_result, cascade_ptr)) {
+  if (ApplyAnimatedStandardProperties(state, cascade_ptr)) {
     INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(),
                                   styles_animated, 1);
     StyleAdjuster::AdjustComputedStyle(state, element);
@@ -967,7 +963,7 @@
 
     if (RuntimeEnabledFeatures::CSSCascadeEnabled()) {
       DCHECK(cascade);
-      CascadeAndApplyMatchedProperties(state, *cascade, match_result);
+      CascadeAndApplyMatchedProperties(state, *cascade);
     } else {
       ApplyMatchedProperties(state, match_result);
     }
@@ -993,7 +989,7 @@
       state.SetParentStyle(InitialStyleForElement(GetDocument()));
       state.SetLayoutParentStyle(state.ParentStyle());
     }
-    MaybeResetCascade(cascade, match_result);
+    MaybeResetCascade(cascade);
     INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(),
                                   base_styles_used, 1);
   }
@@ -1012,15 +1008,12 @@
   state.SetStyle(ComputedStyle::Clone(base_style));
   if (value) {
     if (RuntimeEnabledFeatures::CSSCascadeEnabled()) {
-      MatchResult result;
+      STACK_UNINITIALIZED StyleCascade cascade(state);
       auto* set = MakeGarbageCollected<MutableCSSPropertyValueSet>(
           state.GetParserMode());
       set->SetProperty(property.GetCSSProperty().PropertyID(), *value);
-      result.AddMatchedProperties(set);
-
-      STACK_UNINITIALIZED StyleCascade cascade(state);
-      cascade.Analyze(result, CascadeFilter());
-      cascade.Apply(result, CascadeFilter());
+      cascade.MutableMatchResult().AddMatchedProperties(set);
+      cascade.Apply();
     } else {
       StyleBuilder::ApplyProperty(property.GetCSSPropertyName(), state, *value);
       state.GetFontBuilder().CreateFont(
@@ -1052,7 +1045,6 @@
   StyleCascade* cascade_ptr =
       RuntimeEnabledFeatures::CSSCascadeEnabled() ? &cascade : nullptr;
 
-  MatchResult match_result;
   if (ShouldComputeBaseComputedStyle(animation_base_computed_style)) {
     if (pseudo_style_request.AllowsInheritance(state.ParentStyle())) {
       scoped_refptr<ComputedStyle> style = ComputedStyle::Create();
@@ -1070,7 +1062,7 @@
 
     // Check UA, user and author rules.
     ElementRuleCollector collector(state.ElementContext(), selector_filter_,
-                                   match_result, state.Style(),
+                                   cascade.MutableMatchResult(), state.Style(),
                                    state.Style()->InsideLink());
     collector.SetPseudoElementStyleRequest(pseudo_style_request);
 
@@ -1108,9 +1100,9 @@
     }
 
     if (RuntimeEnabledFeatures::CSSCascadeEnabled())
-      CascadeAndApplyMatchedProperties(state, cascade, match_result);
+      CascadeAndApplyMatchedProperties(state, cascade);
     else
-      ApplyMatchedProperties(state, match_result);
+      ApplyMatchedProperties(state, cascade.GetMatchResult());
 
     ApplyCallbackSelectors(state);
 
@@ -1131,10 +1123,10 @@
   if (animation_base_computed_style) {
     state.SetStyle(ComputedStyle::Clone(*animation_base_computed_style));
     state.Style()->SetStyleType(pseudo_style_request.pseudo_id);
-    MaybeResetCascade(cascade_ptr, match_result);
+    MaybeResetCascade(cascade_ptr);
   }
 
-  if (ApplyAnimatedStandardProperties(state, match_result, cascade_ptr))
+  if (ApplyAnimatedStandardProperties(state, cascade_ptr))
     StyleAdjuster::AdjustComputedStyle(state, nullptr);
 
   GetDocument().GetStyleEngine().IncStyleForElementCount();
@@ -1193,7 +1185,10 @@
   style->InheritFrom(*root_element_style);
   state.SetStyle(std::move(style));
 
-  PageRuleCollector collector(root_element_style, page_index);
+  STACK_UNINITIALIZED StyleCascade cascade(state);
+
+  PageRuleCollector collector(root_element_style, page_index,
+                              cascade.MutableMatchResult());
 
   collector.MatchPageRules(
       CSSDefaultStyleSheets::Instance().DefaultPrintStyle());
@@ -1208,9 +1203,7 @@
   const MatchResult& result = collector.MatchedResult();
 
   if (RuntimeEnabledFeatures::CSSCascadeEnabled()) {
-    StyleCascade cascade(state);
-    cascade.Analyze(result, CascadeFilter());
-    cascade.Apply(result, CascadeFilter());
+    cascade.Apply();
   } else {
     ApplyMatchedProperties<kAnimationPropertyPriority, kUpdateNeedsApplyPass>(
         state, result.AllRules(), false, inherited_only, needs_apply_pass);
@@ -1347,7 +1340,6 @@
 
 bool StyleResolver::ApplyAnimatedStandardProperties(
     StyleResolverState& state,
-    const MatchResult& match_result,
     StyleCascade* cascade) {
   Element& element = state.GetElement();
 
@@ -1388,22 +1380,20 @@
 
   if (RuntimeEnabledFeatures::CSSCascadeEnabled()) {
     DCHECK(cascade);
-    CascadeFilter filter;
+    cascade->AddInterpolations(&standard_animations, CascadeOrigin::kAnimation);
+    cascade->AddInterpolations(&standard_transitions,
+                               CascadeOrigin::kTransition);
+    cascade->AddInterpolations(&custom_animations, CascadeOrigin::kAnimation);
+    cascade->AddInterpolations(&custom_transitions, CascadeOrigin::kTransition);
 
-    using Entry = CascadeInterpolations::Entry;
-    class CascadeInterpolations interpolations(Vector<Entry, 4>{
-        Entry{&standard_animations, CascadeOrigin::kAnimation},
-        Entry{&standard_transitions, CascadeOrigin::kTransition},
-        Entry{&custom_animations, CascadeOrigin::kAnimation},
-        Entry{&custom_transitions, CascadeOrigin::kTransition},
-    });
+    CascadeFilter filter;
     if (IsForcedColorsModeEnabled(state))
       filter = filter.Add(CSSProperty::kIsAffectedByForcedColors, true);
     if (state.Style()->StyleType() == kPseudoIdMarker)
       filter = filter.Add(CSSProperty::kValidForMarker, false);
     filter = filter.Add(CSSProperty::kAnimation, true);
-    cascade->Analyze(interpolations, filter);
-    cascade->Apply(&match_result, &interpolations, filter);
+
+    cascade->Apply(filter);
   } else {
     ApplyAnimatedStandardProperties<kHighPropertyPriority>(state,
                                                            standard_animations);
@@ -2105,22 +2095,16 @@
     Element& element,
     ActiveInterpolationsMap& interpolations) {
   StyleResolverState state(GetDocument(), element);
-
-  MatchResult match_result;
   STACK_UNINITIALIZED StyleCascade cascade(state);
 
   if (RuntimeEnabledFeatures::CSSCascadeEnabled()) {
-    ApplyBaseComputedStyle(&element, state, &cascade, match_result,
-                           kMatchAllRules, true);
-    using Entry = CascadeInterpolations::Entry;
-    class CascadeInterpolations cascade_interpolations(Vector<Entry, 4>({
-        Entry{&interpolations, CascadeOrigin::kAnimation},
-    }));
-    cascade.Analyze(cascade_interpolations, CascadeFilter());
-    cascade.Apply(&match_result, &cascade_interpolations, CascadeFilter());
+    ApplyBaseComputedStyle(&element, state, &cascade,
+                           cascade.MutableMatchResult(), kMatchAllRules, true);
+    cascade.AddInterpolations(&interpolations, CascadeOrigin::kAnimation);
+    cascade.Apply();
   } else {
-    ApplyBaseComputedStyle(&element, state, nullptr /* cascade */, match_result,
-                           kMatchAllRules, true);
+    ApplyBaseComputedStyle(&element, state, nullptr /* cascade */,
+                           cascade.MutableMatchResult(), kMatchAllRules, true);
     ApplyAnimatedStandardProperties<kHighPropertyPriority>(state,
                                                            interpolations);
     UpdateFont(state);
@@ -2131,31 +2115,22 @@
   return state.TakeStyle();
 }
 
-void StyleResolver::CascadeAndApplyMatchedProperties(
-    StyleResolverState& state,
-    StyleCascade& cascade,
-    const MatchResult& result) {
+void StyleResolver::CascadeAndApplyMatchedProperties(StyleResolverState& state,
+                                                     StyleCascade& cascade) {
   DCHECK(RuntimeEnabledFeatures::CSSCascadeEnabled());
+  const MatchResult& result = cascade.GetMatchResult();
 
   CacheSuccess cache_success = ApplyMatchedCache(state, result);
 
-  CascadeFilter filter;
-
   if (cache_success.IsFullCacheHit())
     return;
 
-  cascade.Analyze(result, filter);
-
   if (cache_success.ShouldApplyInheritedOnly()) {
-    auto reject_inherited = filter.Add(CSSProperty::kInherited, true);
-    auto reject_non_inherited = filter.Add(CSSProperty::kInherited, false);
-
-    cascade.Apply(result, reject_non_inherited);
-
+    cascade.Apply(CascadeFilter(CSSProperty::kInherited, false));
     if (cache_success.EffectiveZoomOrFontChanged(state.StyleRef()))
-      cascade.Apply(result, reject_inherited);
+      cascade.Apply(CascadeFilter(CSSProperty::kInherited, true));
   } else {
-    cascade.Apply(result, filter);
+    cascade.Apply();
   }
 
   CascadeAndApplyForcedColors(state, result);
@@ -2175,7 +2150,7 @@
 
   Color prev_bg_color = state.Style()->BackgroundColor().GetColor();
 
-  MatchResult amended_result;
+  STACK_UNINITIALIZED StyleCascade cascade(state);
 
   const CSSValue* unset = cssvalue::CSSUnsetValue::Create();
   const CSSValue* canvas = CSSIdentifierValue::Create(CSSValueID::kCanvas);
@@ -2197,20 +2172,18 @@
   set->SetProperty(CSSPropertyID::kWebkitTapHighlightColor, *unset);
   set->SetProperty(CSSPropertyID::kWebkitTextEmphasisColor, *unset);
 
-  amended_result.AddMatchedProperties(set);
+  cascade.MutableMatchResult().AddMatchedProperties(set);
 
   for (const auto& matched_properties : result.UaRules()) {
-    amended_result.AddMatchedProperties(
+    cascade.MutableMatchResult().AddMatchedProperties(
         matched_properties.properties,
         matched_properties.types_.link_match_type,
         static_cast<ValidPropertyFilter>(
             matched_properties.types_.valid_property_filter));
   }
 
-  STACK_UNINITIALIZED StyleCascade cascade(state);
   CascadeFilter filter(CSSProperty::kIsAffectedByForcedColors, false);
-  cascade.Analyze(amended_result, filter);
-  cascade.Apply(amended_result, filter);
+  cascade.Apply(filter);
 
   Color current_bg_color = state.Style()->BackgroundColor().GetColor();
   Color bg_color(current_bg_color.Red(), current_bg_color.Green(),
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h
index 169f4b8..7f64e22e 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.h
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -273,13 +273,11 @@
   void CascadeAndApplyForcedColors(StyleResolverState&, const MatchResult&);
 
   void CascadeAndApplyMatchedProperties(StyleResolverState&,
-                                        StyleCascade& cascade,
-                                        const MatchResult&);
+                                        StyleCascade& cascade);
 
   void CalculateAnimationUpdate(StyleResolverState&);
 
   bool ApplyAnimatedStandardProperties(StyleResolverState&,
-                                       const MatchResult&,
                                        StyleCascade* cascade = nullptr);
 
   void ApplyCallbackSelectors(StyleResolverState&);
diff --git a/third_party/blink/renderer/core/css/vision_deficiency.cc b/third_party/blink/renderer/core/css/vision_deficiency.cc
index 3e8e5250..6ab3903 100644
--- a/third_party/blink/renderer/core/css/vision_deficiency.cc
+++ b/third_party/blink/renderer/core/css/vision_deficiency.cc
@@ -23,40 +23,46 @@
 
 AtomicString CreateVisionDeficiencyFilterUrl(
     VisionDeficiency vision_deficiency) {
+  // The filter color matrices are based on the following research paper:
+  // Gustavo M. Machado, Manuel M. Oliveira, and Leandro A. F. Fernandes
+  // "A Physiologically-based Model for Simulation of Color Vision Deficiency".
+  // IEEE Transactions on Visualization and Computer Graphics. Volume 15 (2009),
+  // Number 6, November/December 2009. pp. 1291-1298.
+  // https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html
   switch (vision_deficiency) {
     case VisionDeficiency::kAchromatopsia:
       return CreateFilterDataUrl(
           "<feColorMatrix values=\""
-          "0.299 0.587 0.114 0.000 0.000 "
-          "0.299 0.587 0.114 0.000 0.000 "
-          "0.299 0.587 0.114 0.000 0.000 "
-          "0.000 0.000 0.000 1.000 0.000 "
+          "0.299  0.587  0.114  0.000  0.000 "
+          "0.299  0.587  0.114  0.000  0.000 "
+          "0.299  0.587  0.114  0.000  0.000 "
+          "0.000  0.000  0.000  1.000  0.000 "
           "\"/>");
     case VisionDeficiency::kBlurredVision:
       return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
     case VisionDeficiency::kDeuteranopia:
       return CreateFilterDataUrl(
           "<feColorMatrix values=\""
-          "0.625 0.375 0.000 0.000 0.000 "
-          "0.700 0.300 0.000 0.000 0.000 "
-          "0.000 0.300 0.700 0.000 0.000 "
-          "0.000 0.000 0.000 1.000 0.000 "
+          " 0.367  0.861 -0.228  0.000  0.000 "
+          " 0.280  0.673  0.047  0.000  0.000 "
+          "-0.012  0.043  0.969  0.000  0.000 "
+          " 0.000  0.000  0.000  1.000  0.000 "
           "\"/>");
     case VisionDeficiency::kProtanopia:
       return CreateFilterDataUrl(
           "<feColorMatrix values=\""
-          "0.567 0.433 0.000 0.000 0.000 "
-          "0.558 0.442 0.000 0.000 0.000 "
-          "0.000 0.242 0.758 0.000 0.000 "
-          "0.000 0.000 0.000 1.000 0.000 "
+          " 0.152  1.053 -0.205  0.000  0.000 "
+          " 0.115  0.786  0.099  0.000  0.000 "
+          "-0.004 -0.048  1.052  0.000  0.000 "
+          " 0.000  0.000  0.000  1.000  0.000 "
           "\"/>");
     case VisionDeficiency::kTritanopia:
       return CreateFilterDataUrl(
           "<feColorMatrix values=\""
-          "0.950 0.050 0.000 0.000 0.000 "
-          "0.000 0.433 0.567 0.000 0.000 "
-          "0.000 0.475 0.525 0.000 0.000 "
-          "0.000 0.000 0.000 1.000 0.000 "
+          " 1.256 -0.077 -0.179  0.000  0.000 "
+          "-0.078  0.931  0.148  0.000  0.000 "
+          " 0.005  0.691  0.304  0.000  0.000 "
+          " 0.000  0.000  0.000  1.000  0.000 "
           "\"/>");
     case VisionDeficiency::kNoVisionDeficiency:
       NOTREACHED();
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.dict b/third_party/blink/renderer/core/feature_policy/feature_policy.dict
index d4a0523d..9c3841f 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.dict
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.dict
@@ -27,6 +27,7 @@
 "pointer-lock"
 "popups"
 "presentation"
+"screen-wake-lock"
 "scripts"
 "serial"
 "speaker"
@@ -36,7 +37,6 @@
 "unsized-media"
 "usb"
 "vertical-scroll"
-"wake-lock"
 "vr"
 "\"https://example.com/\""
 "*"
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5 b/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
index 6dfc2f3c..7e0db36 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy_features.json5
@@ -246,6 +246,11 @@
       depends_on: ["WebAuthenticationFeaturePolicy"],
     },
     {
+      name: "ScreenWakeLock",
+      feature_policy_name: "screen-wake-lock",
+      depends_on: ["WakeLock"],
+    },
+    {
       name: "Script",
       feature_policy_name: "scripts",
       depends_on: ["FeaturePolicyForSandbox"],
@@ -285,11 +290,6 @@
       depends_on: ["ExperimentalProductivityFeatures"],
     },
     {
-      name: "WakeLock",
-      feature_policy_name: "wake-lock",
-      depends_on: ["WakeLock"],
-    },
-    {
       name: "WebXr",
       feature_policy_name: "xr-spatial-tracking",
       depends_on: ["WebXR"],
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index f5bd38f8..555520d 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -781,8 +781,12 @@
   } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) {
     DCHECK(change.removed_nodes);
     for (Node* node : *change.removed_nodes) {
-      if (auto* option = DynamicTo<HTMLOptionElement>(node))
+      if (auto* option = DynamicTo<HTMLOptionElement>(node)) {
         OptionRemoved(*option);
+      } else if (auto* optgroup = DynamicTo<HTMLOptGroupElement>(node)) {
+        for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+          OptionRemoved(option);
+      }
     }
   }
 }
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element_test.cc b/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
index e947b14..f92a9abb 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
@@ -481,6 +481,23 @@
   ASSERT_TRUE(select->GetLayoutObject());
 }
 
+TEST_F(HTMLSelectElementTest, CrashOnAttachingMenuList2) {
+  // crbug.com/1065125
+  // This test passes if no crash.
+  SetHtmlInnerHTML("<select><optgroup><option>o1</select>");
+  auto* select = To<HTMLSelectElement>(GetDocument().body()->firstChild());
+  select->setTextContent("foo");
+
+  // Detach LayoutObject.
+  select->setAttribute("style", "display:none;");
+  GetDocument().UpdateStyleAndLayoutTree();
+
+  // Attach LayoutObject.  It triggered a DCHECK failure in
+  // MenuListSelectType::OptionToBeShown()
+  select->removeAttribute("style");
+  GetDocument().UpdateStyleAndLayoutTree();
+}
+
 TEST_F(HTMLSelectElementTest, SlotAssignmentRecalcDuringOptionRemoval) {
   // crbug.com/1056094
   // This test passes if no CHECK failure about IsSlotAssignmentRecalcForbidden.
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index 1d0d5e5..f200764 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -612,25 +612,18 @@
         ParseDeclarationBlock("color:-internal-light-dark-color(black, white)");
     auto* dark_declaration = ParseDeclarationBlock("color-scheme:dark");
     auto* light_declaration = ParseDeclarationBlock("color-scheme:light");
-    CascadeFilter filter;
-
-    MatchResult result1;
-    result1.AddMatchedProperties(color_declaration);
-    result1.AddMatchedProperties(dark_declaration);
 
     StyleCascade cascade1(state);
-    cascade1.Analyze(result1, filter);
-    cascade1.Apply(result1, filter);
+    cascade1.MutableMatchResult().AddMatchedProperties(color_declaration);
+    cascade1.MutableMatchResult().AddMatchedProperties(dark_declaration);
+    cascade1.Apply();
     EXPECT_EQ(Color::kWhite,
               style->VisitedDependentColor(GetCSSPropertyColor()));
 
-    MatchResult result2;
-    result2.AddMatchedProperties(color_declaration);
-    result2.AddMatchedProperties(light_declaration);
-
     StyleCascade cascade2(state);
-    cascade2.Analyze(result2, filter);
-    cascade2.Apply(result2, filter);
+    cascade2.MutableMatchResult().AddMatchedProperties(color_declaration);
+    cascade2.MutableMatchResult().AddMatchedProperties(light_declaration);
+    cascade2.Apply();
     EXPECT_EQ(Color::kBlack,
               style->VisitedDependentColor(GetCSSPropertyColor()));
   }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index b6fdfb64..1806def4 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -214,7 +214,7 @@
     return kIgnoreObject;
   }
 
-  if (CanSetFocusAttribute() && GetNode() && !IsA<HTMLBodyElement>(GetNode()))
+  if (GetNode() && !IsA<HTMLBodyElement>(GetNode()) && CanSetFocusAttribute())
     return kIncludeObject;
 
   if (IsLink() || IsInPageLinkTarget())
@@ -3180,8 +3180,7 @@
   // using AOM. To be extra safe, exclude objects that are clickable themselves.
   // This won't prevent anyone from having a click handler on the object's
   // container.
-  if (!IsClickable() && element->FastHasAttribute(html_names::kIdAttr) &&
-      CanBeActiveDescendant()) {
+  if (!IsClickable() && CanBeActiveDescendant()) {
     return OnNativeClickAction();
   }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 65659087..c10fdee 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -39,6 +39,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
 #include "third_party/blink/renderer/core/html/custom/element_internals.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
@@ -1320,20 +1321,6 @@
   return cached_has_inherited_presentational_role_;
 }
 
-bool AXObject::CanReceiveAccessibilityFocus() const {
-  const Element* elem = GetElement();
-  if (!elem)
-    return false;
-
-  // Focusable, and not forwarding the focus somewhere else
-  if (elem->IsFocusable() &&
-      !GetAOMPropertyOrARIAAttribute(AOMRelationProperty::kActiveDescendant))
-    return true;
-
-  // aria-activedescendant focus
-  return elem->FastHasAttribute(html_names::kIdAttr) && CanBeActiveDescendant();
-}
-
 bool AXObject::CanSetValueAttribute() const {
   switch (RoleValue()) {
     case ax::mojom::Role::kColorWell:
@@ -1353,42 +1340,95 @@
   return false;
 }
 
+// This does not use Element::IsFocusable(), as that can sometimes recalculate
+// styles because of IsFocusableStyle() check, resetting the document lifecycle.
 bool AXObject::CanSetFocusAttribute() const {
-  Node* node = GetNode();
-  if (!node)
+  if (IsDetached())
     return false;
 
-  // Elements inside a portal should not be focusable.
+  // NOT focusable: anything inside a <portal> (the portal element itself is).
   if (GetDocument() && GetDocument()->GetPage() &&
-      GetDocument()->GetPage()->InsidePortal())
+      GetDocument()->GetPage()->InsidePortal()) {
     return false;
+  }
 
+  // Focusable: web area -- this is the only focusable non-element.
   if (IsWebArea())
     return true;
 
-  // Children of elements with an aria-activedescendant attribute should be
-  // focusable if they have a (non-presentational) ARIA role.
-  if (!IsPresentational() && AriaRoleAttribute() != ax::mojom::Role::kUnknown &&
-      CanBeActiveDescendant()) {
-    return true;
-  }
-
-  // NOTE: It would be more accurate to ask the document whether
-  // setFocusedNode() would do anything. For example, setFocusedNode() will do
-  // nothing if the current focused node will not relinquish the focus.
-  if (IsDisabledFormControl(node))
+  // NOT focusable: objects with no DOM node, e.g. extra layout blocks inserted
+  // as filler, or objects where the node is not an element, such as a text
+  // node or an HTML comment.
+  Element* elem = GetElement();
+  if (!elem)
     return false;
 
-  // Check for options here because AXListBoxOption and AXMenuListOption
-  // don't help when the <option> is canvas fallback, and because
-  // a common case for aria-owns from a textbox that points to a list
-  // does not change the hierarchy (textboxes don't support children)
-  if (RoleValue() == ax::mojom::Role::kListBoxOption ||
-      RoleValue() == ax::mojom::Role::kMenuListOption)
+  // NOT focusable: inert elements.
+  if (elem->IsInert())
+    return false;
+
+  // NOT focusable: disabled form controls.
+  if (IsDisabledFormControl(elem))
+    return false;
+
+  // Focusable: options in a combobox or listbox.
+  // Even though they are not treated as supporting focus by Blink (the parent
+  // widget is), they are considered focusable in the accessibility sense,
+  // behaving like potential active descendants, and handling focus actions.
+  // Menu list options are handled before visibility check, because they
+  // are considered focusable even when part of collapsed drop down.
+  if (RoleValue() == ax::mojom::Role::kMenuListOption)
     return true;
 
-  auto* element = DynamicTo<Element>(node);
-  return element && element->SupportsFocus();
+  // NOT focusable: hidden elements.
+  // This is imperfect, because the only way to really know whether something
+  // is hidden via style is to EnsureComputedStyle(). The method
+  // AXObject::IsHiddenViaStyle() does this, but it relies on
+  // EnsureComputedStyle() which could cause instability in callers.
+  // This code assumes that a canvas descendant has the same visibility as
+  // the canvas itself.
+  // TODO(aleventhal) Consider caching visibility when it's safe to compute.
+  if (!IsA<HTMLAreaElement>(elem)) {
+    if (!GetLayoutObject()) {
+      if (!elem->IsInCanvasSubtree())
+        return false;
+      const HTMLCanvasElement* canvas =
+          Traversal<HTMLCanvasElement>::FirstAncestorOrSelf(*elem);
+      if (!canvas->GetLayoutObject() ||
+          canvas->GetLayoutObject()->Style()->Visibility() !=
+              EVisibility::kVisible) {
+        return false;
+      }
+    } else if (GetLayoutObject()->Style()->Visibility() !=
+               EVisibility::kVisible) {
+      return false;
+    }
+  }
+
+  // Focusable: options in a combobox or listbox.
+  // Similar to menu list option treatment above, but not focusable if hidden.
+  if (RoleValue() == ax::mojom::Role::kListBoxOption)
+    return true;
+
+  // Focusable: element supports focus.
+  if (elem->SupportsFocus())
+    return true;
+
+  // TODO(accessibility) Focusable: scrollable with the keyboard.
+  // Keyboard-focusable scroll containers feature:
+  // https://www.chromestatus.com/feature/5231964663578624
+  // When adding here, remove similar check from ::NameFromContents().
+  // if (RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
+  //     IsUserScrollable()) {
+  //   return true;
+  // }
+
+  // Focusable: can be an active descendant.
+  if (CanBeActiveDescendant())
+    return true;
+
+  // NOT focusable: everything else.
+  return false;
 }
 
 // From ARIA 1.1.
@@ -1401,6 +1441,25 @@
 // textbox or is a logical descendant of that controlled element as indicated by
 // the aria-owns attribute.
 bool AXObject::CanBeActiveDescendant() const {
+  // Require an element with an id attribute.
+  // TODO(accessibility): this code currently requires both an id and role
+  // attribute, as well as an ancestor or controlling aria-activedescendant.
+  // However, with element reflection it may be possible to set an active
+  // descendant without an id, so at some point we may need to remove the
+  // requirement for an id attribute.
+  if (!GetElement() || !GetElement()->FastHasAttribute(html_names::kIdAttr))
+    return false;
+
+  // Does not make sense to use aria-activedescendant to point to a
+  // presentational object.
+  if (IsPresentational())
+    return false;
+
+  // Does not make sense to use aria-activedescendant to point to an HTML
+  // element that requires real focus, therefore an ARIA role is necessary.
+  if (AriaRoleAttribute() == ax::mojom::Role::kUnknown)
+    return false;
+
   return IsARIAControlledByTextboxWithActiveDescendant() ||
          AncestorExposesActiveDescendant();
 }
@@ -1932,10 +1991,8 @@
   // a click action means the user should trigger the action via a simulated
   // click. If this object cannot receive focus, it's impossible to trigger it
   // with a key press.
-  if (RoleValue() == ax::mojom::Role::kButton &&
-      !CanReceiveAccessibilityFocus()) {
+  if (RoleValue() == ax::mojom::Role::kButton && !CanSetFocusAttribute())
     return ax::mojom::DefaultActionVerb::kClick;
-  }
 
   switch (RoleValue()) {
     case ax::mojom::Role::kButton:
@@ -2114,7 +2171,7 @@
     if (is_disabled)
       return kRestrictionDisabled;
   } else if (CanSetFocusAttribute() && IsDescendantOfDisabledNode()) {
-    // No aria-disabled, but other markup says it's disabled.
+    // aria-disabled on an ancestor propagates to focusable descendants.
     return kRestrictionDisabled;
   }
 
@@ -3716,7 +3773,30 @@
     case ax::mojom::Role::kSection:
     case ax::mojom::Role::kStrong:
     case ax::mojom::Role::kTime:
-      result = recursive || (CanReceiveAccessibilityFocus() && !IsEditable());
+      if (recursive) {
+        // Use contents if part of a recursive name computation.
+        result = true;
+      } else {
+        // Use contents if focusable, so that there is a name in the case
+        // where the author mistakenly forgot to provide one.
+        // Exceptions:
+        // 1.Elements with contenteditable, where using the contents as a name
+        //   would cause them to be double-announced.
+        // 2.Containers with aria-activedescendant, where the focus is being
+        //   forwarded somewhere else.
+        // TODO(accessibility) Scrollables are currently whitelisted here in
+        // order to keep the current behavior. In the future, this can be
+        // removed because this code will be handled in IsFocusable(), once
+        // KeyboardFocusableScrollersEnabled is permanently enabled.
+        // Note: this uses the same scrollable check that element.cc uses.
+        bool is_focusable_scrollable =
+            RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
+            IsUserScrollable();
+        bool is_focusable = is_focusable_scrollable || CanSetFocusAttribute();
+        result = is_focusable && !IsEditable() &&
+                 !GetAOMPropertyOrARIAAttribute(
+                     AOMRelationProperty::kActiveDescendant);
+      }
       break;
 
     case ax::mojom::Role::kPdfActionableHighlight:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index 0a7c91c..4d1c24f 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -496,10 +496,12 @@
   virtual bool IsVisible() const;
   virtual bool IsVisited() const { return false; }
 
-  // Check whether certain properties can be modified.
-  bool CanSetFocusAttribute() const;
+  // Check whether value can be modified.
   bool CanSetValueAttribute() const;
 
+  // Is the element focusable?
+  bool CanSetFocusAttribute() const;
+
   // Whether objects are ignored, i.e. hidden from the AT.
   bool AccessibilityIsIgnored() const;
   // Whether objects are ignored but included in the tree.
@@ -1083,7 +1085,6 @@
     return nullptr;
   }
 
-  bool CanReceiveAccessibilityFocus() const;
   bool NameFromContents(bool recursive) const;
   bool NameFromSelectedOption(bool recursive) const;
 
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_renderer_factory_impl.cc b/third_party/blink/renderer/modules/mediastream/media_stream_renderer_factory_impl.cc
index 0d3fa5b..cb6e666 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_renderer_factory_impl.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_renderer_factory_impl.cc
@@ -39,6 +39,10 @@
              : base::UnguessableToken();
 }
 
+void SendLogMessage(const WTF::String& message) {
+  WebRtcLogMessage("MSRFI::" + message.Utf8());
+}
+
 }  // namespace
 
 std::unique_ptr<WebMediaStreamRendererFactory>
@@ -78,15 +82,16 @@
     WebLocalFrame* web_frame,
     const WebString& device_id) {
   DCHECK(!web_stream.IsNull());
+  SendLogMessage(String::Format("%s({web_stream_id=%s}, {device_id=%s})",
+                                __func__, web_stream.Id().Utf8().c_str(),
+                                device_id.Utf8().c_str()));
   WebVector<WebMediaStreamTrack> audio_tracks = web_stream.AudioTracks();
   if (audio_tracks.empty()) {
-    WebRtcLogMessage("No audio tracks in media stream (return null).");
+    SendLogMessage(String::Format(
+        "%s => (ERROR: no audio tracks in media stream)", __func__));
     return nullptr;
   }
 
-  DVLOG(1) << "MediaStreamRendererFactoryImpl::GetAudioRenderer stream:"
-           << web_stream.Id().Utf8();
-
   // TODO(tommi): We need to fix the data flow so that
   // it works the same way for all track implementations, local, remote or what
   // have you.
@@ -99,7 +104,8 @@
   if (!audio_track) {
     // This can happen if the track was cloned.
     // TODO(tommi, perkj): Fix cloning of tracks to handle extra data too.
-    WebRtcLogMessage("Error: No native track for WebMediaStreamTrack");
+    SendLogMessage(String::Format(
+        "%s => (ERROR: no native track for WebMediaStreamTrack)", __func__));
     return nullptr;
   }
 
@@ -108,9 +114,10 @@
   if (!PeerConnectionRemoteAudioTrack::From(audio_track)) {
     // TODO(xians): Add support for the case where the media stream contains
     // multiple audio tracks.
-    DVLOG(1) << "Creating TrackAudioRenderer for "
-             << (audio_track->is_local_track() ? "local" : "remote")
-             << " track.";
+    SendLogMessage(String::Format(
+        "%s => (creating TrackAudioRenderer for %s audio track)", __func__,
+        audio_track->is_local_track() ? "local" : "remote"));
+
     return new TrackAudioRenderer(audio_tracks[0], web_frame,
                                   /*session_id=*/base::UnguessableToken(),
                                   String(device_id));
@@ -120,13 +127,19 @@
   WebRtcAudioDeviceImpl* audio_device =
       PeerConnectionDependencyFactory::GetInstance()->GetWebRtcAudioDevice();
   DCHECK(audio_device);
-
+  SendLogMessage(String::Format(
+      "%s => (media stream is a remote WebRTC stream)", __func__));
   // Share the existing renderer if any, otherwise create a new one.
   scoped_refptr<WebRtcAudioRenderer> renderer(audio_device->renderer());
+
   if (renderer) {
-    DVLOG(1) << "Using existing WebRtcAudioRenderer for remote WebRTC track.";
+    SendLogMessage(String::Format(
+        "%s => (using existing WebRtcAudioRenderer for remote stream)",
+        __func__));
   } else {
-    DVLOG(1) << "Creating WebRtcAudioRenderer for remote WebRTC track.";
+    SendLogMessage(String::Format(
+        "%s => (creating new WebRtcAudioRenderer for remote stream)",
+        __func__));
 
     renderer = new WebRtcAudioRenderer(
         PeerConnectionDependencyFactory::GetInstance()
@@ -135,14 +148,17 @@
         device_id.Utf8());
 
     if (!audio_device->SetAudioRenderer(renderer.get())) {
-      WebRtcLogMessage("Error: SetAudioRenderer failed for remote track.");
+      SendLogMessage(String::Format(
+          "%s => (ERROR: WRADI::SetAudioRenderer failed)", __func__));
       return nullptr;
     }
   }
 
   auto ret = renderer->CreateSharedAudioRendererProxy(web_stream);
-  if (!ret)
-    WebRtcLogMessage("Error: CreateSharedAudioRendererProxy failed.");
+  if (!ret) {
+    SendLogMessage(String::Format(
+        "%s => (ERROR: CreateSharedAudioRendererProxy failed)", __func__));
+  }
   return ret;
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
index f40539f..f404aa6 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms.cc
@@ -57,6 +57,51 @@
              blink::WebMediaStreamSource::kReadyStateEnded;
 }
 
+const char* LoadTypeToString(blink::WebMediaPlayer::LoadType type) {
+  switch (type) {
+    case blink::WebMediaPlayer::kLoadTypeURL:
+      return "URL";
+    case blink::WebMediaPlayer::kLoadTypeMediaSource:
+      return "MediaSource";
+    case blink::WebMediaPlayer::kLoadTypeMediaStream:
+      return "MediaStream";
+  }
+}
+
+const char* ReadyStateToString(blink::WebMediaPlayer::ReadyState state) {
+  switch (state) {
+    case blink::WebMediaPlayer::kReadyStateHaveNothing:
+      return "HaveNothing";
+    case blink::WebMediaPlayer::kReadyStateHaveMetadata:
+      return "HaveMetadata";
+    case blink::WebMediaPlayer::kReadyStateHaveCurrentData:
+      return "HaveCurrentData";
+    case blink::WebMediaPlayer::kReadyStateHaveFutureData:
+      return "HaveFutureData";
+    case blink::WebMediaPlayer::kReadyStateHaveEnoughData:
+      return "HaveEnoughData";
+  }
+}
+
+const char* NetworkStateToString(blink::WebMediaPlayer::NetworkState state) {
+  switch (state) {
+    case blink::WebMediaPlayer::kNetworkStateEmpty:
+      return "Empty";
+    case blink::WebMediaPlayer::kNetworkStateIdle:
+      return "Idle";
+    case blink::WebMediaPlayer::kNetworkStateLoading:
+      return "Loading";
+    case blink::WebMediaPlayer::kNetworkStateLoaded:
+      return "Loaded";
+    case blink::WebMediaPlayer::kNetworkStateFormatError:
+      return "FormatError";
+    case blink::WebMediaPlayer::kNetworkStateNetworkError:
+      return "NetworkError";
+    case blink::WebMediaPlayer::kNetworkStateDecodeError:
+      return "DecodeError";
+  }
+}
+
 }  // namespace
 
 namespace WTF {
@@ -287,11 +332,13 @@
       create_bridge_callback_(std::move(create_bridge_callback)),
       submitter_(std::move(submitter)),
       surface_layer_mode_(surface_layer_mode) {
-  DVLOG(1) << __func__;
   DCHECK(client);
   DCHECK(delegate_);
   weak_this_ = weak_factory_.GetWeakPtr();
   delegate_id_ = delegate_->AddObserver(this);
+  SendLogMessage(String::Format("%s({delegate_id=%d}, {is_audio_element=%s})",
+                                __func__, delegate_id_,
+                                client->IsAudioElement() ? "true" : "false"));
 
   // TODO(tmathmeyer) WebMediaPlayerImpl gets the URL from the WebLocalFrame.
   // doing that here causes a nullptr deref.
@@ -299,7 +346,6 @@
 }
 
 WebMediaPlayerMS::~WebMediaPlayerMS() {
-  DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   if (!web_stream_.IsNull())
@@ -334,8 +380,9 @@
     LoadType load_type,
     const WebMediaPlayerSource& source,
     CorsMode /*cors_mode*/) {
-  DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s({load_type=%s})", __func__,
+                                LoadTypeToString(load_type)));
 
   // TODO(acolwell): Change this to DCHECK_EQ(load_type, LoadTypeMediaStream)
   // once Blink-side changes land.
@@ -361,6 +408,8 @@
   std::string stream_id =
       web_stream_.IsNull() ? std::string() : web_stream_.Id().Utf8();
   media_log_->AddEvent<media::MediaLogEvent::kLoad>(stream_id);
+  SendLogMessage(
+      String::Format("%s => (stream_id=%s)", __func__, stream_id.c_str()));
 
   frame_deliverer_.reset(new WebMediaPlayerMS::FrameDeliverer(
       weak_this_,
@@ -383,11 +432,15 @@
       web_stream_, internal_frame_->web_frame(),
       initial_audio_output_device_id_);
 
-  if (!audio_renderer_)
-    WebRtcLogMessage("Warning: Failed to instantiate audio renderer.");
+  if (!audio_renderer_) {
+    SendLogMessage(String::Format(
+        "%s => (WARNING: failed to instantiate audio renderer)", __func__));
+  }
 
   if (!video_frame_provider_ && !audio_renderer_) {
     SetNetworkState(WebMediaPlayer::kNetworkStateNetworkError);
+    SendLogMessage(String::Format(
+        "%s => (ERROR: WebMediaPlayer::kNetworkStateNetworkError)", __func__));
     return WebMediaPlayer::LoadTiming::kImmediate;
   }
 
@@ -395,31 +448,35 @@
     audio_renderer_->SetVolume(volume_);
     audio_renderer_->Start();
 
-    // Store the ID of audio track being played in |current_video_track_id_|
+    // Store the ID of audio track being played in |current_audio_track_id_|.
     if (!web_stream_.IsNull()) {
       WebVector<WebMediaStreamTrack> audio_tracks = web_stream_.AudioTracks();
       DCHECK_GT(audio_tracks.size(), 0U);
       current_audio_track_id_ = audio_tracks[0].Id();
+      SendLogMessage(String::Format("%s => (audio_track_id=%s)", __func__,
+                                    current_audio_track_id_.Utf8().c_str()));
     }
   }
 
   if (video_frame_provider_) {
     video_frame_provider_->Start();
 
-    // Store the ID of video track being played in |current_video_track_id_|
+    // Store the ID of video track being played in |current_video_track_id_|.
     if (!web_stream_.IsNull()) {
       WebVector<WebMediaStreamTrack> video_tracks = web_stream_.VideoTracks();
       DCHECK_GT(video_tracks.size(), 0U);
       current_video_track_id_ = video_tracks[0].Id();
+      SendLogMessage(String::Format("%s => (video_track_id=%s)", __func__,
+                                    current_video_track_id_.Utf8().c_str()));
     }
   }
   // When associated with an <audio> element, we don't want to wait for the
-  // first video fram to become available as we do for <video> elements
+  // first video frame to become available as we do for <video> elements
   // (<audio> elements can also be assigned video tracks).
   // For more details, see https://crbug.com/738379
   if (audio_renderer_ &&
       (client_->IsAudioElement() || !video_frame_provider_)) {
-    // This is audio-only mode.
+    SendLogMessage(String::Format("%s => (audio only mode)", __func__));
     SetReadyState(WebMediaPlayer::kReadyStateHaveMetadata);
     SetReadyState(WebMediaPlayer::kReadyStateHaveEnoughData);
   }
@@ -453,20 +510,26 @@
 }
 
 void WebMediaPlayerMS::TrackAdded(const WebMediaStreamTrack& track) {
+  SendLogMessage(
+      String::Format("%s({track_id=%s})", __func__, track.Id().Utf8().c_str()));
   Reload();
 }
 
 void WebMediaPlayerMS::TrackRemoved(const WebMediaStreamTrack& track) {
+  SendLogMessage(
+      String::Format("%s({track_id=%s})", __func__, track.Id().Utf8().c_str()));
   Reload();
 }
 
 void WebMediaPlayerMS::ActiveStateChanged(bool is_active) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s({is_active=%s})", __func__,
+                                is_active ? "true" : "false"));
   // The case when the stream becomes active is handled by TrackAdded().
   if (is_active)
     return;
 
-  // This makes the media element elegible to be garbage collected. Otherwise,
+  // This makes the media element eligible to be garbage collected. Otherwise,
   // the element will be considered active and will never be garbage
   // collected.
   SetNetworkState(kNetworkStateIdle);
@@ -553,6 +616,7 @@
   DCHECK(!web_stream_.IsNull());
   if (!internal_frame_->web_frame())
     return;
+  SendLogMessage(String::Format("%s()", __func__));
 
   WebVector<WebMediaStreamTrack> audio_tracks = web_stream_.AudioTracks();
 
@@ -597,8 +661,8 @@
 }
 
 void WebMediaPlayerMS::Play() {
-  DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s()", __func__));
 
   media_log_->AddEvent<media::MediaLogEvent::kPlay>();
   if (!paused_)
@@ -631,8 +695,8 @@
 }
 
 void WebMediaPlayerMS::Pause() {
-  DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s()", __func__));
 
   should_play_upon_shown_ = false;
   media_log_->AddEvent<media::MediaLogEvent::kPause>();
@@ -663,8 +727,8 @@
 }
 
 void WebMediaPlayerMS::SetVolume(double volume) {
-  DVLOG(1) << __func__ << "(volume=" << volume << ")";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s({volume=%.2f})", __func__, volume));
   volume_ = volume;
   if (audio_renderer_.get())
     audio_renderer_->SetVolume(volume_ * volume_multiplier_);
@@ -693,14 +757,21 @@
 void WebMediaPlayerMS::SetSinkId(
     const WebString& sink_id,
     WebSetSinkIdCompleteCallback completion_callback) {
-  DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(
+      String::Format("%s({sink_id=%s})", __func__, sink_id.Utf8().c_str()));
+  if (!audio_renderer_) {
+    SendLogMessage(String::Format(
+        "%s => (WARNING: failed to instantiate audio renderer)", __func__));
+  }
   media::OutputDeviceStatusCB callback =
       ConvertToOutputDeviceStatusCB(std::move(completion_callback));
   if (audio_renderer_) {
     audio_renderer_->SwitchOutputDevice(sink_id.Utf8(), std::move(callback));
   } else {
     std::move(callback).Run(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
+    SendLogMessage(String::Format(
+        "%s => (ERROR: OUTPUT_DEVICE_STATUS_ERROR_INTERNAL)", __func__));
   }
 }
 
@@ -776,13 +847,11 @@
 }
 
 WebMediaPlayer::NetworkState WebMediaPlayerMS::GetNetworkState() const {
-  DVLOG(2) << __func__ << ", state:" << network_state_;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   return network_state_;
 }
 
 WebMediaPlayer::ReadyState WebMediaPlayerMS::GetReadyState() const {
-  DVLOG(1) << __func__ << ", state:" << ready_state_;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   return ready_state_;
 }
@@ -953,6 +1022,8 @@
 }
 
 void WebMediaPlayerMS::OnMuted(bool muted) {
+  SendLogMessage(
+      String::Format("%s({muted=%s})", __func__, muted ? "true" : "false"));
   client_->RequestMuted(muted);
 }
 
@@ -1181,6 +1252,8 @@
 
 void WebMediaPlayerMS::SetNetworkState(WebMediaPlayer::NetworkState state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s => (state=%s)", __func__,
+                                NetworkStateToString(network_state_)));
   network_state_ = state;
   // Always notify to ensure client has the latest value.
   get_client()->NetworkStateChanged();
@@ -1188,6 +1261,8 @@
 
 void WebMediaPlayerMS::SetReadyState(WebMediaPlayer::ReadyState state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s => (state=%s)", __func__,
+                                ReadyStateToString(ready_state_)));
   ready_state_ = state;
   // Always notify to ensure client has the latest value.
   get_client()->ReadyStateChanged();
@@ -1234,6 +1309,12 @@
   client_->OnRequestAnimationFrame();
 }
 
+void WebMediaPlayerMS::SendLogMessage(const WTF::String& message) const {
+  blink::WebRtcLogMessage(
+      "WMPMS::" + message.Utf8() +
+      String::Format(" [delegate_id=%d]", delegate_id_).Utf8());
+}
+
 std::unique_ptr<WebMediaPlayer::VideoFramePresentationMetadata>
 WebMediaPlayerMS::GetVideoFramePresentationMetadata() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc
index 36f9388..dffb6fb 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc
@@ -25,6 +25,14 @@
   frame_data_ = data;
 }
 
+uint32_t RTCEncodedAudioFrame::synchronizationSource() const {
+  return 0;
+}
+
+Vector<uint32_t> RTCEncodedAudioFrame::contributingSources() const {
+  return Vector<uint32_t>();
+}
+
 String RTCEncodedAudioFrame::toString() const {
   StringBuilder sb;
   sb.Append("RTCEncodedAudioFrame{timestamp: ");
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h
index a8ff051..ff0cb07 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h
@@ -25,6 +25,8 @@
   DOMArrayBuffer* data() const;
   DOMArrayBuffer* additionalData() const;
   void setData(DOMArrayBuffer*);
+  uint32_t synchronizationSource() const;
+  Vector<uint32_t> contributingSources() const;
   String toString() const;
 
   void Trace(Visitor*) override;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.idl b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.idl
index 197a0eba..a5996bc 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.idl
@@ -10,8 +10,10 @@
 interface RTCEncodedAudioFrame {
     readonly attribute unsigned long long timestamp;  // microseconds
     attribute ArrayBuffer data;
-    // TODO(crbug.com/1052765): Replace this ArrayBuffer with structured data
-    // once we have decided what will be exposed.
+    // TODO(crbug.com/1052765): Replace the following fields with a dictionary
+    // with structured data once we have decided what will be exposed.
     readonly attribute ArrayBuffer additionalData;
+    readonly attribute unsigned long synchronizationSource;
+    readonly attribute FrozenArray<unsigned long> contributingSources;
     stringifier;
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
index c4fcbe8..e9fa44b 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
@@ -13,9 +13,11 @@
 
 RTCEncodedVideoFrame::RTCEncodedVideoFrame(
     std::unique_ptr<webrtc::video_coding::EncodedFrame> delegate,
-    Vector<uint8_t> additional_data)
+    Vector<uint8_t> additional_data,
+    uint32_t ssrc)
     : delegate_(std::move(delegate)),
-      additional_data_vector_(std::move(additional_data)) {}
+      additional_data_vector_(std::move(additional_data)),
+      ssrc_(ssrc) {}
 
 String RTCEncodedVideoFrame::type() const {
   if (!delegate_)
@@ -44,6 +46,10 @@
   return additional_data_;
 }
 
+uint32_t RTCEncodedVideoFrame::synchronizationSource() const {
+  return ssrc_;
+}
+
 void RTCEncodedVideoFrame::setData(DOMArrayBuffer* data) {
   frame_data_ = data;
 }
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
index e8054a7..b52cc557 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h
@@ -30,7 +30,8 @@
  public:
   explicit RTCEncodedVideoFrame(
       std::unique_ptr<webrtc::video_coding::EncodedFrame> delegate,
-      Vector<uint8_t> generic_descriptor);
+      Vector<uint8_t> generic_descriptor,
+      uint32_t ssrc);
 
   // rtc_encoded_video_frame.idl implementation.
   String type() const;
@@ -39,6 +40,7 @@
   DOMArrayBuffer* data() const;
   DOMArrayBuffer* additionalData() const;
   void setData(DOMArrayBuffer*);
+  uint32_t synchronizationSource() const;
   String toString() const;
 
   // Internal API
@@ -53,6 +55,7 @@
  private:
   std::unique_ptr<webrtc::video_coding::EncodedFrame> delegate_;
   Vector<uint8_t> additional_data_vector_;
+  const uint32_t ssrc_;
   // Exposes encoded frame data from |delegate_|.
   mutable Member<DOMArrayBuffer> frame_data_;
   // Exposes data from |additional_data_vector_|.
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
index 97eed3a2..14de6bbc 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.idl
@@ -17,8 +17,9 @@
     readonly attribute RTCCodedVideoFrameType type;
     readonly attribute unsigned long long timestamp;  // microseconds
     attribute ArrayBuffer data;
-    // TODO(crbug.com/1052765): Replace this ArrayBuffer with structured data
-    // once we have decided what will be exposed.
+    // TODO(crbug.com/1052765): Replace the following fields with a dictionary
+    // with structured data once we have decided what will be exposed.
     readonly attribute ArrayBuffer additionalData;
+    readonly attribute unsigned long synchronizationSource;
     stringifier;
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
index c597ce0..9d793d8 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
@@ -69,7 +69,7 @@
 
   ScriptValue CreateEncodedVideoFrameChunk(ScriptState* script_state) {
     RTCEncodedVideoFrame* frame = MakeGarbageCollected<RTCEncodedVideoFrame>(
-        /*frame_delegate=*/nullptr, Vector<uint8_t>());
+        /*frame_delegate=*/nullptr, Vector<uint8_t>(), 0);
     return ScriptValue(script_state->GetIsolate(),
                        ToV8(frame, script_state->GetContext()->Global(),
                             script_state->GetIsolate()));
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.cc
index 27f472e..abac0c1 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.cc
@@ -50,7 +50,8 @@
 
 void RTCEncodedVideoUnderlyingSource::OnFrameFromSource(
     std::unique_ptr<webrtc::video_coding::EncodedFrame> webrtc_frame,
-    std::vector<uint8_t> additional_data) {
+    std::vector<uint8_t> additional_data,
+    uint32_t ssrc) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // If the source is canceled or there are too many queued frames,
   // drop the new frame.
@@ -67,7 +68,7 @@
 
   RTCEncodedVideoFrame* encoded_frame =
       MakeGarbageCollected<RTCEncodedVideoFrame>(
-          std::move(webrtc_frame), std::move(wtf_additional_data));
+          std::move(webrtc_frame), std::move(wtf_additional_data), ssrc);
   Controller()->Enqueue(encoded_frame);
 }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.h b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.h
index cba48cf5..45136e3 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source.h
@@ -29,7 +29,8 @@
   ScriptPromise Cancel(ScriptState*, ScriptValue reason) override;
 
   void OnFrameFromSource(std::unique_ptr<webrtc::video_coding::EncodedFrame>,
-                         std::vector<uint8_t> additional_data);
+                         std::vector<uint8_t> additional_data,
+                         uint32_t ssrc);
   void Close();
 
   void Trace(Visitor*) override;
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
index 5610d816..1ef26a7c 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
@@ -41,7 +41,7 @@
   ScriptPromiseTester read_tester(script_state,
                                   reader->read(script_state, exception_state));
   EXPECT_FALSE(read_tester.IsFulfilled());
-  source->OnFrameFromSource(nullptr, std::vector<uint8_t>());
+  source->OnFrameFromSource(nullptr, std::vector<uint8_t>(), 0);
   read_tester.WaitUntilSettled();
   EXPECT_TRUE(read_tester.IsFulfilled());
 
@@ -70,12 +70,12 @@
   for (int i = 0; i > RTCEncodedVideoUnderlyingSource::kMinQueueDesiredSize;
        --i) {
     EXPECT_EQ(source->Controller()->DesiredSize(), i);
-    source->OnFrameFromSource(nullptr, std::vector<uint8_t>());
+    source->OnFrameFromSource(nullptr, std::vector<uint8_t>(), 0);
   }
   EXPECT_EQ(source->Controller()->DesiredSize(),
             RTCEncodedVideoUnderlyingSource::kMinQueueDesiredSize);
 
-  source->OnFrameFromSource(nullptr, std::vector<uint8_t>());
+  source->OnFrameFromSource(nullptr, std::vector<uint8_t>(), 0);
   EXPECT_EQ(source->Controller()->DesiredSize(),
             RTCEncodedVideoUnderlyingSource::kMinQueueDesiredSize);
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_receiver.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_receiver.cc
index ed576047..6f1830243 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_receiver.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_receiver.cc
@@ -402,7 +402,7 @@
     uint32_t ssrc) {
   if (video_from_depacketizer_underlying_source_) {
     video_from_depacketizer_underlying_source_->OnFrameFromSource(
-        std::move(encoded_video_frame), std::move(additional_data));
+        std::move(encoded_video_frame), std::move(additional_data), ssrc);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
index e4115f8..3b48515 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
@@ -758,7 +758,7 @@
     uint32_t ssrc) {
   if (video_from_encoder_underlying_source_) {
     video_from_encoder_underlying_source_->OnFrameFromSource(
-        std::move(frame), std::move(additional_data));
+        std::move(frame), std::move(additional_data), ssrc);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/shapedetection/face_detector.cc b/third_party/blink/renderer/modules/shapedetection/face_detector.cc
index 2012392..766f30a 100644
--- a/third_party/blink/renderer/modules/shapedetection/face_detector.cc
+++ b/third_party/blink/renderer/modules/shapedetection/face_detector.cc
@@ -31,7 +31,7 @@
 
 FaceDetector::FaceDetector(ExecutionContext* context,
                            const FaceDetectorOptions* options)
-    : ShapeDetector() {
+    : face_service_(context) {
   auto face_detector_options =
       shape_detection::mojom::blink::FaceDetectorOptions::New();
   face_detector_options->max_detected_faces = options->maxDetectedFaces();
@@ -54,7 +54,7 @@
 ScriptPromise FaceDetector::DoDetect(ScriptPromiseResolver* resolver,
                                      SkBitmap bitmap) {
   ScriptPromise promise = resolver->Promise();
-  if (!face_service_) {
+  if (!face_service_.is_bound()) {
     resolver->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kNotSupportedError,
         "Face detection service unavailable."));
@@ -116,6 +116,7 @@
 
 void FaceDetector::Trace(Visitor* visitor) {
   ShapeDetector::Trace(visitor);
+  visitor->Trace(face_service_);
   visitor->Trace(face_service_requests_);
 }
 
diff --git a/third_party/blink/renderer/modules/shapedetection/face_detector.h b/third_party/blink/renderer/modules/shapedetection/face_detector.h
index 97dc3be..bdfc333 100644
--- a/third_party/blink/renderer/modules/shapedetection/face_detector.h
+++ b/third_party/blink/renderer/modules/shapedetection/face_detector.h
@@ -5,13 +5,14 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SHAPEDETECTION_FACE_DETECTOR_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SHAPEDETECTION_FACE_DETECTOR_H_
 
-#include "mojo/public/cpp/bindings/remote.h"
 #include "services/shape_detection/public/mojom/facedetection.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/shapedetection/shape_detector.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
 
 namespace blink {
 
@@ -37,7 +38,9 @@
       Vector<shape_detection::mojom::blink::FaceDetectionResultPtr>);
   void OnFaceServiceConnectionError();
 
-  mojo::Remote<shape_detection::mojom::blink::FaceDetection> face_service_;
+  HeapMojoRemote<shape_detection::mojom::blink::FaceDetection,
+                 HeapMojoWrapperMode::kWithoutContextObserver>
+      face_service_;
 
   HeapHashSet<Member<ScriptPromiseResolver>> face_service_requests_;
 };
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
index 919d34f..e7dfc4a 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
@@ -68,23 +68,27 @@
     return ScriptPromise();
   }
 
-  // 2.1. If document is not allowed to use the policy-controlled feature named
-  //      "wake-lock", reject promise with a "NotAllowedError" DOMException and
-  //      return promise.
+  // 2.1 If type is 'screen' and the document is not allowed to use the
+  //     policy-controlled feature named "screen-wake-lock", reject promise with
+  //     a "NotAllowedError" DOMException and return promise.
   // [N.B. Per https://github.com/w3c/webappsec-feature-policy/issues/207 there
   // is no official support for workers in the Feature Policy spec, but we can
   // perform FP checks in workers in Blink]
   // 2.2. If the user agent denies the wake lock of this type for document,
   //      reject promise with a "NotAllowedError" DOMException and return
   //      promise.
-  if (!context->IsFeatureEnabled(mojom::blink::FeaturePolicyFeature::kWakeLock,
-                                 ReportOptions::kReportOnFailure)) {
+  if (type == "screen" &&
+      !context->IsFeatureEnabled(
+          mojom::blink::FeaturePolicyFeature::kScreenWakeLock,
+          ReportOptions::kReportOnFailure)) {
     exception_state.ThrowDOMException(
         DOMExceptionCode::kNotAllowedError,
-        "Access to WakeLock features is disallowed by feature policy");
+        "Access to Screen Wake Lock features is disallowed by feature policy");
     return ScriptPromise();
   }
 
+  // TODO: Check feature policy enabling for System Wake Lock
+
   if (context->IsDedicatedWorkerGlobalScope()) {
     // 3. If the current global object is the DedicatedWorkerGlobalScope object:
     // 3.1. If the current global object's owner set is empty, reject promise
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
index 30b1b8bb..579fdf7 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.cc
@@ -17,7 +17,9 @@
 
 WakeLockManager::WakeLockManager(ExecutionContext* execution_context,
                                  WakeLockType type)
-    : wake_lock_type_(type), execution_context_(execution_context) {
+    : wake_lock_(execution_context),
+      wake_lock_type_(type),
+      execution_context_(execution_context) {
   DCHECK_NE(execution_context, nullptr);
 }
 
@@ -36,15 +38,16 @@
   //      document and type.
   // 4.3. Add lock to record.[[ActiveLocks]].
   // 5. Return active.
-  if (!wake_lock_) {
+  if (!wake_lock_.is_bound()) {
     mojo::Remote<mojom::blink::WakeLockService> wake_lock_service;
     execution_context_->GetBrowserInterfaceBroker().GetInterface(
         wake_lock_service.BindNewPipeAndPassReceiver());
 
-    wake_lock_service->GetWakeLock(ToMojomWakeLockType(wake_lock_type_),
-                                   device::mojom::blink::WakeLockReason::kOther,
-                                   "Blink Wake Lock",
-                                   wake_lock_.BindNewPipeAndPassReceiver());
+    wake_lock_service->GetWakeLock(
+        ToMojomWakeLockType(wake_lock_type_),
+        device::mojom::blink::WakeLockReason::kOther, "Blink Wake Lock",
+        wake_lock_.BindNewPipeAndPassReceiver(
+            execution_context_->GetTaskRunner(TaskType::kMiscPlatformAPI)));
     wake_lock_.set_disconnect_handler(WTF::Bind(
         &WakeLockManager::OnWakeLockConnectionError, WrapWeakPersistent(this)));
     wake_lock_->RequestWakeLock();
@@ -99,6 +102,7 @@
 void WakeLockManager::Trace(Visitor* visitor) {
   visitor->Trace(execution_context_);
   visitor->Trace(wake_lock_sentinels_);
+  visitor->Trace(wake_lock_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
index 4a5ce7f..1375d9e5 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h
@@ -6,11 +6,12 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WAKE_LOCK_MANAGER_H_
 
 #include "base/gtest_prod_util.h"
-#include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/wake_lock.mojom-blink-forward.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_type.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
 
 namespace blink {
 
@@ -42,7 +43,9 @@
 
   // An actual platform WakeLock. If bound, it means there is an active wake
   // lock for a given type.
-  mojo::Remote<device::mojom::blink::WakeLock> wake_lock_;
+  HeapMojoRemote<device::mojom::blink::WakeLock,
+                 HeapMojoWrapperMode::kWithoutContextObserver>
+      wake_lock_;
   WakeLockType wake_lock_type_;
 
   // ExecutionContext from which we will connect to |wake_lock_service_|.
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager_test.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager_test.cc
index 720fd5a..dcb9df8 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_manager_test.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_manager_test.cc
@@ -194,6 +194,7 @@
   context.WaitForPromiseFulfillment(promise1);
   context.WaitForPromiseFulfillment(promise2);
 
+  EXPECT_TRUE(manager->wake_lock_.is_bound());
   EXPECT_EQ(2U, manager->wake_lock_sentinels_.size());
 
   // Unbind and wait for the disconnection to reach |wake_lock_|'s
@@ -202,7 +203,7 @@
   manager->wake_lock_.FlushForTesting();
 
   EXPECT_EQ(0U, manager->wake_lock_sentinels_.size());
-  EXPECT_FALSE(manager->wake_lock_);
+  EXPECT_FALSE(manager->wake_lock_.is_bound());
   EXPECT_FALSE(system_lock.is_acquired());
 }
 
diff --git a/third_party/blink/renderer/modules/webrtc/DEPS b/third_party/blink/renderer/modules/webrtc/DEPS
index 9a092ef..d12ed38 100644
--- a/third_party/blink/renderer/modules/webrtc/DEPS
+++ b/third_party/blink/renderer/modules/webrtc/DEPS
@@ -5,6 +5,7 @@
     "+media/base/audio_bus.h",
     "+media/base/audio_capturer_source.h",
     "+media/base/audio_decoder.h",
+    "+media/base/audio_power_monitor.h",
     "+media/base/audio_latency.h",
     "+media/base/audio_parameters.h",
     "+media/base/audio_pull_fifo.h",
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.cc b/third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.cc
index 481bf01..e8f6097 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.cc
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_device_impl.cc
@@ -36,7 +36,7 @@
       initialized_(false),
       playing_(false),
       recording_(false) {
-  SendLogMessage(base::StringPrintf("WebRtcAudioDeviceImpl({id=%s})",
+  SendLogMessage(base::StringPrintf("%s({id=%s})", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   // This object can be constructed on either the signaling thread or the main
   // thread, so we need to detach these thread checkers here and have them
@@ -49,7 +49,7 @@
 }
 
 WebRtcAudioDeviceImpl::~WebRtcAudioDeviceImpl() {
-  SendLogMessage(base::StringPrintf("~WebRtcAudioDeviceImpl([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
   DCHECK(!initialized_) << "Terminate must have been called.";
@@ -142,6 +142,9 @@
 void WebRtcAudioDeviceImpl::SetOutputDeviceForAec(
     const String& output_device_id) {
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
+  SendLogMessage(base::StringPrintf("%s({output_device_id=%s}) [id=%s]",
+                                    __func__, output_device_id.Utf8().c_str(),
+                                    GetAudioProcessingId().ToString().c_str()));
   output_device_id_for_aec_ = output_device_id;
   base::AutoLock lock(lock_);
   for (auto* capturer : capturers_) {
@@ -156,7 +159,7 @@
 int32_t WebRtcAudioDeviceImpl::RegisterAudioCallback(
     webrtc::AudioTransport* audio_callback) {
   DCHECK_CALLED_ON_VALID_THREAD(signaling_thread_checker_);
-  SendLogMessage(base::StringPrintf("RegisterAudioCallback([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   base::AutoLock lock(lock_);
   DCHECK_EQ(!audio_transport_callback_, !!audio_callback);
@@ -269,7 +272,7 @@
 int32_t WebRtcAudioDeviceImpl::StartRecording() {
   DCHECK_CALLED_ON_VALID_THREAD(worker_thread_checker_);
   DCHECK(initialized_);
-  SendLogMessage(base::StringPrintf("StartRecording([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   base::AutoLock auto_lock(lock_);
   if (!audio_transport_callback_) {
@@ -292,7 +295,7 @@
   DCHECK(signaling_thread_checker_.CalledOnValidThread() ||
          worker_thread_checker_.CalledOnValidThread());
 #endif
-  SendLogMessage(base::StringPrintf("StopRecording([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   base::AutoLock auto_lock(lock_);
   recording_ = false;
@@ -394,7 +397,7 @@
 void WebRtcAudioDeviceImpl::AddAudioCapturer(
     ProcessedLocalAudioSource* capturer) {
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
-  SendLogMessage(base::StringPrintf("AddAudioCapturer([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   DCHECK(capturer);
   DCHECK(!capturer->device().id.empty());
@@ -408,7 +411,7 @@
 void WebRtcAudioDeviceImpl::RemoveAudioCapturer(
     ProcessedLocalAudioSource* capturer) {
   DCHECK_CALLED_ON_VALID_THREAD(main_thread_checker_);
-  SendLogMessage(base::StringPrintf("RemoveAudioCapturer([id=%s])",
+  SendLogMessage(base::StringPrintf("%s() [id=%s]", __func__,
                                     GetAudioProcessingId().ToString().c_str()));
   DCHECK(capturer);
   base::AutoLock auto_lock(lock_);
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
index d0993d5e..ad23ef6 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
@@ -24,8 +24,10 @@
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_track.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/webrtc/peer_connection_remote_audio_source.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/webrtc/api/media_stream_interface.h"
 
 namespace WTF {
@@ -47,10 +49,45 @@
 const media::AudioParameters::Format kFormat =
     media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
 
+// Time constant for AudioPowerMonitor. See See AudioPowerMonitor ctor comments
+// for details.
+constexpr base::TimeDelta kPowerMeasurementTimeConstant =
+    base::TimeDelta::FromMilliseconds(10);
+
+// Time in seconds between two successive measurements of audio power levels.
+constexpr base::TimeDelta kPowerMonitorLogInterval =
+    base::TimeDelta::FromSeconds(15);
+
 // Used for UMA histograms.
 const int kRenderTimeHistogramMinMicroseconds = 100;
 const int kRenderTimeHistogramMaxMicroseconds = 1 * 1000 * 1000;  // 1 second
 
+const char* OutputDeviceStatusToString(media::OutputDeviceStatus status) {
+  switch (status) {
+    case media::OUTPUT_DEVICE_STATUS_OK:
+      return "OK";
+    case media::OUTPUT_DEVICE_STATUS_ERROR_NOT_FOUND:
+      return "ERROR_NOT_FOUND";
+    case media::OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED:
+      return "ERROR_NOT_AUTHORIZED";
+    case media::OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT:
+      return "ERROR_TIMED_OUT";
+    case media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL:
+      return "ERROR_INTERNAL";
+  }
+}
+
+const char* StateToString(WebRtcAudioRenderer::State state) {
+  switch (state) {
+    case WebRtcAudioRenderer::UNINITIALIZED:
+      return "UNINITIALIZED";
+    case WebRtcAudioRenderer::PLAYING:
+      return "PLAYING";
+    case WebRtcAudioRenderer::PAUSED:
+      return "PAUSED";
+  }
+}
+
 // This is a simple wrapper class that's handed out to users of a shared
 // WebRtcAudioRenderer instance.  This class maintains the per-user 'playing'
 // and 'started' states to avoid problems related to incorrect usage which
@@ -178,25 +215,109 @@
   WeakPersistent<LocalFrame> frame_;
 };
 
+WebRtcAudioRenderer::AudioStreamTracker::AudioStreamTracker(
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    WebRtcAudioRenderer* renderer,
+    int sample_rate)
+    : task_runner_(std::move(task_runner)),
+      renderer_(renderer),
+      start_time_(base::TimeTicks::Now()),
+      render_callbacks_started_(false),
+      check_alive_timer_(task_runner_,
+                         this,
+                         &WebRtcAudioRenderer::AudioStreamTracker::CheckAlive),
+      power_monitor_(sample_rate, kPowerMeasurementTimeConstant),
+      last_audio_level_log_time_(base::TimeTicks::Now()) {
+  weak_this_ = weak_factory_.GetWeakPtr();
+  // CheckAlive() will look to see if |render_callbacks_started_| is true
+  // after the timeout expires and log this. If the stream is paused/closed
+  // before the timer fires, a warning is logged instead.
+  check_alive_timer_.StartOneShot(base::TimeDelta::FromSeconds(5), FROM_HERE);
+}
+
+WebRtcAudioRenderer::AudioStreamTracker::~AudioStreamTracker() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  DCHECK(renderer_);
+  const auto duration = base::TimeTicks::Now() - start_time_;
+  renderer_->SendLogMessage(
+      String::Format("%s => (media stream duration=%" PRId64 " seconds)",
+                     __func__, duration.InSeconds()));
+}
+
+void WebRtcAudioRenderer::AudioStreamTracker::OnRenderCallbackCalled() {
+  DCHECK(renderer_->CurrentThreadIsRenderingThread());
+  // Indicate that render callbacks has started as expected and within a
+  // reasonable time. Since this thread is the only writer of
+  // |render_callbacks_started_| once the thread starts, it's safe to compare
+  // and then change the state once.
+  if (!render_callbacks_started_)
+    render_callbacks_started_ = true;
+}
+
+void WebRtcAudioRenderer::AudioStreamTracker::MeasurePower(
+    const media::AudioBus& buffer,
+    int frames) {
+  DCHECK(renderer_->CurrentThreadIsRenderingThread());
+  // Update the average power estimate on the rendering thread to avoid posting
+  // a task which also has to copy the audio bus. According to comments in
+  // AudioPowerMonitor::Scan(), it should be safe. Note that, we only check the
+  // power once every ten seconds (on the |task_runner_| thread) and the result
+  // is only used for logging purposes.
+  power_monitor_.Scan(buffer, frames);
+  const auto now = base::TimeTicks::Now();
+  if ((now - last_audio_level_log_time_) > kPowerMonitorLogInterval) {
+    // Log the current audio level but avoid using the render thread to reduce
+    // its load and to ensure that |power_monitor_| is mainly accessed on one
+    // thread. |weak_ptr_factory_| ensures that the task is canceled when
+    // |this| is destroyed since we can't guarantee that |this| outlives the
+    // task.
+    PostCrossThreadTask(
+        *task_runner_, FROM_HERE,
+        CrossThreadBindOnce(&AudioStreamTracker::LogAudioPowerLevel,
+                            weak_this_));
+    last_audio_level_log_time_ = now;
+  }
+}
+
+void WebRtcAudioRenderer::AudioStreamTracker::LogAudioPowerLevel() {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  std::pair<float, bool> power_and_clip =
+      power_monitor_.ReadCurrentPowerAndClip();
+  renderer_->SendLogMessage(String::Format(
+      "%s => (average audio level=%.2f dBFS)", __func__, power_and_clip.first));
+}
+
+void WebRtcAudioRenderer::AudioStreamTracker::CheckAlive(TimerBase*) {
+  DCHECK(task_runner_->BelongsToCurrentThread());
+  DCHECK(renderer_);
+  renderer_->SendLogMessage(String::Format(
+      "%s => (%s)", __func__,
+      render_callbacks_started_ ? "stream is alive"
+                                : "WARNING: stream is not alive"));
+}
+
 WebRtcAudioRenderer::WebRtcAudioRenderer(
     const scoped_refptr<base::SingleThreadTaskRunner>& signaling_thread,
     const WebMediaStream& media_stream,
     WebLocalFrame* web_frame,
     const base::UnguessableToken& session_id,
     const std::string& device_id)
-    : state_(UNINITIALIZED),
+    : task_runner_(Thread::Current()->GetTaskRunner()),
+      state_(UNINITIALIZED),
       source_internal_frame_(std::make_unique<InternalFrame>(web_frame)),
       session_id_(session_id),
       signaling_thread_(signaling_thread),
       media_stream_(media_stream),
+      media_stream_id_(media_stream_.Id().Utf8()),
       source_(nullptr),
       play_ref_count_(0),
       start_ref_count_(0),
       sink_params_(kFormat, media::CHANNEL_LAYOUT_STEREO, 0, 0),
       output_device_id_(device_id) {
-  WebRtcLogMessage(base::StringPrintf("WAR::WAR. session_id=%s, effects=%i",
-                                      session_id.ToString().c_str(),
-                                      sink_params_.effects()));
+  SendLogMessage(
+      String::Format("%s({session_id=%s}, {device_id=%s})", __func__,
+                     session_id.is_empty() ? "" : session_id.ToString().c_str(),
+                     device_id.c_str()));
 }
 
 WebRtcAudioRenderer::~WebRtcAudioRenderer() {
@@ -205,16 +326,16 @@
 }
 
 bool WebRtcAudioRenderer::Initialize(WebRtcAudioRendererSource* source) {
-  DVLOG(1) << "WebRtcAudioRenderer::Initialize()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(source);
   DCHECK(!sink_.get());
-  // DCHECK_GE(session_id_, 0);
   {
     base::AutoLock auto_lock(lock_);
     DCHECK_EQ(state_, UNINITIALIZED);
     DCHECK(!source_);
   }
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
 
   media::AudioSinkParameters sink_params(session_id_, output_device_id_);
   sink_params.processing_id = source->GetAudioProcessingId();
@@ -226,15 +347,19 @@
       sink_->GetOutputDeviceInfo().device_status();
   UMA_HISTOGRAM_ENUMERATION("Media.Audio.WebRTCAudioRenderer.DeviceStatus",
                             sink_status, media::OUTPUT_DEVICE_STATUS_MAX + 1);
+  SendLogMessage(String::Format("%s => (sink device_status=%s)", __func__,
+                                OutputDeviceStatusToString(sink_status)));
   if (sink_status != media::OUTPUT_DEVICE_STATUS_OK) {
+    SendLogMessage(String::Format("%s => (ERROR: invalid output device status)",
+                                  __func__));
     sink_->Stop();
     return false;
   }
 
   PrepareSink();
   {
-    // No need to reassert the preconditions because the other thread accessing
-    // the fields only reads them.
+    // No need to reassert the preconditions because the other thread
+    // accessing the fields only reads them.
     base::AutoLock auto_lock(lock_);
     source_ = source;
 
@@ -272,15 +397,16 @@
 }
 
 void WebRtcAudioRenderer::Start() {
-  DVLOG(1) << "WebRtcAudioRenderer::Start()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
   ++start_ref_count_;
 }
 
 void WebRtcAudioRenderer::Play() {
-  DVLOG(1) << "WebRtcAudioRenderer::Play()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
   if (playing_state_.playing())
     return;
 
@@ -293,6 +419,8 @@
   DVLOG(1) << "WebRtcAudioRenderer::EnterPlayState()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK_GT(start_ref_count_, 0) << "Did you forget to call Start()?";
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
   base::AutoLock auto_lock(lock_);
   if (state_ == UNINITIALIZED)
     return;
@@ -303,16 +431,22 @@
   if (state_ != PLAYING) {
     state_ = PLAYING;
 
+    audio_stream_tracker_.emplace(task_runner_, this,
+                                  sink_params_.sample_rate());
+
     if (audio_fifo_) {
       audio_delay_ = base::TimeDelta();
       audio_fifo_->Clear();
     }
   }
+  SendLogMessage(
+      String::Format("%s => (state=%s)", __func__, StateToString(state_)));
 }
 
 void WebRtcAudioRenderer::Pause() {
-  DVLOG(1) << "WebRtcAudioRenderer::Pause()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
   if (!playing_state_.playing())
     return;
 
@@ -322,9 +456,10 @@
 }
 
 void WebRtcAudioRenderer::EnterPauseState() {
-  DVLOG(1) << "WebRtcAudioRenderer::EnterPauseState()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK_GT(start_ref_count_, 0) << "Did you forget to call Start()?";
+  SendLogMessage(
+      String::Format("%s([state=%s])", __func__, StateToString(state_)));
   base::AutoLock auto_lock(lock_);
   if (state_ == UNINITIALIZED)
     return;
@@ -333,12 +468,15 @@
   DCHECK_GT(play_ref_count_, 0);
   if (!--play_ref_count_)
     state_ = PAUSED;
+  SendLogMessage(
+      String::Format("%s => (state=%s)", __func__, StateToString(state_)));
 }
 
 void WebRtcAudioRenderer::Stop() {
-  DVLOG(1) << "WebRtcAudioRenderer::Stop()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   {
+    SendLogMessage(
+        String::Format("%s([state=%s])", __func__, StateToString(state_)));
     base::AutoLock auto_lock(lock_);
     if (state_ == UNINITIALIZED)
       return;
@@ -346,8 +484,7 @@
     if (--start_ref_count_)
       return;
 
-    DVLOG(1) << "Calling RemoveAudioRenderer and Stop().";
-
+    audio_stream_tracker_.reset();
     source_->RemoveAudioRenderer(this);
     source_ = nullptr;
     state_ = UNINITIALIZED;
@@ -363,6 +500,8 @@
         static_cast<int>(max_render_time_.InMicroseconds()),
         kRenderTimeHistogramMinMicroseconds,
         kRenderTimeHistogramMaxMicroseconds, 50);
+    SendLogMessage(String::Format("%s => (max_render_time=%.3f ms)", __func__,
+                                  max_render_time_.InMillisecondsF()));
     max_render_time_ = base::TimeDelta();
   }
 
@@ -393,9 +532,12 @@
 void WebRtcAudioRenderer::SwitchOutputDevice(
     const std::string& device_id,
     media::OutputDeviceStatusCB callback) {
-  DVLOG(1) << "WebRtcAudioRenderer::SwitchOutputDevice()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  SendLogMessage(String::Format("%s({device_id=%s} [state=%s])", __func__,
+                                device_id.c_str(), StateToString(state_)));
   if (!source_) {
+    SendLogMessage(String::Format(
+        "%s => (ERROR: OUTPUT_DEVICE_STATUS_ERROR_INTERNAL)", __func__));
     std::move(callback).Run(media::OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
     return;
   }
@@ -416,8 +558,12 @@
   UMA_HISTOGRAM_ENUMERATION(
       "Media.Audio.WebRTCAudioRenderer.SwitchDeviceStatus", status,
       media::OUTPUT_DEVICE_STATUS_MAX + 1);
+  SendLogMessage(String::Format("%s => (sink device_status=%s)", __func__,
+                                OutputDeviceStatusToString(status)));
 
   if (status != media::OUTPUT_DEVICE_STATUS_OK) {
+    SendLogMessage(
+        String::Format("%s => (ERROR: invalid sink device status)", __func__));
     new_sink->Stop();
     std::move(callback).Run(status);
     return;
@@ -451,9 +597,6 @@
   if (!source_)
     return 0;
 
-  DVLOG(2) << "WebRtcAudioRenderer::Render()";
-  DVLOG(2) << "audio_delay: " << delay;
-
   audio_delay_ = delay;
 
   // If there are skipped frames, pull and throw away the same amount. We always
@@ -483,6 +626,12 @@
   else
     SourceCallback(0, audio_bus);
 
+  if (state_ == PLAYING && audio_stream_tracker_) {
+    // Mark the stream as alive the first time this method is called.
+    audio_stream_tracker_->OnRenderCallbackCalled();
+    audio_stream_tracker_->MeasurePower(*audio_bus, audio_bus->frames());
+  }
+
   return (state_ == PLAYING) ? audio_bus->frames() : 0;
 }
 
@@ -496,7 +645,7 @@
                                          media::AudioBus* audio_bus) {
   DCHECK(sink_->CurrentThreadIsRenderingThread());
   base::TimeTicks start_time = base::TimeTicks::Now();
-  DVLOG(2) << "WebRtcAudioRenderer::SourceCallback(" << fifo_frame_delay << ", "
+  DVLOG(2) << "WRAR::SourceCallback(" << fifo_frame_delay << ", "
            << audio_bus->channels() << ", " << audio_bus->frames() << ")";
 
   int output_delay_milliseconds =
@@ -556,7 +705,8 @@
   if (volume > 10.0f)
     volume = 10.0f;
 
-  DVLOG(1) << "Setting remote source volume: " << volume;
+  SendLogMessage(String::Format("%s => (source volume changed to %.2f)",
+                                __func__, volume));
   if (!signaling_thread_->BelongsToCurrentThread()) {
     // Libjingle hands out proxy objects in most cases, but the audio source
     // object is an exception (bug?).  So, to work around that, we need to make
@@ -655,7 +805,6 @@
 }
 
 void WebRtcAudioRenderer::PrepareSink() {
-  DVLOG(1) << "WebRtcAudioRenderer::PrepareSink()";
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   media::AudioParameters new_sink_params;
   {
@@ -665,20 +814,22 @@
 
   const media::OutputDeviceInfo& device_info = sink_->GetOutputDeviceInfo();
   DCHECK_EQ(device_info.device_status(), media::OUTPUT_DEVICE_STATUS_OK);
-  DVLOG(1) << "Sink parameters: " << sink_params_.AsHumanReadableString();
-  DVLOG(1) << "Hardware parameters: "
-           << device_info.output_params().AsHumanReadableString();
+  SendLogMessage(String::Format(
+      "%s => (hardware parameters=[%s])", __func__,
+      device_info.output_params().AsHumanReadableString().c_str()));
 
-  // WebRTC does not yet support higher rates than 96000 on the client side
+  // WebRTC does not yet support higher rates than 192000 on the client side
   // and 48000 is the preferred sample rate. Therefore, if 192000 is detected,
   // we change the rate to 48000 instead. The consequence is that the native
   // layer will be opened up at 192kHz but WebRTC will provide data at 48kHz
   // which will then be resampled by the audio converted on the browser side
   // to match the native audio layer.
   int sample_rate = device_info.output_params().sample_rate();
-  DVLOG(1) << "Audio output hardware sample rate: " << sample_rate;
   if (sample_rate >= 192000) {
-    DVLOG(1) << "Resampling from 48000 to " << sample_rate << " is required";
+    SendLogMessage(
+        String::Format("%s => (WARNING: WebRTC provides audio at 48kHz and "
+                       "resampling takes place to match %dHz)",
+                       __func__, sample_rate));
     sample_rate = 48000;
   }
   media::AudioSampleRate asr;
@@ -694,7 +845,8 @@
   // use 10 ms of data since the WebRTC client only supports multiples of 10 ms
   // as buffer size where 10 ms is preferred for lowest possible delay.
   const int source_frames_per_buffer = (sample_rate / 100);
-  DVLOG(1) << "Using WebRTC output buffer size: " << source_frames_per_buffer;
+  SendLogMessage(String::Format("%s => (source_frames_per_buffer=%d)", __func__,
+                                source_frames_per_buffer));
 
   // Setup sink parameters using same channel configuration as the source.
   // This sink is an AudioRendererSink which is implemented by an
@@ -708,7 +860,8 @@
     // WebRTC does not support channel remixing for more than 8 channels (7.1).
     // This is an attempt to "support" more than 8 channels by falling back to
     // stereo instead. See crbug.com/1003735.
-    LOG(WARNING) << "Falling back to stereo sink";
+    SendLogMessage(
+        String::Format("%s => (WARNING: sink falls back to stereo)", __func__));
     channels = 2;
     channel_layout = media::CHANNEL_LAYOUT_STEREO;
   }
@@ -719,7 +872,6 @@
   if (channel_layout == media::CHANNEL_LAYOUT_DISCRETE) {
     new_sink_params.set_channels_for_discrete(channels);
   }
-  DVLOG(1) << new_sink_params.AsHumanReadableString();
   DCHECK(new_sink_params.IsValid());
 
   // Create a FIFO if re-buffering is required to match the source input with
@@ -728,8 +880,9 @@
   const bool different_source_sink_frames =
       source_frames_per_buffer != new_sink_params.frames_per_buffer();
   if (different_source_sink_frames) {
-    DVLOG(1) << "Rebuffering from " << source_frames_per_buffer << " to "
-             << new_sink_params.frames_per_buffer();
+    SendLogMessage(String::Format("%s => (INFO: rebuffering from %d to %d)",
+                                  __func__, source_frames_per_buffer,
+                                  new_sink_params.frames_per_buffer()));
   }
   {
     base::AutoLock lock(lock_);
@@ -743,7 +896,9 @@
                                        CrossThreadUnretained(this))));
     }
     sink_params_ = new_sink_params;
-    DVLOG(1) << "New sink parameters: " << sink_params_.AsHumanReadableString();
+    SendLogMessage(
+        String::Format("%s => (sink_params=[%s])", __func__,
+                       sink_params_.AsHumanReadableString().c_str()));
   }
 
   // Specify the latency info to be passed to the browser side.
@@ -753,4 +908,10 @@
   sink_->Initialize(new_sink_params, this);
 }
 
+void WebRtcAudioRenderer::SendLogMessage(const WTF::String& message) {
+  WebRtcLogMessage(String::Format("WRAR::%s [label=%s]", message.Utf8().c_str(),
+                                  media_stream_id_.c_str())
+                       .Utf8());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
index d9ab184..ba7df5b 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <atomic>
 #include <map>
 #include <memory>
 #include <string>
@@ -14,19 +15,25 @@
 
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
+#include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "media/base/audio_decoder.h"
+#include "media/base/audio_power_monitor.h"
 #include "media/base/audio_pull_fifo.h"
 #include "media/base/audio_renderer_sink.h"
 #include "media/base/channel_layout.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_renderer.h"
 #include "third_party/blink/public/platform/web_media_stream.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/timer.h"
 #include "third_party/blink/renderer/platform/webrtc/webrtc_source.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace webrtc {
 class AudioSourceInterface;
@@ -81,6 +88,12 @@
     SEQUENCE_CHECKER(sequence_checker_);
   };
 
+  enum State {
+    UNINITIALIZED,
+    PLAYING,
+    PAUSED,
+  };
+
   WebRtcAudioRenderer(
       const scoped_refptr<base::SingleThreadTaskRunner>& signaling_thread,
       const blink::WebMediaStream& media_stream,
@@ -130,6 +143,76 @@
   void SwitchOutputDevice(const std::string& device_id,
                           media::OutputDeviceStatusCB callback) override;
 
+  // Private utility class which keeps track of the state and duration of
+  // playing (rendered on an HTML5 audio tag) audio streams. Mainly intended
+  // for logging purposes to track down "can't hear" type of issues.
+  class AudioStreamTracker {
+   public:
+    // The internal on-shot timer will use the same |task_runner| as the outer
+    // class (OC) who creates this object. |renderer| must outlive the
+    // AudioStreamTracker. See comments for |AudioStreamTracker::renderer_| why
+    // it is safe to use a raw pointer here. |sample_rate| is the current sample
+    // rate used by the audio sink (see WebRtcAudioRenderer::sink_params_).
+    explicit AudioStreamTracker(
+        scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+        WebRtcAudioRenderer* renderer,
+        int sample_rate);
+
+    // Note: the destructor takes care of logging of the duration of the stream.
+    ~AudioStreamTracker();
+
+    // This function should be called from the audio device callback thread,
+    // i.e., the so-called render thread.
+    void OnRenderCallbackCalled();
+
+    // Scans the provided audio samples and updates a power measurement. The
+    // "average power" is a running average calculated by using a first-order
+    // low-pass filter over the square of the samples scanned.
+    // Called from the audio render thread and it is safe. See comments in
+    // AudioPowerMonitor::Scan() for more details.
+    void MeasurePower(const media::AudioBus& buffer, int frames);
+
+   private:
+    // Called by the timer when it fires once after a delay of ~5 seconds from
+    // start. Reads the state of atomic |render_callbacks_started_|.
+    void CheckAlive(TimerBase*);
+
+    void LogAudioPowerLevel();
+
+    // Task runner of outer class (the creating WebRtcAudioRenderer).
+    const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+    // Using a raw pointer is safe since the OC instance will outlive this
+    // object.
+    WebRtcAudioRenderer* const renderer_;
+
+    // Stores when the timer starts. Used to calculate the stream duration.
+    const base::TimeTicks start_time_;
+
+    // Set to true in first render callback by the high-priority audio thread.
+    // Use an atomic variable since it can also be read by the outer class (OC)
+    // thread once to verify that callbacks started as intended. See comments
+    // for CheckAlive().
+    std::atomic_bool render_callbacks_started_;
+
+    // One-shot timer that fires ~5 seconds after rendering should start and
+    // calls the calls CheckAlive() which checks if |render_callbacks_started_|
+    // has been set to true or not. A message is logged to track this state.
+    // The timer uses the same task runner as the OC. Hence, the only writer of
+    // |render_callbacks_started_| is the render thread and the only reader is
+    // the OC thread. DCHECKs are used to confirm this.
+    TaskRunnerTimer<AudioStreamTracker> check_alive_timer_;
+
+    // Scans audio samples from Render() as input to compute power levels.
+    media::AudioPowerMonitor power_monitor_;
+
+    // Updated each time a power measurement is logged.
+    base::TimeTicks last_audio_level_log_time_;
+
+    base::WeakPtr<AudioStreamTracker> weak_this_;
+    base::WeakPtrFactory<AudioStreamTracker> weak_factory_{this};
+  };
+
   // Called when an audio renderer, either the main or a proxy, starts playing.
   // Here we maintain a reference count of how many renderers are currently
   // playing so that the shared play state of all the streams can be reflected
@@ -144,12 +227,6 @@
   ~WebRtcAudioRenderer() override;
 
  private:
-  enum State {
-    UNINITIALIZED,
-    PLAYING,
-    PAUSED,
-  };
-
   // Holds raw pointers to PlaingState objects.  Ownership is managed outside
   // of this type.
   typedef std::vector<PlayingState*> PlayingStates;
@@ -161,6 +238,9 @@
   // Used to DCHECK that we are called on the correct thread.
   THREAD_CHECKER(thread_checker_);
 
+  // Task runner of the creating thread.
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
   // Flag to keep track the state of the renderer.
   State state_;
 
@@ -206,6 +286,8 @@
   // |sink_|.
   void PrepareSink();
 
+  void SendLogMessage(const WTF::String& message);
+
   // The WebLocalFrame in which the audio is rendered into |sink_|.
   //
   // TODO(crbug.com/704136): Replace |source_internal_frame_| with regular
@@ -223,6 +305,12 @@
   // The media stream that holds the audio tracks that this renderer renders.
   const blink::WebMediaStream media_stream_;
 
+  // Contains a copy the unique id of the media stream. By taking a copy at
+  // construction, we can convert the id from a WebString to an std::string
+  // once and that saves resources when |media_stream_id_| is added to log
+  // messages.
+  std::string media_stream_id_;
+
   // Audio data source from the browser process.
   //
   // TODO(crbug.com/704136): Make it a Member.
@@ -230,7 +318,8 @@
 
   // Protects access to |state_|, |source_|, |audio_fifo_|,
   // |audio_delay_milliseconds_|, |fifo_delay_milliseconds_|, |current_time_|,
-  // |sink_params_|, |render_callback_count_| and |max_render_time_|.
+  // |sink_params_|, |render_callback_count_|, |max_render_time_| and
+  // |audio_stream_tracker_|.
   mutable base::Lock lock_;
 
   // Ref count for the MediaPlayers which are playing audio.
@@ -270,6 +359,11 @@
   // for logging UMA data. Logged and reset when Stop() is called.
   base::TimeDelta max_render_time_;
 
+  // Used for keeping track of and logging stats for playing audio streams.
+  // Created when a stream starts and destroyed when a stream stops.
+  // See comments for AudioStreamTracker for more details.
+  base::Optional<AudioStreamTracker> audio_stream_tracker_;
+
   DISALLOW_IMPLICIT_CONSTRUCTORS(WebRtcAudioRenderer);
 };
 
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream.cc b/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
index 9c82363..06f4c9f 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream.cc
@@ -228,18 +228,36 @@
 }
 
 void IncomingStream::ReadFromPipeAndEnqueue() {
-  DVLOG(1) << "IncomingStream::ReadFromPipeAndEnqueue() this=" << this;
+  DVLOG(1) << "IncomingStream::ReadFromPipeAndEnqueue() this=" << this
+           << " in_two_phase_read_=" << in_two_phase_read_
+           << " read_pending_=" << read_pending_;
+
+  // Protect against re-entrancy.
+  if (in_two_phase_read_) {
+    read_pending_ = true;
+    return;
+  }
+  DCHECK(!read_pending_);
 
   const void* buffer = nullptr;
   uint32_t buffer_num_bytes = 0;
   auto result = data_pipe_->BeginReadData(&buffer, &buffer_num_bytes,
                                           MOJO_BEGIN_READ_DATA_FLAG_NONE);
-
   switch (result) {
-    case MOJO_RESULT_OK:
+    case MOJO_RESULT_OK: {
+      in_two_phase_read_ = true;
+      // EnqueueBytes() may re-enter this method via pull().
       EnqueueBytes(buffer, buffer_num_bytes);
       data_pipe_->EndReadData(buffer_num_bytes);
+      in_two_phase_read_ = false;
+      if (read_pending_) {
+        read_pending_ = false;
+        // pull() will not be called when another pull() is in progress, so the
+        // maximum recursion depth is 1.
+        ReadFromPipeAndEnqueue();
+      }
       break;
+    }
 
     case MOJO_RESULT_SHOULD_WAIT:
       read_watcher_.ArmOrNotify();
@@ -250,7 +268,7 @@
       return;
 
     default:
-      DLOG(FATAL) << "FATAL ERROR";
+      NOTREACHED() << "Unexpected result: " << result;
       return;
   }
 }
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream.h b/third_party/blink/renderer/modules/webtransport/incoming_stream.h
index 9ed4de75..82ede00 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream.h
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream.h
@@ -153,6 +153,14 @@
   // True when |data_pipe_| has been detected to be closed. The close is not
   // processed until |fin_received_| is also set.
   bool is_pipe_closed_ = false;
+
+  // Indicates if we are currently performing a two-phase read from the pipe and
+  // so can't start another read.
+  bool in_two_phase_read_ = false;
+
+  // Indicates if we need to perform another read after the current one
+  // completes.
+  bool read_pending_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc b/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
index 0251898..3fcdeb5c 100644
--- a/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
+++ b/third_party/blink/renderer/modules/webtransport/incoming_stream_test.cc
@@ -72,8 +72,8 @@
   void ClosePipe() { data_pipe_producer_.reset(); }
 
   // Copies the contents of a v8::Value containing a Uint8Array to a Vector.
-  Vector<uint8_t> ToVector(const V8TestingScope& scope,
-                           v8::Local<v8::Value> v8value) {
+  static Vector<uint8_t> ToVector(const V8TestingScope& scope,
+                                  v8::Local<v8::Value> v8value) {
     Vector<uint8_t> ret;
 
     DOMUint8Array* value =
@@ -94,19 +94,23 @@
 
   // Performs a single read from |reader|, converting the output to the
   // Iterator type. Assumes that the readable stream is not errored.
-  Iterator Read(const V8TestingScope& scope,
-                ReadableStreamDefaultReader* reader) {
+  static Iterator Read(const V8TestingScope& scope,
+                       ReadableStreamDefaultReader* reader) {
     auto* script_state = scope.GetScriptState();
     ScriptPromise read_promise =
         reader->read(script_state, ASSERT_NO_EXCEPTION);
     ScriptPromiseTester tester(script_state, read_promise);
     tester.WaitUntilSettled();
     EXPECT_TRUE(tester.IsFulfilled());
-    v8::Local<v8::Value> result = tester.Value().V8Value();
+    return IteratorFromReadResult(scope, tester.Value().V8Value());
+  }
+
+  static Iterator IteratorFromReadResult(const V8TestingScope& scope,
+                                         v8::Local<v8::Value> result) {
     CHECK(result->IsObject());
     Iterator ret;
     v8::Local<v8::Value> v8value;
-    if (!V8UnpackIteratorResult(script_state, result.As<v8::Object>(),
+    if (!V8UnpackIteratorResult(scope.GetScriptState(), result.As<v8::Object>(),
                                 &ret.done)
              .ToLocal(&v8value)) {
       ADD_FAILURE() << "Couldn't unpack iterator";
@@ -378,6 +382,29 @@
   EXPECT_FALSE(incoming_stream);
 }
 
+TEST_F(IncomingStreamTest, WriteToPipeWithPendingRead) {
+  V8TestingScope scope;
+
+  auto* incoming_stream = CreateIncomingStream(scope);
+  auto* script_state = scope.GetScriptState();
+  auto* reader =
+      incoming_stream->readable()->getReader(script_state, ASSERT_NO_EXCEPTION);
+  ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
+  ScriptPromiseTester tester(script_state, read_promise);
+
+  test::RunPendingTasks();
+
+  WriteToPipe({'A'});
+
+  tester.WaitUntilSettled();
+  EXPECT_TRUE(tester.IsFulfilled());
+
+  Iterator result = IteratorFromReadResult(scope, tester.Value().V8Value());
+  EXPECT_FALSE(result.done);
+  EXPECT_THAT(result.value, ElementsAre('A'));
+  EXPECT_CALL(*mock_close_proxy_, ForgetStream());
+}
+
 }  // namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 476cd2f..fe6d0c08 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -443,6 +443,8 @@
     "bindings/v8_binding.h",
     "bindings/v8_binding_macros.h",
     "bindings/v8_cross_origin_callback_info.h",
+    "bindings/v8_cross_origin_property_support.cc",
+    "bindings/v8_cross_origin_property_support.h",
     "bindings/v8_dom_activity_logger.cc",
     "bindings/v8_dom_activity_logger.h",
     "bindings/v8_dom_wrapper.cc",
diff --git a/third_party/blink/renderer/platform/bindings/v8_binding.h b/third_party/blink/renderer/platform/bindings/v8_binding.h
index 028ef596..ed84d11 100644
--- a/third_party/blink/renderer/platform/bindings/v8_binding.h
+++ b/third_party/blink/renderer/platform/bindings/v8_binding.h
@@ -229,6 +229,12 @@
   V8SetReturnValue(info, ToV8(value, creation_context, info.GetIsolate()));
 }
 
+template <class CallbackInfo>
+void V8SetReturnValue(const CallbackInfo& info,
+                      bindings::DictionaryBase* value) {
+  V8SetReturnValue(info, ToV8(value, info.Holder(), info.GetIsolate()));
+}
+
 // Convert v8::String to a WTF::String. If the V8 string is not already
 // an external string then it is transformed into an external string at this
 // point to avoid repeated conversions.
diff --git a/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.cc b/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.cc
new file mode 100644
index 0000000..a434695b
--- /dev/null
+++ b/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.cc
@@ -0,0 +1,96 @@
+// Copyright 2020 The Chromium 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/bindings/v8_cross_origin_property_support.h"
+
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
+#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
+
+namespace blink {
+
+namespace bindings {
+
+v8::MaybeLocal<v8::Function> GetCrossOriginFunction(
+    v8::Isolate* isolate,
+    v8::FunctionCallback callback,
+    int func_length,
+    const WrapperTypeInfo* wrapper_type_info) {
+  v8::Local<v8::Context> current_context = isolate->GetCurrentContext();
+  ScriptState* script_state = ScriptState::From(current_context);
+  V8PerIsolateData* per_isolate_data = V8PerIsolateData::From(isolate);
+  const void* callback_key = reinterpret_cast<const void*>(callback);
+
+  // ES functions accessible across origins are not interface objects, but we
+  // reuse the cache of interface objects, which just works because both are
+  // V8 function template.
+  v8::Local<v8::FunctionTemplate> function_template =
+      per_isolate_data->FindInterfaceTemplate(script_state->World(),
+                                              callback_key);
+  if (function_template.IsEmpty()) {
+    v8::Local<v8::FunctionTemplate> interface_template =
+        per_isolate_data->FindInterfaceTemplate(script_state->World(),
+                                                wrapper_type_info);
+    v8::Local<v8::Signature> signature =
+        v8::Signature::New(isolate, interface_template);
+    function_template = v8::FunctionTemplate::New(
+        isolate, callback, v8::Local<v8::Value>(), signature, func_length,
+        v8::ConstructorBehavior::kThrow, v8::SideEffectType::kHasSideEffect);
+    per_isolate_data->SetInterfaceTemplate(script_state->World(), callback_key,
+                                           function_template);
+  }
+  return function_template->GetFunction(current_context);
+}
+
+v8::MaybeLocal<v8::Value> GetCrossOriginFunctionOrUndefined(
+    v8::Isolate* isolate,
+    v8::FunctionCallback callback,
+    int func_length,
+    const WrapperTypeInfo* wrapper_type_info) {
+  if (!callback) {
+    return v8::Undefined(isolate);
+  }
+  v8::Local<v8::Function> function;
+  if (GetCrossOriginFunction(isolate, callback, func_length, wrapper_type_info)
+          .ToLocal(&function)) {
+    return function;
+  }
+  return v8::MaybeLocal<v8::Value>();
+}
+
+bool IsSupportedInCrossOriginPropertyFallback(
+    v8::Isolate* isolate,
+    v8::Local<v8::Name> property_name) {
+  return (property_name == V8AtomicString(isolate, "then") ||
+          property_name == v8::Symbol::GetToStringTag(isolate) ||
+          property_name == v8::Symbol::GetHasInstance(isolate) ||
+          property_name == v8::Symbol::GetIsConcatSpreadable(isolate));
+}
+
+v8::Local<v8::Array> EnumerateCrossOriginProperties(
+    v8::Isolate* isolate,
+    base::span<const CrossOriginAttributeTableEntry> attributes,
+    base::span<const CrossOriginOperationTableEntry> operations) {
+  v8::Local<v8::Value> default_supported[] = {
+      V8AtomicString(isolate, "then"),
+      v8::Symbol::GetToStringTag(isolate),
+      v8::Symbol::GetHasInstance(isolate),
+      v8::Symbol::GetIsConcatSpreadable(isolate),
+  };
+  const uint32_t length =
+      attributes.size() + operations.size() + base::size(default_supported);
+  Vector<v8::Local<v8::Value>> elements;
+  elements.ReserveCapacity(length);
+  for (const auto& attribute : attributes)
+    elements.UncheckedAppend(V8AtomicString(isolate, attribute.name));
+  for (const auto& operation : operations)
+    elements.UncheckedAppend(V8AtomicString(isolate, operation.name));
+  for (const auto& name : default_supported)
+    elements.UncheckedAppend(name);
+  return v8::Array::New(isolate, elements.data(), elements.size());
+}
+
+}  // namespace bindings
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.h b/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.h
new file mode 100644
index 0000000..d441696a
--- /dev/null
+++ b/third_party/blink/renderer/platform/bindings/v8_cross_origin_property_support.h
@@ -0,0 +1,65 @@
+// Copyright 2020 The Chromium 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_BINDINGS_V8_CROSS_ORIGIN_PROPERTY_SUPPORT_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_V8_CROSS_ORIGIN_PROPERTY_SUPPORT_H_
+
+#include "base/containers/span.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "v8/include/v8.h"
+
+// This file provides utilities to support implementation of cross origin
+// properties in generated bindings code.  Should be used only in generated
+// bindings code.
+
+namespace blink {
+
+struct WrapperTypeInfo;
+
+namespace bindings {
+
+struct CrossOriginAttributeTableEntry final {
+  const char* name;
+  v8::FunctionCallback get_callback;
+  v8::FunctionCallback set_callback;
+  v8::GenericNamedPropertyGetterCallback get_value;
+  v8::GenericNamedPropertySetterCallback set_value;
+};
+
+struct CrossOriginOperationTableEntry final {
+  const char* name;
+  v8::FunctionCallback callback;
+  int func_length;
+};
+
+PLATFORM_EXPORT v8::MaybeLocal<v8::Function> GetCrossOriginFunction(
+    v8::Isolate* isolate,
+    v8::FunctionCallback callback,
+    int func_length,
+    const WrapperTypeInfo* wrapper_type_info);
+
+PLATFORM_EXPORT v8::MaybeLocal<v8::Value> GetCrossOriginFunctionOrUndefined(
+    v8::Isolate* isolate,
+    v8::FunctionCallback callback,
+    int func_length,
+    const WrapperTypeInfo* wrapper_type_info);
+
+// HTML 7.2.3.2 CrossOriginPropertyFallback ( P )
+// https://html.spec.whatwg.org/C/#crossoriginpropertyfallback-(-p-)
+PLATFORM_EXPORT bool IsSupportedInCrossOriginPropertyFallback(
+    v8::Isolate* isolate,
+    v8::Local<v8::Name> property_name);
+
+// HTML 7.2.3.7 CrossOriginOwnPropertyKeys ( O )
+// https://html.spec.whatwg.org/C/#crossoriginownpropertykeys-(-o-)
+PLATFORM_EXPORT v8::Local<v8::Array> EnumerateCrossOriginProperties(
+    v8::Isolate* isolate,
+    base::span<const CrossOriginAttributeTableEntry> attributes,
+    base::span<const CrossOriginOperationTableEntry> operations);
+
+}  // namespace bindings
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_BINDINGS_V8_CROSS_ORIGIN_PROPERTY_SUPPORT_H_
diff --git a/third_party/blink/renderer/platform/bindings/v8_set_return_value.h b/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
index ed639ef..6c7ffa11 100644
--- a/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
+++ b/third_party/blink/renderer/platform/bindings/v8_set_return_value.h
@@ -116,7 +116,7 @@
 
 // nullptr
 template <typename CallbackInfo>
-void V8SetReturnValue(const CallbackInfo& info, nullptr_t) {
+void V8SetReturnValue(const CallbackInfo& info, std::nullptr_t) {
   info.GetReturnValue().SetNull();
 }
 
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
index 6a2d4fd..24f05b8f 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_audio_track.cc
@@ -27,7 +27,7 @@
     : WebPlatformMediaStreamTrack(is_local_track), is_enabled_(1) {
   SendLogMessage(
       base::StringPrintf("MediaStreamAudioTrack([this=%p] {is_local_track=%s})",
-                         this, (is_local_track ? "local" : "remote")));
+                         this, (is_local_track ? "true" : "false")));
 }
 
 MediaStreamAudioTrack::~MediaStreamAudioTrack() {
diff --git a/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h b/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
index cf456d9..dd5da3c 100644
--- a/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
+++ b/third_party/blink/renderer/platform/mojo/heap_mojo_remote.h
@@ -53,6 +53,7 @@
     DCHECK(task_runner);
     wrapper_->remote().Bind(std::move(pending_remote), std::move(task_runner));
   }
+  void FlushForTesting() { return wrapper_->remote().FlushForTesting(); }
 
   void Trace(Visitor* visitor) { visitor->Trace(wrapper_); }
 
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 7be2da08..9ddc8b8 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -991,10 +991,10 @@
   typedef typename HashTraits<Value>::PeekInType ValuePeekInType;
 
   NewLinkedHashSet();
-  NewLinkedHashSet(const NewLinkedHashSet&);
-  NewLinkedHashSet(NewLinkedHashSet&&);
-  NewLinkedHashSet& operator=(const NewLinkedHashSet&);
-  NewLinkedHashSet& operator=(NewLinkedHashSet&&);
+  NewLinkedHashSet(const NewLinkedHashSet&) = default;
+  NewLinkedHashSet(NewLinkedHashSet&&) = default;
+  NewLinkedHashSet& operator=(const NewLinkedHashSet&) = default;
+  NewLinkedHashSet& operator=(NewLinkedHashSet&&) = default;
 
   ~NewLinkedHashSet() = default;
 
@@ -1072,7 +1072,7 @@
 };
 
 template <typename T, typename Allocator>
-NewLinkedHashSet<T, Allocator>::NewLinkedHashSet() {
+inline NewLinkedHashSet<T, Allocator>::NewLinkedHashSet() {
   static_assert(Allocator::kIsGarbageCollected ||
                     !IsPointerToGarbageCollectedType<T>::value,
                 "Cannot put raw pointers to garbage-collected classes into "
@@ -1080,30 +1080,6 @@
                 "HeapNewLinkedHashSet<Member<T>> instead.");
 }
 
-// TODO(keinakashima): add copy constructor after implementing iterator if
-// anybody uses it.
-
-template <typename T, typename Allocator>
-inline NewLinkedHashSet<T, Allocator>::NewLinkedHashSet(
-    NewLinkedHashSet&& other) {
-  Swap(other);
-}
-
-template <typename T, typename Allocator>
-inline NewLinkedHashSet<T, Allocator>& NewLinkedHashSet<T, Allocator>::
-operator=(const NewLinkedHashSet& other) {
-  NewLinkedHashSet tmp(other);
-  Swap(tmp);
-  return *this;
-}
-
-template <typename T, typename Allocator>
-inline NewLinkedHashSet<T, Allocator>& NewLinkedHashSet<T, Allocator>::
-operator=(NewLinkedHashSet&& other) {
-  Swap(other);
-  return *this;
-}
-
 template <typename T, typename Allocator>
 inline void NewLinkedHashSet<T, Allocator>::Swap(NewLinkedHashSet& other) {
   value_to_index_.swap(other.value_to_index_);
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc b/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
index 0c7622c..bcfb0be1 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
@@ -7,13 +7,178 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/wtf_test_helper.h"
 
 namespace WTF {
 
-TEST(NewLinkedHashSetTest, Construct) {
-  NewLinkedHashSet<int> test;
-  EXPECT_EQ(test.size(), 0u);
-  EXPECT_TRUE(test.IsEmpty());
+TEST(NewLinkedHashSetTest, CopyConstructAndAssignInt) {
+  NewLinkedHashSet<int> set1;
+  EXPECT_EQ(set1.size(), 0u);
+  EXPECT_TRUE(set1.IsEmpty());
+  set1.insert(1);
+  set1.insert(2);
+  set1.insert(3);
+  EXPECT_EQ(set1.size(), 3u);
+  NewLinkedHashSet<int> set2(set1);
+  EXPECT_EQ(set2.size(), 3u);
+  NewLinkedHashSet<int> set3;
+  EXPECT_EQ(set3.size(), 0u);
+  set3 = set2;
+  EXPECT_EQ(set3.size(), 3u);
+  auto it1 = set1.begin();
+  auto it2 = set2.begin();
+  auto it3 = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(*it1, i + 1);
+    EXPECT_EQ(*it2, i + 1);
+    EXPECT_EQ(*it3, i + 1);
+    ++it1;
+    ++it2;
+    ++it3;
+  }
+}
+
+TEST(NewLinkedHashSetTest, CopyConstructAndAssignIntPtr) {
+  NewLinkedHashSet<int*> set1;
+  EXPECT_EQ(set1.size(), 0u);
+  EXPECT_TRUE(set1.IsEmpty());
+  std::unique_ptr<int> int1 = std::make_unique<int>(1);
+  std::unique_ptr<int> int2 = std::make_unique<int>(2);
+  std::unique_ptr<int> int3 = std::make_unique<int>(3);
+  set1.insert(int1.get());
+  set1.insert(int2.get());
+  set1.insert(int3.get());
+  EXPECT_EQ(set1.size(), 3u);
+  NewLinkedHashSet<int*> set2(set1);
+  EXPECT_EQ(set2.size(), 3u);
+  NewLinkedHashSet<int*> set3;
+  EXPECT_EQ(set3.size(), 0u);
+  set3 = set2;
+  EXPECT_EQ(set3.size(), 3u);
+  auto it1 = set1.begin();
+  auto it2 = set2.begin();
+  auto it3 = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(**it1, i + 1);
+    EXPECT_EQ(**it2, i + 1);
+    EXPECT_EQ(**it3, i + 1);
+    ++it1;
+    ++it2;
+    ++it3;
+  }
+
+  for (int* ptr : set1)
+    *ptr += 1000;
+  it1 = set1.begin();
+  it2 = set2.begin();
+  it3 = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(**it1, i + 1001);
+    EXPECT_EQ(**it2, i + 1001);
+    EXPECT_EQ(**it3, i + 1001);
+    ++it1;
+    ++it2;
+    ++it3;
+  }
+}
+
+TEST(NewLinkedHashSetTest, CopyConstructAndAssignString) {
+  NewLinkedHashSet<String> set1;
+  EXPECT_EQ(set1.size(), 0u);
+  EXPECT_TRUE(set1.IsEmpty());
+  set1.insert("1");
+  set1.insert("2");
+  set1.insert("3");
+  EXPECT_EQ(set1.size(), 3u);
+  NewLinkedHashSet<String> set2(set1);
+  EXPECT_EQ(set2.size(), 3u);
+  NewLinkedHashSet<String> set3;
+  EXPECT_EQ(set3.size(), 0u);
+  set3 = set2;
+  EXPECT_EQ(set3.size(), 3u);
+  auto it1 = set1.begin();
+  auto it2 = set2.begin();
+  auto it3 = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(*it1, String(Vector<UChar>({'1' + i})));
+    EXPECT_EQ(*it2, String(Vector<UChar>({'1' + i})));
+    EXPECT_EQ(*it3, String(Vector<UChar>({'1' + i})));
+    ++it1;
+    ++it2;
+    ++it3;
+  }
+}
+
+TEST(NewLinkedHashSetTest, MoveConstructAndAssignInt) {
+  NewLinkedHashSet<ValueInstanceCount<int>> set1;
+  EXPECT_EQ(set1.size(), 0u);
+  EXPECT_TRUE(set1.IsEmpty());
+  int counter1 = 0;
+  int counter2 = 0;
+  int counter3 = 0;
+  set1.insert(ValueInstanceCount<int>(&counter1, 1));
+  set1.insert(ValueInstanceCount<int>(&counter2, 2));
+  set1.insert(ValueInstanceCount<int>(&counter3, 3));
+  EXPECT_EQ(set1.size(), 3u);
+  NewLinkedHashSet<ValueInstanceCount<int>> set2(std::move(set1));
+  EXPECT_EQ(set2.size(), 3u);
+  NewLinkedHashSet<ValueInstanceCount<int>> set3;
+  EXPECT_EQ(set3.size(), 0u);
+  set3 = std::move(set2);
+  EXPECT_EQ(set3.size(), 3u);
+  auto it = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(it->Value(), i + 1);
+    ++it;
+  }
+
+  // Only move constructors were used, each object is only in set3.
+  // Count 2x because each set uses hash map and vector.
+  EXPECT_EQ(counter1, 2);
+  EXPECT_EQ(counter2, 2);
+  EXPECT_EQ(counter3, 2);
+
+  NewLinkedHashSet<ValueInstanceCount<int>> set4(set3);
+  // Copy constructor was used, each object is in set3 and set4.
+  EXPECT_EQ(counter1, 4);
+  EXPECT_EQ(counter2, 4);
+  EXPECT_EQ(counter3, 4);
+}
+
+TEST(NewLinkedHashSetTest, MoveConstructAndAssignString) {
+  NewLinkedHashSet<ValueInstanceCount<String>> set1;
+  EXPECT_EQ(set1.size(), 0u);
+  EXPECT_TRUE(set1.IsEmpty());
+  int counter1 = 0;
+  int counter2 = 0;
+  int counter3 = 0;
+  set1.insert(ValueInstanceCount<String>(&counter1, "1"));
+  set1.insert(ValueInstanceCount<String>(&counter2, "2"));
+  set1.insert(ValueInstanceCount<String>(&counter3, "3"));
+  EXPECT_EQ(set1.size(), 3u);
+  NewLinkedHashSet<ValueInstanceCount<String>> set2(std::move(set1));
+  EXPECT_EQ(set2.size(), 3u);
+  NewLinkedHashSet<ValueInstanceCount<String>> set3;
+  EXPECT_EQ(set3.size(), 0u);
+  set3 = std::move(set2);
+  EXPECT_EQ(set3.size(), 3u);
+  auto it = set3.begin();
+  for (int i = 0; i < 3; i++) {
+    EXPECT_EQ(it->Value(), String(Vector<UChar>({'1' + i})));
+    ++it;
+  }
+
+  // Only move constructors were used, each object is only in set3.
+  // Count 2x because each set uses hash map and vector.
+  EXPECT_EQ(counter1, 2);
+  EXPECT_EQ(counter2, 2);
+  EXPECT_EQ(counter3, 2);
+
+  NewLinkedHashSet<ValueInstanceCount<String>> set4(set3);
+  // Copy constructor was used, each object is in set3 and set4.
+  EXPECT_EQ(counter1, 4);
+  EXPECT_EQ(counter2, 4);
+  EXPECT_EQ(counter3, 4);
 }
 
 TEST(NewLinkedHashSetTest, Iterator) {
diff --git a/third_party/blink/renderer/platform/wtf/vector_backed_linked_list.h b/third_party/blink/renderer/platform/wtf/vector_backed_linked_list.h
index 02a481a..60383b8a 100644
--- a/third_party/blink/renderer/platform/wtf/vector_backed_linked_list.h
+++ b/third_party/blink/renderer/platform/wtf/vector_backed_linked_list.h
@@ -44,13 +44,10 @@
         next_index_(next_index),
         value_(std::move(value)) {}
 
-  VectorBackedLinkedListNode(const VectorBackedLinkedListNode& other) = delete;
-
+  VectorBackedLinkedListNode(const VectorBackedLinkedListNode& other) = default;
   VectorBackedLinkedListNode(VectorBackedLinkedListNode&& other) = default;
-
   VectorBackedLinkedListNode& operator=(
-      const VectorBackedLinkedListNode& other) = delete;
-
+      const VectorBackedLinkedListNode& other) = default;
   VectorBackedLinkedListNode& operator=(VectorBackedLinkedListNode&& other) =
       default;
 
@@ -119,9 +116,10 @@
 
   VectorBackedLinkedList();
 
-  // TODO(keinakashima): implement copy constructor & copy assignment operator
-  VectorBackedLinkedList(VectorBackedLinkedList&&);
-  VectorBackedLinkedList& operator=(VectorBackedLinkedList&&);
+  VectorBackedLinkedList(const VectorBackedLinkedList&) = default;
+  VectorBackedLinkedList(VectorBackedLinkedList&&) = default;
+  VectorBackedLinkedList& operator=(const VectorBackedLinkedList&) = default;
+  VectorBackedLinkedList& operator=(VectorBackedLinkedList&&) = default;
 
   ~VectorBackedLinkedList() = default;
 
@@ -471,20 +469,6 @@
 }
 
 template <typename T, typename Allocator>
-inline VectorBackedLinkedList<T, Allocator>::VectorBackedLinkedList(
-    VectorBackedLinkedList&& other) {
-  swap(other);
-}
-
-template <typename T, typename Allocator>
-inline VectorBackedLinkedList<T, Allocator>&
-VectorBackedLinkedList<T, Allocator>::operator=(
-    VectorBackedLinkedList&& other) {
-  swap(other);
-  return *this;
-}
-
-template <typename T, typename Allocator>
 inline void VectorBackedLinkedList<T, Allocator>::swap(
     VectorBackedLinkedList& other) {
   nodes_.swap(other.nodes_);
diff --git a/third_party/blink/renderer/platform/wtf/wtf_test_helper.h b/third_party/blink/renderer/platform/wtf/wtf_test_helper.h
index 8f31365..35b1e73c 100644
--- a/third_party/blink/renderer/platform/wtf/wtf_test_helper.h
+++ b/third_party/blink/renderer/platform/wtf/wtf_test_helper.h
@@ -5,6 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_WTF_TEST_HELPER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_WTF_TEST_HELPER_H_
 
+#include <type_traits>
+
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -177,6 +179,75 @@
   using Hash = CountCopyHash;
 };
 
+template <typename T>
+class ValueInstanceCount final {
+ public:
+  ValueInstanceCount() : counter_(nullptr), value_(T()) {}
+  explicit ValueInstanceCount(int* counter, T value = T())
+      : counter_(counter), value_(value) {
+    DCHECK(counter_);
+    *counter = 1;
+  }
+  ValueInstanceCount(const ValueInstanceCount& other)
+      : counter_(other.counter_), value_(other.value_) {
+    if (counter_)
+      ++*counter_;
+  }
+  ValueInstanceCount& operator=(const ValueInstanceCount& other) {
+    counter_ = other.counter_;
+    value_ = other.value_;
+    if (counter_)
+      ++*counter_;
+    return *this;
+  }
+  ~ValueInstanceCount() {
+    if (counter_)
+      --*counter_;
+  }
+
+  const int* Counter() const { return counter_; }
+  const T& Value() const { return value_; }
+
+ private:
+  int* counter_;
+  T value_;
+};
+
+template <typename T>
+struct ValueInstanceCountHashTraits
+    : public GenericHashTraits<ValueInstanceCount<T>> {
+  static const bool kEmptyValueIsZero = false;
+  static const bool kHasIsEmptyValueFunction = true;
+  static bool IsEmptyValue(const ValueInstanceCount<T>& value) {
+    return !value.Counter();
+  }
+  static void ConstructDeletedValue(ValueInstanceCount<T>& slot, bool) {}
+  static bool IsDeletedValue(const ValueInstanceCount<T>& value) {
+    return false;
+  }
+};
+
+template <typename T>
+struct ValueInstanceCountHash : public PtrHash<const int*> {
+  static unsigned GetHash(const ValueInstanceCount<T>& value) {
+    return PtrHash<const int>::GetHash(value.Counter());
+  }
+  static bool Equal(const ValueInstanceCount<T>& left,
+                    const ValueInstanceCount<T>& right) {
+    return PtrHash<const int>::Equal(left.Counter(), right.Counter());
+  }
+  static const bool safe_to_compare_to_empty_or_deleted = true;
+};
+
+template <typename T>
+struct HashTraits<ValueInstanceCount<T>>
+    : public ValueInstanceCountHashTraits<T> {};
+
+template <typename T>
+struct DefaultHash<ValueInstanceCount<T>> {
+  using Hash = ValueInstanceCountHash<T>;
+};
+
 class DummyRefCounted : public RefCounted<DummyRefCounted> {
  public:
   DummyRefCounted(bool& is_deleted) : is_deleted_(is_deleted) {
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 7fceee1..dad01ff 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2938,6 +2938,7 @@
 crbug.com/947951 [ Win ] external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch.html [ Pass Timeout ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac10.14 ] external/wpt/preload/download-resources.html [ Timeout ]
 crbug.com/626703 [ Mac10.10 ] virtual/threaded/external/wpt/css/css-scroll-snap/snap-after-initial-layout/writing-mode-vertical-rl.html [ Timeout ]
 crbug.com/626703 [ Mac10.10 ] virtual/threaded/external/wpt/requestidlecallback/callback-invoked.html [ Timeout ]
 crbug.com/626703 [ Mac10.10 ] virtual/threaded/external/wpt/css/css-animations/parsing/animation-shorthand.html [ Timeout ]
@@ -4714,11 +4715,7 @@
 crbug.com/791529 external/wpt/css/css-variables/variable-transitions-value-before-transition-property-variable.html [ Skip ]
 
 # Feature Policy changes fullscreen behaviour, tests need updating
-crbug.com/718155 fullscreen/full-screen-iframe-not-allowed.html [ Failure ]
 crbug.com/718155 fullscreen/full-screen-restrictions.html [ Failure Timeout ]
-crbug.com/718155 media/video-controls-fullscreen-iframe-not-allowed.html [ Failure ]
-crbug.com/718155 virtual/audio-service/media/video-controls-fullscreen-iframe-not-allowed.html [ Failure ]
-crbug.com/718155 virtual/android/fullscreen/full-screen-iframe-not-allowed.html [ Failure ]
 crbug.com/718155 virtual/android/fullscreen/full-screen-restrictions.html [ Failure Timeout ]
 
 crbug.com/852645 gamepad/full-screen-gamepad.html [ Timeout ]
@@ -5718,19 +5715,6 @@
 # Sheriff 2019-04-25
 crbug.com/956736 virtual/gpu-rasterization/images/imagemap-focus-ring-outline-color-not-inherited-from-map.html [ Pass Failure ]
 
-crbug.com/946534 [ Mac10.10 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.10 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.10 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
-crbug.com/946534 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
-
 crbug.com/1065059 [ Linux ] virtual/threaded-no-composited-antialiasing/animations/animationworklet/scroll-timeline-non-scrollable.html [ Pass Crash ]
 crbug.com/1065059 [ Linux ] virtual/threaded-no-composited-antialiasing/animations/animationworklet/playback-rate-scroll-timeline-accelerated-property.html [ Pass Crash ]
 crbug.com/1065059 [ Linux ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html [ Pass Crash ]
diff --git a/third_party/blink/web_tests/css3/flexbox/minimum-size-image.html b/third_party/blink/web_tests/css3/flexbox/minimum-size-image.html
deleted file mode 100644
index 67bd361..0000000
--- a/third_party/blink/web_tests/css3/flexbox/minimum-size-image.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!DOCTYPE html>
-
-<link href="resources/flexbox.css" rel="stylesheet">
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
-
-<body onload="checkLayout('.flexbox')">
-<div id=log></div>
-
-<div class="flexbox" style="width: 10px;" data-expected-width="10">
-    <!-- should use content width, clamped by converted max-height, as minimum width. -->
-    <img src="../../images/resources/green-100.png" style="max-height: 5px;"
-         data-expected-width="5" data-expected-height="5">
-</div>
-
-<div class="flexbox" style="width: 10px;" data-expected-width="10">
-    <!-- should use min(specified, content width) = 10px as minimum width,
-         which the image will shrink to due to default flex-shrink. -->
-    <img src="../../images/resources/green-10.png" style="width: 100px;" data-expected-width="10">
-</div>
-
-
-<div class="flexbox" style="width: 10px;" data-expected-width="10">
-    <!-- should use content width, clamped by converted min-height, as minimum width. -->
-    <img src="../../images/resources/green-100.png" style="max-height: 5px;"
-         data-expected-width="5" data-expected-height="5">
-</div>
-
-<div class="flexbox" style="width: 10px;" data-expected-width="10">
-    <!-- should use content width, clamped by converted min-height, as minimum width. -->
-    <img src="../../images/resources/green-100.png" style="max-height: 5px; min-height: 10px;"
-         data-expected-width="10" data-expected-height="10">
-</div>
-
-<div class="flexbox" style="width: 10px;" data-expected-width="10">
-    <!-- should use min(transferred, content width) = 10px as minimum width,
-         which the image will shrink to due to default flex-shrink. -->
-    <img src="../../images/resources/green-10.png" style="height: 100px;" data-expected-width="10">
-</div>
-
-<div class="flexbox column" style="height: 10px;" data-expected-height="10">
-    <!-- should use content height, clamped by converted max-width, as minimum height. -->
-    <img src="../../images/resources/green-100.png" style="max-width: 5px;"
-         data-expected-width="5" data-expected-height="5">
-</div>
-
-<div class="flexbox column" style="height: 10px;" data-expected-height="10">
-    <!-- should use min(specified, content height) = 10px as minimum height,
-         which the image will shrink to due to default flex-shrink. -->
-    <img src="../../images/resources/green-10.png" style="height: 100px;" data-expected-height="10">
-</div>
-
-<div class="flexbox" style="height: 10px;" data-expected-height="10">
-    <!-- should use content height, clamped by converted min-height, as minimum height. -->
-    <img src="../../images/resources/green-100.png" style="max-height: 5px;"
-         data-expected-height="5" data-expected-height="5">
-</div>
-
-<div class="flexbox" style="height: 10px;" data-expected-height="10">
-    <!-- should use content height, clamped by converted min-width, as minimum height. -->
-    <img src="../../images/resources/green-100.png" style="max-width: 5px; min-width: 10px;"
-         data-expected-height="10" data-expected-width="10">
-</div>
-
-<div class="flexbox column" style="height: 10px;" data-expected-height="10">
-    <!-- should use min(transferred, content height) = 10px as minimum height,
-         which the image will shrink to due to default flex-shrink. -->
-    <img src="../../images/resources/green-10.png" style="width: 100px;" data-expected-height="10">
-</div>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index 33fe265..c42401f 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -142448,6 +142448,9 @@
    "css/css-flexbox/support/100x100-green.png": [
     []
    ],
+   "css/css-flexbox/support/10x10-green.png": [
+    []
+   ],
    "css/css-flexbox/support/1x1-green.png": [
     []
    ],
@@ -142982,6 +142985,9 @@
    "css/css-fonts/font-weight-xxx-large-ref.html": [
     []
    ],
+   "css/css-fonts/generic-family-keywords-001-expected.txt": [
+    []
+   ],
    "css/css-fonts/idlharness-expected.txt": [
     []
    ],
@@ -143201,6 +143207,9 @@
    "css/css-fonts/support/diamond.png": [
     []
    ],
+   "css/css-fonts/support/font-family-keywords.js": [
+    []
+   ],
    "css/css-fonts/support/font-weight-bolder-001-ref.png": [
     []
    ],
@@ -163757,6 +163766,9 @@
    "feature-policy/resources/feature-policy-report-json.js": [
     []
    ],
+   "feature-policy/resources/feature-policy-screen-wakelock.html": [
+    []
+   ],
    "feature-policy/resources/feature-policy-serial-worker.html": [
     []
    ],
@@ -163775,9 +163787,6 @@
    "feature-policy/resources/feature-policy-usb.html": [
     []
    ],
-   "feature-policy/resources/feature-policy-wakelock.html": [
-    []
-   ],
    "feature-policy/resources/featurepolicy.js": [
     []
    ],
@@ -218804,6 +218813,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-aspect-ratio-img-column-011.html": [
+    [
+     "css/css-flexbox/flex-aspect-ratio-img-column-011.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/flex-basis-009.html": [
     [
      "css/css-flexbox/flex-basis-009.html",
@@ -218864,6 +218879,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-minimum-width-flex-items-014.html": [
+    [
+     "css/css-flexbox/flex-minimum-width-flex-items-014.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/flex-outer-flexbox-column-recalculate-height-on-resize-001.html": [
     [
      "css/css-flexbox/flex-outer-flexbox-column-recalculate-height-on-resize-001.html",
@@ -219368,12 +219389,6 @@
      {}
     ]
    ],
-   "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html": [
-    [
-     "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html",
-     {}
-    ]
-   ],
    "css/css-flexbox/intrinsic-width-orthogonal-writing-mode.html": [
     [
      "css/css-flexbox/intrinsic-width-orthogonal-writing-mode.html",
@@ -219386,6 +219401,12 @@
      {}
     ]
    ],
+   "css/css-flexbox/max-width-violation.html": [
+    [
+     "css/css-flexbox/max-width-violation.html",
+     {}
+    ]
+   ],
    "css/css-flexbox/order_value.html": [
     [
      "css/css-flexbox/order_value.html",
@@ -219746,6 +219767,12 @@
      {}
     ]
    ],
+   "css/css-fonts/generic-family-keywords-002.html": [
+    [
+     "css/css-fonts/generic-family-keywords-002.html",
+     {}
+    ]
+   ],
    "css/css-fonts/idlharness.html": [
     [
      "css/css-fonts/idlharness.html",
@@ -234704,6 +234731,12 @@
      {}
     ]
    ],
+   "css/cssom/font-family-serialization-001.html": [
+    [
+     "css/cssom/font-family-serialization-001.html",
+     {}
+    ]
+   ],
    "css/cssom/font-shorthand-serialization.html": [
     [
      "css/cssom/font-shorthand-serialization.html",
@@ -323203,6 +323236,14 @@
      {}
     ]
    ],
+   "webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html": [
+    [
+     "webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html",
+     {
+      "timeout": "long"
+     }
+    ]
+   ],
    "webrtc-identity/RTCPeerConnection-constructor.html": [
     [
      "webrtc-identity/RTCPeerConnection-constructor.html",
@@ -382069,6 +382110,10 @@
    "70ebd378a4a7c00059bed9e7ff57febd5a252fba",
    "reftest"
   ],
+  "css/css-flexbox/flex-aspect-ratio-img-column-011.html": [
+   "66cb0e015f6618eb7cc3daee99f29ddbf7ed4729",
+   "testharness"
+  ],
   "css/css-flexbox/flex-aspect-ratio-img-row-001.html": [
    "14fabf760ca4293abf5af9e618b2e2ed627be2b2",
    "reftest"
@@ -382501,6 +382546,10 @@
    "6ee9ed10b5663b381f0238096117e8d70ca0347b",
    "reftest"
   ],
+  "css/css-flexbox/flex-minimum-width-flex-items-014.html": [
+   "080169b52d7fdf39cbf6ff970c3100480e46d2a3",
+   "testharness"
+  ],
   "css/css-flexbox/flex-order-ref.html": [
    "02f0eb35752e805aa2bc0bd339f73ff2b197c99e",
    "support"
@@ -384741,10 +384790,6 @@
    "684233223b82c7105a9550e4957597acc0153e75",
    "manual"
   ],
-  "css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html": [
-   "080169b52d7fdf39cbf6ff970c3100480e46d2a3",
-   "testharness"
-  ],
   "css/css-flexbox/intrinsic-width-orthogonal-writing-mode.html": [
    "79ce88a64c266f5ec02702d510668c642c0f91cb",
    "testharness"
@@ -384809,6 +384854,10 @@
    "1d860b44003fa9e2a765e37fdff8a8afbf78a27e",
    "reftest"
   ],
+  "css/css-flexbox/max-width-violation.html": [
+   "ea2779d81c5e4bab26b77337173f64ac0c08f493",
+   "testharness"
+  ],
   "css/css-flexbox/negative-margins-001.html": [
    "cd14ce0d4ede6f950ea6fd92176cfea662e45170",
    "reftest"
@@ -385301,6 +385350,10 @@
    "25b76c3c6f216793a36b1f29287dafd993898c67",
    "support"
   ],
+  "css/css-flexbox/support/10x10-green.png": [
+   "8c39b0d02d342bbf1edd2ebbcc2ff4ff8b032e10",
+   "support"
+  ],
   "css/css-flexbox/support/1x1-green.png": [
    "b98ca0ba0a03c580ac339e4a3653539cfa8edc71",
    "support"
@@ -386821,8 +386874,16 @@
    "480396ea16f81e96b411b7c75a7cd33e758b3e34",
    "testharness"
   ],
+  "css/css-fonts/generic-family-keywords-001-expected.txt": [
+   "85ec8839fba729aa82168c814ca086c7daa88411",
+   "support"
+  ],
   "css/css-fonts/generic-family-keywords-001.html": [
-   "aa9fb5a6a03bb330d35669f903a1ee7c0f138e18",
+   "bd39bac3ff8b3cda7e7eb60c75b0b5e6589f6110",
+   "testharness"
+  ],
+  "css/css-fonts/generic-family-keywords-002.html": [
+   "7399860b03c6cd69dc48750b884221177112373b",
    "testharness"
   ],
   "css/css-fonts/idlharness-expected.txt": [
@@ -387501,6 +387562,10 @@
    "51112efc7cab5b20a86bae9e4c2aebd68a5230b0",
    "support"
   ],
+  "css/css-fonts/support/font-family-keywords.js": [
+   "fc5b723e6875c8abe4905a23b8ab631db9934dfd",
+   "support"
+  ],
   "css/css-fonts/support/font-weight-bolder-001-ref.png": [
    "42675efe43e614e824ae500ce6cbd511a22f7a8e",
    "support"
@@ -438337,6 +438402,10 @@
    "721c6ea35572d42e2782d6f40f7e73539df35b3a",
    "testharness"
   ],
+  "css/cssom/font-family-serialization-001.html": [
+   "6c59b985c4fd5483b4c3f186e2917a20b428ee5b",
+   "testharness"
+  ],
   "css/cssom/font-shorthand-serialization.html": [
    "29082f83eaf75a0831e5175b8b1217c4ecf4ebcd",
    "testharness"
@@ -455425,6 +455494,10 @@
    "08a0ecaded8c5af917be1ae1287d455483205e45",
    "support"
   ],
+  "feature-policy/resources/feature-policy-screen-wakelock.html": [
+   "7d3dab45af1d57c805f14cfdbed142ddb2415eba",
+   "support"
+  ],
   "feature-policy/resources/feature-policy-serial-worker.html": [
    "9e6a7d02ba2b8eef1fcc12d8049af830688e6946",
    "support"
@@ -455449,10 +455522,6 @@
    "99d47c6f3929bd61f7dfdefb1c5488c396abc172",
    "support"
   ],
-  "feature-policy/resources/feature-policy-wakelock.html": [
-   "7d3dab45af1d57c805f14cfdbed142ddb2415eba",
-   "support"
-  ],
   "feature-policy/resources/featurepolicy.js": [
    "9aba0ab8c675352301802f2386f07470585d7e97",
    "support"
@@ -521982,11 +522051,11 @@
    "testharness"
   ],
   "wake-lock/wakelock-disabled-by-feature-policy.https.sub.html": [
-   "3c902cc91952a3ee849ffc19a3ccc1e25098b107",
+   "821ec62885d61b214f766b4c8e173f8b67855f38",
    "testharness"
   ],
   "wake-lock/wakelock-disabled-by-feature-policy.https.sub.html.headers": [
-   "1d3769ec794b353e8c614e64ce2c1b0b1010e528",
+   "5d910ce6df801980a26fb191589201f614ed02af",
    "support"
   ],
   "wake-lock/wakelock-document-hidden-manual.https.html": [
@@ -521994,27 +522063,27 @@
    "manual"
   ],
   "wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html": [
-   "f28a96141c8994125b04c29e440743d2e7837544",
+   "2b2e0c94cc744ff6c19f70397e4383fa2d809cff",
    "testharness"
   ],
   "wake-lock/wakelock-enabled-by-feature-policy-attribute.https.sub.html": [
-   "5587a85d4b027d360c35103f99bb6e6afe31f18a",
+   "b8ab3b0301798bc7ceaed025e89b0a36dfa5b58c",
    "testharness"
   ],
   "wake-lock/wakelock-enabled-by-feature-policy.https.sub.html": [
-   "d0f5a15c84bf83dfdaf801e8709d1d7c37f6d91a",
+   "376359e888d6191b9eb1241373b3d411bb5bd2d0",
    "testharness"
   ],
   "wake-lock/wakelock-enabled-by-feature-policy.https.sub.html.headers": [
-   "34b7437443cd9998c423260d05202be05d7b5d38",
+   "7810751f8051c14818b290aa9ea7edab04bca3e9",
    "support"
   ],
   "wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html": [
-   "73f343867e62c46c4f6e5a4458a384ef652bafd1",
+   "a2d3725857be76f33b33257c0c3373edb68396be",
    "testharness"
   ],
   "wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers": [
-   "6f05d23550e2b0c967fac5b436a5e3336222ffe2",
+   "6bc94277cf9a4610c33274bd5b984e41d389622a",
    "support"
   ],
   "wake-lock/wakelock-insecure-context.any.js": [
@@ -522034,7 +522103,7 @@
    "testharness"
   ],
   "wake-lock/wakelock-supported-by-feature-policy.html": [
-   "d6289fff43c8717ac6e9ace59713e6fd70bd249d",
+   "e7f9c8cb283c20d72c6d936398b9c352a2a0b88a",
    "testharness"
   ],
   "wake-lock/wakelock-type.https.any.js": [
@@ -526301,6 +526370,10 @@
    "29dfc19a6b68900679b06b3c3527c177c4e38b48",
    "testharness"
   ],
+  "webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html": [
+   "0902118a456e6f072d4a9b0791a4f2ea86a54e72",
+   "testharness"
+  ],
   "webrtc-identity/META.yml": [
    "940144cee1da5f2f0f2bdd27a8dfc8f5bfcc6a1d",
    "support"
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
index 7dea4609..ceb5f2d7 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html
@@ -21,7 +21,13 @@
     background-color: red;
   }
 
+  /* Hide scrollbars to avoid unnecessary visual issues related to them */
+  #scroller::-webkit-scrollbar {
+    display: none;
+  }
+
   #scroller {
+    scrollbar-width: none;
     overflow: auto;
     height: 100px;
     width: 100px;
@@ -75,4 +81,4 @@
       });
     });
   });
-</script>
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
index fe92232..1316d69a 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-ref.html
@@ -16,7 +16,13 @@
     background-color: red;
   }
 
+  /* Hide scrollbars to avoid unnecessary visual issues related to them */
+  #scroller::-webkit-scrollbar {
+    display: none;
+  }
+
   #scroller {
+    scrollbar-width: none;
     overflow: auto;
     height: 100px;
     width: 100px;
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
index 5810e17..917b044 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller-ref.html
@@ -1,7 +1,13 @@
 <!DOCTYPE html>
 <title>Reference for Scroll timeline with WorkletAnimation using the root scroller</title>
 <style>
+  /* Hide scrollbars to avoid unnecessary visual issues related to them */
+  html::-webkit-scrollbar {
+    display: none;
+  }
+
   html {
+    scrollbar-width: none;
     min-height: 100%;
     min-width: 100%;
     padding-bottom: 100px;
@@ -34,4 +40,4 @@
     const maxScroll = scroller.scrollHeight - scroller.clientHeight;
     scroller.scrollTop = 0.5 * maxScroll;
   });
-</script>
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html
index be577dcc..2b2a383 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html
@@ -1,7 +1,8 @@
 <html class="reftest-wait">
 <title>Scroll timeline with WorkletAnimation using the root scroller</title>
 <link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
-<meta name="assert" content="Worklet animation correctly updates values when using the root scroller as the source for the ScrollTimeline">
+<meta name="assert"
+  content="Worklet animation correctly updates values when using the root scroller as the source for the ScrollTimeline">
 <link rel="match" href="worklet-animation-with-scroll-timeline-root-scroller-ref.html">
 
 <script src="/web-animations/testcommon.js"></script>
@@ -9,7 +10,13 @@
 <script src="common.js"></script>
 
 <style>
+  /* Hide scrollbars to avoid unnecessary visual issues related to them */
+  html::-webkit-scrollbar {
+    display: none;
+  }
+
   html {
+    scrollbar-width: none;
     min-height: 100%;
     min-width: 100%;
     padding-bottom: 100px;
@@ -33,15 +40,15 @@
 <div id="covered"></div>
 
 <script>
-  registerPassthroughAnimator().then(()=>{
+  registerPassthroughAnimator().then(() => {
     const box = document.getElementById('box');
     const effect = new KeyframeEffect(box,
       [
-        {transform: 'translateY(0)', opacity: 1},
-        {transform: 'translateY(200px)', opacity: 0}
+        { transform: 'translateY(0)', opacity: 1 },
+        { transform: 'translateY(200px)', opacity: 0 }
       ], {
-        duration: 1000,
-      }
+      duration: 1000,
+    }
     );
 
     const scroller = document.scrollingElement;
diff --git a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html
index 7006f8f..35eaddc 100644
--- a/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html
+++ b/third_party/blink/web_tests/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline.https.html
@@ -21,7 +21,13 @@
     background-color: red;
   }
 
+  /* Hide scrollbars to avoid unnecessary visual issues related to them */
+  #scroller::-webkit-scrollbar {
+    display: none;
+  }
+
   #scroller {
+    scrollbar-width: none;
     overflow: auto;
     height: 100px;
     width: 100px;
@@ -45,11 +51,11 @@
     const box = document.getElementById('box');
     const effect = new KeyframeEffect(box,
       [
-      { transform: 'translateY(0)', opacity: 1},
-      { transform: 'translateY(200px)', opacity: 0}
+        { transform: 'translateY(0)', opacity: 1 },
+        { transform: 'translateY(200px)', opacity: 0 }
       ], {
-        duration: 1000,
-      }
+      duration: 1000,
+    }
     );
 
     const scroller = document.getElementById('scroller');
@@ -65,4 +71,4 @@
       takeScreenshot();
     });
   });
-</script>
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/animation-base-response-003.html b/third_party/blink/web_tests/external/wpt/css/css-animations/animation-base-response-003.html
new file mode 100644
index 0000000..d6e6da7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/animation-base-response-003.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Tests that identical elements in the base style responds to font-size animation</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  @keyframes font_size_animation {
+    from { font-size: 10px; }
+    to { font-size: 20px; }
+  }
+  div {
+    font-size: 1px;
+    min-width: 1em;
+    animation: font_size_animation steps(2, end) 1000s -500s;
+  }
+</style>
+<div></div>
+<div></div>
+<div></div>
+<script>
+test(() => {
+  let divs = document.querySelectorAll('div');
+  for (let div of divs)
+    assert_equals(getComputedStyle(div).getPropertyValue('min-width'), '15px');
+}, 'Identical elements are all responsive to font-size animation');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-aspect-ratio-img-column-011.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-aspect-ratio-img-column-011.html
new file mode 100644
index 0000000..66cb0e015
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-aspect-ratio-img-column-011.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<title>CSS Flexbox: images' aspect ratio in flex box with flex-direction: column for min-size: auto</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-containers">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-direction-property">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#min-size-auto">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#replaced-intrinsic">
+<link rel="help" href="https://crbug.com/581535">
+<link rel="help" href="https://crbug.com/581361">
+<link href="support/flexbox.css" rel="stylesheet">
+<meta name="assert" content="Images maintain aspect ratio in flex box with column direction with min-size: auto.">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.flexbox')">
+<div id=log></div>
+
+<div class="flexbox" style="width: 10px;" data-expected-width="10">
+    <!-- should use content width, clamped by converted max-height, as minimum width. -->
+    <img src="support/100x100-green.png" style="max-height: 5px;"
+         data-expected-width="5" data-expected-height="5">
+</div>
+
+<div class="flexbox" style="width: 10px;" data-expected-width="10">
+    <!-- should use min(specified, content width) = 10px as minimum width,
+         which the image will shrink to due to default flex-shrink. -->
+    <img src="support/10x10-green.png" style="width: 100px;" data-expected-width="10">
+</div>
+
+<div class="flexbox" style="width: 10px;" data-expected-width="10">
+    <!-- should use content width, clamped by converted min-height, as minimum width. -->
+    <img src="support/100x100-green.png" style="max-height: 5px;"
+         data-expected-width="5" data-expected-height="5">
+</div>
+
+<div class="flexbox" style="width: 10px;" data-expected-width="10">
+    <!-- should use content width, clamped by converted min-height, as minimum width. -->
+    <img src="support/100x100-green.png" style="max-height: 5px; min-height: 10px;"
+         data-expected-width="10" data-expected-height="10">
+</div>
+
+<div class="flexbox" style="width: 10px;" data-expected-width="10">
+    <!-- should use min(transferred, content width) = 10px as minimum width,
+         which the image will shrink to due to default flex-shrink. -->
+    <img src="support/10x10-green.png" style="height: 100px;" data-expected-width="10">
+</div>
+
+<div class="flexbox column" style="height: 10px;" data-expected-height="10">
+    <!-- should use content height, clamped by converted max-width, as minimum height. -->
+    <img src="support/100x100-green.png" style="max-width: 5px;"
+         data-expected-width="5" data-expected-height="5">
+</div>
+
+<div class="flexbox column" style="height: 10px;" data-expected-height="10">
+    <!-- should use min(specified, content height) = 10px as minimum height,
+         which the image will shrink to due to default flex-shrink. -->
+    <img src="support/10x10-green.png" style="height: 100px;" data-expected-height="10">
+</div>
+
+<div class="flexbox" style="height: 10px;" data-expected-height="10">
+    <!-- should use content height, clamped by converted min-height, as minimum height. -->
+    <img src="support/100x100-green.png" style="max-height: 5px;"
+         data-expected-height="5" data-expected-height="5">
+</div>
+
+<div class="flexbox" style="height: 10px;" data-expected-height="10">
+    <!-- should use content height, clamped by converted min-width, as minimum height. -->
+    <img src="support/100x100-green.png" style="max-width: 5px; min-width: 10px;"
+         data-expected-height="10" data-expected-width="10">
+</div>
+
+<div class="flexbox column" style="height: 10px;" data-expected-height="10">
+    <!-- should use min(transferred, content height) = 10px as minimum height,
+         which the image will shrink to due to default flex-shrink. -->
+    <img src="support/10x10-green.png" style="width: 100px;" data-expected-height="10">
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-minimum-width-flex-items-014.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-min-width-applies-with-fixed-width.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/flex-minimum-width-flex-items-014.html
diff --git a/third_party/blink/web_tests/css3/flexbox/max-width-violation.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/max-width-violation.html
similarity index 60%
rename from third_party/blink/web_tests/css3/flexbox/max-width-violation.html
rename to third_party/blink/web_tests/external/wpt/css/css-flexbox/max-width-violation.html
index f5b0fe1..ea2779d 100644
--- a/third_party/blink/web_tests/css3/flexbox/max-width-violation.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/max-width-violation.html
@@ -1,5 +1,9 @@
 <!DOCTYPE html>
-
+<title>CSS Flexbox: max-width of inflexible items</title>
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flexibility">
+<link rel="bookmark" href="https://crbug.com/617956">
+<meta name="assert" content="Inflexible items (flex factor 0 or flex basis violating the min/max size constraint) take max-width into account.">
 <style>
 .columns {
   display: flex;
@@ -33,16 +37,14 @@
 }
 </style>
 
-<script src="../../resources/testharness.js"></script>
-<script src="../../resources/testharnessreport.js"></script>
-<script src="../../resources/check-layout-th.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
 
 <body onload="checkLayout('.columns')">
 <div id=log></div>
 
-
 <p>You should see no red</p>
-
 <div class="columns">
   <div class="red"></div>
   <div class="column1" data-expected-width="150">
@@ -54,7 +56,6 @@
 </div>
 
 <p>This second part is just to ensure we don't assert</p>
-
 <div class="columns">
   <div class="column1 abspos" data-expected-width="150">
     Column 1
@@ -63,4 +64,3 @@
     Column 2
   </div>
 </div>
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/support/10x10-green.png b/third_party/blink/web_tests/external/wpt/css/css-flexbox/support/10x10-green.png
new file mode 100644
index 0000000..8c39b0d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/support/10x10-green.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001-expected.txt
new file mode 100644
index 0000000..85ec883
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001-expected.txt
@@ -0,0 +1,16 @@
+This is a testharness.js-based test.
+PASS @font-face matching for quoted and unquoted serif
+PASS @font-face matching for quoted and unquoted sans-serif
+PASS @font-face matching for quoted and unquoted cursive
+PASS @font-face matching for quoted and unquoted fantasy
+PASS @font-face matching for quoted and unquoted monospace
+FAIL @font-face matching for quoted and unquoted system assert_equals: unquoted system does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted emoji assert_equals: unquoted emoji does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted math assert_equals: unquoted math does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted fangsong assert_equals: unquoted fangsong does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted ui-serif assert_equals: unquoted ui-serif does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted ui-sans-serif assert_equals: unquoted ui-sans-serif does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted ui-monospace assert_equals: unquoted ui-monospace does not match @font-face rule expected 25 but got 50
+FAIL @font-face matching for quoted and unquoted ui-rounded assert_equals: unquoted ui-rounded does not match @font-face rule expected 25 but got 50
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001.html
index aa9fb5a..bd39bac 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-001.html
@@ -1,36 +1,56 @@
 <!DOCTYPE html>
-<title>CSS Test: Test generic family keywords do not match @font-face</title>
+<title>CSS Test: Test generic family keywords matching for @font-face</title>
 <link rel="help" href="https://drafts.csswg.org/css-fonts-4/#family-name-syntax">
 <link rel="author" title="Koji Ishii" href="mailto:kojii@chromium.com">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-fonts/support/font-family-keywords.js"></script>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css"/>
 <style>
 div {
   font-size: 10px;
 }
+#ahem {
+  font-family: Ahem;
+}
 </style>
-<template id="fonts">
+<body>
+  <div><span id="ahem">00000</span></div>
+  <div><span id="test">00000</span></div>
+<script>
+  setup({ explicit_done: true });
+  window.addEventListener("load", () => { document.fonts.ready.then(runTests); });
+  function runTests() {
+    let ahem = document.getElementById('ahem');
+    let ahem_expected_width = ahem.offsetWidth;
+    kGenericFontFamilyKeywords.forEach(keyword => {
+      test(() => {
+        let element = document.getElementById('test');
+        element.setAttribute("style", `font-family: ${keyword};`);
+        let expected_width = element.offsetWidth;
+
+        // Insert the @font-face rules for quoted and unquoted keywords.
+        document.documentElement.insertAdjacentHTML('beforeend', `
 <style>
 @font-face {
-  font-family: system-ui;
+  font-family: ${keyword};
   src: local(Ahem), url('/fonts/Ahem.ttf');
 }
 </style>
-</template>
-<body onload="onLoad()">
-  <div><span id="system-ui" style="font-family: system-ui">00000</span></div>
-<script>
-function onLoad() {
-  test(() => {
-    let element = document.getElementById('system-ui');
-    let expected_width = element.offsetWidth;
+<style>
+@font-face {
+  font-family: "${keyword}";
+  src: local(Ahem), url('/fonts/Ahem.ttf');
+}
+</style>`);
 
-    // Insert the @font-face rule.
-    let template = document.getElementById('fonts');
-    document.documentElement.appendChild(template.content.cloneNode(true));
+        assert_equals(element.offsetWidth, expected_width, `unquoted ${keyword} does not match @font-face rule`);
 
-    assert_equals(element.offsetWidth, expected_width);
-  });
-};
+        element.setAttribute("style", `font-family: "${keyword}";`);
+        assert_equals(element.offsetWidth, ahem_expected_width, `quoted ${keyword} matches  @font-face rule`);
+      }, `@font-face matching for quoted and unquoted ${keyword}`);
+    });
+    done();
+  }
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-002.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-002.html
new file mode 100644
index 0000000..7399860b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/generic-family-keywords-002.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Generic font family: -webkit-* treated as &lt;font-family&gt;</title>
+    <link rel="help" href="https://drafts.csswg.org/css-fonts-4/#generic-font-families">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/css/css-fonts/support/font-family-keywords.js"></script>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css"/>
+    <style>
+      div {
+        font-size: 10px;
+      }
+      #ahem {
+        font-family: Ahem;
+      }
+    </style>
+  </head>
+  <body>
+    <div><span id="ahem">00000</span></div>
+    <div><span id="test">00000</span></div>
+  </body>
+<script>
+  setup({ explicit_done: true });
+  window.addEventListener("load", () => { document.fonts.ready.then(runTests); });
+  function SetFontFamilyAndMeasure(fontFamilyValue) {
+    var element = document.getElementById('test');
+    element.setAttribute("style", `font-family: ${fontFamilyValue}, Ahem;`);
+    return element.offsetWidth;
+  }
+  function runTests() {
+    let ahem = document.getElementById('ahem');
+    let ahem_expected_width = ahem.offsetWidth;
+    let families = kGenericFontFamilyKeywords.map(keyword => `-webkit-${keyword}`).concat(kWebKitPrefixKeywords);
+    families.forEach(name => {
+      test(function() {
+      assert_equals(SetFontFamilyAndMeasure(name), ahem_expected_width);
+      }, `font-family: ${name} treated as <font-family>, not <generic-name>`);
+    });
+    done();
+  }
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/support/font-family-keywords.js b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/font-family-keywords.js
new file mode 100644
index 0000000..fc5b723
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/support/font-family-keywords.js
@@ -0,0 +1,17 @@
+var kGenericFontFamilyKeywords = ["serif",
+                                  "sans-serif",
+                                  "cursive",
+                                  "fantasy",
+                                  "monospace",
+                                  "system",
+                                  "emoji",
+                                  "math",
+                                  "fangsong",
+                                  "ui-serif",
+                                  "ui-sans-serif",
+                                  "ui-monospace",
+                                  "ui-rounded"];
+
+var kWebKitPrefixKeywords = ["-webkit-body",
+                             "-webkit-standard",
+                             "-webkit-pictograph"];
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/transition-base-response-003.html b/third_party/blink/web_tests/external/wpt/css/css-transitions/transition-base-response-003.html
new file mode 100644
index 0000000..045da5db
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/transition-base-response-003.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Tests that identical elements in the base style responds to font-size transition</title>
+<link rel="help" href="https://drafts.csswg.org/css-animations/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  div {
+    font-size: 10px;
+    min-width: 1em;
+    transition: font-size steps(2, start) 10s;
+  }
+  .change {
+    font-size: 20px;
+  }
+</style>
+<div></div>
+<div></div>
+<div></div>
+<script>
+test(() => {
+  let divs = document.querySelectorAll('div');
+  for (let div of divs) {
+    let unused = getComputedStyle(div).getPropertyValue('min-width');
+    div.className = 'change';
+    assert_equals(getComputedStyle(div).getPropertyValue('min-width'), '15px');
+  }
+}, 'Identical elements are all responsive to font-size transition');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/font-family-serialization-001.html b/third_party/blink/web_tests/external/wpt/css/cssom/font-family-serialization-001.html
new file mode 100644
index 0000000..6c59b98
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/font-family-serialization-001.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<title>Serialization of font-family</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-family-prop">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-fonts/support/font-family-keywords.js"></script>
+<div id="target"></div>
+<script>
+  function SetFontFamilyAndSerialize(fontFamilyValue) {
+    var target = document.getElementById('target');
+    target.setAttribute("style", `font-family: ${fontFamilyValue}`);
+    return window.getComputedStyle(target).getPropertyValue('font-family');
+  }
+  test(function() {
+    kGenericFontFamilyKeywords.forEach(keyword => {
+      assert_equals(SetFontFamilyAndSerialize(keyword), keyword);
+    });
+  }, "Serialization of <generic-family>");
+
+  test(function() {
+    kGenericFontFamilyKeywords.forEach(keyword => {
+      var quoted_keyword = `"${keyword}"`;
+      assert_equals(SetFontFamilyAndSerialize(quoted_keyword), quoted_keyword);
+    });
+  }, "Serialization of quoted \"<generic-family>\"");
+
+  test(function() {
+    kGenericFontFamilyKeywords.forEach(keyword => {
+      var prefixed_keyword = `-webkit-${keyword}`;
+      assert_equals(SetFontFamilyAndSerialize(prefixed_keyword), prefixed_keyword);
+    });
+  }, "Serialization of prefixed -webkit-<generic-family>");
+
+  test(function() {
+    kWebKitPrefixKeywords.forEach(keyword => {
+      assert_equals(SetFontFamilyAndSerialize(keyword), keyword);
+    });
+  }, `Serialization of ${kWebKitPrefixKeywords}`);
+
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-wakelock.html b/third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-screen-wakelock.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-wakelock.html
rename to third_party/blink/web_tests/external/wpt/feature-policy/resources/feature-policy-screen-wakelock.html
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html
index 3c902cc..821ec628 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html
@@ -7,13 +7,13 @@
   "use strict";
 
   const same_origin_src =
-    "/feature-policy/resources/feature-policy-wakelock.html";
+    "/feature-policy/resources/feature-policy-screen-wakelock.html";
   const cross_origin_src =
     "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
 
   promise_test(t => {
     return promise_rejects_dom(t, "NotAllowedError", navigator.wakeLock.request("screen"));
-  }, 'Feature-Policy header {"wake-lock" : []} disallows the top-level document.');
+  }, 'Feature-Policy header {"screen-wake-lock" : []} disallows the top-level document.');
 
   async_test(t => {
     test_feature_availability(
@@ -22,7 +22,7 @@
       same_origin_src,
       expect_feature_unavailable_default
     );
-  }, 'Feature-Policy header {"wake-lock" : []} disallows same-origin iframes.');
+  }, 'Feature-Policy header {"screen-wake-lock" : []} disallows same-origin iframes.');
 
   async_test(t => {
     test_feature_availability(
@@ -31,6 +31,6 @@
       cross_origin_src,
       expect_feature_unavailable_default
     );
-  }, 'Feature-Policy header {"wake-lock" : []} disallows cross-origin iframes.');
+  }, 'Feature-Policy header {"screen-wake-lock" : []} disallows cross-origin iframes.');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html.headers
index 1d3769ec..5d910ce 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html.headers
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-disabled-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: wake-lock 'none'
+Feature-Policy: screen-wake-lock 'none'
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
index f28a961..2b2e0c9 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -6,7 +6,7 @@
 <script>
   "use strict";
 
-  const relative_path = "/feature-policy/resources/feature-policy-wakelock.html";
+  const relative_path = "/feature-policy/resources/feature-policy-screen-wakelock.html";
   const base_src = "/feature-policy/resources/redirect-on-load.html#";
   const same_origin_src = base_src + relative_path;
   const cross_origin_src =
@@ -18,9 +18,9 @@
       t,
       same_origin_src,
       expect_feature_available_default,
-      "wake-lock"
+      "screen-wake-lock"
     );
-  }, 'Feature-Policy allow="wake-lock" allows same-origin relocation');
+  }, 'Feature-Policy allow="screen-wake-lock" allows same-origin relocation');
 
   async_test(t => {
     test_feature_availability(
@@ -28,9 +28,9 @@
       t,
       cross_origin_src,
       expect_feature_unavailable_default,
-      "wake-lock"
+      "screen-wake-lock"
     );
-  }, 'Feature-Policy allow="wake-lock" disallows cross-origin relocation');
+  }, 'Feature-Policy allow="screen-wake-lock" disallows cross-origin relocation');
 
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute.https.sub.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute.https.sub.html
index 5587a85..b8ab3b03 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy-attribute.https.sub.html
@@ -7,7 +7,7 @@
   "use strict";
 
   const same_origin_src =
-    "/feature-policy/resources/feature-policy-wakelock.html";
+    "/feature-policy/resources/feature-policy-screen-wakelock.html";
   const cross_origin_src =
     "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
 
@@ -17,9 +17,9 @@
       t,
       same_origin_src,
       expect_feature_available_default,
-      "wake-lock"
+      "screen-wake-lock"
     );
-  }, 'Feature policy "wake-lock" can be enabled in same-origin iframe using allow="wake-lock" attribute');
+  }, 'Feature policy "screen-wake-lock" can be enabled in same-origin iframe using allow="screen-wake-lock" attribute');
 
   async_test(t => {
     test_feature_availability(
@@ -27,8 +27,8 @@
       t,
       cross_origin_src,
       expect_feature_available_default,
-      "wake-lock"
+      "screen-wake-lock"
     );
-  }, 'Feature policy "wake-lock" can be enabled in cross-origin iframe using allow="wake-lock" attribute');
+  }, 'Feature policy "screen-wake-lock" can be enabled in cross-origin iframe using allow="screen-wake-lock" attribute');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html
index d0f5a15..376359e 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html
@@ -9,7 +9,7 @@
   "use strict";
 
   const same_origin_src =
-    "/feature-policy/resources/feature-policy-wakelock.html";
+    "/feature-policy/resources/feature-policy-screen-wakelock.html";
   const cross_origin_src =
     "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
 
@@ -17,7 +17,7 @@
     await test_driver.set_permission(
         { name: 'wake-lock', type: 'screen' }, 'granted', false);
     await navigator.wakeLock.request('screen').then(lock => lock.release());
-  }, 'Feature-Policy header {"wake-lock" : ["*"]} allows the top-level document.');
+  }, 'Feature-Policy header {"screen-wake-lock" : ["*"]} allows the top-level document.');
 
   async_test(t => {
     test_feature_availability(
@@ -26,7 +26,7 @@
       same_origin_src,
       expect_feature_available_default
     );
-  }, 'Feature-Policy header {"wake-lock" : ["*"]} allows same-origin iframes.');
+  }, 'Feature-Policy header {"screen-wake-lock" : ["*"]} allows same-origin iframes.');
 
   async_test(t => {
     test_feature_availability(
@@ -35,7 +35,7 @@
       cross_origin_src,
       expect_feature_available_default
     );
-  }, 'Feature-Policy header {"wake-lock" : ["*"]} allows cross-origin iframes.');
+  }, 'Feature-Policy header {"screen-wake-lock" : ["*"]} allows cross-origin iframes.');
 
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html.headers
index 34b7437..7810751 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html.headers
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: wake-lock *
+Feature-Policy: screen-wake-lock *
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html
index 73f3438..a2d3725 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html
@@ -10,7 +10,7 @@
   "use strict";
 
   const same_origin_src =
-    "/feature-policy/resources/feature-policy-wakelock.html";
+    "/feature-policy/resources/feature-policy-screen-wakelock.html";
   const cross_origin_src =
     "https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;
 
@@ -18,7 +18,7 @@
     await test_driver.set_permission(
         { name: 'wake-lock', type: 'screen' }, 'granted', false);
     await navigator.wakeLock.request('screen').then(lock => lock.release());
-  }, 'Feature-Policy header wake-lock "self" allows the top-level document.');
+  }, 'Feature-Policy header screen-wake-lock "self" allows the top-level document.');
 
   async_test(t => {
     test_feature_availability(
@@ -27,7 +27,7 @@
       same_origin_src,
       expect_feature_available_default
     );
-  }, 'Feature-Policy header wake-lock "self" allows same-origin iframes.');
+  }, 'Feature-Policy header screen-wake-lock "self" allows same-origin iframes.');
 
   async_test(t => {
     test_feature_availability(
@@ -36,6 +36,6 @@
       cross_origin_src,
       expect_feature_unavailable_default
     );
-  }, 'Feature-Policy header wake-lock "self" disallows cross-origin iframes.');
+  }, 'Feature-Policy header screen-wake-lock "self" disallows cross-origin iframes.');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers
index 6f05d235..6bc94277 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-enabled-on-self-origin-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: wake-lock 'self'
+Feature-Policy: screen-wake-lock 'self'
diff --git a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-supported-by-feature-policy.html b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-supported-by-feature-policy.html
index d6289ff..e7f9c8cb 100644
--- a/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-supported-by-feature-policy.html
+++ b/third_party/blink/web_tests/external/wpt/wake-lock/wakelock-supported-by-feature-policy.html
@@ -6,6 +6,6 @@
 <script src="/resources/testharnessreport.js"></script>
 <script>
 test(() => {
-    assert_in_array('wake-lock', document.featurePolicy.features());
-}, 'document.featurePolicy.features should advertise wake-lock.');
+    assert_in_array('screen-wake-lock', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise screen-wake-lock.');
 </script>
diff --git a/third_party/blink/web_tests/fullscreen/full-screen-iframe-not-allowed.html b/third_party/blink/web_tests/fullscreen/full-screen-iframe-not-allowed.html
deleted file mode 100644
index e9a92df..0000000
--- a/third_party/blink/web_tests/fullscreen/full-screen-iframe-not-allowed.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<p>Test for <a href="http://bugs.webkit.org/show_bug.cgi?id=56264">bug 56264</a>:
-Handle entering full screen security restrictions</p>
-<p>To test manually, click the "Go full screen" button - the page should not enter full screen mode.</p>
-<script src="full-screen-test.js"></script>
-<script>
-function runTest() {
-    var frame = document.getElementById('frame');
-
-    waitForEvent(frame.contentDocument, 'webkitfullscreenchange', function() {
-            consoleWrite("FAIL - entered full screen!"); 
-            endTest();
-    });
-
-    waitForEventAndEnd(frame.contentDocument, 'webkitfullscreenerror', function() {
-        consoleWrite("SUCCEED - did not enter full screen!");
-    });
-
-    var x = frame.getBoundingClientRect().left + 10;
-    var y = frame.getBoundingClientRect().top + 10;
-    eventSender.mouseMoveTo(x, y);
-    eventSender.mouseDown();
-    eventSender.mouseUp();
-}
-</script>
-<iframe id="frame" src="resources/inner.html" onload="runTest()">
-</iframe>
diff --git a/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt
index 6547710..d11895bf 100644
--- a/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/emulation/set-vision-deficiency-expected.txt
@@ -8,15 +8,15 @@
 <p>Emulating "none":
 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFRJREFUWIXt18ENACAMw8DA5N0chnCQePgGiKz+ujI5aZru3K6uPWAgZSBlIGUgZSBlIGUgZSBlILWS030iyr6/oIGUgZSBlIGUgZSBlIGUgZSB1AXNwwVLtuf+dAAAAABJRU5ErkJggg==">
 <p>Emulating "deuteranopia":
-<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF9JREFUWIXt17ENwCAQBMGHYohdqKuhEsduBorYQyLYKeC0+uzbM8aqoO9/k3PVo2sHGEgZSBlIGUgZSBlIGUgZSBlItaoZ/UnSrr+ggZSBlIGUgZSBlIGUgZSBlIHUBmSnBbHzufJtAAAAAElFTkSuQmCC">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAGJJREFUWIXt17ENgDAQBMG3AyLqcxkuxsXQFxEhFLGH5GCngNPqs29zHG8FretOzlWPrv3AQMpAykDKQMpAykDKQMpAykCq1flEf5K07S9oIGUgZSBlIGUgZSBlIGUgZSD1AVMZBlN0SH01AAAAAElFTkSuQmCC">
 <p>Emulating "none":
 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFRJREFUWIXt18ENACAMw8DA5N0chnCQePgGiKz+ujI5aZru3K6uPWAgZSBlIGUgZSBlIGUgZSBlILWS030iyr6/oIGUgZSBlIGUgZSBlIGUgZSB1AXNwwVLtuf+dAAAAABJRU5ErkJggg==">
 <p>Emulating "protanopia":
-<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF9JREFUWIXt10ENwCAQBdEFI5VBahcnnEhVURHzm/QwT8DPZG/bxn2dCnr2TM5Vj659wEDKQMpAykDKQMpAykDKQMpAqlWt6E+S9vsLGkgZSBlIGUgZSBlIGUgZSBlIvZuvBc8DKP+1AAAAAElFTkSuQmCC">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF1JREFUWIXt10ENACEQBMEFFwhExCnAEP5ARA/JPboETDr727ZmnQr6dnSuenTtAQMpAykDKQMpAykDKQMpAykDqVbjZJ+IsN9f0EDKQMpAykDKQMpAykDKQMpA6gJvXgZ9Xlbz3wAAAABJRU5ErkJggg==">
 <p>Emulating "tritanopia":
-<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFhJREFUWIXt10ENACAQxEAgiMQJsnDHF0R0SXh0BGya+10tc5yStHZ0rkXXHjCQMpAykDKQMpAykDKQMpAykOrpHyLt+wsaSBlIGUgZSBlIGUgZSBlIGUhdCWsFapTFH60AAAAASUVORK5CYII=">
 <p>Emulating "tritanopia":
-<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFhJREFUWIXt10ENACAQxEAgiMQJsnDHF0R0SXh0BGya+10tc5yStHZ0rkXXHjCQMpAykDKQMpAykDKQMpAykOrpHyLt+wsaSBlIGUgZSBlIGUgZSBlIGUhdCWsFapTFH60AAAAASUVORK5CYII=">
 <p>Emulating "some-invalid-deficiency":
 {
   "code": -32602,
@@ -48,7 +48,7 @@
   "message": "Unknown vision deficiency type"
 }
 <p>Navigating&mldr;
-<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAF5JREFUWIXt17ENwCAQBEGwEC0RuQBaoP8qnJgi9pAIdgo4rT772sf7l6BvzeRceaJrBxhIGUgZSBlIGUgZSBlIGUgZSLX0D5F2/QUNpAykDKQMpAykDKQMpAykDKQ2h2YFBXR3unwAAAAASUVORK5CYII=">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFhJREFUWIXt10ENACAQxEAgiMQJsnDHF0R0SXh0BGya+10tc5yStHZ0rkXXHjCQMpAykDKQMpAykDKQMpAykOrpHyLt+wsaSBlIGUgZSBlIGUgZSBlIGUhdCWsFapTFH60AAAAASUVORK5CYII=">
 <p>Emulating "achromatopsia":
 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAFpJREFUWIXt10ENADEMxMD2eARR2IfQFYS3Uh8eACsrv+zu/lfQzCTn1hddu8BAykDKQMpAykDKQMpAykDKQGpXVfQnSXv+ggZSBlIGUgZSBlIGUgZSBlIGUgcPVwX7L3bHewAAAABJRU5ErkJggg==">
 
diff --git a/third_party/blink/web_tests/media/video-controls-fullscreen-iframe-not-allowed.html b/third_party/blink/web_tests/media/video-controls-fullscreen-iframe-not-allowed.html
index a4457c7e..efccf4f 100644
--- a/third_party/blink/web_tests/media/video-controls-fullscreen-iframe-not-allowed.html
+++ b/third_party/blink/web_tests/media/video-controls-fullscreen-iframe-not-allowed.html
@@ -8,7 +8,7 @@
         <script src="video-controls-fullscreen.js"></script>
     </head>
     <body>
-        <iframe></iframe>
+        <iframe allow="fullscreen 'none';"></iframe>
         <script>fullscreen_iframe_test();</script>
     </body>
 </html>
diff --git a/third_party/blink/web_tests/platform/linux/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt b/third_party/blink/web_tests/platform/linux/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
new file mode 100644
index 0000000..fbda0d6
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
@@ -0,0 +1,19 @@
+This is a testharness.js-based test.
+FAIL font-family: -webkit-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-sans-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-cursive treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-fantasy treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-monospace treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-system treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-emoji treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-math treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-fangsong treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-ui-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-ui-sans-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-ui-monospace treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-ui-rounded treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-body treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-standard treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-pictograph treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
new file mode 100644
index 0000000..d85a0cf
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
@@ -0,0 +1,19 @@
+This is a testharness.js-based test.
+FAIL font-family: -webkit-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-sans-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 28
+FAIL font-family: -webkit-cursive treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 26
+FAIL font-family: -webkit-fantasy treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 33
+FAIL font-family: -webkit-monospace treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+PASS font-family: -webkit-system treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-emoji treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-math treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-fangsong treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-serif treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-sans-serif treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-monospace treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-rounded treated as <font-family>, not <generic-name>
+FAIL font-family: -webkit-body treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-standard treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-pictograph treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/external/wpt/css/cssom/font-family-serialization-001-expected.txt b/third_party/blink/web_tests/platform/mac/external/wpt/css/cssom/font-family-serialization-001-expected.txt
new file mode 100644
index 0000000..1adb625
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/external/wpt/css/cssom/font-family-serialization-001-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS Serialization of <generic-family>
+FAIL Serialization of quoted "<generic-family>" assert_equals: expected "\"serif\"" but got "serif"
+FAIL Serialization of prefixed -webkit-<generic-family> assert_equals: expected "-webkit-serif" but got "serif"
+FAIL Serialization of -webkit-body,-webkit-standard,-webkit-pictograph assert_equals: expected "-webkit-body" but got "Times"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
new file mode 100644
index 0000000..2ff76e0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/external/wpt/css/css-fonts/generic-family-keywords-002-expected.txt
@@ -0,0 +1,19 @@
+This is a testharness.js-based test.
+FAIL font-family: -webkit-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-sans-serif treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-cursive treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+FAIL font-family: -webkit-fantasy treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-monospace treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 30
+PASS font-family: -webkit-system treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-emoji treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-math treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-fangsong treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-serif treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-sans-serif treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-monospace treated as <font-family>, not <generic-name>
+PASS font-family: -webkit-ui-rounded treated as <font-family>, not <generic-name>
+FAIL font-family: -webkit-body treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-standard treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+FAIL font-family: -webkit-pictograph treated as <font-family>, not <generic-name> assert_equals: expected 50 but got 25
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/win/external/wpt/css/cssom/font-family-serialization-001-expected.txt b/third_party/blink/web_tests/platform/win/external/wpt/css/cssom/font-family-serialization-001-expected.txt
new file mode 100644
index 0000000..3f14ad3d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/external/wpt/css/cssom/font-family-serialization-001-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+PASS Serialization of <generic-family>
+FAIL Serialization of quoted "<generic-family>" assert_equals: expected "\"serif\"" but got "serif"
+FAIL Serialization of prefixed -webkit-<generic-family> assert_equals: expected "-webkit-serif" but got "serif"
+FAIL Serialization of -webkit-body,-webkit-standard,-webkit-pictograph assert_equals: expected "-webkit-body" but got "\"times new roman\""
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index 323b7a04..1734d1e 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -45,6 +45,7 @@
 pointer-lock
 popups
 presentation
+screen-wake-lock
 scripts
 serial
 sync-script
@@ -53,6 +54,5 @@
 trust-token-redemption
 usb
 vertical-scroll
-wake-lock
 xr-spatial-tracking
 
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 4e524ef8..0a2db48 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -6301,7 +6301,9 @@
 interface RTCEncodedAudioFrame
     attribute @@toStringTag
     getter additionalData
+    getter contributingSources
     getter data
+    getter synchronizationSource
     getter timestamp
     method constructor
     method toString
@@ -6310,6 +6312,7 @@
     attribute @@toStringTag
     getter additionalData
     getter data
+    getter synchronizationSource
     getter timestamp
     getter type
     method constructor
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-051.html b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-051.html
index 410508f9..c407c6b 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-051.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-051.html
@@ -10,12 +10,6 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
-<style>
-.auto {
-  subtree-visibility: auto;
-}
-</style>
-
 <div id="hidden" style="subtree-visibility: auto">
   foo
   <div id="child" tabindex="0">
@@ -29,19 +23,25 @@
 }
 
 async_test(async(t) => {
-  const hiddenEl = document.getElementById("hidden");
-  let axHidden = axElementById("hidden");
-  t.step(() => { assert_equals(axHidden.childrenCount, 3, "Child count after acquire"); });
-  axElementById("child").takeFocus();
+  t.step(() => {
+    let axChild = axElementById("child");
+    assert_false(Boolean(axChild.backgroundColor), "Background color after activation");
+  });
 
-  // Wait for the next frame for the ax object to be recreated.
+  axElementById("child").takeFocus();  // Use accessibility to set the focus.
+
+  // Wait for a few frames for the ax tree to be refreshed.
   requestAnimationFrame(() => {
     requestAnimationFrame(() => {
-      axHidden = axElementById("hidden");
-      t.step(() => { assert_equals(axHidden.childrenCount, 2, "Child count after activation"); });
-      t.done();
+-     requestAnimationFrame(() => {
+  -     t.step(() => {
+          let axChild = axElementById("child");
+          assert_equals(axChild.backgroundColor, 0xFFFFFFFF, "Background color after activation");
+        });
+        t.done();
+      });
     });
   });
-}, "Accessiblility focus causes activatable hidden tree to activate");
+}, "Accessiblility focus causes activatable hidden tree to activate, and thus accessibility details such as background color become available");
 
 </script>
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-075.html b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-075.html
new file mode 100644
index 0000000..4c8b128
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/css-subtree-visibility/subtree-visibility-075.html
@@ -0,0 +1,41 @@
+<!doctype HTML>
+<html>
+<meta charset="utf8">
+<title>Subtree Visibility: accessibility focus</title>
+<link rel="author" title="Aaron Leventhal" href="mailto:aleventhal@chromium.org">
+<link rel="help" href="https://github.com/WICG/display-locking">
+<link rel="match" href="container-ref.html">
+<meta name="assert" content="subtree-visibility auto subtrees are exposed by accessibility focus">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.auto {
+  subtree-visibility: auto;
+}
+</style>
+
+<div id="hidden" style="subtree-visibility: auto">
+  foo
+  <div id="child" tabindex="0">
+    bar
+  </div>
+</div>
+
+<script>
+function axElementById(id) {
+  return accessibilityController.accessibleElementById(id);
+}
+
+async_test(async(t) => {
+  const hiddenEl = document.getElementById("hidden");
+  let axHidden = axElementById("hidden");
+  t.step(() => {
+    assert_equals(axHidden.childrenCount, 3, "Child count after acquire" );
+    axHidden.childAtIndex(1).name;   // Referring to the name should have no effect on the number of children.
+    assert_equals(axHidden.childrenCount, 3, "Child count after acquire, and accessing name of first child" );
+    t.done();
+  });
+}, "Accessiblility name calculation on child of subtree-visibility: auto does not destroy tree");
+
+</script>
diff --git a/tools/binary_size/libsupersize/archive.py b/tools/binary_size/libsupersize/archive.py
index 93020d5..ae20bff 100644
--- a/tools/binary_size/libsupersize/archive.py
+++ b/tools/binary_size/libsupersize/archive.py
@@ -1577,19 +1577,26 @@
   return section_sizes, raw_symbols
 
 
-def CreateSizeInfo(section_sizes,
-                   raw_symbols,
-                   metadata=None,
+def CreateSizeInfo(section_sizes_list,
+                   raw_symbols_list,
+                   metadata_list,
                    normalize_names=True):
   """Performs operations on all symbols and creates a SizeInfo object."""
-  file_format.SortSymbols(raw_symbols)
-  file_format.CalculatePadding(raw_symbols)
+  for raw_symbols in raw_symbols_list:
+    file_format.SortSymbols(raw_symbols)
+    file_format.CalculatePadding(raw_symbols)
 
-  # Do not call _NormalizeNames() during archive since that method tends to need
-  # tweaks over time. Calling it only when loading .size files allows for more
-  # flexibility.
-  if normalize_names:
-    _NormalizeNames(raw_symbols)
+    # Do not call _NormalizeNames() during archive since that method tends to
+    # need tweaks over time. Calling it only when loading .size files allows for
+    # more flexibility.
+    if normalize_names:
+      _NormalizeNames(raw_symbols)
+
+  # TODO(huangs): Implement data fusing to compute the following for real.
+  assert len(section_sizes_list) == 1
+  section_sizes = section_sizes_list[0]
+  raw_symbols = raw_symbols_list[0]
+  metadata = metadata_list[0]
 
   return models.SizeInfo(section_sizes, raw_symbols, metadata=metadata)
 
@@ -1813,20 +1820,15 @@
   return elf_path, map_path, apk_so_path
 
 
-def _DeduceMainPaths(args, knobs, on_config_error):
-  """Computes main paths based on input, and deduces them if needed."""
-  aab_or_apk = args.apk_file or args.minimal_apks_file
+def _DeduceAuxPaths(args, apk_prefix):
   mapping_path = args.mapping_file
   resources_pathmap_path = args.resources_pathmap_file
-  if aab_or_apk:
-    # Allow either .minimal.apks or just .apks.
-    aab_or_apk = aab_or_apk.replace('.minimal.apks', '.aab')
-    aab_or_apk = aab_or_apk.replace('.apks', '.aab')
+  if apk_prefix:
     if not mapping_path:
-      mapping_path = aab_or_apk + '.mapping'
+      mapping_path = apk_prefix + '.mapping'
       logging.debug('Detected --mapping-file=%s', mapping_path)
     if not resources_pathmap_path:
-      possible_pathmap_path = aab_or_apk + '.pathmap.txt'
+      possible_pathmap_path = apk_prefix + '.pathmap.txt'
       # This could be pointing to a stale pathmap file if path shortening was
       # previously enabled but is disabled for the current build. However, since
       # current apk/aab will have unshortened paths, looking those paths up in
@@ -1836,90 +1838,76 @@
         resources_pathmap_path = possible_pathmap_path
         logging.debug('Detected --resources-pathmap-file=%s',
                       resources_pathmap_path)
+  return mapping_path, resources_pathmap_path
+
+
+def _DeduceMainPaths(args, knobs, on_config_error):
+  """Generates main paths (may be deduced) for each containers given by input.
+
+  Yields:
+    For each container, main paths and other info needed to create size_info.
+  """
 
   output_directory_finder = path_util.OutputDirectoryFinder(
       value=args.output_directory,
       any_path_within_output_directory=args.any_path_within_output_directory)
 
-  apk_path = args.extracted_minimal_apk_path or args.apk_file
-  linker_name = None
-  tool_prefix = None
-  if knobs.analyze_native:
-    elf_path, map_path, apk_so_path = _DeduceNativeInfo(
-        output_directory_finder.Tentative(), apk_path, args.elf_file,
-        args.map_file, on_config_error)
-    if map_path:
-      linker_name = _DetectLinkerName(map_path)
-      logging.info('Linker name: %s' % linker_name)
+  def _Inner(apk_prefix, apk_path):
+    """Inner helper for _DeduceMainPaths(), for one container.
 
-      tool_prefix_finder = path_util.ToolPrefixFinder(
-          value=args.tool_prefix,
-          output_directory_finder=output_directory_finder,
-          linker_name=linker_name)
-      tool_prefix = tool_prefix_finder.Finalized()
+    Params:
+      apk_prefix: Prefix used to search for auxiliary .apk related files.
+      apk_path: Path to .apk file that can be opened for processing, but whose
+        filename is unimportant (e.g., can be a temp file).
+    """
+    if apk_prefix:
+      # Allow either .minimal.apks or just .apks.
+      apk_prefix = apk_prefix.replace('.minimal.apks', '.aab')
+      apk_prefix = apk_prefix.replace('.apks', '.aab')
+
+    mapping_path, resources_pathmap_path = _DeduceAuxPaths(args, apk_prefix)
+    linker_name = None
+    tool_prefix = None
+    if knobs.analyze_native:
+      elf_path, map_path, apk_so_path = _DeduceNativeInfo(
+          output_directory_finder.Tentative(), apk_path, args.elf_file,
+          args.map_file, on_config_error)
+      if map_path:
+        linker_name = _DetectLinkerName(map_path)
+        logging.info('Linker name: %s' % linker_name)
+
+        tool_prefix_finder = path_util.ToolPrefixFinder(
+            value=args.tool_prefix,
+            output_directory_finder=output_directory_finder,
+            linker_name=linker_name)
+        tool_prefix = tool_prefix_finder.Finalized()
+    else:
+      # Trust that these values will not be used, and set to None.
+      elf_path = None
+      map_path = None
+      apk_so_path = None
+
+    # TODO(huangs): See if this can be pulled out of _Inner().
+    output_directory = None
+    if not args.no_source_paths:
+      output_directory = output_directory_finder.Finalized()
+
+    size_info_prefix = None
+    if output_directory and apk_prefix:
+      size_info_prefix = os.path.join(output_directory, 'size-info',
+                                      os.path.basename(apk_prefix))
+
+    return (output_directory, tool_prefix, apk_path, mapping_path, apk_so_path,
+            elf_path, map_path, resources_pathmap_path, linker_name,
+            size_info_prefix)
+
+  # Process each container.
+  # If needed, extract .apk file to a temp file and process that instead.
+  if args.minimal_apks_file:
+    with zip_util.UnzipToTemp(args.minimal_apks_file, _APKS_MAIN_APK) as temp:
+      yield _Inner(args.minimal_apks_file, temp)
   else:
-    # Trust that these values will not be used, and set to None.
-    elf_path = None
-    map_path = None
-    apk_so_path = None
-
-  output_directory = None
-  if not args.no_source_paths:
-    output_directory = output_directory_finder.Finalized()
-
-  size_info_prefix = None
-  if output_directory and aab_or_apk:
-    size_info_prefix = os.path.join(
-        output_directory, 'size-info', os.path.basename(aab_or_apk))
-
-  return (output_directory, tool_prefix, apk_path, mapping_path, apk_so_path,
-          elf_path, map_path, resources_pathmap_path, linker_name,
-          size_info_prefix)
-
-
-def _RunInternal(args, on_config_error):
-  knobs = SectionSizeKnobs(args.is_bundle)
-  knobs.ModifyWithArgs(args)
-
-  (output_directory, tool_prefix, apk_path, mapping_path, apk_so_path, elf_path,
-   map_path, resources_pathmap_path, linker_name,
-   size_info_prefix) = _DeduceMainPaths(args, knobs, on_config_error)
-
-  # Note that |args.apk_file| is used instead of |apk_path|, since the latter
-  # may be an extracted temporary file.
-  metadata = CreateMetadata(map_path, elf_path, args.apk_file,
-                            args.minimal_apks_file, tool_prefix,
-                            output_directory, linker_name)
-  section_sizes, raw_symbols = CreateSectionSizesAndSymbols(
-      map_path=map_path,
-      tool_prefix=tool_prefix,
-      elf_path=elf_path,
-      apk_path=apk_path,
-      mapping_path=mapping_path,
-      output_directory=output_directory,
-      resources_pathmap_path=resources_pathmap_path,
-      track_string_literals=args.track_string_literals,
-      metadata=metadata,
-      apk_so_path=apk_so_path,
-      pak_files=args.pak_file,
-      pak_info_file=args.pak_info_file,
-      linker_name=linker_name,
-      size_info_prefix=size_info_prefix,
-      knobs=knobs)
-  size_info = CreateSizeInfo(
-      section_sizes, raw_symbols, metadata=metadata, normalize_names=False)
-
-  if logging.getLogger().isEnabledFor(logging.DEBUG):
-    for line in describe.DescribeSizeInfoCoverage(size_info):
-      logging.debug(line)
-  logging.info('Recorded info for %d symbols', len(size_info.raw_symbols))
-  logging.info('Recording metadata: \n  %s',
-               '\n  '.join(describe.DescribeMetadata(size_info.metadata)))
-  logging.info('Saving result to %s', args.size_file)
-  file_format.SaveSizeInfo(
-      size_info, args.size_file, include_padding=args.include_padding)
-  size_in_mb = os.path.getsize(args.size_file) / 1024.0 / 1024.0
-  logging.info('Done. File size is %.2fMiB.', size_in_mb)
+    yield _Inner(args.apk_file, args.apk_file)
 
 
 def Run(args, on_config_error):
@@ -1941,11 +1929,58 @@
         'Must pass at least one of --apk-file, --minimal-apks-file, '
         '--elf-file, --map-file')
   setattr(args, 'any_path_within_output_directory', any_path)
-  setattr(args, 'extracted_minimal_apk_path', None)
 
-  if args.minimal_apks_file:
-    with zip_util.UnzipToTemp(args.minimal_apks_file, _APKS_MAIN_APK) as temp:
-      args.extracted_minimal_apk_path = temp
-      _RunInternal(args, on_config_error)
-  else:
-    _RunInternal(args, on_config_error)
+  knobs = SectionSizeKnobs(args.is_bundle)
+  knobs.ModifyWithArgs(args)
+
+  metadata_list = []
+  section_sizes_list = []
+  raw_symbols_list = []
+  # Generate one size info for each container.
+  for (output_directory, tool_prefix, apk_path, mapping_path, apk_so_path,
+       elf_path, map_path, resources_pathmap_path, linker_name,
+       size_info_prefix) in _DeduceMainPaths(args, knobs, on_config_error):
+    # Note that |args.apk_file| is used instead of |apk_path|, since the latter
+    # may be an extracted temporary file.
+    metadata = CreateMetadata(map_path, elf_path, args.apk_file,
+                              args.minimal_apks_file, tool_prefix,
+                              output_directory, linker_name)
+    section_sizes, raw_symbols = CreateSectionSizesAndSymbols(
+        map_path=map_path,
+        tool_prefix=tool_prefix,
+        elf_path=elf_path,
+        apk_path=apk_path,
+        mapping_path=mapping_path,
+        output_directory=output_directory,
+        resources_pathmap_path=resources_pathmap_path,
+        track_string_literals=args.track_string_literals,
+        metadata=metadata,
+        apk_so_path=apk_so_path,
+        pak_files=args.pak_file,
+        pak_info_file=args.pak_info_file,
+        linker_name=linker_name,
+        size_info_prefix=size_info_prefix,
+        knobs=knobs)
+
+    metadata_list.append(metadata)
+    section_sizes_list.append(section_sizes)
+    raw_symbols_list.append(raw_symbols)
+
+  size_info = CreateSizeInfo(
+      section_sizes_list,
+      raw_symbols_list,
+      metadata_list,
+      normalize_names=False)
+
+  if logging.getLogger().isEnabledFor(logging.DEBUG):
+    for line in describe.DescribeSizeInfoCoverage(size_info):
+      logging.debug(line)
+  logging.info('Recorded info for %d symbols', len(size_info.raw_symbols))
+  logging.info('Recording metadata: \n  %s', '\n  '.join(
+      describe.DescribeMetadata(size_info.metadata)))
+
+  logging.info('Saving result to %s', args.size_file)
+  file_format.SaveSizeInfo(
+      size_info, args.size_file, include_padding=args.include_padding)
+  size_in_mb = os.path.getsize(args.size_file) / 1024.0 / 1024.0
+  logging.info('Done. File size is %.2fMiB.', size_in_mb)
diff --git a/tools/binary_size/libsupersize/file_format.py b/tools/binary_size/libsupersize/file_format.py
index aa96839..eda89e16 100644
--- a/tools/binary_size/libsupersize/file_format.py
+++ b/tools/binary_size/libsupersize/file_format.py
@@ -233,13 +233,15 @@
 
   Args:
     size_info: Data to write to the file
-    file_obj: File opened for writing
-    sparse_symbols: If present, only save these symbols to the file
+    file_obj: File opened for writing.
+    include_padding: Whether to save padding data, useful if adding a subset of
+      symbols.
+    sparse_symbols: If present, only save these symbols to the file.
   """
   if sparse_symbols is not None:
-    # Any aliases of sparse symbols must also be included, or else file parsing
-    # will attribute symbols that happen to follow an incomplete alias group to
-    # that alias group.
+    # Any aliases of sparse symbols must also be included, or else file
+    # parsing will attribute symbols that happen to follow an incomplete alias
+    # group to that alias group.
     raw_symbols = _ExpandSparseSymbols(sparse_symbols)
   else:
     raw_symbols = size_info.raw_symbols
@@ -362,7 +364,7 @@
 def _LoadSizeInfoFromFile(file_obj, size_path):
   """Loads a size_info from the given file.
 
-  See _SaveSizeInfoToFile for details on the .size file format.
+  See _SaveSizeInfoToFile() for details on the .size file format.
 
   Args:
     file_obj: File to read, should be a GzipFile
diff --git a/tools/binary_size/libsupersize/integration_test.py b/tools/binary_size/libsupersize/integration_test.py
index 83f29ac..e11bdb1c 100755
--- a/tools/binary_size/libsupersize/integration_test.py
+++ b/tools/binary_size/libsupersize/integration_test.py
@@ -216,7 +216,7 @@
             size_info_prefix=size_info_prefix,
             knobs=knobs)
         IntegrationTest.cached_size_info[cache_key] = archive.CreateSizeInfo(
-            section_sizes, raw_symbols, metadata=metadata)
+            [section_sizes], [raw_symbols], [metadata])
     return copy.deepcopy(IntegrationTest.cached_size_info[cache_key])
 
   def _DoArchive(self,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4b479e8..2d4e57f6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26913,7 +26913,7 @@
   <int value="28" label="DocumentWrite"/>
   <int value="29" label="LazyLoad"/>
   <int value="30" label="LayoutAnimations"/>
-  <int value="31" label="WakeLock"/>
+  <int value="31" label="ScreenWakeLock"/>
   <int value="32" label="FontDisplay"/>
   <int value="33" label="TopNavigation"/>
   <int value="34" label="FormSubmission"/>
@@ -50044,6 +50044,7 @@
   <int value="3" label="Change password"/>
   <int value="4" label="Edit password"/>
   <int value="5" label="Remove password"/>
+  <int value="6" label="Show password"/>
 </enum>
 
 <enum name="PasswordCheckReferrer">
@@ -52192,6 +52193,30 @@
   <int value="1" label="Error"/>
 </enum>
 
+<enum name="PluginVmSetupFailureReason">
+  <int value="1" label="SIGNAL_NOT_CONNECTED"/>
+  <int value="2" label="OPERATION_IN_PROGRESS"/>
+  <int value="3" label="NOT_ALLOWED"/>
+  <int value="4" label="INVALID_IMAGE_URL"/>
+  <int value="5" label="UNEXPECTED_DISK_IMAGE_STATUS"/>
+  <int value="6" label="INVALID_DISK_IMAGE_STATUS_RESPONSE"/>
+  <int value="7" label="DOWNLOAD_FAILED_UNKNOWN"/>
+  <int value="8" label="DOWNLOAD_FAILED_NETWORK"/>
+  <int value="9" label="DOWNLOAD_FAILED_ABORTED"/>
+  <int value="10" label="HASH_MISMATCH"/>
+  <int value="11" label="DISPATCHER_NOT_AVAILABLE"/>
+  <int value="12" label="CONCIERGE_NOT_AVAILABLE"/>
+  <int value="13" label="COULD_NOT_OPEN_IMAGE"/>
+  <int value="14" label="INVALID_IMPORT_RESPONSE"/>
+  <int value="15" label="IMAGE_IMPORT_FAILED"/>
+  <int value="18" label="DLC_INTERNAL"/>
+  <int value="19" label="DLC_UNSUPPORTED"/>
+  <int value="20" label="DLC_BUSY"/>
+  <int value="21" label="DLC_NEED_REBOOT"/>
+  <int value="22" label="DLC_NEED_SPACE"/>
+  <int value="23" label="INSUFFICIENT_DISK_SPACE"/>
+</enum>
+
 <enum name="PluginVmSetupResult">
   <int value="0" label="Success"/>
   <int value="1" label="Plugin VM is not allowed for this user"/>
@@ -52203,6 +52228,11 @@
   <int value="6" label="Error while downloading Plugin VM DLC"/>
   <int value="7" label="User cancelled setup while downloading Plugin VM DLC"/>
   <int value="8" label="Plugin VM was already installed"/>
+  <int value="9" label="User cancelled setup while checking for existing VM"/>
+  <int value="10" label="User didn't meet minimum required free disk space"/>
+  <int value="11" label="User cancelled setup due to low free disk space"/>
+  <int value="12" label="User cancelled setup during disk space check"/>
+  <int value="13" label="Error, see PluginVm.SetupFailureReason"/>
 </enum>
 
 <enum name="PNaClOptionsOptLevelEnum">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 35824c2..fe391d9 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -14992,10 +14992,13 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.AdapterDecisionAtUserChange.AlsDelta"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
 <!-- Name completed by histogram_suffixes name="AlsBrightnessDirection" and name="AdapterDecision" -->
 
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. We also log the als-delta from the
@@ -15006,10 +15009,13 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.AdapterDecisionAtUserChange.AlsStd"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
 <!-- Name completed by histogram_suffixes name="AlsBrightnessDirection" and name="AdapterDecision" -->
 
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. We also log the als-stddev in its
@@ -15020,8 +15026,11 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.BrightnessChange.Cause"
-    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2020-09-20">
+    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. This enum states the reason why the
@@ -15031,10 +15040,13 @@
 
 <histogram base="true"
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.ModelIteration"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
 <!-- Name completed by histogram_suffixes name="AdapterDecision" -->
 
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. We also log the number of model
@@ -15045,8 +15057,11 @@
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.NoBrightnessChange.Cause"
     enum="AutoScreenBrightnessNoBrightnessChangeCause"
-    expires_after="2020-06-30">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. This enum states the reason why the
@@ -15056,10 +15071,13 @@
 
 <histogram
     name="AutoScreenBrightness.AdapterDecisionAtUserChange.Unknown.AlsStd"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
 <!-- Name completed by histogram_suffixes name="AdapterDecision" -->
 
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, we will ask the model whether it also
     thinks brightness should be changed. We also log the als-stddev in its
@@ -15069,31 +15087,42 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.AlsReaderStatus"
-    enum="AutoScreenBrightnessAlsReaderStatus" expires_after="2020-07-26">
+    enum="AutoScreenBrightnessAlsReaderStatus" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether the ALS is enabled or the error otherwise. Chrome OS only.
   </summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.Cause"
-    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2020-09-20">
+    enum="AutoScreenBrightnessBrightnessChangeCause" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>Reason for the model to change brightness. Chrome OS only.</summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.ElapsedTime" units="ms"
-    expires_after="2020-05-03">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     The time between two consecutive auto brightness changes. Chrome OS only.
   </summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.BrightnessChange.ModelIteration"
-    units="count" expires_after="2020-05-10">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of model iterations when brightness is changed by a personal curve.
     Chrome OS only.
@@ -15102,9 +15131,11 @@
 
 <histogram name="AutoScreenBrightness.BrightnessMonitorStatus"
     enum="AutoScreenBrightnessBrightnessMonitorStatus"
-    expires_after="2020-09-01">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether the brightness monitor is enabled or the error otherwise. Chrome OS
     only.
@@ -15112,8 +15143,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Atlas" units="count"
-    expires_after="2020-07-26">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on an Atlas
     device with an ambient sensor that is supported by the auto screen
@@ -15127,8 +15161,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Eve" units="count"
-    expires_after="2020-09-06">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on an Eve device
     with an ambient sensor that is supported by the auto screen brightness
@@ -15141,9 +15178,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Kohaku" units="count"
-    expires_after="2020-08-02">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on a Kohaku
     device with an ambient sensor that is supported by the auto screen
@@ -15157,8 +15196,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.NoAls" units="count"
-    expires_after="2020-07-26">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on a device
     without an ambient sensor. Reported daily. The count is accumulated through
@@ -15171,9 +15213,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.Nocturne"
-    units="count" expires_after="2020-08-02">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on a Nocturne
     device with an ambient sensor that is supported by the auto screen
@@ -15187,8 +15231,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.SupportedAls"
-    units="count" expires_after="2020-09-20">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on a device with
     an ambient sensor that is supported by the auto screen brightness model.
@@ -15201,8 +15248,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.UnsupportedAls"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Number of times that a user has made brightness adjustments on a device with
     an ambient sensor that is not supported by the auto screen brightness model.
@@ -15215,8 +15265,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.DataError"
-    enum="AutoScreenBrightnessDataError" expires_after="2020-06-30">
+    enum="AutoScreenBrightnessDataError" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Type of error seen when handling data related to automatic screen brightness
     adjustments. Only reported on Chrome OS.
@@ -15225,8 +15278,11 @@
 
 <histogram
     name="AutoScreenBrightness.ElapsedTimeBetweenModelAndUserAdjustments"
-    units="ms" expires_after="2020-05-03">
+    units="ms" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     The elapsed time from a model brightness adjustment to the next user manual
     brightness change. Chrome OS only.
@@ -15234,24 +15290,31 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.GlobalCurveResetOnInitialization"
-    enum="Boolean" expires_after="2020-12-31">
+    enum="Boolean" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>Whether the global curve is reset at initialization.</summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.MissingAlsWhenBrightnessChanged"
-    enum="BooleanError" expires_after="2020-12-31">
+    enum="BooleanError" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether there was no recorded ALS reading when brightness was changed.
   </summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.MissingPriorUserBrightnessRequest"
-    enum="BooleanError" expires_after="2020-06-30">
+    enum="BooleanError" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Under the current implementation of brightness monitor, user brightness
     request should be received before brightness change signal. Adapter operates
@@ -15262,25 +15325,34 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelIterationCountAtInitialization"
-    units="count" expires_after="2020-06-30">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Personal model iteration count when modeller is initialized. Chrome OS only.
   </summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelLoadingStatus"
-    enum="AutoScreenBrightnessModelLoadingStatus" expires_after="2020-06-30">
+    enum="AutoScreenBrightnessModelLoadingStatus" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>Status of the model loading from disk. Chrome OS only.</summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.BrightnessChange"
     enum="AutoScreenBrightnessBoundedBrightnessChange"
-    expires_after="2020-05-31">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether a new brightness exceeds the preset bound that defines reasonble
     range of change. Chrome OS only.
@@ -15288,8 +15360,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.BrightnessOutlier"
-    enum="Boolean" expires_after="2020-05-31">
+    enum="Boolean" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether a training data is an outlier and should be discarded by the model
     during training. Chrome OS only.
@@ -15297,9 +15372,12 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.Inaccuracy.NoUpdate"
-    units="%" expires_after="2020-05-31">
+    units="%" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When training data comes in, the model may or may not be updated. We measure
     the error of the model as compared with the target value from training data.
@@ -15308,9 +15386,12 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.Inaccuracy.Update"
-    units="%" expires_after="2020-05-31">
+    units="%" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
   <owner>napper@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When training data comes in, the model may or may not be updated. We measure
     the error of the model as compared with the target value from training data.
@@ -15319,8 +15400,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ModelTraining.ModelUserConsistent"
-    enum="Boolean" expires_after="2020-05-31">
+    enum="Boolean" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether user brightness change is consistent with the model so that model is
     not updated.
@@ -15328,8 +15412,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.NewCurveSaved.Duration" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     The time elapsed between training start and a new curve saved to disk. Only
     reported if a new curve was created and saved successfully to disk. Chrome
@@ -15338,8 +15425,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.NewCurveSaved.Success"
-    enum="BooleanSuccess" expires_after="2020-07-06">
+    enum="BooleanSuccess" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether a new curve was successfully saved to disk. Only reported if a new
     curve was created during the training process. Chrome OS only.
@@ -15347,8 +15437,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.Opposite.UserModelBrightnessAdjustments"
-    units="count" expires_after="2020-07-06">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, if the previous change was caused by
     the model, then we log bucketized absolute brightness changes of the two
@@ -15358,8 +15451,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.ParameterError"
-    enum="AutoScreenBrightnessParameterError" expires_after="2020-06-30">
+    enum="AutoScreenBrightnessParameterError" expires_after="2020-11-30">
+  <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When there is an error in the automatic screen brightness parameters, this
     records which kind of parameter is invalid. Chrome OS only.
@@ -15367,16 +15463,22 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.PersonalCurveValid" enum="BooleanValid"
-    expires_after="2020-08-30">
+    expires_after="2020-11-30">
+  <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
   <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     Whether the user's personal brightness curve is valid. Chrome OS only.
   </summary>
 </histogram>
 
 <histogram name="AutoScreenBrightness.Same.UserModelBrightnessAdjustments"
-    units="count" expires_after="2020-06-28">
+    units="count" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     When user changes brightness manually, if the previous change was caused by
     the model, then we log bucketized absolute brightness changes of the two
@@ -15386,8 +15488,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.TrainingCompleteDuration.NewCurve"
-    units="ms" expires_after="2020-07-13">
+    units="ms" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     The time elapsed to complete training after which a new curve was generated.
     Chrome OS only.
@@ -15395,8 +15500,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.TrainingCompleteDuration.NoNewCurve"
-    units="ms" expires_after="2020-07-06">
+    units="ms" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     The time elapsed to complete training after which no new curve was
     generated. Chrome OS only.
@@ -15413,8 +15521,11 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.UserAdjustmentEffect"
-    enum="AutoScreenBrightnessUserAdjustmentEffect" expires_after="2020-05-03">
+    enum="AutoScreenBrightnessUserAdjustmentEffect" expires_after="2020-11-30">
   <owner>jiameng@chromium.org</owner>
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>thanhdng@chromium.org</owner>
   <summary>
     How user manual screen brightness adjustment changes the model. Chrome OS
     only.
@@ -69631,6 +69742,10 @@
 
 <histogram base="true" name="MachineLearningService.ElapsedTimeMicrosec"
     units="microseconds" expires_after="2020-02-01">
+  <obsolete>
+    Removed 2020-02-01 because elapsed time should be measured by clients, if
+    they need it.
+  </obsolete>
 <!-- Name completed by histogram_suffixes
      name="MachineLearningServiceRequests" -->
 
@@ -116922,7 +117037,7 @@
 </histogram>
 
 <histogram name="PasswordManager.StoreDecryptionResult"
-    enum="PasswordDecryptionResult" expires_after="2020-05-01">
+    enum="PasswordDecryptionResult" expires_after="2020-11-01">
   <owner>cfroussios@chromium.org</owner>
   <owner>dvadym@chromium.org</owner>
   <summary>
@@ -123504,13 +123619,22 @@
   </summary>
 </histogram>
 
+<histogram name="PluginVm.SetupFailureReason" enum="PluginVmSetupFailureReason"
+    expires_after="2020-12-31">
+  <owner>kimjae@chromium.org</owner>
+  <owner>timloh@chromium.org</owner>
+  <summary>Recorded when the Plugin VM installer fails.</summary>
+</histogram>
+
 <histogram name="PluginVm.SetupResult" enum="PluginVmSetupResult"
     expires_after="2020-12-31">
   <owner>aoldemeier@chromium.org</owner>
   <owner>okalitova@chromium.org</owner>
   <owner>timloh@chromium.org</owner>
   <summary>
-    Recorded at each attempt to set up PluginVm, recording the setup result.
+    Recorded at each attempt to set up PluginVm, recording the setup result. As
+    of M83, errors are grouped together here and broken down in
+    PluginVm.SetupFailureReason.
   </summary>
 </histogram>
 
@@ -149926,7 +150050,8 @@
 
 <histogram base="true" name="Sharing.WebRtc.TimingEvents" units="ms"
     expires_after="M84">
-<!-- Name completed by histogram_suffixes name="SharingWebRtcTimingEvent" -->
+<!-- Name completed by histogram_suffixes name="SharingWebRtcTimingEventRole"
+       and name="SharingWebRtcTimingEvent" -->
 
   <owner>himanshujaju@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
@@ -150845,7 +150970,7 @@
 </histogram>
 
 <histogram name="Signin.OAuth2MintToken.ApiCallResult"
-    enum="OAuth2MintTokenApiCallResult" expires_after="2020-05-02">
+    enum="OAuth2MintTokenApiCallResult" expires_after="2020-09-02">
   <owner>alexilin@chromium.org</owner>
   <owner>droger@chromium.org</owner>
   <summary>
@@ -190392,7 +190517,12 @@
   <affected-histogram
       name="MachineLearningService.CreateGraphExecutorResult.CpuTimeMicrosec"/>
   <affected-histogram
-      name="MachineLearningService.CreateGraphExecutorResult.ElapsedTimeMicrosec"/>
+      name="MachineLearningService.CreateGraphExecutorResult.ElapsedTimeMicrosec">
+    <obsolete>
+      Removed 2020-02-01 because elapsed time should be measured by clients, if
+      they need it.
+    </obsolete>
+  </affected-histogram>
   <affected-histogram
       name="MachineLearningService.CreateGraphExecutorResult.Event"/>
   <affected-histogram
@@ -190400,14 +190530,24 @@
   <affected-histogram
       name="MachineLearningService.ExecuteResult.CpuTimeMicrosec"/>
   <affected-histogram
-      name="MachineLearningService.ExecuteResult.ElapsedTimeMicrosec"/>
+      name="MachineLearningService.ExecuteResult.ElapsedTimeMicrosec">
+    <obsolete>
+      Removed 2020-02-01 because elapsed time should be measured by clients, if
+      they need it.
+    </obsolete>
+  </affected-histogram>
   <affected-histogram name="MachineLearningService.ExecuteResult.Event"/>
   <affected-histogram
       name="MachineLearningService.ExecuteResult.TotalMemoryDeltaKb"/>
   <affected-histogram
       name="MachineLearningService.LoadModelResult.CpuTimeMicrosec"/>
   <affected-histogram
-      name="MachineLearningService.LoadModelResult.ElapsedTimeMicrosec"/>
+      name="MachineLearningService.LoadModelResult.ElapsedTimeMicrosec">
+    <obsolete>
+      Removed 2020-02-01 because elapsed time should be measured by clients, if
+      they need it.
+    </obsolete>
+  </affected-histogram>
   <affected-histogram name="MachineLearningService.LoadModelResult.Event"/>
   <affected-histogram
       name="MachineLearningService.LoadModelResult.TotalMemoryDeltaKb"/>
@@ -198446,6 +198586,16 @@
   <suffix name="SendingMessage" label="Message sending"/>
   <suffix name="SignalingStable" label="Signaling process stable"/>
   <affected-histogram name="Sharing.WebRtc.TimingEvents"/>
+  <affected-histogram name="Sharing.WebRtc.TimingEvents.Receiver"/>
+  <affected-histogram name="Sharing.WebRtc.TimingEvents.Sender"/>
+  <affected-histogram name="Sharing.WebRtc.TimingEvents.Unknown"/>
+</histogram_suffixes>
+
+<histogram_suffixes name="SharingWebRtcTimingEventRole" separator=".">
+  <suffix name="Receiver" label="Receiver device"/>
+  <suffix name="Sender" label="Sender device"/>
+  <suffix name="Unknown" label="Unknown"/>
+  <affected-histogram name="Sharing.WebRtc.TimingEvents"/>
 </histogram_suffixes>
 
 <histogram_suffixes name="ShillCumulativeTimeOnline" separator=".">
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index 92bdb30..fb95731 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -1151,6 +1151,10 @@
   width: 36px;
 }
 
+html:not(.pointer-active) .dialog-header.files-ng #search-box .clear:hover {
+  background-color: rgba(0, 0, 0, 4%);
+}
+
 .dialog-header.files-ng #search-box .clear > .icon {
   -webkit-mask-image: url(../images/files/ui/search_clear_filled.svg);
 }
diff --git a/ui/file_manager/file_manager/foreground/elements/files_xf_elements_unittest.js b/ui/file_manager/file_manager/foreground/elements/files_xf_elements_unittest.js
index 77ec00d..9f5c056 100644
--- a/ui/file_manager/file_manager/foreground/elements/files_xf_elements_unittest.js
+++ b/ui/file_manager/file_manager/foreground/elements/files_xf_elements_unittest.js
@@ -318,6 +318,41 @@
   assertEquals('success', summaryPanelItem.status);
 }
 
+function testFilesDisplayPanelMixedProgress() {
+  // Get the host display panel container element.
+  /** @type {!DisplayPanel|!Element} */
+  const displayPanel = assert(document.querySelector('#test-xf-display-panel'));
+
+  // Add a generic progress panel item to the display panel container.
+  const progressPanel = displayPanel.addPanelItem('testpanel1');
+  progressPanel.panelType = progressPanel.panelTypeProgress;
+  progressPanel.progress = '1';
+
+  // Add a format progress panel item to the display panel container.
+  const formatProgressPanel = displayPanel.addPanelItem('testpanel2');
+  formatProgressPanel.panelType = formatProgressPanel.panelTypeFormatProgress;
+  formatProgressPanel.progress = '2';
+
+  // Confirm that format progress panels do not have a cancel button.
+  assertEquals(null, formatProgressPanel.secondaryButton);
+
+  // Add a drive sync progress panel item to the display panel container.
+  const syncProgressPanel = displayPanel.addPanelItem('testpanel3');
+  syncProgressPanel.panelType = syncProgressPanel.panelTypeSyncProgress;
+  syncProgressPanel.progress = '6';
+
+  // Confirm that sync progress panels do not have a cancel button.
+  assertEquals(null, syncProgressPanel.secondaryButton);
+
+  // Verify a summary panel item is created with the correct average.
+  const summaryContainer = displayPanel.shadowRoot.querySelector('#summary');
+  const summaryPanelItem = summaryContainer.querySelector('xf-panel-item');
+  assertEquals(summaryPanelItem.panelTypeSummary, summaryPanelItem.panelType);
+  assertEquals('largeprogress', summaryPanelItem.indicator);
+  assertEquals('hidden', summaryPanelItem.errorMarkerVisibility);
+  assertEquals('3', summaryPanelItem.progress);
+}
+
 function testFilesDisplayPanelCircularProgress() {
   // Get the host display panel container element.
   /** @type {!DisplayPanel|!Element} */
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_display_panel.js b/ui/file_manager/file_manager/foreground/elements/xf_display_panel.js
index 59fed822..28c3631 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_display_panel.js
+++ b/ui/file_manager/file_manager/foreground/elements/xf_display_panel.js
@@ -241,7 +241,9 @@
     const connectedPanels = this.connectedPanelItems_();
     for (const panel of connectedPanels) {
       // Only sum progress for attached progress panels.
-      if (panel.panelType === panel.panelTypeProgress) {
+      if (panel.panelType === panel.panelTypeProgress ||
+          panel.panelType === panel.panelTypeFormatProgress ||
+          panel.panelType === panel.panelTypeSyncProgress) {
         total += Number(panel.progress);
         progressCount++;
       } else if (panel.panelType === panel.panelTypeError) {
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
index 9d11c507..96e8a61 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
+++ b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
@@ -27,6 +27,7 @@
     this.panelTypeError = 3;
     this.panelTypeInfo = 4;
     this.panelTypeFormatProgress = 5;
+    this.panelTypeSyncProgress = 6;
 
     /** @private {number} */
     this.panelType_ = this.panelTypeDefault;
@@ -265,6 +266,9 @@
         this.setAttribute('indicator', 'status');
         this.setAttribute('status', 'hard-drive');
         break;
+      case this.panelTypeSyncProgress:
+        this.setAttribute('indicator', 'progress');
+        break;
     }
 
     this.panelType_ = type;
diff --git a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
index 49c115b0..43d60cc 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
@@ -426,6 +426,8 @@
         }, this.PENDING_TIME_MS_);
         if (item.type === 'format') {
           panelItem.panelType = panelItem.panelTypeFormatProgress;
+        } else if (item.type === 'sync') {
+          panelItem.panelType = panelItem.panelTypeSyncProgress;
         } else {
           panelItem.panelType = panelItem.panelTypeProgress;
         }
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index 4322a623..e49ee587 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -373,6 +373,7 @@
       "//components/minidump_uploader",
       "//components/navigation_interception",
       "//components/permissions/android:native",
+      "//components/resources:android_resources",
       "//components/safe_browsing/content/common:interfaces",
       "//components/safe_browsing/content/renderer:throttles",
       "//components/safe_browsing/core/common",
diff --git a/weblayer/browser/DEPS b/weblayer/browser/DEPS
index 22ae82e5..ef4fd49 100644
--- a/weblayer/browser/DEPS
+++ b/weblayer/browser/DEPS
@@ -24,6 +24,7 @@
   "+components/prefs",
   "+components/url_formatter",
   "+components/user_prefs",
+  "+components/resources/android",
   "+components/safe_browsing/core/common",
   "+components/safe_browsing/core/features.h",
   "+components/security_interstitials",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
index b55fef9..8fd8e12c 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
@@ -18,6 +18,7 @@
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.weblayer.Browser;
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
@@ -34,6 +35,10 @@
     private static final String INTENT_TO_CHROME_URL =
             "intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end";
     private static final String NON_RESOLVABLE_INTENT_URL = "intent://garbage;end";
+    private static final String LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE =
+            "link_with_intent_to_chrome_in_same_tab.html";
+    private static final String LINK_WITH_INTENT_TO_CHROME_IN_NEW_TAB_FILE =
+            "link_with_intent_to_chrome_in_new_tab.html";
 
     // The test server handles "echo" with a response containing "Echo" :).
     private final String mTestServerSiteUrl = mActivityTestRule.getTestServer().getURL("/echo");
@@ -135,6 +140,83 @@
     }
 
     /**
+     * Tests that clicking on a link that goes to an external intent in the same tab results in the
+     * external intent being launched.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentInSameTabLaunchedOnLinkClick() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE);
+
+        mActivityTestRule.navigateAndWait(url);
+
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed, and the intent should have been launched.
+        Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals("com.android.chrome", intent.getPackage());
+        Assert.assertEquals("android.intent.action.VIEW", intent.getAction());
+        Assert.assertEquals("https://play.google.com/store/apps/details?id=com.facebook.katana/",
+                intent.getDataString());
+    }
+
+    /**
+     * Tests that clicking on a link that goes to an external intent in a new tab results in
+     * a new tab being opened whose URL is that of the intent and the intent being launched.
+     */
+    @Test
+    @SmallTest
+    public void testExternalIntentInNewTabLaunchedOnLinkClick() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
+        IntentInterceptor intentInterceptor = new IntentInterceptor();
+        activity.setIntentInterceptor(intentInterceptor);
+
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_NEW_TAB_FILE);
+
+        mActivityTestRule.navigateAndWait(url);
+
+        // Grab the existing tab before causing a new one to be opened.
+        Tab tab = mActivityTestRule.getActivity().getTab();
+
+        mActivityTestRule.executeScriptSync(
+                "document.onclick = function() {document.getElementById('link').click()}",
+                true /* useSeparateIsolate */);
+        EventUtils.simulateTouchCenterOfView(
+                mActivityTestRule.getActivity().getWindow().getDecorView());
+
+        intentInterceptor.waitForIntent();
+
+        // The current URL should not have changed in the existing tab, and the intent should have
+        // been launched.
+        Assert.assertEquals(url, mActivityTestRule.getLastCommittedUrlInTab(tab));
+        Intent intent = intentInterceptor.mLastIntent;
+        Assert.assertNotNull(intent);
+        Assert.assertEquals("com.android.chrome", intent.getPackage());
+        Assert.assertEquals("android.intent.action.VIEW", intent.getAction());
+        Assert.assertEquals("https://play.google.com/store/apps/details?id=com.facebook.katana/",
+                intent.getDataString());
+
+        // A new tab should have been created whose URL is that of the intent.
+        Browser browser = mActivityTestRule.getActivity().getBrowser();
+        int numTabs =
+                TestThreadUtils.runOnUiThreadBlocking(() -> { return browser.getTabs().size(); });
+        Assert.assertEquals(2, numTabs);
+        Assert.assertEquals(INTENT_TO_CHROME_URL, mActivityTestRule.getCurrentDisplayUrl());
+    }
+
+    /**
      * Tests that a navigation that redirects to an external intent with a fallback URL results in
      * the external intent being launched.
      */
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java
index b5af1eb1..20e6fc0 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/InstrumentationActivityTestRule.java
@@ -260,15 +260,22 @@
         return getTestServer().getURL("/weblayer/test/data/" + path);
     }
 
-    public String getCurrentDisplayUrl() {
+    // Returns the display URL of the last committed navigation entry in |tab|. Note that this will
+    // return an empty URL if there have been no committed navigations in |tab|.
+    public String getLastCommittedUrlInTab(Tab tab) {
         return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
-            NavigationController navController = getActivity().getTab().getNavigationController();
+            NavigationController navController = tab.getNavigationController();
             return navController
                     .getNavigationEntryDisplayUri(navController.getNavigationListCurrentIndex())
                     .toString();
         });
     }
 
+    // Returns the URL that is currently being displayed to the user.
+    public String getCurrentDisplayUrl() {
+        return getActivity().getCurrentDisplayUrl();
+    }
+
     public void setRetainInstance(boolean retain) {
         TestThreadUtils.runOnUiThreadBlocking(() -> getActivity().setRetainInstance(retain));
     }
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java
index 9eccc256..c20ea5cf 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ProfileTest.java
@@ -12,6 +12,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -119,6 +120,7 @@
 
     @Test
     @SmallTest
+    @DisabledTest
     public void testEnumerateAllProfileNames() throws Exception {
         final String profileName = "TestEnumerateAllProfileNames";
         final WebLayer weblayer = mActivityTestRule.getWebLayer();
diff --git a/weblayer/browser/android/resource_mapper.cc b/weblayer/browser/android/resource_mapper.cc
index 3d091df..e7f1a4d6 100644
--- a/weblayer/browser/android/resource_mapper.cc
+++ b/weblayer/browser/android/resource_mapper.cc
@@ -10,7 +10,7 @@
 #include "base/android/jni_array.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
-#include "components/permissions/android/theme_resources.h"
+#include "components/resources/android/theme_resources.h"
 #include "weblayer/browser/java/jni/ResourceMapper_jni.h"
 
 namespace weblayer {
@@ -37,7 +37,7 @@
   (*GetIdMap())[c_id] = resource_id_list[next_id++];
 #define DECLARE_RESOURCE_ID(c_id, java_id) \
   (*GetIdMap())[c_id] = resource_id_list[next_id++];
-#include "components/permissions/android/resource_id.h"
+#include "components/resources/android/permissions_resource_id.h"
 #undef LINK_RESOURCE_ID
 #undef DECLARE_RESOURCE_ID
   // Make sure ID list sizes match up.
diff --git a/weblayer/browser/java/BUILD.gn b/weblayer/browser/java/BUILD.gn
index 123a4e8..2712bcd6 100644
--- a/weblayer/browser/java/BUILD.gn
+++ b/weblayer/browser/java/BUILD.gn
@@ -23,7 +23,7 @@
 java_cpp_template("resource_id_javagen") {
   sources = [ "ResourceId.template" ]
   package_path = "org/chromium/weblayer_private/resources"
-  inputs = [ "//components/permissions/android/resource_id.h" ]
+  inputs = [ "//components/resources/android/permissions_resource_id.h" ]
 }
 
 java_cpp_enum("generated_enums") {
diff --git a/weblayer/browser/java/ResourceId.template b/weblayer/browser/java/ResourceId.template
index ec5f017..15429c1 100644
--- a/weblayer/browser/java/ResourceId.template
+++ b/weblayer/browser/java/ResourceId.template
@@ -11,7 +11,7 @@
         int[] resourceList = {
 #define LINK_RESOURCE_ID(c_id,java_id) java_id,
 #define DECLARE_RESOURCE_ID(c_id,java_id) java_id,
-#include "components/permissions/android/resource_id.h"
+#include "components/resources/android/permissions_resource_id.h"
         };
         return resourceList;
     }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
index 686e3bb..937fa2f 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/DownloadImpl.java
@@ -300,21 +300,6 @@
         mNotificationId = getNextNotificationId();
         sMap.put(Integer.valueOf(mNotificationId), this);
 
-        Intent pauseIntent = createIntent();
-        pauseIntent.setAction(PAUSE_INTENT);
-        pauseIntent.putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
-
-        // See PendingIntent's documentation on why we must use a different requestId as we need
-        // multiple distinct PendingIntents at a time, one for each notification.
-        PendingIntent pausePendingIntent = PendingIntent.getBroadcast(
-                ContextUtils.getApplicationContext(), mNotificationId, pauseIntent, 0);
-
-        Intent cancelIntent = createIntent();
-        cancelIntent.setAction(CANCEL_INTENT);
-        cancelIntent.putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
-        PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(
-                ContextUtils.getApplicationContext(), mNotificationId, cancelIntent, 0);
-
         Intent deleteIntent = createIntent();
         deleteIntent.setAction(DELETE_INTENT);
         deleteIntent.putExtra(EXTRA_NOTIFICATION_ID, mNotificationId);
@@ -323,41 +308,17 @@
 
         mBuilder = new NotificationCompat.Builder(ContextUtils.getApplicationContext(), CHANNEL_ID)
                            .setSmallIcon(android.R.drawable.stat_sys_download)
-                           .setContentTitle((new File(getLocation())).getName())
                            .setOngoing(true)
-                           .addAction(0 /* no icon */, "Pause", pausePendingIntent)
-                           .addAction(0 /* no icon */, "Cancel", cancelPendingIntent)
                            .setDeleteIntent(deletePendingIntent)
                            .setPriority(NotificationCompat.PRIORITY_DEFAULT);
 
-        if (getTotalBytes() == -1) {
-            mBuilder.setProgress(0, 0, true);
-        } else {
-            mBuilder.setProgress(100, 0, false);
-        }
-
-        NotificationManagerCompat notificationManager = getNotificationManager();
-        if (notificationManager != null) {
-            // mNotificationId is a unique int for each notification that you must define
-            notificationManager.notify(mNotificationId, mBuilder.build());
-        }
+        updateNotification();
     }
 
     public void downloadProgressChanged() {
         if (mBuilder == null) return;
 
-        // The filename might not have been available initially.
-        mBuilder.setContentTitle((new File(getLocation())).getName());
-        if (getTotalBytes() > 0) {
-            int progressCurrent = (int) (getReceivedBytes() * 100 / getTotalBytes());
-
-            mBuilder.setProgress(100, progressCurrent, false);
-
-            NotificationManagerCompat notificationManager = getNotificationManager();
-            if (notificationManager != null) {
-                notificationManager.notify(mNotificationId, mBuilder.build());
-            }
-        }
+        updateNotification();
     }
 
     public void downloadCompleted() {
@@ -392,6 +353,18 @@
 
         NotificationManagerCompat notificationManager = getNotificationManager();
 
+        // The filename might not have been available initially.
+        String location = getLocation();
+        if (!TextUtils.isEmpty((location))) {
+            mBuilder.setContentTitle((new File(location)).getName());
+        }
+
+        if (getTotalBytes() > 0) {
+            int progressCurrent = (int) (getReceivedBytes() * 100 / getTotalBytes());
+
+            mBuilder.setProgress(100, progressCurrent, false);
+        }
+
         @DownloadState
         int state = getState();
         if (state == DownloadState.CANCELLED) {
@@ -451,6 +424,7 @@
         }
 
         if (notificationManager != null) {
+            // mNotificationId is a unique int for each notification that you must define.
             notificationManager.notify(mNotificationId, mBuilder.build());
         }
     }
diff --git a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/InstrumentationActivity.java b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/InstrumentationActivity.java
index d6cf0c8..573980b 100644
--- a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/InstrumentationActivity.java
+++ b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/InstrumentationActivity.java
@@ -26,6 +26,8 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.weblayer.Browser;
+import org.chromium.weblayer.NewTabCallback;
+import org.chromium.weblayer.NewTabType;
 import org.chromium.weblayer.Profile;
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.TabCallback;
@@ -248,6 +250,26 @@
             }
         };
         mTab.registerTabCallback(mTabCallback);
+
+        mTab.setNewTabCallback(new NewTabCallback() {
+            @Override
+            public void onNewTab(Tab newTab, @NewTabType int type) {
+                // NOTE: At this time there isn't a need to hang on to the previous tab as this
+                // activity doesn't support closing tabs. If needed that could be added following
+                // the implementation in WebLayerShellActivity.java.
+                mTab.unregisterTabCallback(mTabCallback);
+                mTabCallback = null;
+                mTab = null;
+
+                setTab(newTab);
+                mBrowser.setActiveTab(newTab);
+            }
+
+            @Override
+            public void onCloseTab() {
+                assert false;
+            }
+        });
     }
 
     private Fragment getOrCreateBrowserFragment() {
@@ -284,6 +306,10 @@
         return fragment;
     }
 
+    public String getCurrentDisplayUrl() {
+        return mUrlView.getText().toString();
+    }
+
     public void loadUrl(String url) {
         mTab.getNavigationController().navigate(Uri.parse(url));
         mUrlView.clearFocus();
diff --git a/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html b/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html
new file mode 100644
index 0000000..44994a17
--- /dev/null
+++ b/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link' target='_blank' href='intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end'>
+  Click to intent to production Chrome in a new tab
+</a>
+</body>
+</html>
diff --git a/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html b/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html
new file mode 100644
index 0000000..e442dc92
--- /dev/null
+++ b/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link' href='intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end'>
+  Click to intent to production Chrome in this tab
+</a>
+</body>
+</html>