diff --git a/DEPS b/DEPS
index 9baa217..78c96be 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': 'd99b64678d6a4a71323c3fe9693fc0a24d3fc4f4',
+  'skia_revision': '1171d314efc7e131b039c94126d26575cfb9e70c',
   # 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': 'd3ac36c81a08ec16adca1734830fe5d319a13dcb',
+  'v8_revision': '633234050d9781560f814e4914c31954459aecd4',
   # 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,11 +187,11 @@
   # 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': 'dadeffa315e7c95b36ebf30fd4094b3fae12c179',
+  'angle_revision': '7daf31d8029b03d71f2e51288756c1f453fa429a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'bc98fbeec99b50d9843eef1cf58ad536f4678183',
+  'swiftshader_revision': 'fb670f56acbdad6eed0a3faa126e9bbef177a66c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # 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': '64e0899efa07a281ed44288cf6fab12e83e47f8a',
+  'devtools_frontend_revision': '747295674924c671f86fe791f5ac81044dfeac12',
   # 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.
@@ -286,11 +286,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': '25ede1ced6797f9a3689ae7dac5085ce8236c573',
+  'spv_tools_revision': '18d3896a15fc90e71b9eb58901b2eb1728185115',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_headers_revision': 'a17e17e36da44d2cd1740132ecd7a8cb078f1d15',
+  'spv_headers_revision': 'f8bf11a0253a32375c32cad92c841237b96696c0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # 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': '1f9b5960d8b20a7aa82cbf1adebea8380f49c936',
+  'dawn_revision': '81bcbbc20ee522b3039d981d5d24aa8e82f4b94c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -521,7 +521,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'ecb6de8a5a229721f761f13056c584c2b9208b6c',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '65758b73af4c97551fce65ae4f8bfedd39a360b3',
       'condition': 'checkout_ios',
   },
 
@@ -852,7 +852,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '829a21222fe39a764a83bbe6eb8feb634ba2691b',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f845b816079e72207f8d8c6bab56896fb8e7a3ec',
       'condition': 'checkout_linux',
   },
 
@@ -1448,7 +1448,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '84ee597cdeae08bb26e578fc66a35bcf35f633f4',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '93bcaae445243cde9d4cb500f7dfc1cd7210bf56',
+    Var('webrtc_git') + '/src.git' + '@' + 'dc5522b4bf4d729c2dfe269f84a5148212c57a88',
 
   '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@53a925b3221aff550f0bea4e6ddab63df14ccd0b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d0550b3138749715ace7875cf3618c19f5ae7e37',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index efde999e..d19b670 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1337,6 +1337,7 @@
     'build/android/gyp/merge_manifest.pydeps',
     'build/android/gyp/prepare_resources.pydeps',
     'build/android/gyp/proguard.pydeps',
+    'build/android/gyp/turbine.pydeps',
     'build/android/gyp/validate_static_library_dex_references.pydeps',
     'build/android/gyp/write_build_config.pydeps',
     'build/android/gyp/write_native_libraries_java.pydeps',
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index c78890b72..e12e002 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -983,6 +983,7 @@
             ->overriding_factory.InitWithNewPipeAndPassReceiver();
     (*factory_override)->overridden_factory_receiver =
         target_factory_remote.InitWithNewPipeAndPassReceiver();
+    (*factory_override)->skip_cors_enabled_scheme_check = true;
   } else {
     // In this case, |factory_override| is not given. But all callers of
     // ContentBrowserClient::WillCreateURLLoaderFactory guarantee that
diff --git a/android_webview/docs/cors-and-webview-api.md b/android_webview/docs/cors-and-webview-api.md
index 053a02f..d728e05 100644
--- a/android_webview/docs/cors-and-webview-api.md
+++ b/android_webview/docs/cors-and-webview-api.md
@@ -53,4 +53,10 @@
 TODO
 
 ### shouldInterceptRequest
-TODO
+Custom scheme should not be permitted for CORS-enabled requests usually.
+However, when shouldInterceptRequest is used, the API allows developers to
+handle CORS-enabled requests over custom schemes.
+
+When a custom scheme is used, `*` or `null` should appear in the
+Access-Control-Allow-Origin response header as such custom scheme is processed
+as an [opaque origin](https://html.spec.whatwg.org/multipage/origin.html#concept-origin-opaque).
diff --git a/android_webview/nonembedded/BUILD.gn b/android_webview/nonembedded/BUILD.gn
index 5f795bb..dfaedf7 100644
--- a/android_webview/nonembedded/BUILD.gn
+++ b/android_webview/nonembedded/BUILD.gn
@@ -114,16 +114,34 @@
   custom_package = "org.chromium.android_webview.devui"
 }
 
-android_resources("devui_launcher_icon_resources") {
-  resource_dirs = []
-  custom_package = "org.chromium.android_webview.devui.icon"
-  android_manifest = "java/DeveloperUiLauncherManifest.xml"
+template("webview_devui_launcher") {
+  forward_variables_from(invoker, "*")
+
+  _manifest_file = "$root_gen_dir/android_webview/DeveloperUiLauncherManifest__${target_name}.xml"
+
+  jinja_template("${target_name}__manifest") {
+    input = "java/DeveloperUiLauncherManifest.xml"
+    output = _manifest_file
+    variables = [ "icon_enabled=$default_enabled_state" ]
+  }
+
+  # Define an android resources target with an AndroidManifest and no actual resources to merge
+  # the activity alias defined in DeveloperUiLauncherManifest.xml to the final APK mainfest of the
+  # target that depends on it.
+  android_resources(target_name) {
+    resource_dirs = []
+    custom_package = "org.chromium.android_webview.devui.icon"
+    android_manifest = _manifest_file
+    android_manifest_dep = ":${target_name}__manifest"
+  }
 }
 
-android_resources("monochrome_devui_launcher_icon_resources") {
-  resource_dirs = []
-  android_manifest = "java/MonochromeDeveloperUiLauncherManifest.xml"
-  custom_package = "org.chromium.android_webview.devui.icon"
+webview_devui_launcher("system_webview_devui_launcher_icon_resources") {
+  default_enabled_state = standalone_or_trichrome_icon_default_enabled_state
+}
+
+webview_devui_launcher("monochrome_devui_launcher_icon_resources") {
+  default_enabled_state = monochrome_launcher_icon_default_enabled_state
 }
 
 jinja_template("system_webview_manifest") {
diff --git a/android_webview/nonembedded/java/DeveloperUiLauncherManifest.xml b/android_webview/nonembedded/java/DeveloperUiLauncherManifest.xml
index a1457e8..77533f6 100644
--- a/android_webview/nonembedded/java/DeveloperUiLauncherManifest.xml
+++ b/android_webview/nonembedded/java/DeveloperUiLauncherManifest.xml
@@ -8,6 +8,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="org.chromium.android_webview.devui">
 
+    <!-- required so Manifest merger doesn't add unused permissions see https://crbug.com/1046380 -->
     <uses-sdk android:minSdkVersion="21" />
 
     <!--suppress HardcodedText -->
@@ -15,7 +16,8 @@
         <activity-alias android:name="org.chromium.android_webview.devui.LauncherActivity"
                   android:targetActivity="org.chromium.android_webview.devui.MainActivity"
                   android:label="WebView DevTools"
-                  android:exported="true">
+                  android:exported="true"
+                  android:enabled="{{ icon_enabled }}">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
diff --git a/android_webview/nonembedded/java/MonochromeDeveloperUiLauncherManifest.xml b/android_webview/nonembedded/java/MonochromeDeveloperUiLauncherManifest.xml
deleted file mode 100644
index 1a156d9d..0000000
--- a/android_webview/nonembedded/java/MonochromeDeveloperUiLauncherManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="org.chromium.android_webview.devui">
-
-    <uses-sdk android:minSdkVersion="24" />
-
-    <!-- This should only be merged to Monochrome manifest -->
-    <!--suppress HardcodedText -->
-    <application>
-        <activity-alias android:name="org.chromium.android_webview.devui.MonochromeLauncherActivity"
-                  android:targetActivity="org.chromium.android_webview.devui.MainActivity"
-                  android:label="WebView DevTools"
-                  android:exported="true"
-                  android:enabled="false">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity-alias>
-    </application>
-</manifest>
diff --git a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
index 71e8715f..17e89e5b 100644
--- a/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
+++ b/android_webview/nonembedded/java/src/org/chromium/android_webview/nonembedded/WebViewApkApplication.java
@@ -99,23 +99,18 @@
     public static void postDeveloperUiLauncherIconTask() {
         PostTask.postTask(TaskTraits.BEST_EFFORT, () -> {
             Context context = ContextUtils.getApplicationContext();
-            try {
-                ComponentName devToolsLauncherActivity = new ComponentName(
-                        context, "org.chromium.android_webview.devui.MonochromeLauncherActivity");
-                if (WebViewPackageHelper.isCurrentSystemWebViewImplementation(context)) {
-                    // Enable the component to show the launcher icon.
-                    context.getPackageManager().setComponentEnabledSetting(devToolsLauncherActivity,
-                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                            PackageManager.DONT_KILL_APP);
-                } else {
-                    // Disable the component to hide the launcher icon.
-                    context.getPackageManager().setComponentEnabledSetting(devToolsLauncherActivity,
-                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                            PackageManager.DONT_KILL_APP);
-                }
-            } catch (IllegalArgumentException e) {
-                // If MonochromeLauncherActivity doesn't exist, Dynamically showing/hiding DevTools
-                // launcher icon is not enabled in this package; e.g when it is a stable channel.
+            ComponentName devToolsLauncherActivity = new ComponentName(
+                    context, "org.chromium.android_webview.devui.LauncherActivity");
+            if (WebViewPackageHelper.isCurrentSystemWebViewImplementation(context)) {
+                // Enable the component to show the launcher icon.
+                context.getPackageManager().setComponentEnabledSetting(devToolsLauncherActivity,
+                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                        PackageManager.DONT_KILL_APP);
+            } else {
+                // Disable the component to hide the launcher icon.
+                context.getPackageManager().setComponentEnabledSetting(devToolsLauncherActivity,
+                        PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                        PackageManager.DONT_KILL_APP);
             }
         });
     }
diff --git a/android_webview/system_webview_apk_tmpl.gni b/android_webview/system_webview_apk_tmpl.gni
index 8a13d74..6a28c2f 100644
--- a/android_webview/system_webview_apk_tmpl.gni
+++ b/android_webview/system_webview_apk_tmpl.gni
@@ -44,6 +44,7 @@
     deps += [
       "//android_webview:locale_pak_assets",
       "//android_webview:pak_file_assets",
+      "//android_webview/nonembedded:system_webview_devui_launcher_icon_resources",
     ]
 
     if (_exclude_weblayer_java) {
@@ -68,10 +69,6 @@
       alternative_android_sdk_dep = webview_framework_dep
     }
 
-    if (webview_devui_show_icon) {
-      deps += [ "//android_webview/nonembedded:devui_launcher_icon_resources" ]
-    }
-
     _use_trichrome_library =
         defined(use_trichrome_library) && use_trichrome_library
     assert(
diff --git a/android_webview/variables.gni b/android_webview/variables.gni
index aaa4d94..946f84e 100644
--- a/android_webview/variables.gni
+++ b/android_webview/variables.gni
@@ -6,13 +6,19 @@
 import("//weblayer/variables.gni")
 
 declare_args() {
-  # Show a launcher icon to open WebView developer UI. This is enabled by
-  # default for all prestable builds. The icon for Monochrome is shown
-  # dynamically at runtime if Monochrome is the current selected system WebView
-  # implementation or hidden otherwise.
-  webview_devui_show_icon = android_channel != "stable"
+  # Always show a launcher icon to open WebView developer UI for all build variants and channels.
+  # When this is off, all prestable build variants will still show launcher icons except for
+  # Monochrome which will only show its launcher icon when it's the selected WebView provider.
+  webview_devui_always_show_icon = false
 }
 
+# Used in standalone and trichrome, configures whether the launcher icon is on or off all the time
+standalone_or_trichrome_icon_default_enabled_state =
+    android_channel != "stable" || webview_devui_always_show_icon
+
+# Used only in monochrome, configures whether the launcher icon is on all the time vs. on conditionally
+monochrome_launcher_icon_default_enabled_state = webview_devui_always_show_icon
+
 system_webview_android_manifest =
     "$root_gen_dir/android_webview/system_webview_apk/AndroidManifest.xml"
 trichrome_webview_android_manifest =
diff --git a/ash/login/ui/login_menu_view.cc b/ash/login/ui/login_menu_view.cc
index e8c8b9f8..e9f059b 100644
--- a/ash/login/ui/login_menu_view.cc
+++ b/ash/login/ui/login_menu_view.cc
@@ -135,7 +135,7 @@
   SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
 
   scroller_ = new views::ScrollView();
-  scroller_->SetBackgroundColor(SK_ColorTRANSPARENT);
+  scroller_->SetBackgroundColor(base::nullopt);
   scroller_->SetDrawOverflowIndicator(false);
   scroller_->ClipHeightTo(kMenuItemHeightDp, kMenuItemHeightDp * 5);
   AddChildView(scroller_);
diff --git a/ash/media/media_notification_controller_impl.h b/ash/media/media_notification_controller_impl.h
index 939e4d5..6170360 100644
--- a/ash/media/media_notification_controller_impl.h
+++ b/ash/media/media_notification_controller_impl.h
@@ -50,7 +50,9 @@
   void HideNotification(const std::string& id) override;
   void RemoveItem(const std::string& id) override;
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
-  void LogMediaSessionActionButtonPressed(const std::string& id) override {}
+  void LogMediaSessionActionButtonPressed(
+      const std::string& id,
+      media_session::mojom::MediaSessionAction action) override {}
 
   std::unique_ptr<MediaNotificationContainerImpl> CreateMediaNotification(
       const message_center::Notification& notification);
diff --git a/ash/system/tray/detailed_view_delegate.cc b/ash/system/tray/detailed_view_delegate.cc
index ad6974e3..c8e4128 100644
--- a/ash/system/tray/detailed_view_delegate.cc
+++ b/ash/system/tray/detailed_view_delegate.cc
@@ -114,8 +114,8 @@
   tray_controller_->CloseBubble();
 }
 
-SkColor DetailedViewDelegate::GetBackgroundColor() {
-  return SK_ColorTRANSPARENT;
+base::Optional<SkColor> DetailedViewDelegate::GetBackgroundColor() {
+  return base::nullopt;
 }
 
 bool DetailedViewDelegate::IsOverflowIndicatorEnabled() const {
diff --git a/ash/system/tray/detailed_view_delegate.h b/ash/system/tray/detailed_view_delegate.h
index 799e3873..997844e 100644
--- a/ash/system/tray/detailed_view_delegate.h
+++ b/ash/system/tray/detailed_view_delegate.h
@@ -7,6 +7,7 @@
 
 #include "ash/ash_export.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "third_party/skia/include/core/SkColor.h"
 
@@ -44,7 +45,7 @@
   virtual void CloseBubble();
 
   // Get the background color of the detailed view.
-  virtual SkColor GetBackgroundColor();
+  virtual base::Optional<SkColor> GetBackgroundColor();
 
   // Return true if overflow indicator of ScrollView is enabled.
   virtual bool IsOverflowIndicatorEnabled() const;
diff --git a/ash/system/tray/tray_detailed_view.cc b/ash/system/tray/tray_detailed_view.cc
index 335ea73..f7f43255 100644
--- a/ash/system/tray/tray_detailed_view.cc
+++ b/ash/system/tray/tray_detailed_view.cc
@@ -268,16 +268,11 @@
 // TrayDetailedView:
 
 TrayDetailedView::TrayDetailedView(DetailedViewDelegate* delegate)
-    : delegate_(delegate),
-      box_layout_(nullptr),
-      scroller_(nullptr),
-      scroll_content_(nullptr),
-      progress_bar_(nullptr),
-      tri_view_(nullptr),
-      back_button_(nullptr) {
+    : delegate_(delegate) {
   box_layout_ = SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical));
-  SetBackground(views::CreateSolidBackground(delegate_->GetBackgroundColor()));
+  SetBackground(views::CreateSolidBackground(
+      delegate_->GetBackgroundColor().value_or(SK_ColorTRANSPARENT)));
 }
 
 TrayDetailedView::~TrayDetailedView() = default;
@@ -314,13 +309,12 @@
 void TrayDetailedView::CreateScrollableList() {
   DCHECK(!scroller_);
   auto scroll_content = std::make_unique<ScrollContentsView>(delegate_);
-  scroller_ = new views::ScrollView;
+  scroller_ = AddChildView(std::make_unique<views::ScrollView>());
   scroller_->SetDrawOverflowIndicator(delegate_->IsOverflowIndicatorEnabled());
   scroll_content_ = scroller_->SetContents(std::move(scroll_content));
   // TODO(varkha): Make the sticky rows work with EnableViewPortLayer().
   scroller_->SetBackgroundColor(delegate_->GetBackgroundColor());
 
-  AddChildView(scroller_);
   box_layout_->SetFlexForView(scroller_, 1);
 }
 
@@ -423,7 +417,9 @@
 void TrayDetailedView::ShowProgress(double value, bool visible) {
   DCHECK(tri_view_);
   if (!progress_bar_) {
-    progress_bar_ = new views::ProgressBar(kTitleRowProgressBarHeight);
+    progress_bar_ = AddChildViewAt(
+        std::make_unique<views::ProgressBar>(kTitleRowProgressBarHeight),
+        kTitleRowSeparatorIndex + 1);
     progress_bar_->GetViewAccessibility().OverrideName(
         l10n_util::GetStringUTF16(
             IDS_ASH_STATUS_TRAY_NETWORK_PROGRESS_ACCESSIBLE_NAME));
@@ -432,7 +428,6 @@
         AshColorProvider::Get()->GetContentLayerColor(
             AshColorProvider::ContentLayerType::kProminentIconButton,
             AshColorProvider::AshColorMode::kDark));
-    AddChildViewAt(progress_bar_, kTitleRowSeparatorIndex + 1);
   }
 
   progress_bar_->SetValue(value);
diff --git a/ash/system/tray/tray_detailed_view.h b/ash/system/tray/tray_detailed_view.h
index acc18935..265f132 100644
--- a/ash/system/tray/tray_detailed_view.h
+++ b/ash/system/tray/tray_detailed_view.h
@@ -154,18 +154,18 @@
   void TransitionToMainView();
 
   DetailedViewDelegate* const delegate_;
-  views::BoxLayout* box_layout_;
-  views::ScrollView* scroller_;
-  views::View* scroll_content_;
-  views::ProgressBar* progress_bar_;
+  views::BoxLayout* box_layout_ = nullptr;
+  views::ScrollView* scroller_ = nullptr;
+  views::View* scroll_content_ = nullptr;
+  views::ProgressBar* progress_bar_ = nullptr;
 
-  ScrollBorder* scroll_border_;  // Weak reference
+  ScrollBorder* scroll_border_ = nullptr;  // Weak reference
 
   // The container view for the top-most title row in material design.
-  TriView* tri_view_;
+  TriView* tri_view_ = nullptr;
 
   // The back button that appears in the material design title row. Not owned.
-  views::Button* back_button_;
+  views::Button* back_button_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(TrayDetailedView);
 };
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 07bb238..2da5339f 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -1987,7 +1987,7 @@
   auto* overview_item =
       overview_session->GetOverviewItemForWindow(window.get());
   DragItemToPoint(overview_item, window->GetBoundsInScreen().CenterPoint(),
-                  GetEventGenerator(), /*drop=*/true);
+                  GetEventGenerator(), /*by_touch_gestures=*/true);
 
   // Exit overview and add a new desk, then re-enter overview. Expect that now
   // the desks bar is visible.
diff --git a/ash/wm/splitview/split_view_drag_indicators_unittest.cc b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
index 8baa5467..460857c 100644
--- a/ash/wm/splitview/split_view_drag_indicators_unittest.cc
+++ b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
@@ -13,6 +13,7 @@
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
 #include "ash/wm/overview/overview_session.h"
+#include "ash/wm/overview/overview_test_util.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -82,19 +83,6 @@
            SplitViewController::NONE;
   }
 
-  OverviewItem* GetOverviewItemForWindow(aura::Window* window,
-                                         int grid_index = 0) {
-    auto& windows = overview_session_->grid_list()[grid_index]->window_list();
-    auto iter =
-        std::find_if(windows.cbegin(), windows.cend(),
-                     [window](const std::unique_ptr<OverviewItem>& item) {
-                       return item->Contains(window);
-                     });
-    if (iter == windows.end())
-      return nullptr;
-    return iter->get();
-  }
-
   float GetEdgeInset(int screen_width) const {
     return screen_width * kHighlightScreenPrimaryAxisRatio +
            kHighlightScreenEdgePaddingDp;
@@ -296,8 +284,7 @@
   ToggleOverview();
 
   // Start dragging from overview.
-  OverviewItem* item =
-      GetOverviewItemForWindow(window1.get(), /*is_touch_dragging=*/false);
+  OverviewItem* item = GetOverviewItemForWindow(window1.get());
   gfx::PointF start_location(item->target_bounds().CenterPoint());
   overview_session_->InitiateDrag(item, start_location,
                                   /*is_touch_dragging=*/false);
diff --git a/base/android/jni_array.cc b/base/android/jni_array.cc
index c339c39..9626abe 100644
--- a/base/android/jni_array.cc
+++ b/base/android/jni_array.cc
@@ -32,8 +32,8 @@
   CheckException(env);
   DCHECK(byte_array);
 
-  env->SetByteArrayRegion(
-      byte_array, 0, len, reinterpret_cast<const jbyte*>(bytes));
+  env->SetByteArrayRegion(byte_array, 0, len,
+                          reinterpret_cast<const jbyte*>(bytes));
   CheckException(env);
 
   return ScopedJavaLocalRef<jbyteArray>(env, byte_array);
@@ -41,7 +41,7 @@
 
 ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
     JNIEnv* env,
-    const std::vector<uint8_t>& bytes) {
+    base::span<const uint8_t> bytes) {
   return ToJavaByteArray(env, bytes.data(), bytes.size());
 }
 
@@ -65,21 +65,22 @@
   return ScopedJavaLocalRef<jbooleanArray>(env, boolean_array);
 }
 
-ScopedJavaLocalRef<jintArray> ToJavaIntArray(
-    JNIEnv* env, const int* ints, size_t len) {
+ScopedJavaLocalRef<jintArray> ToJavaIntArray(JNIEnv* env,
+                                             const int* ints,
+                                             size_t len) {
   jintArray int_array = env->NewIntArray(len);
   CheckException(env);
   DCHECK(int_array);
 
-  env->SetIntArrayRegion(
-      int_array, 0, len, reinterpret_cast<const jint*>(ints));
+  env->SetIntArrayRegion(int_array, 0, len,
+                         reinterpret_cast<const jint*>(ints));
   CheckException(env);
 
   return ScopedJavaLocalRef<jintArray>(env, int_array);
 }
 
-ScopedJavaLocalRef<jintArray> ToJavaIntArray(
-    JNIEnv* env, const std::vector<int>& ints) {
+ScopedJavaLocalRef<jintArray> ToJavaIntArray(JNIEnv* env,
+                                             base::span<const int> ints) {
   return ToJavaIntArray(env, ints.data(), ints.size());
 }
 
@@ -90,8 +91,8 @@
   CheckException(env);
   DCHECK(long_array);
 
-  env->SetLongArrayRegion(
-      long_array, 0, len, reinterpret_cast<const jlong*>(longs));
+  env->SetLongArrayRegion(long_array, 0, len,
+                          reinterpret_cast<const jlong*>(longs));
   CheckException(env);
 
   return ScopedJavaLocalRef<jlongArray>(env, long_array);
@@ -100,19 +101,19 @@
 // Returns a new Java long array converted from the given int64_t array.
 BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
     JNIEnv* env,
-    const std::vector<int64_t>& longs) {
+    base::span<const int64_t> longs) {
   return ToJavaLongArray(env, longs.data(), longs.size());
 }
 
 // Returns a new Java float array converted from the given C++ float array.
-BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
-    JNIEnv* env, const float* floats, size_t len) {
+BASE_EXPORT ScopedJavaLocalRef<jfloatArray>
+ToJavaFloatArray(JNIEnv* env, const float* floats, size_t len) {
   jfloatArray float_array = env->NewFloatArray(len);
   CheckException(env);
   DCHECK(float_array);
 
-  env->SetFloatArrayRegion(
-      float_array, 0, len, reinterpret_cast<const jfloat*>(floats));
+  env->SetFloatArrayRegion(float_array, 0, len,
+                           reinterpret_cast<const jfloat*>(floats));
   CheckException(env);
 
   return ScopedJavaLocalRef<jfloatArray>(env, float_array);
@@ -120,7 +121,7 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
     JNIEnv* env,
-    const std::vector<float>& floats) {
+    base::span<const float> floats) {
   return ToJavaFloatArray(env, floats.data(), floats.size());
 }
 
@@ -139,15 +140,16 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jdoubleArray> ToJavaDoubleArray(
     JNIEnv* env,
-    const std::vector<double>& doubles) {
+    base::span<const double> doubles) {
   return ToJavaDoubleArray(env, doubles.data(), doubles.size());
 }
 
 ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
-    JNIEnv* env, const std::vector<std::string>& v) {
+    JNIEnv* env,
+    base::span<const std::string> v) {
   ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
-  jobjectArray joa = env->NewObjectArray(v.size(),
-                                         byte_array_clazz.obj(), NULL);
+  jobjectArray joa =
+      env->NewObjectArray(v.size(), byte_array_clazz.obj(), nullptr);
   CheckException(env);
 
   for (size_t i = 0; i < v.size(); ++i) {
@@ -158,10 +160,27 @@
   return ScopedJavaLocalRef<jobjectArray>(env, joa);
 }
 
+ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
+    JNIEnv* env,
+    base::span<std::vector<uint8_t>> v) {
+  ScopedJavaLocalRef<jclass> byte_array_clazz = GetClass(env, "[B");
+  jobjectArray joa =
+      env->NewObjectArray(v.size(), byte_array_clazz.obj(), nullptr);
+  CheckException(env);
+
+  for (size_t i = 0; i < v.size(); ++i) {
+    ScopedJavaLocalRef<jbyteArray> byte_array =
+        ToJavaByteArray(env, v[i].data(), v[i].size());
+    env->SetObjectArrayElement(joa, i, byte_array.obj());
+  }
+  return ScopedJavaLocalRef<jobjectArray>(env, joa);
+}
+
 ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
-    JNIEnv* env, const std::vector<std::string>& v) {
+    JNIEnv* env,
+    base::span<const std::string> v) {
   ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
-  jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
+  jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), nullptr);
   CheckException(env);
 
   for (size_t i = 0; i < v.size(); ++i) {
@@ -173,12 +192,12 @@
 
 ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStringArray(
     JNIEnv* env,
-    const std::vector<std::vector<string16>>& vec_outer) {
+    base::span<const std::vector<string16>> vec_outer) {
   ScopedJavaLocalRef<jclass> string_array_clazz =
       GetClass(env, "[Ljava/lang/String;");
 
   jobjectArray joa =
-      env->NewObjectArray(vec_outer.size(), string_array_clazz.obj(), NULL);
+      env->NewObjectArray(vec_outer.size(), string_array_clazz.obj(), nullptr);
   CheckException(env);
 
   for (size_t i = 0; i < vec_outer.size(); ++i) {
@@ -191,9 +210,10 @@
 }
 
 ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
-    JNIEnv* env, const std::vector<string16>& v) {
+    JNIEnv* env,
+    base::span<const string16> v) {
   ScopedJavaLocalRef<jclass> string_clazz = GetClass(env, "java/lang/String");
-  jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), NULL);
+  jobjectArray joa = env->NewObjectArray(v.size(), string_clazz.obj(), nullptr);
   CheckException(env);
 
   for (size_t i = 0; i < v.size(); ++i) {
diff --git a/base/android/jni_array.h b/base/android/jni_array.h
index 2bb10379..a6a2ebe 100644
--- a/base/android/jni_array.h
+++ b/base/android/jni_array.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/android/scoped_java_ref.h"
+#include "base/containers/span.h"
 #include "base/strings/string16.h"
 
 namespace base {
@@ -24,7 +25,7 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jbyteArray> ToJavaByteArray(
     JNIEnv* env,
-    const std::vector<uint8_t>& bytes);
+    base::span<const uint8_t> bytes);
 
 // Returns a new Java byte array converted from the given string. No UTF-8
 // conversion is performed.
@@ -41,7 +42,8 @@
     JNIEnv* env, const int* ints, size_t len);
 
 BASE_EXPORT ScopedJavaLocalRef<jintArray> ToJavaIntArray(
-    JNIEnv* env, const std::vector<int>& ints);
+    JNIEnv* env,
+    base::span<const int> ints);
 
 // Returns a new Java long array converted from the given int64_t array.
 BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(JNIEnv* env,
@@ -50,7 +52,7 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jlongArray> ToJavaLongArray(
     JNIEnv* env,
-    const std::vector<int64_t>& longs);
+    base::span<const int64_t> longs);
 
 // Returns a new Java float array converted from the given C++ float array.
 BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
@@ -58,7 +60,7 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jfloatArray> ToJavaFloatArray(
     JNIEnv* env,
-    const std::vector<float>& floats);
+    base::span<const float> floats);
 
 // Returns a new Java double array converted from the given C++ double array.
 BASE_EXPORT ScopedJavaLocalRef<jdoubleArray>
@@ -66,21 +68,28 @@
 
 BASE_EXPORT ScopedJavaLocalRef<jdoubleArray> ToJavaDoubleArray(
     JNIEnv* env,
-    const std::vector<double>& doubles);
+    base::span<const double> doubles);
 
 // Returns a array of Java byte array converted from |v|.
 BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
-    JNIEnv* env, const std::vector<std::string>& v);
+    JNIEnv* env,
+    base::span<const std::string> v);
+
+BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfByteArray(
+    JNIEnv* env,
+    base::span<std::vector<uint8_t>> v);
 
 BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
-    JNIEnv* env,  const std::vector<std::string>& v);
+    JNIEnv* env,
+    base::span<const std::string> v);
 
 BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStrings(
-    JNIEnv* env,  const std::vector<string16>& v);
+    JNIEnv* env,
+    base::span<const string16> v);
 
 BASE_EXPORT ScopedJavaLocalRef<jobjectArray> ToJavaArrayOfStringArray(
     JNIEnv* env,
-    const std::vector<std::vector<string16>>& v);
+    base::span<const std::vector<string16>> v);
 
 // Converts a Java string array to a native array.
 BASE_EXPORT void AppendJavaStringArrayToStringVector(
diff --git a/base/fuchsia/time_zone_data_unittest.cc b/base/fuchsia/time_zone_data_unittest.cc
index 5d078c4..bb6f4ae 100644
--- a/base/fuchsia/time_zone_data_unittest.cc
+++ b/base/fuchsia/time_zone_data_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "base/i18n/icu_util.h"
 
-#include "base/environment.h"
 #include "base/files/file_util.h"
 #include "base/strings/string_util.h"
 #include "build/build_config.h"
@@ -28,8 +27,6 @@
 
 class TimeZoneDataTest : public testing::Test {
  protected:
-  TimeZoneDataTest() : env_(base::Environment::Create()) {}
-
   void SetUp() override { ResetIcu(); }
 
   void TearDown() override { ResetIcu(); }
@@ -48,8 +45,6 @@
     *icu_version = icu::TimeZone::getTZDataVersion(err);
     ASSERT_EQ(U_ZERO_ERROR, err) << u_errorName(err);
   }
-
-  std::unique_ptr<base::Environment> env_;
 };
 
 // Loads a file revision.txt from the actual underlying filesystem, which
@@ -67,8 +62,7 @@
     return;
   }
 
-  // Ensure that timezone data is loaded from the default location.
-  ASSERT_TRUE(env_->UnSetVar("ICU_TIMEZONE_FILES_DIR"));
+  // ResetIcu() ensures that time zone data is loaded from the default location.
 
   ASSERT_TRUE(InitializeICU());
   std::string expected;
@@ -87,7 +81,7 @@
 // ICU library versions.
 TEST_F(TimeZoneDataTest, TestLoadingTimeZoneDataFromKnownConfigs) {
   ASSERT_TRUE(base::DirectoryExists(base::FilePath(kTzDataDirPath)));
-  ASSERT_TRUE(env_->SetVar("ICU_TIMEZONE_FILES_DIR", kTzDataDirPath));
+  SetIcuTimeZoneDataDirForTesting(kTzDataDirPath);
 
   EXPECT_TRUE(InitializeICU());
   std::string actual;
@@ -97,7 +91,7 @@
 }
 
 TEST_F(TimeZoneDataTest, DoesNotCrashWithInvalidPath) {
-  ASSERT_TRUE(env_->SetVar("ICU_TIMEZONE_FILES_DIR", "/some/nonexistent/path"));
+  SetIcuTimeZoneDataDirForTesting("/some/nonexistent/path");
 
   EXPECT_TRUE(InitializeICU());
   std::string actual;
diff --git a/base/i18n/icu_util.cc b/base/i18n/icu_util.cc
index 50b7faf..425a0b2 100644
--- a/base/i18n/icu_util.cc
+++ b/base/i18n/icu_util.cc
@@ -89,7 +89,7 @@
 // only implemented for OS_FUCHSIA.
 #if defined(OS_FUCHSIA)
 // The environment variable used to point the ICU data loader to the directory
-// containing timezone data. This is available from ICU version 54. The env
+// containing time zone data. This is available from ICU version 54. The env
 // variable approach is antiquated by today's standards (2019), but is the
 // recommended way to configure ICU.
 //
@@ -120,6 +120,12 @@
 MemoryMappedFile* g_icudtl_extra_mapped_file = nullptr;
 MemoryMappedFile::Region g_icudtl_extra_region;
 
+#if defined(OS_FUCHSIA)
+// The directory from which the ICU data loader will be configured to load time
+// zone data. It is only changed by SetIcuTimeZoneDataDirForTesting().
+const char* g_icu_time_zone_data_dir = kIcuTimeZoneDataDir;
+#endif  // defined(OS_FUCHSIA)
+
 struct PfRegion {
  public:
   PlatformFile pf;
@@ -208,30 +214,20 @@
   g_icudtl_region = pf_region->region;
 }
 
-// Loads external timezone data, for configurations where it makes sense.
-// If the timezone data directory is not set up properly, we continue but log
-// appropriate information for debugging.
+// Configures ICU to load external time zone data, if appropriate.
 void InitializeExternalTimeZoneData() {
 #if defined(OS_FUCHSIA)
-  std::unique_ptr<base::Environment> env = base::Environment::Create();
-
-  // We only set the variable if it was not already set by a test.  Fuchsia
-  // normally does not otherwise set env variables in production code.
-  if (!env->HasVar(kIcuTimeZoneEnvVariable)) {
-    env->SetVar(kIcuTimeZoneEnvVariable, kIcuTimeZoneDataDir);
-  }
-  std::string tzdata_dir;
-  env->GetVar(kIcuTimeZoneEnvVariable, &tzdata_dir);
-
-  // Try opening the path to check if present. No need to verify that it is a
-  // directory since ICU loading will return an error if the TimeZone data is
-  // wrong.
-  if (!base::DirectoryExists(base::FilePath(tzdata_dir))) {
+  if (!base::DirectoryExists(base::FilePath(g_icu_time_zone_data_dir))) {
     // TODO(https://crbug.com/1061262): Make this FATAL unless expected.
-    PLOG(WARNING) << "Could not open: '" << tzdata_dir
+    PLOG(WARNING) << "Could not open: '" << g_icu_time_zone_data_dir
                   << "'. Using built-in timezone database";
     return;
   }
+
+  // Set the environment variable to override the location used by ICU.
+  // Loading can still fail if the directory is empty or its data is invalid.
+  std::unique_ptr<base::Environment> env = base::Environment::Create();
+  env->SetVar(kIcuTimeZoneEnvVariable, g_icu_time_zone_data_dir);
 #endif  // defined(OS_FUCHSIA)
 }
 
@@ -323,10 +319,10 @@
 // than relying on ICU's internal initialization.
 void InitializeIcuTimeZone() {
 #if defined(OS_ANDROID)
-  // On Android, we can't leave it up to ICU to set the default timezone
-  // because ICU's timezone detection does not work in many timezones (e.g.
+  // On Android, we can't leave it up to ICU to set the default time zone
+  // because ICU's time zone detection does not work in many time zones (e.g.
   // Australia/Sydney, Asia/Seoul, Europe/Paris ). Use JNI to detect the host
-  // timezone and set the ICU default timezone accordingly in advance of
+  // time zone and set the ICU default time zone accordingly in advance of
   // actual use. See crbug.com/722821 and
   // https://ssl.icu-project.org/trac/ticket/13208 .
   string16 zone_id = android::GetDefaultTimeZoneId();
@@ -334,7 +330,7 @@
       icu::UnicodeString(FALSE, zone_id.data(), zone_id.length())));
 #elif defined(OS_FUCHSIA)
   // The platform-specific mechanisms used by ICU's detectHostTimeZone() to
-  // determine the default timezone will not work on Fuchsia. Therefore,
+  // determine the default time zone will not work on Fuchsia. Therefore,
   // proactively set the default system.
   // This is also required by TimeZoneMonitorFuchsia::ProfileMayHaveChanged(),
   // which uses the current default to detect whether the time zone changed in
@@ -346,7 +342,7 @@
   icu::TimeZone::adoptDefault(
       icu::TimeZone::createTimeZone(icu::UnicodeString::fromUTF8(zone_id)));
 #elif defined(OS_LINUX) && !BUILDFLAG(IS_CHROMECAST)
-  // To respond to the timezone change properly, the default timezone
+  // To respond to the time zone change properly, the default time zone
   // cache in ICU has to be populated on starting up.
   // See TimeZoneMonitorLinux::NotifyClientsFromImpl().
   std::unique_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault());
@@ -471,7 +467,17 @@
   g_icudtl_mapped_file = nullptr;
   g_icudtl_extra_pf = kInvalidPlatformFile;
   g_icudtl_extra_mapped_file = nullptr;
+#if defined(OS_FUCHSIA)
+  g_icu_time_zone_data_dir = kIcuTimeZoneDataDir;
+#endif  // defined(OS_FUCHSIA)
 }
+
+#if defined(OS_FUCHSIA)
+// |dir| must remain valid until ResetGlobalsForTesting() is called.
+void SetIcuTimeZoneDataDirForTesting(const char* dir) {
+  g_icu_time_zone_data_dir = dir;
+}
+#endif  // defined(OS_FUCHSIA)
 #endif  // (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE)
 
 bool InitializeICU() {
diff --git a/base/i18n/icu_util.h b/base/i18n/icu_util.h
index 6a03863..cc133079 100644
--- a/base/i18n/icu_util.h
+++ b/base/i18n/icu_util.h
@@ -49,6 +49,11 @@
     const MemoryMappedFile::Region& data_region);
 
 BASE_I18N_EXPORT void ResetGlobalsForTesting();
+
+#if defined(OS_FUCHSIA)
+// Overrides the directory used by ICU for external time zone data.
+BASE_I18N_EXPORT void SetIcuTimeZoneDataDirForTesting(const char* dir);
+#endif  // defined(OS_FUCHSIA)
 #endif  // ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE
 
 // In a test binary, initialize functions might be called twice.
diff --git a/base/process/process_unittest.cc b/base/process/process_unittest.cc
index 8f449dd..465825f 100644
--- a/base/process/process_unittest.cc
+++ b/base/process/process_unittest.cc
@@ -248,7 +248,6 @@
   Process process(SpawnChild("FastSleepyChildProcess"));
   ASSERT_TRUE(process.IsValid());
 
-  const int kDummyExitCode = 42;
   int exit_code = kDummyExitCode;
   EXPECT_TRUE(process.WaitForExit(&exit_code));
   EXPECT_EQ(0, exit_code);
@@ -258,7 +257,6 @@
   Process process(SpawnChild("SleepyChildProcess"));
   ASSERT_TRUE(process.IsValid());
 
-  const int kDummyExitCode = 42;
   int exit_code = kDummyExitCode;
   TimeDelta timeout = TestTimeouts::tiny_timeout();
   EXPECT_FALSE(process.WaitForExitWithTimeout(timeout, &exit_code));
@@ -275,7 +273,6 @@
   base::win::ScopedHandle stop_watching_handle(
       CreateEvent(nullptr, TRUE, FALSE, nullptr));
 
-  const int kDummyExitCode = 42;
   int exit_code = kDummyExitCode;
   EXPECT_EQ(process.WaitForExitOrEvent(stop_watching_handle, &exit_code),
             base::Process::WaitExitStatus::PROCESS_EXITED);
@@ -289,7 +286,6 @@
   base::win::ScopedHandle stop_watching_handle(
       CreateEvent(nullptr, TRUE, TRUE, nullptr));
 
-  const int kDummyExitCode = 42;
   int exit_code = kDummyExitCode;
   EXPECT_EQ(process.WaitForExitOrEvent(stop_watching_handle, &exit_code),
             base::Process::WaitExitStatus::STOP_EVENT_SIGNALED);
diff --git a/base/trace_event/trace_event_impl.h b/base/trace_event/trace_event_impl.h
index 161cd6d..eb41386 100644
--- a/base/trace_event/trace_event_impl.h
+++ b/base/trace_event/trace_event_impl.h
@@ -112,13 +112,6 @@
       const ArgumentFilterPredicate& argument_filter_predicate) const;
   void AppendPrettyPrinted(std::ostringstream* out) const;
 
-  // TODO(898794): Remove once caller has been updated.
-  static void AppendValueAsJSON(unsigned char type,
-                                TraceValue value,
-                                std::string* out) {
-    value.AppendAsJSON(type, out);
-  }
-
   TimeTicks timestamp() const { return timestamp_; }
   ThreadTicks thread_timestamp() const { return thread_timestamp_; }
   ThreadInstructionCount thread_instruction_count() const {
diff --git a/base/trace_event/traced_value.cc b/base/trace_event/traced_value.cc
index 3dc5409c..152d9f2 100644
--- a/base/trace_event/traced_value.cc
+++ b/base/trace_event/traced_value.cc
@@ -269,7 +269,7 @@
           TraceEvent::TraceValue json_value;
           CHECK(it.ReadBool(&json_value.as_bool));
           maybe_append_key_name(state_stack[current_state_index], &it, out);
-          TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_BOOL, json_value, out);
+          json_value.AppendAsJSON(TRACE_VALUE_TYPE_BOOL, out);
           break;
         }
 
@@ -279,7 +279,7 @@
           maybe_append_key_name(state_stack[current_state_index], &it, out);
           TraceEvent::TraceValue json_value;
           json_value.as_int = value;
-          TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_INT, json_value, out);
+          json_value.AppendAsJSON(TRACE_VALUE_TYPE_INT, out);
           break;
         }
 
@@ -287,8 +287,7 @@
           TraceEvent::TraceValue json_value;
           CHECK(it.ReadDouble(&json_value.as_double));
           maybe_append_key_name(state_stack[current_state_index], &it, out);
-          TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_DOUBLE, json_value,
-                                        out);
+          json_value.AppendAsJSON(TRACE_VALUE_TYPE_DOUBLE, out);
           break;
         }
 
@@ -298,8 +297,7 @@
           maybe_append_key_name(state_stack[current_state_index], &it, out);
           TraceEvent::TraceValue json_value;
           json_value.as_string = value.c_str();
-          TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_STRING, json_value,
-                                        out);
+          json_value.AppendAsJSON(TRACE_VALUE_TYPE_STRING, out);
           break;
         }
 
@@ -399,12 +397,23 @@
         } break;
 
         case kTypeDouble: {
-          double value;
-          CHECK(it.ReadDouble(&value));
-          if (cur_dict) {
-            cur_dict->SetDoubleKey(ReadKeyName(it), value);
+          TraceEvent::TraceValue trace_value;
+          CHECK(it.ReadDouble(&trace_value.as_double));
+          Value base_value;
+          if (!std::isfinite(trace_value.as_double)) {
+            // base::Value doesn't support nan and infinity values. Use strings
+            // for them instead. This follows the same convention in
+            // AppendAsTraceFormat(), supported by TraceValue::Append*().
+            std::string value_string;
+            trace_value.AppendAsString(TRACE_VALUE_TYPE_DOUBLE, &value_string);
+            base_value = Value(value_string);
           } else {
-            cur_list->Append(value);
+            base_value = Value(trace_value.as_double);
+          }
+          if (cur_dict) {
+            cur_dict->SetKey(ReadKeyName(it), std::move(base_value));
+          } else {
+            cur_list->Append(std::move(base_value));
           }
         } break;
 
diff --git a/base/trace_event/traced_value_unittest.cc b/base/trace_event/traced_value_unittest.cc
index 4278c453c..09b03d4 100644
--- a/base/trace_event/traced_value_unittest.cc
+++ b/base/trace_event/traced_value_unittest.cc
@@ -8,6 +8,7 @@
 
 #include <utility>
 
+#include "base/strings/string_util.h"
 #include "base/values.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -126,5 +127,29 @@
   EXPECT_EQ("{\"b\":2,\"c\":[\"foo\"],\"f\":3,\"g\":{}}", json);
 }
 
+TEST(TraceEventArgumentTest, NanAndInfinityJSON) {
+  TracedValueJSON value;
+  value.SetDouble("nan", std::nan(""));
+  value.SetDouble("infinity", INFINITY);
+  value.SetDouble("negInfinity", -INFINITY);
+  std::string json;
+  value.AppendAsTraceFormat(&json);
+  EXPECT_EQ(
+      "{\"nan\":\"NaN\",\"infinity\":\"Infinity\","
+      "\"negInfinity\":\"-Infinity\"}",
+      json);
+
+  std::string formatted_json = value.ToFormattedJSON();
+  // Remove CR and LF to make the result platform-independent.
+  ReplaceChars(formatted_json, "\n\r", "", &formatted_json);
+  EXPECT_EQ(
+      "{"
+      "   \"infinity\": \"Infinity\","
+      "   \"nan\": \"NaN\","
+      "   \"negInfinity\": \"-Infinity\""
+      "}",
+      formatted_json);
+}
+
 }  // namespace trace_event
 }  // namespace base
diff --git a/build/android/BUILD.gn b/build/android/BUILD.gn
index 503d6ea..f796e8f 100644
--- a/build/android/BUILD.gn
+++ b/build/android/BUILD.gn
@@ -76,7 +76,7 @@
 
   # Proguard is needed only when using apks (rather than native executables).
   if (enable_java_templates) {
-    deps = [ "//third_party/proguard:proguard603_java" ]
+    deps = [ "//build/android/stacktrace:java_deobfuscate" ]
   }
 }
 
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index 8ed169f..8bd8390 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -1460,12 +1460,7 @@
   def Run(self):
     deobfuscate = None
     if self.args.proguard_mapping_path and not self.args.no_deobfuscate:
-      try:
-        deobfuscate = deobfuscator.Deobfuscator(self.args.proguard_mapping_path)
-      except OSError:
-        sys.stderr.write('Error executing "bin/java_deobfuscate". '
-                         'Did you forget to build it?\n')
-        sys.exit(1)
+      deobfuscate = deobfuscator.Deobfuscator(self.args.proguard_mapping_path)
 
     stack_script_context = _StackScriptContext(
         self.args.output_directory,
diff --git a/build/android/docs/java_optimization.md b/build/android/docs/java_optimization.md
index e0f0ec7..ec3df1df 100644
--- a/build/android/docs/java_optimization.md
+++ b/build/android/docs/java_optimization.md
@@ -82,7 +82,7 @@
   * `$OUT/bin/chrome_public_apk run`  # Launch chrome and run adb logcat
 
 2. Using `java_deobfuscate`
-  * `$OUT/bin/java_deobfuscate $OUT/apks/ChromePublic.apk.mapping < logcat.txt`
+  * build/android/stacktrace/java_deobfuscate.py $OUT/apks/ChromePublic.apk.mapping < logcat.txt`
     * ProGuard mapping files are located beside APKs (ex.
       `$OUT/apks/ChromePublic.apk` and `$OUT/apks/ChromePublic.apk.mapping`)
 
diff --git a/build/android/gyp/turbine.py b/build/android/gyp/turbine.py
new file mode 100755
index 0000000..1b0f0a22
--- /dev/null
+++ b/build/android/gyp/turbine.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+# 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.
+"""Wraps bin/helper/turbine and expands @FileArgs."""
+
+import argparse
+import logging
+import os
+import shutil
+import subprocess
+import sys
+import time
+
+from util import build_utils
+from util import md5_check
+
+
+def _OnStaleMd5(options, cmd, javac_cmd, files, classpath):
+  if classpath:
+    cmd += ['--classpath']
+    cmd += classpath
+
+  if options.java_srcjars:
+    cmd += ['--source_jars']
+    cmd += options.java_srcjars
+
+  if files:
+    # Use jar_path to ensure paths are relative (needed for goma).
+    files_rsp_path = options.jar_path + '.files_list.txt'
+    with open(files_rsp_path, 'w') as f:
+      f.write(' '.join(files))
+    # Pass source paths as response files to avoid extremely long command lines
+    # that are tedius to debug.
+    cmd += ['--sources']
+    cmd += ['@' + files_rsp_path]
+
+  if javac_cmd:
+    cmd.append('--javacopts')
+    cmd += javac_cmd
+    cmd.append('--')  # Terminate javacopts
+
+  # Use AtomicOutput so that output timestamps are not updated when outputs
+  # are not changed.
+  with build_utils.AtomicOutput(options.jar_path) as f:
+    cmd += ['--output', f.name]
+    logging.info('Command: %s' % ' '.join(cmd))
+    start = time.time()
+    subprocess.check_call(cmd)
+    end = time.time() - start
+    logging.info('Header compilation took %ss', end)
+
+  logging.info('Completed all steps in _OnStaleMd5')
+
+
+def main(argv):
+  build_utils.InitLogging('TURBINE_DEBUG')
+  argv = build_utils.ExpandFileArgs(argv[1:])
+  parser = argparse.ArgumentParser()
+  build_utils.AddDepfileOption(parser)
+  parser.add_argument(
+      '--turbine-jar-path', required=True, help='Path to the turbine jar file.')
+  parser.add_argument(
+      '--java-srcjars',
+      action='append',
+      default=[],
+      help='List of srcjars to include in compilation.')
+  parser.add_argument(
+      '--bootclasspath',
+      action='append',
+      default=[],
+      help='Boot classpath for javac. If this is specified multiple times, '
+      'they will all be appended to construct the classpath.')
+  parser.add_argument(
+      '--java-version',
+      help='Java language version to use in -source and -target args to javac.')
+  parser.add_argument('--classpath', action='append', help='Classpath to use.')
+  parser.add_argument(
+      '--processors',
+      action='append',
+      help='GN list of annotation processor main classes.')
+  parser.add_argument(
+      '--processorpath',
+      action='append',
+      help='GN list of jars that comprise the classpath used for Annotation '
+      'Processors.')
+  parser.add_argument(
+      '--processor-args',
+      action='append',
+      help='key=value arguments for the annotation processors.')
+  parser.add_argument('--jar-path', help='Jar output path.', required=True)
+  options, unknown_args = parser.parse_known_args(argv)
+
+  options.bootclasspath = build_utils.ParseGnList(options.bootclasspath)
+  options.classpath = build_utils.ParseGnList(options.classpath)
+  options.processorpath = build_utils.ParseGnList(options.processorpath)
+  options.processors = build_utils.ParseGnList(options.processors)
+  options.java_srcjars = build_utils.ParseGnList(options.java_srcjars)
+
+  files = []
+  for arg in unknown_args:
+    # Interpret a path prefixed with @ as a file containing a list of sources.
+    if arg.startswith('@'):
+      files.extend(build_utils.ReadSourcesList(arg[1:]))
+
+  cmd = [
+      build_utils.JAVA_PATH, '-classpath', options.turbine_jar_path,
+      'com.google.turbine.main.Main'
+  ]
+  javac_cmd = []
+
+  # Turbine reads lists from command line args by consuming args until one
+  # starts with double dash (--). Thus command line args should be grouped
+  # together and passed in together.
+  if options.processors:
+    cmd += ['--processors']
+    cmd += options.processors
+
+  if options.java_version:
+    javac_cmd.extend([
+        '-source',
+        options.java_version,
+        '-target',
+        options.java_version,
+    ])
+  if options.java_version == '1.8':
+    # Android's boot jar doesn't contain all java 8 classes.
+    options.bootclasspath.append(build_utils.RT_JAR_PATH)
+
+  if options.bootclasspath:
+    cmd += ['--bootclasspath']
+    for bootclasspath in options.bootclasspath:
+      cmd += bootclasspath.split(':')
+
+  if options.processorpath:
+    cmd += ['--processorpath']
+    cmd += options.processorpath
+
+  if options.processor_args:
+    for arg in options.processor_args:
+      javac_cmd.extend(['-A%s' % arg])
+
+  classpath_inputs = (
+      options.bootclasspath + options.classpath + options.processorpath)
+
+  # GN already knows of the java files, so avoid listing individual java files
+  # in the depfile.
+  depfile_deps = classpath_inputs + options.java_srcjars
+  input_paths = depfile_deps + files
+
+  output_paths = [
+      options.jar_path,
+  ]
+
+  input_strings = cmd + options.classpath + files
+
+  md5_check.CallAndWriteDepfileIfStale(
+      lambda: _OnStaleMd5(options, cmd, javac_cmd, files, options.classpath),
+      options,
+      depfile_deps=depfile_deps,
+      input_paths=input_paths,
+      input_strings=input_strings,
+      output_paths=output_paths)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
diff --git a/build/android/gyp/turbine.pydeps b/build/android/gyp/turbine.pydeps
new file mode 100644
index 0000000..1939645
--- /dev/null
+++ b/build/android/gyp/turbine.pydeps
@@ -0,0 +1,7 @@
+# Generated by running:
+#   build/print_python_deps.py --root build/android/gyp --output build/android/gyp/turbine.pydeps build/android/gyp/turbine.py
+../../gn_helpers.py
+turbine.py
+util/__init__.py
+util/build_utils.py
+util/md5_check.py
diff --git a/build/android/gyp/write_build_config.py b/build/android/gyp/write_build_config.py
index 03607d5..f96e3292 100755
--- a/build/android/gyp/write_build_config.py
+++ b/build/android/gyp/write_build_config.py
@@ -248,8 +248,8 @@
 
 * `deps_info['interface_jar_path']:
 Path to the interface jar generated for this library. This corresponds to
-a jar file that only contains declarations. Generated by running the `ijar`
-tool on `deps_info['jar_path']`
+a jar file that only contains declarations. Generated by running the `ijar` on
+`deps_info['jar_path']` or the `turbine` tool on source files.
 
 * `deps_info['dex_path']`:
 Path to the `.dex` file generated for this target, from `deps_info['jar_path']`
diff --git a/build/android/pylib/symbols/deobfuscator.py b/build/android/pylib/symbols/deobfuscator.py
index ac4ff7e4..9eb62060 100644
--- a/build/android/pylib/symbols/deobfuscator.py
+++ b/build/android/pylib/symbols/deobfuscator.py
@@ -20,8 +20,8 @@
 
 class Deobfuscator(object):
   def __init__(self, mapping_path):
-    script_path = os.path.join(
-        constants.GetOutDirectory(), 'bin', 'java_deobfuscate')
+    script_path = os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'android',
+                               'stacktrace', 'java_deobfuscate.py')
     cmd = [script_path, mapping_path]
     # Allow only one thread to call TransformLines() at a time.
     self._lock = threading.Lock()
@@ -134,7 +134,7 @@
 
 class DeobfuscatorPool(object):
   # As of Sep 2017, each instance requires about 500MB of RAM, as measured by:
-  # /usr/bin/time -v out/Release/bin/java_deobfuscate \
+  # /usr/bin/time -v build/android/stacktrace/java_deobfuscate.py \
   #     out/Release/apks/ChromePublic.apk.mapping
   def __init__(self, mapping_path, pool_size=4):
     self._mapping_path = mapping_path
diff --git a/build/android/pylib/utils/device_dependencies.py b/build/android/pylib/utils/device_dependencies.py
index f20d3b3..a6470da 100644
--- a/build/android/pylib/utils/device_dependencies.py
+++ b/build/android/pylib/utils/device_dependencies.py
@@ -8,7 +8,7 @@
 from pylib import constants
 
 
-_BLACKLIST = [
+_EXCLUSIONS = [
     re.compile(r'.*OWNERS'),  # Should never be included.
     re.compile(r'.*\.crx'),  # Chrome extension zip files.
     re.compile(os.path.join('.*',
@@ -36,15 +36,17 @@
     re.compile(os.path.join('.*', 'development', 'scripts', 'stack')),
 
     # Required for java deobfuscation on the host:
+    re.compile(r'.*build/android/stacktrace/.*'),
     re.compile(r'.*third_party/jdk/.*'),
+    re.compile(r'.*third_party/proguard/.*'),
 ]
 
 
 def _FilterDataDeps(abs_host_files):
-  blacklist = _BLACKLIST + [
-      re.compile(os.path.join(constants.GetOutDirectory(), 'bin'))]
-  return [p for p in abs_host_files
-          if not any(r.match(p) for r in blacklist)]
+  exclusions = _EXCLUSIONS + [
+      re.compile(os.path.join(constants.GetOutDirectory(), 'bin'))
+  ]
+  return [p for p in abs_host_files if not any(r.match(p) for r in exclusions)]
 
 
 def DevicePathComponentsFor(host_path, output_directory):
diff --git a/build/android/pylib/utils/proguard.py b/build/android/pylib/utils/proguard.py
index 2d439a5..9d5bae28 100644
--- a/build/android/pylib/utils/proguard.py
+++ b/build/android/pylib/utils/proguard.py
@@ -33,11 +33,8 @@
 
 
 def _GetProguardPath():
-  # Use the one in lib.java rather than source tree because it is the one that
-  # is added to swarming .isolate files.
-  return os.path.join(
-      constants.GetOutDirectory(), 'lib.java', 'third_party', 'proguard',
-      'proguard603.jar')
+  return os.path.join(constants.DIR_SOURCE_ROOT, 'third_party', 'proguard',
+                      'lib', 'proguard603.jar')
 
 
 def Dump(jar_path):
diff --git a/build/android/stacktrace/BUILD.gn b/build/android/stacktrace/BUILD.gn
index 978dcba..d6c5ffb 100644
--- a/build/android/stacktrace/BUILD.gn
+++ b/build/android/stacktrace/BUILD.gn
@@ -4,12 +4,24 @@
 
 import("//build/config/android/rules.gni")
 
-java_binary("java_deobfuscate") {
-  main_class = "org.chromium.build.FlushingReTrace"
+java_library("java_deobfuscate_java") {
   sources = [ "java/org/chromium/build/FlushingReTrace.java" ]
-  deps = [ "//third_party/proguard:retrace_java" ]
+
+  # Avoid using java_prebuilt() to ensure all uses go through the checked-in
+  # wrapper script.
+  input_jars_paths = [
+    "//third_party/proguard/lib/proguard603.jar",
+    "//third_party/proguard/lib/retrace603.jar",
+  ]
+}
+
+# Use the checked-in copy of the wrapper script & .jar rather than the built
+# one to simplify usage of the tool.
+group("java_deobfuscate") {
   data = [
-    "$root_build_dir/lib.java/build/android/stacktrace/java_deobfuscate.jar",
-    "$root_build_dir/bin/java_deobfuscate",
+    "java_deobfuscate.py",
+    "java_deobfuscate.jar",
+    "//third_party/proguard/lib/proguard603.jar",
+    "//third_party/proguard/lib/retrace603.jar",
   ]
 }
diff --git a/build/android/stacktrace/README.md b/build/android/stacktrace/README.md
index bfa537c5..58ea94b 100644
--- a/build/android/stacktrace/README.md
+++ b/build/android/stacktrace/README.md
@@ -1,4 +1,4 @@
-# java_deobfuscate
+# java_deobfuscate.py
 
 A wrapper around ProGuard's ReTrace tool, which:
 
@@ -7,11 +7,16 @@
 
 The second point here is what allows you to run:
 
-    adb logcat | out/Default/bin/java_deobfuscate out/Default/apks/ChromePublic.apk.mapping
+    adb logcat | build/android/stacktrace/java_deobfuscate.py out/Default/apks/ChromePublic.apk.mapping
 
 And have it actually show output without logcat terminating.
 
 
+## Update Instructions:
+
+    ninja -C out/Release java_deobfuscate
+    cp out/Release/lib.java/build/android/stacktrace/java_deobfuscate.jar build/android/stacktrace
+
 # stackwalker.py
 
 Extracts Breakpad microdumps from a log file and uses `stackwalker` to symbolize
diff --git a/build/android/stacktrace/java_deobfuscate.jar b/build/android/stacktrace/java_deobfuscate.jar
new file mode 100644
index 0000000..36a1b70
--- /dev/null
+++ b/build/android/stacktrace/java_deobfuscate.jar
Binary files differ
diff --git a/build/android/stacktrace/java_deobfuscate.py b/build/android/stacktrace/java_deobfuscate.py
new file mode 100755
index 0000000..8c231ecf
--- /dev/null
+++ b/build/android/stacktrace/java_deobfuscate.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+#
+# 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.
+"""Wrapper script for java_deobfuscate.
+
+This is also a buildable target, but having it pre-built here simplifies usage.
+"""
+
+import os
+import sys
+
+DIR_SOURCE_ROOT = os.path.normpath(
+    os.path.join(os.path.dirname(__file__), '../../../'))
+
+
+def main():
+  classpath = [
+      os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'stacktrace',
+                   'java_deobfuscate.jar'),
+      os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib',
+                   'proguard603.jar'),
+      os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard', 'lib',
+                   'retrace603.jar'),
+  ]
+  java_path = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'jdk', 'current',
+                           'bin', 'java')
+
+  cmd = [
+      java_path, '-classpath', ':'.join(classpath),
+      'org.chromium.build.FlushingReTrace'
+  ]
+  cmd.extend(sys.argv[1:])
+  os.execvp(cmd[0], cmd)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 2c3b380..8afa139 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -17,8 +17,7 @@
 assert(is_android)
 
 # These identify targets that have .build_config files (except for android_apk,
-# java_binary, android_app_bundle since we never need to
-# depend on these).
+# java_binary, android_app_bundle since we never need to depend on these).
 _java_target_patterns = [
   "*:*_java",
   "*:*_javalib",
@@ -44,6 +43,13 @@
 # Targets that match the patterns but are not actually java targets.
 _java_target_exceptions = [ "*:*_unpack_aar" ]
 
+# Targets that should be replace by their headers during javac.
+_java_header_target_patterns = [
+  "*:*_java",
+  "*:*_javalib",
+  "*:java",
+]
+
 _r8_path = "//third_party/r8/lib/r8.jar"
 
 _dexdump_path = "$android_sdk_build_tools/dexdump"
@@ -2688,10 +2694,11 @@
   #  additional_jar_files: Optional list of files to copy into the resulting
   #    .jar file (by default, only .class files are put there). Each entry
   #    has the 'srcPath:dstPath' format.
-  #  enable_errorprone: Optional. If True, use the errorprone compiler to
-  #    check for error-prone constructs in the language. If not provided,
-  #    whether this is enabled depends on chromium_code and the global
+  #  enable_errorprone: If True, use the errorprone compiler to check for
+  #    error-prone constructs in the language. If not provided, whether this is
+  #    enabled depends on chromium_code and the global
   #    use_errorprone_java_compiler variable.
+  #  use_turbine: If True, compile headers using turbine.py.
   #  apk_name: Optional APK name. If provided, will tell compile_java.py to also
   #    generate an .apk.jar.info file under size-info/${apk_name}.apk.jar.info
   #  provider_configurations: Optional list of paths to Java service
@@ -2743,16 +2750,20 @@
     }
 
     action_with_pydeps(target_name) {
-      script = "//build/android/gyp/compile_java.py"
+      if (invoker.use_turbine) {
+        script = "//build/android/gyp/turbine.py"
+      } else {
+        script = "//build/android/gyp/compile_java.py"
+      }
       depfile = "$target_gen_dir/$target_name.d"
       deps = _srcjar_deps
       if (defined(invoker.deps)) {
         deps += invoker.deps
       }
 
-      outputs = [ invoker.javac_jar_path ]
-      if (!invoker.enable_errorprone) {
-        outputs += [ invoker.javac_jar_path + ".info" ]
+      outputs = [ invoker.output_jar_path ]
+      if (!invoker.enable_errorprone && !invoker.use_turbine) {
+        outputs += [ invoker.output_jar_path + ".info" ]
       }
       inputs = invoker.java_files + _java_srcjars + [ _build_config ]
       if (invoker.java_files != []) {
@@ -2760,8 +2771,8 @@
       }
 
       _rebased_build_config = rebase_path(_build_config, root_build_dir)
-      _rebased_javac_jar_path =
-          rebase_path(invoker.javac_jar_path, root_build_dir)
+      _rebased_output_jar_path =
+          rebase_path(invoker.output_jar_path, root_build_dir)
       _rebased_java_srcjars = rebase_path(_java_srcjars, root_build_dir)
       _rebased_depfile = rebase_path(depfile, root_build_dir)
       _rebased_generated_dir = rebase_path(
@@ -2770,13 +2781,23 @@
       args = [
         "--depfile=$_rebased_depfile",
         "--generated-dir=$_rebased_generated_dir",
-        "--jar-path=$_rebased_javac_jar_path",
+        "--jar-path=$_rebased_output_jar_path",
         "--java-srcjars=$_rebased_java_srcjars",
         "--classpath=@FileArg($_rebased_build_config:deps_info:javac_full_interface_classpath)",
         "--processorpath=@FileArg($_rebased_build_config:javac:processor_classpath)",
         "--processors=@FileArg($_rebased_build_config:javac:processor_classes)",
       ]
-      if (invoker.supports_android) {
+      if (invoker.use_turbine) {
+        _turbine_jar_path = "//third_party/turbine/turbine.jar"
+        inputs += [ _turbine_jar_path ]
+        args += [
+          "--turbine-jar-path",
+          rebase_path(_turbine_jar_path, root_build_dir),
+        ]
+      }
+
+      # Currently turbine does not support JDK11.
+      if (invoker.supports_android || invoker.use_turbine) {
         args += [ "--java-version=1.8" ]
       }
       if ((!defined(invoker.never_goma) || !invoker.never_goma) &&
@@ -2846,6 +2867,27 @@
     }
   }
 
+  template("java_header_group") {
+    forward_variables_from(invoker, [ "testonly" ])
+    group(target_name) {
+      if (defined(invoker.deps)) {
+        deps = []
+        foreach(_dep, invoker.deps) {
+          set_sources_assignment_filter(_java_header_target_patterns)
+          _target_label = get_label_info(_dep, "label_no_toolchain")
+          sources = [ _target_label ]
+          if (sources == []) {
+            # This is a java dep, so replace it with its header.
+            deps += [ "${_target_label}__header" ]
+          } else {
+            deps += [ _dep ]
+          }
+          sources = []
+        }
+      }
+    }
+  }
+
   # Create an interface jar from a normal jar.
   #
   # Variables
@@ -2935,6 +2977,9 @@
   #  testonly: True iff target should only be used for tests.
   #  no_build_hooks: Disables bytecode rewriting of asserts and android
   #    resources methods.
+  #  enable_turbine: If exists then will be used to determine whether to run
+  #    turbine or not. Useful for disabling turbine headers for problematic
+  #    targets.
   #  chromium_code: Optional. Whether this is Chromium-specific code. If not
   #    provided, this is determined automatically, based on the location of
   #    the source files (i.e. anything under third_party/ is not
@@ -3113,15 +3158,32 @@
 
     _accumulated_public_deps = []
     _accumulated_deps = []
+    _java_header_deps = []
+    _java_full_deps = []
     if (defined(invoker.deps)) {
-      _accumulated_deps = invoker.deps
+      foreach(_dep, invoker.deps) {
+        set_sources_assignment_filter(_java_header_target_patterns)
+        _target_label = get_label_info(_dep, "label_no_toolchain")
+        sources = [ _target_label ]
+        if (sources == []) {
+          # This is a java header dep, so replace it with its header.
+          _java_header_deps += [ "${_target_label}__header" ]
+          _java_full_deps += [ _dep ]
+        } else {
+          # Not a java header dep, so no need to replace it with its header.
+          _accumulated_deps += [ _dep ]
+        }
+        sources = []
+      }
     }
 
     _enable_build_hooks =
         _supports_android &&
         (!defined(invoker.no_build_hooks) || !invoker.no_build_hooks)
     if (_enable_build_hooks) {
-      _accumulated_deps += [ "//build/android/buildhooks:build_hooks_java" ]
+      _java_full_deps += [ "//build/android/buildhooks:build_hooks_java" ]
+      _java_header_deps +=
+          [ "//build/android/buildhooks:build_hooks_java__header" ]
     }
 
     # Some testonly targets use their own resources and the code being
@@ -3131,7 +3193,9 @@
         _enable_build_hooks && _requires_android &&
         (!defined(invoker.testonly) || !invoker.testonly)
     if (_enable_build_hooks_android) {
-      _accumulated_deps +=
+      # This is only needed for the process_java_prebuilt step, so no header
+      # necessary.
+      _java_full_deps +=
           [ "//build/android/buildhooks:build_hooks_android_java" ]
     }
 
@@ -3161,7 +3225,8 @@
             !invoker.jacoco_never_instrument && _jacoco_instrument
       }
       if (_jacoco_instrument) {
-        _accumulated_deps += [ "//third_party/jacoco:jacocoagent_java" ]
+        _java_header_deps += [ "//third_party/jacoco:jacocoagent_java__header" ]
+        _java_full_deps += [ "//third_party/jacoco:jacocoagent_java" ]
       }
     }
 
@@ -3178,11 +3243,13 @@
       _include_android_sdk = invoker.include_android_sdk
     }
     if (_include_android_sdk) {
+      _sdk_java_dep = "//third_party/android_sdk:android_sdk_java"
       if (defined(invoker.alternative_android_sdk_dep)) {
-        _accumulated_deps += [ invoker.alternative_android_sdk_dep ]
-      } else {
-        _accumulated_deps += [ "//third_party/android_sdk:android_sdk_java" ]
+        _sdk_java_dep = invoker.alternative_android_sdk_dep
       }
+
+      # This is an android_system_java_prebuilt target, so no headers.
+      _accumulated_deps += [ _sdk_java_dep ]
     }
     _jetified_jar_path =
         "$target_out_dir/${target_name}__process_prebuilt-jetified.jar"
@@ -3246,7 +3313,7 @@
       build_config = _build_config
       is_prebuilt = _is_prebuilt
       jetified_jar_path = _jetified_jar_path
-      possible_config_deps = _accumulated_deps
+      possible_config_deps = _java_full_deps + _accumulated_deps
       skip_jetify = defined(invoker.skip_jetify) && invoker.skip_jetify
       if (defined(apk_under_test)) {
         possible_config_deps += [ apk_under_test ]
@@ -3289,12 +3356,14 @@
 
     # Don't need to depend on the apk-under-test to be packaged.
     if (defined(invoker.apk_under_test)) {
-      _accumulated_deps += [ "${invoker.apk_under_test}__java" ]
+      _java_full_deps += [ "${invoker.apk_under_test}__java" ]
+      _java_header_deps += [ "${invoker.apk_under_test}__java__header" ]
     }
     if (defined(invoker.android_manifest_dep)) {
       _accumulated_deps += [ invoker.android_manifest_dep ]
     }
     if (defined(invoker.annotation_processor_deps)) {
+      # We need the full annotation processors rather than just the headers.
       _accumulated_deps += invoker.annotation_processor_deps
     }
 
@@ -3321,8 +3390,10 @@
       template("compile_java_helper") {
         compile_java(target_name) {
           forward_variables_from(invoker, "*")
-          enable_errorprone = invoker.enable_errorprone
-          javac_jar_path = invoker.javac_jar_path
+          output_jar_path = invoker.output_jar_path
+          enable_errorprone =
+              defined(invoker.enable_errorprone) && invoker.enable_errorprone
+          use_turbine = defined(invoker.use_turbine) && invoker.use_turbine
 
           main_target_name = _main_target_name
           build_config = _build_config
@@ -3334,7 +3405,8 @@
           chromium_code = _chromium_code
           supports_android = _supports_android
           requires_android = _requires_android
-          deps = _accumulated_deps + _accumulated_public_deps
+          deps =
+              _java_header_deps + _accumulated_deps + _accumulated_public_deps
 
           # android_apk and junit_binary pass R.java srcjars via srcjar_deps.
           if (_type == "java_library" && _requires_android) {
@@ -3343,8 +3415,6 @@
           }
         }
       }
-      _analysis_public_deps = []
-      _compile_java_target = "${_main_target_name}__compile_java"
       _compile_java_forward_variables = [
         "additional_jar_files",
         "apk_name",
@@ -3354,10 +3424,11 @@
         "jar_excluded_patterns",
         "never_goma",
       ]
+      _analysis_public_deps = []
+      _compile_java_target = "${_main_target_name}__compile_java"
       compile_java_helper(_compile_java_target) {
         forward_variables_from(invoker, _compile_java_forward_variables)
-        enable_errorprone = false
-        javac_jar_path = _javac_jar_path
+        output_jar_path = _javac_jar_path
       }
       if (_enable_errorprone) {
         _compile_java_errorprone_target =
@@ -3371,7 +3442,7 @@
             }
             javac_args += invoker.errorprone_args
           }
-          javac_jar_path = _javac_jar_path + ".errorprone_stamp"
+          output_jar_path = _javac_jar_path + ".errorprone_stamp"
         }
         _analysis_public_deps += [ ":$_compile_java_errorprone_target" ]
       }
@@ -3421,6 +3492,36 @@
       _accumulated_public_deps += [ ":$_compile_java_target" ]
     }  # _has_sources
 
+    if (_is_prebuilt || _has_sources) {
+      _header_target_name = "${target_name}__header"
+      _enable_turbine = _has_sources && _chromium_code
+      if (defined(invoker.enable_turbine)) {
+        _enable_turbine = invoker.enable_turbine
+      }
+      if (_enable_turbine) {
+        compile_java_helper(_header_target_name) {
+          forward_variables_from(invoker, _compile_java_forward_variables)
+          use_turbine = true
+          output_jar_path = _final_ijar_path
+        }
+      } else {
+        generate_interface_jar(_header_target_name) {
+          # Always used the unfiltered .jar to create the interface jar so that
+          # other targets will resolve filtered classes when depending on
+          # BuildConfig, NativeLibraries, etc.
+          input_jar = _unprocessed_jar_path
+          output_jar = _final_ijar_path
+
+          # Some prebuilts have java deps (e.g. //third_party/proguard:retrace_java).
+          deps = _accumulated_deps + _java_header_deps
+          if (_has_sources) {
+            deps += [ ":$_compile_java_target" ]
+          }
+        }
+      }
+      _accumulated_public_deps += [ ":$_header_target_name" ]
+    }  # _is_prebuilt || _has_sources
+
     if (defined(_final_jar_path)) {
       if (_is_system_library) {
         _copy_system_library_target_name = "${target_name}__copy_system_library"
@@ -3456,7 +3557,20 @@
             java_sources_file = _java_sources_file
           }
           output_jar_path = _final_jar_path
-          deps = _accumulated_deps + _accumulated_public_deps
+          deps = _java_full_deps + _accumulated_deps + _accumulated_public_deps
+
+          # proguard_configs listed on java_library targets need to be marked
+          # as inputs to at least one action so that "gn analyze" will know
+          # about them. Although ijar doesn't use them, it's a convenient spot
+          # to list them.
+          # https://crbug.com/827197
+          if (defined(invoker.proguard_configs)) {
+            inputs = invoker.proguard_configs
+            if (!defined(deps)) {
+              deps = []
+            }
+            deps += _srcjar_deps  # For the aapt-generated proguard rules.
+          }
         }
         _accumulated_public_deps += [ ":$_process_prebuilt_target_name" ]
 
@@ -3480,12 +3594,7 @@
       }
 
       if (!_is_java_binary) {
-        # Export the interface jar as the main target (rather than a group)
-        # so that ninja will notice when the output is unchanged and not rebuild
-        # reverse-dependencies. Targets that should be rebuilt when the
-        # non-interface .jar changes use a depfile to indicate that they should
-        # be rebuilt even when the interface jar does not change.
-        generate_interface_jar(target_name) {
+        group(target_name) {
           forward_variables_from(invoker,
                                  [
                                    "assert_no_deps",
@@ -3499,30 +3608,12 @@
           # as inputs to other targets.
           public_deps = _accumulated_public_deps
 
-          # Always used the unfiltered .jar to create the interface jar so that
-          # other targets will resolve filtered classes when depending on
-          # BuildConfig, NativeLibraries, etc.
-          input_jar = _unprocessed_jar_path
-          output_jar = _final_ijar_path
           if (_lint_enabled || _enable_errorprone) {
             if (!defined(data_deps)) {
               data_deps = []
             }
             data_deps += [ ":${_main_target_name}__analysis" ]
           }
-
-          # proguard_configs listed on java_library targets need to be marked
-          # as inputs to at least one action so that "gn analyze" will know
-          # about them. Although ijar doesn't use them, it's a convenient spot
-          # to list them.
-          # https://crbug.com/827197
-          if (defined(invoker.proguard_configs)) {
-            inputs = invoker.proguard_configs
-            if (!defined(deps)) {
-              deps = []
-            }
-            deps += _srcjar_deps  # For the aapt-generated proguard rules.
-          }
         }
       }
     }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 379844e..899444d3 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -1130,6 +1130,9 @@
         possible_config_deps = invoker.deps
       }
     }
+    java_header_group("${target_name}__header") {
+      forward_variables_from(invoker, [ "deps" ])
+    }
     group(target_name) {
       forward_variables_from(invoker, "*")
       if (!defined(deps)) {
@@ -3753,7 +3756,6 @@
           _final_apk_path = "$root_build_dir/apks/${apk_name}.apk"
         }
         data = [ "$_final_apk_path.mapping" ]
-        data_deps += [ "//build/android/stacktrace:java_deobfuscate" ]
       }
 
       dist_ijar_path = "$root_build_dir/test.lib.java/${invoker.apk_name}.jar"
@@ -4891,6 +4893,8 @@
           "--proguard-mapping-path",
           rebase_path(_proguard_mapping_path, root_build_dir),
         ]
+
+        # Required by logcat command.
         data_deps += [ "//build/android/stacktrace:java_deobfuscate" ]
         data += [ _proguard_mapping_path ]
       }
diff --git a/build/config/ios/ios_test_runner_wrapper.gni b/build/config/ios/ios_test_runner_wrapper.gni
index 3985dc6..1f2ca0f1 100644
--- a/build/config/ios/ios_test_runner_wrapper.gni
+++ b/build/config/ios/ios_test_runner_wrapper.gni
@@ -118,7 +118,12 @@
     } else {
       _wrapper_output_name = wrapper_output_name
     }
-    wrapper_script = "${root_build_dir}/bin/${_wrapper_output_name}"
+
+    # Test targets may attempt to generate multiple wrappers for a suite with
+    # multiple different toolchains when running with additional_target_cpus.
+    # Generate the wrapper script into root_out_dir rather than root_build_dir
+    # to ensure those wrappers are distinct.
+    wrapper_script = "${root_out_dir}/bin/${_wrapper_output_name}"
 
     # ios/build/bot/scripts/*.py needs to be present for test runner
     if (!defined(data)) {
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 24b0039..c5df5ad 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200318.3.1
\ No newline at end of file
+0.20200319.1.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 24b0039..c5df5ad 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200318.3.1
\ No newline at end of file
+0.20200319.1.1
\ No newline at end of file
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index a305bf6..58fdec5 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2801,6 +2801,7 @@
     "java/src/org/chromium/chrome/browser/password_manager/Credential.java",
     "java/src/org/chromium/chrome/browser/password_manager/CredentialLeakDialogBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/OnboardingDialogBridge.java",
+    "java/src/org/chromium/chrome/browser/password_manager/PasswordChangeLauncher.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogBridge.java",
     "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationPopupBridge.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 08a3a98..ac76225b 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -802,10 +802,6 @@
   "java/src/org/chromium/chrome/browser/historyreport/SearchJniBridge.java",
   "java/src/org/chromium/chrome/browser/historyreport/UsageReport.java",
   "java/src/org/chromium/chrome/browser/homepage/HomepagePolicyManager.java",
-  "java/src/org/chromium/chrome/browser/homepage/settings/HomepageEditor.java",
-  "java/src/org/chromium/chrome/browser/homepage/settings/HomepageMetricsEnums.java",
-  "java/src/org/chromium/chrome/browser/homepage/settings/HomepageSettings.java",
-  "java/src/org/chromium/chrome/browser/homepage/settings/RadioButtonGroupHomepagePreference.java",
   "java/src/org/chromium/chrome/browser/identity/SettingsSecureBasedIdentificationGenerator.java",
   "java/src/org/chromium/chrome/browser/identity/UniqueIdentificationGenerator.java",
   "java/src/org/chromium/chrome/browser/identity/UniqueIdentificationGeneratorFactory.java",
@@ -1240,6 +1236,7 @@
   "java/src/org/chromium/chrome/browser/password_manager/CredentialLeakDialogBridge.java",
   "java/src/org/chromium/chrome/browser/password_manager/GooglePasswordManagerUIProvider.java",
   "java/src/org/chromium/chrome/browser/password_manager/OnboardingDialogBridge.java",
+  "java/src/org/chromium/chrome/browser/password_manager/PasswordChangeLauncher.java",
   "java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java",
   "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogBridge.java",
   "java/src/org/chromium/chrome/browser/password_manager/PasswordGenerationDialogCoordinator.java",
@@ -1419,6 +1416,10 @@
   "java/src/org/chromium/chrome/browser/settings/MainSettings.java",
   "java/src/org/chromium/chrome/browser/settings/SettingsActivity.java",
   "java/src/org/chromium/chrome/browser/settings/SettingsLauncher.java",
+  "java/src/org/chromium/chrome/browser/settings/homepage/HomepageEditor.java",
+  "java/src/org/chromium/chrome/browser/settings/homepage/HomepageMetricsEnums.java",
+  "java/src/org/chromium/chrome/browser/settings/homepage/HomepageSettings.java",
+  "java/src/org/chromium/chrome/browser/settings/homepage/RadioButtonGroupHomepagePreference.java",
   "java/src/org/chromium/chrome/browser/share/LensUtils.java",
   "java/src/org/chromium/chrome/browser/share/OptionalShareTargetsManager.java",
   "java/src/org/chromium/chrome/browser/share/ShareActivity.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 1345ad2..8e39afd 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -202,8 +202,6 @@
   "javatests/src/org/chromium/chrome/browser/history/HistoryActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/homepage/HomepageTestRule.java",
-  "javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java",
-  "javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentWithEditorTest.java",
   "javatests/src/org/chromium/chrome/browser/identity/SettingsSecureBasedIdentificationGeneratorTest.java",
   "javatests/src/org/chromium/chrome/browser/identity/UniqueIdentificationGeneratorFactoryTest.java",
   "javatests/src/org/chromium/chrome/browser/identity/UuidBasedUniqueIdentificationGeneratorTest.java",
@@ -425,6 +423,9 @@
   "javatests/src/org/chromium/chrome/browser/searchwidget/SearchWidgetProviderTest.java",
   "javatests/src/org/chromium/chrome/browser/services/GoogleServicesManagerIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/settings/SettingsActivityTest.java",
+  "javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentTest.java",
+  "javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentWithEditorTest.java",
+  "javatests/src/org/chromium/chrome/browser/shape_detection/ShapeDetectionTest.java",
   "javatests/src/org/chromium/chrome/browser/share/LensUtilsTest.java",
   "javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java",
   "javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplIntegrationTest.java",
diff --git a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 9176386..9369c76 100644
--- a/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/features/autofill_assistant/public/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -37,7 +37,7 @@
      * <p>Intent starting with this prefix are reported to the controller as parameters, except for
      * the ones starting with {@code INTENT_SPECIAL_PREFIX}.
      */
-    private static final String INTENT_EXTRA_PREFIX =
+    public static final String INTENT_EXTRA_PREFIX =
             "org.chromium.chrome.browser.autofill_assistant.";
 
     /** Prefix for intent extras which are not parameters. */
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 2707a3d..f3224b5 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -38,7 +38,6 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -231,7 +230,6 @@
     @MediumTest
     @Feature({"StartSurface"})
     @CommandLineFlags.Add({BASE_PARAMS + "/single"})
-    @DisabledTest(message = "crbug.com/1051643")
     public void testSearchInSingleSurface() {
         // TODO(crbug.com/1025296): Set cached flag before starting the activity and mimic clicking
         // the 'home' button to show the single start surface home page.
@@ -258,8 +256,13 @@
         assertThat(
                 mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel().getCount(),
                 equalTo(2));
+    }
 
-        // Search in incognito mode.
+    @Test
+    @MediumTest
+    @Feature({"StartSurface"})
+    @CommandLineFlags.Add({BASE_PARAMS + "/single"})
+    public void testSearchInIncognitoSingleSurface() {
         TestThreadUtils.runOnUiThreadBlocking(
                 ()
                         -> mActivityTestRule.getActivity()
@@ -268,10 +271,12 @@
                                    .setOverviewState(OverviewModeState.SHOWING_HOMEPAGE));
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> mActivityTestRule.getActivity().getLayoutManager().showOverview(false));
+        assertTrue(mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
         onView(withId(org.chromium.chrome.start_surface.R.id.incognito_switch)).perform(click());
         assertTrue(mActivityTestRule.getActivity().getTabModelSelector().isIncognitoSelected());
 
-        hideWatcher = TabUiTestHelper.createOverviewHideWatcher(mActivityTestRule.getActivity());
+        OverviewModeBehaviorWatcher hideWatcher =
+                TabUiTestHelper.createOverviewHideWatcher(mActivityTestRule.getActivity());
         onView(allOf(withId(org.chromium.chrome.start_surface.R.id.search_box_text), isDisplayed()))
                 .perform(typeText(mUrl));
         onView(withId(org.chromium.chrome.start_surface.R.id.url_bar))
diff --git a/chrome/android/features/tab_ui/java/res/layout/selectable_tab_list_card_item.xml b/chrome/android/features/tab_ui/java/res/layout/selectable_tab_list_card_item.xml
deleted file mode 100644
index b7e6cd6..0000000
--- a/chrome/android/features/tab_ui/java/res/layout/selectable_tab_list_card_item.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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. -->
-<!-- TODO(crbug.com/1023557): Rename SelectableTabGridView to SelectableTabView. -->
-<org.chromium.chrome.browser.tasks.tab_management.SelectableTabGridView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/selectable_tab_list_card_item"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-
-    <include layout="@layout/tab_list_card_item"/>
-
-</org.chromium.chrome.browser.tasks.tab_management.SelectableTabGridView>
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 614b113..3dddfae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -47,20 +47,14 @@
         mComponentName = animationSourceViewProvider == null ? "TabGridDialogFromStrip"
                                                              : "TabGridDialogInSwitcher";
 
-        @TabListCoordinator.TabListMode
-        int mode = TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled()
-                        && SysUtils.isLowEndDevice()
-                ? TabListCoordinator.TabListMode.LIST
-                : TabListCoordinator.TabListMode.GRID;
-
         mToolbarPropertyModel = new PropertyModel(TabGridPanelProperties.ALL_KEYS);
 
         mParentLayout = new TabGridDialogParent(context, containerView);
 
         TabSelectionEditorCoordinator.TabSelectionEditorController controller = null;
         if (TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled()) {
-            mTabSelectionEditorCoordinator = new TabSelectionEditorCoordinator(context,
-                    containerView, tabModelSelector, tabContentManager, mParentLayout, mode);
+            mTabSelectionEditorCoordinator = new TabSelectionEditorCoordinator(
+                    context, containerView, tabModelSelector, tabContentManager, mParentLayout);
 
             controller = mTabSelectionEditorCoordinator.getController();
         } else {
@@ -71,9 +65,15 @@
                 tabModelSelector, tabCreatorManager, resetHandler, animationSourceViewProvider,
                 controller, tabGroupTitleEditor, shareDelegateSupplier, mComponentName);
 
-        mTabListCoordinator = new TabListCoordinator(mode, context, tabModelSelector,
-                tabContentManager::getTabThumbnailWithCallback, null, false,
-                gridCardOnClickListenerProvider, mMediator.getTabGridDialogHandler(),
+        // TODO(crbug.com/1031349) : Remove the inline mode logic here, make the constructor to take
+        // in a mode parameter instead.
+        mTabListCoordinator = new TabListCoordinator(
+                TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled()
+                                && SysUtils.isLowEndDevice()
+                        ? TabListCoordinator.TabListMode.LIST
+                        : TabListCoordinator.TabListMode.GRID,
+                context, tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null,
+                false, gridCardOnClickListenerProvider, mMediator.getTabGridDialogHandler(),
                 TabProperties.UiType.CLOSABLE, null, containerView, null, false, mComponentName);
 
         TabListRecyclerView recyclerView = mTabListCoordinator.getContainerView();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index 81305e2..15a3ccf 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -184,13 +184,6 @@
 
                 return group;
             }, TabListViewBinder::bindListTab);
-
-            mAdapter.registerType(UiType.SELECTABLE, parent -> {
-                ViewGroup group = (ViewGroup) LayoutInflater.from(context).inflate(
-                        R.layout.selectable_tab_list_card_item, parentView, false);
-                group.setClickable(true);
-                return group;
-            }, TabListViewBinder::bindSelectableListTab);
         } else {
             throw new IllegalArgumentException(
                     "Attempting to create a tab list UI with invalid mode");
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 9bd2472..41caf5ffd7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -1185,9 +1185,13 @@
                                         tab.isIncognito()))
                         .with(TabProperties.IS_SELECTED, isSelected)
                         .with(TabProperties.IPH_PROVIDER, showIPH ? mIphProvider : null)
+                        .with(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener)
+                        .with(TabProperties.TAB_CLOSED_LISTENER, mTabClosedListener)
                         .with(CARD_ALPHA, 1f)
                         .with(TabProperties.CARD_ANIMATION_STATUS,
                                 ClosableTabGridView.AnimationStatus.CARD_RESTORE)
+                        .with(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER,
+                                mSelectableTabOnClickListener)
                         .with(TabProperties.TAB_SELECTION_DELEGATE, getTabSelectionDelegate())
                         .with(TabProperties.IS_INCOGNITO, tab.isIncognito())
                         .with(TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
@@ -1228,11 +1232,6 @@
                     actionButtonBackgroundColorList);
             tabInfo.set(TabProperties.SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND,
                     actionbuttonSelectedBackgroundColorList);
-            tabInfo.set(
-                    TabProperties.SELECTABLE_TAB_CLICKED_LISTENER, mSelectableTabOnClickListener);
-        } else {
-            tabInfo.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener);
-            tabInfo.set(TabProperties.TAB_CLOSED_LISTENER, mTabClosedListener);
         }
 
         if (index >= mModel.size()) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
index a775583f..6da5fae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
@@ -8,8 +8,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
@@ -18,7 +16,6 @@
 import androidx.annotation.Nullable;
 import androidx.core.content.res.ResourcesCompat;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -79,51 +76,4 @@
             ((TextView) fastView.findViewById(R.id.description)).setText(title);
         }
     }
-
-    /**
-     * Bind a selectable tab to view.
-     * @param model The model to bind.
-     * @param view The view to bind to.
-     * @param propertyKey The property that changed.
-     */
-    public static void bindSelectableListTab(
-            PropertyModel model, ViewGroup view, @Nullable PropertyKey propertyKey) {
-        bindListTab(model, view, propertyKey);
-
-        final int tabId = model.get(TabProperties.TAB_ID);
-        final int defaultLevel = view.getResources().getInteger(R.integer.list_item_level_default);
-        final int selectedLevel =
-                view.getResources().getInteger(R.integer.list_item_level_selected);
-
-        if (TabProperties.SELECTABLE_TAB_CLICKED_LISTENER == propertyKey) {
-            view.setOnClickListener(v -> {
-                model.get(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER).run(tabId);
-                ((SelectableTabGridView) view).onClick();
-            });
-            view.setOnLongClickListener(v -> {
-                model.get(TabProperties.SELECTABLE_TAB_CLICKED_LISTENER).run(tabId);
-                return ((SelectableTabGridView) view).onLongClick(view);
-            });
-        } else if (TabProperties.TAB_SELECTION_DELEGATE == propertyKey) {
-            assert model.get(TabProperties.TAB_SELECTION_DELEGATE) != null;
-
-            ((SelectableTabGridView) view)
-                    .setSelectionDelegate(model.get(TabProperties.TAB_SELECTION_DELEGATE));
-            ((SelectableTabGridView) view).setItem(tabId);
-        } else if (TabProperties.IS_SELECTED == propertyKey) {
-            boolean isSelected = model.get(TabProperties.IS_SELECTED);
-            ImageView actionButton = (ImageView) view.findViewById(R.id.action_button);
-            actionButton.getBackground().setLevel(isSelected ? selectedLevel : defaultLevel);
-            DrawableCompat.setTintList(actionButton.getBackground().mutate(),
-                    isSelected ? model.get(
-                            TabProperties.SELECTABLE_TAB_ACTION_BUTTON_SELECTED_BACKGROUND)
-                               : model.get(TabProperties.SELECTABLE_TAB_ACTION_BUTTON_BACKGROUND));
-
-            // The check should be invisible if not selected.
-            actionButton.getDrawable().setAlpha(isSelected ? 255 : 0);
-            ApiCompatibilityUtils.setImageTintList(actionButton,
-                    isSelected ? model.get(TabProperties.CHECKED_DRAWABLE_STATE_LIST) : null);
-            if (isSelected) ((AnimatedVectorDrawableCompat) actionButton.getDrawable()).start();
-        }
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
index c5b8844..ce58e59e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
@@ -9,10 +9,9 @@
 
 import android.content.Context;
 import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.View;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -102,7 +101,7 @@
     }
 
     private final Context mContext;
-    private final ViewGroup mParentView;
+    private final View mParentView;
     private final TabModelSelector mTabModelSelector;
     private final TabSelectionEditorLayout mTabSelectionEditorLayout;
     private final TabListCoordinator mTabListCoordinator;
@@ -111,18 +110,19 @@
     private final PropertyModelChangeProcessor mTabSelectionEditorLayoutChangeProcessor;
     private final TabSelectionEditorMediator mTabSelectionEditorMediator;
 
-    public TabSelectionEditorCoordinator(Context context, ViewGroup parentView,
+    public TabSelectionEditorCoordinator(Context context, View parentView,
             TabModelSelector tabModelSelector, TabContentManager tabContentManager,
             @Nullable TabSelectionEditorMediator
-                    .TabSelectionEditorPositionProvider positionProvider,
-            @TabListCoordinator.TabListMode int mode) {
+                    .TabSelectionEditorPositionProvider positionProvider) {
         mContext = context;
         mParentView = parentView;
         mTabModelSelector = tabModelSelector;
 
-        mTabListCoordinator = new TabListCoordinator(mode, context, mTabModelSelector,
-                tabContentManager::getTabThumbnailWithCallback, null, false, null, null,
-                TabProperties.UiType.SELECTABLE, this::getSelectionDelegate, mParentView, null,
+        // TODO(crbug.com/1007598): construct TabListCoordinator with List mode if it's a low end
+        // device, and TabGroupContinuation is turned on.
+        mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
+                mTabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false,
+                null, null, TabProperties.UiType.SELECTABLE, this::getSelectionDelegate, null, null,
                 false, COMPONENT_NAME);
         mTabListCoordinator.registerItemType(TabProperties.UiType.DIVIDER,
                 new LayoutViewBuilder(R.layout.divider_preference),
@@ -197,12 +197,4 @@
         mTabSelectionEditorMediator.destroy();
         mTabSelectionEditorLayoutChangeProcessor.destroy();
     }
-
-    /**
-     * @return The {@link TabSelectionEditorLayout} for testing.
-     */
-    @VisibleForTesting
-    public TabSelectionEditorLayout getTabSelectionEditorLayoutForTesting() {
-        return mTabSelectionEditorLayout;
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index ebad219..65c81609 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -103,7 +103,7 @@
         PropertyModel containerViewModel = new PropertyModel(TabListContainerProperties.ALL_KEYS);
 
         mTabSelectionEditorCoordinator = new TabSelectionEditorCoordinator(
-                context, container, tabModelSelector, tabContentManager, null, mode);
+                context, container, tabModelSelector, tabContentManager, null);
 
         mMediator = new TabSwitcherMediator(this, containerViewModel, tabModelSelector,
                 fullscreenManager, container, mTabSelectionEditorCoordinator.getController(),
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
index 4935097..2adc3c0 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
@@ -68,9 +68,6 @@
     private SelectionDelegate<Integer> mSelectionDelegate;
     private int mSelectedTabBackgroundDrawableId = R.drawable.selected_tab_background;
 
-    private ViewGroup mSelectableTabListView;
-    private PropertyModelChangeProcessor mSelectableListMCP;
-
     private TabListMediator.ThumbnailFetcher mMockThumbnailProvider =
             new TabListMediator.ThumbnailFetcher(new TabListMediator.ThumbnailProvider() {
                 @Override
@@ -137,13 +134,10 @@
                     R.layout.tab_strip_item, null);
             mSelectableTabGridView = (ViewGroup) getActivity().getLayoutInflater().inflate(
                     R.layout.selectable_tab_grid_card_item, null);
-            mSelectableTabListView = (ViewGroup) getActivity().getLayoutInflater().inflate(
-                    R.layout.selectable_tab_list_card_item, null);
 
             view.addView(mTabGridView);
             view.addView(mTabStripView);
             view.addView(mSelectableTabGridView);
-            view.addView(mSelectableTabListView);
         });
 
         mSelectionDelegate = new SelectionDelegate<>();
@@ -174,8 +168,6 @@
                     mStripModel, mTabStripView, TabStripViewBinder::bind);
             mSelectableMCP = PropertyModelChangeProcessor.create(
                     mSelectableModel, mSelectableTabGridView, TabGridViewBinder::bindSelectableTab);
-            mSelectableListMCP = PropertyModelChangeProcessor.create(mSelectableModel,
-                    mSelectableTabListView, TabListViewBinder::bindSelectableListTab);
         });
     }
 
@@ -192,26 +184,10 @@
             model.set(TabProperties.IS_SELECTED, false);
             Assert.assertTrue(selectedView.getVisibility() == View.GONE);
         }
-    }
-
-    private void testSelectableTabClickToSelect(
-            ViewGroup view, PropertyModel model, boolean isLongClick) {
-        Runnable clickTask = () -> {
-            if (isLongClick) {
-                view.performLongClick();
-            } else {
-                view.performClick();
-            }
-        };
-
-        model.set(TabProperties.IS_SELECTED, false);
-        clickTask.run();
-        Assert.assertTrue(mSelectClicked.get());
-        mSelectClicked.set(false);
-
-        model.set(TabProperties.IS_SELECTED, true);
-        clickTask.run();
-        Assert.assertTrue(mSelectClicked.get());
+        mStripModel.set(TabProperties.IS_SELECTED, true);
+        Assert.assertTrue(((FrameLayout) mTabStripView).getForeground() != null);
+        mStripModel.set(TabProperties.IS_SELECTED, false);
+        Assert.assertFalse(((FrameLayout) mTabStripView).getForeground() != null);
     }
 
     @Test
@@ -235,17 +211,6 @@
         mSelectableModel.set(TabProperties.IS_SELECTED, false);
         Assert.assertTrue(actionButton.getBackground().getLevel() == 0);
         Assert.assertEquals(0, actionButton.getDrawable().getAlpha());
-
-        testGridSelected(mSelectableTabListView, mSelectableModel);
-        mSelectableModel.set(TabProperties.IS_SELECTED, true);
-        ImageView actionButtonList = mSelectableTabListView.findViewById(R.id.action_button);
-        Assert.assertTrue(actionButtonList.getBackground().getLevel() == 1);
-        Assert.assertTrue(actionButtonList.getDrawable() != null);
-        Assert.assertEquals(255, actionButton.getDrawable().getAlpha());
-
-        mSelectableModel.set(TabProperties.IS_SELECTED, false);
-        Assert.assertTrue(actionButtonList.getBackground().getLevel() == 0);
-        Assert.assertEquals(0, actionButtonList.getDrawable().getAlpha());
     }
 
     @Test
@@ -300,9 +265,6 @@
         mSelectableModel.set(TabProperties.TITLE, title);
         textView = mSelectableTabGridView.findViewById(R.id.tab_title);
         Assert.assertEquals(textView.getText(), title);
-
-        textView = mSelectableTabListView.findViewById(R.id.title);
-        Assert.assertEquals(textView.getText(), title);
     }
 
     @Test
@@ -447,11 +409,14 @@
         Assert.assertFalse(mSelectClicked.get());
         mSelectClicked.set(false);
 
-        testSelectableTabClickToSelect(mSelectableTabGridView, mSelectableModel, false);
-        testSelectableTabClickToSelect(mSelectableTabGridView, mSelectableModel, true);
+        mSelectableModel.set(TabProperties.IS_SELECTED, false);
+        mSelectableTabGridView.performClick();
+        Assert.assertTrue(mSelectClicked.get());
+        mSelectClicked.set(false);
 
-        testSelectableTabClickToSelect(mSelectableTabListView, mSelectableModel, false);
-        testSelectableTabClickToSelect(mSelectableTabListView, mSelectableModel, true);
+        mSelectableModel.set(TabProperties.IS_SELECTED, true);
+        mSelectableTabGridView.performClick();
+        Assert.assertTrue(mSelectClicked.get());
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
index c9e3e78..95859098 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java
@@ -6,7 +6,6 @@
 
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
-import android.view.ViewGroup;
 
 import org.junit.After;
 import org.junit.Before;
@@ -15,10 +14,7 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.BaseSwitches;
-import org.chromium.base.SysUtils;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -29,14 +25,11 @@
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -53,15 +46,11 @@
     @Rule
     public TestRule mProcessor = new Features.InstrumentationProcessor();
 
-    @Rule
-    public ChromeRenderTestRule mRenderTestRule = new ChromeRenderTestRule();
-
     private TabSelectionEditorTestingRobot mRobot = new TabSelectionEditorTestingRobot();
 
     private TabModelSelector mTabModelSelector;
     private TabSelectionEditorCoordinator
             .TabSelectionEditorController mTabSelectionEditorController;
-    private TabSelectionEditorLayout mTabSelectionEditorLayout;
 
     @Before
     public void setUp() throws Exception {
@@ -75,14 +64,10 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             TabSelectionEditorCoordinator tabSelectionEditorCoordinator =
                     new TabSelectionEditorCoordinator(mActivityTestRule.getActivity(),
-                            (ViewGroup) mActivityTestRule.getActivity().getCurrentFocus(),
-                            mTabModelSelector,
-                            mActivityTestRule.getActivity().getTabContentManager(), null,
-                            getMode());
+                            mActivityTestRule.getActivity().getCurrentFocus(), mTabModelSelector,
+                            mActivityTestRule.getActivity().getTabContentManager(), null);
 
             mTabSelectionEditorController = tabSelectionEditorCoordinator.getController();
-            mTabSelectionEditorLayout =
-                    tabSelectionEditorCoordinator.getTabSelectionEditorLayoutForTesting();
         });
     }
 
@@ -91,13 +76,6 @@
         CachedFeatureFlags.setForTesting(ChromeFeatureList.TAB_GROUPS_ANDROID, null);
     }
 
-    private @TabListCoordinator.TabListMode int getMode() {
-        return TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled()
-                        && SysUtils.isLowEndDevice()
-                ? TabListCoordinator.TabListMode.LIST
-                : TabListCoordinator.TabListMode.GRID;
-    }
-
     @Test
     @MediumTest
     public void testShowTabs() {
@@ -262,34 +240,6 @@
         mRobot.resultRobot.verifyDividerNotClickableNotFocusable();
     }
 
-    @Test
-    @MediumTest
-    @Feature({"RenderTest"})
-    @CommandLineFlags.Add(BaseSwitches.ENABLE_LOW_END_DEVICE_MODE)
-    @EnableFeatures({ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID})
-    public void testListViewAppearance() throws IOException {
-        List<Tab> tabs = getTabsInCurrentTabModel();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
-
-        mRenderTestRule.render(mTabSelectionEditorLayout, "list_view");
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"RenderTest"})
-    @CommandLineFlags.Add(BaseSwitches.ENABLE_LOW_END_DEVICE_MODE)
-    @EnableFeatures({ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID})
-    public void testListViewAppearance_oneSelectedTab() throws IOException {
-        List<Tab> tabs = getTabsInCurrentTabModel();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mTabSelectionEditorController.show(tabs); });
-
-        mRobot.actionRobot.clickItemAtAdapterPosition(0);
-
-        mRenderTestRule.render(mTabSelectionEditorLayout, "list_view_one_selected_tab");
-    }
-
     private List<Tab> getTabsInCurrentTabModel() {
         List<Tab> tabs = new ArrayList<>();
 
diff --git a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
index 4647d7d8..299da1c 100644
--- a/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_bundle__base_bundle_module.AndroidManifest.expected
@@ -1058,7 +1058,7 @@
         android:exported="true"
         android:label="WebView
         DevTools"
-        android:name="org.chromium.android_webview.devui.MonochromeLauncherActivity"
+        android:name="org.chromium.android_webview.devui.LauncherActivity"
         android:targetActivity="org.chromium.android_webview.devui.MainActivity">
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>
diff --git a/chrome/android/java/res/xml/homepage_preferences.xml b/chrome/android/java/res/xml/homepage_preferences.xml
index f641eb2..14f411fc 100644
--- a/chrome/android/java/res/xml/homepage_preferences.xml
+++ b/chrome/android/java/res/xml/homepage_preferences.xml
@@ -18,7 +18,7 @@
         android:visibility="gone"
         android:fragment="org.chromium.chrome.browser.settings.homepage.HomepageEditor" />
 
-    <org.chromium.chrome.browser.homepage.settings.RadioButtonGroupHomepagePreference
+    <org.chromium.chrome.browser.settings.homepage.RadioButtonGroupHomepagePreference
         android:key="homepage_radio_group"
         android:visibility="gone"
         android:selectable="false"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeCachedFlags.java
index 2b96d30..cf8b1610 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeCachedFlags.java
@@ -57,7 +57,6 @@
                 ChromeFeatureList.PAINT_PREVIEW_DEMO,
                 ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS,
                 ChromeFeatureList.INSTANT_START,
-                ChromeFeatureList.SHARE_BUTTON_IN_TOP_TOOLBAR,
                 ChromeFeatureList.START_SURFACE_ANDROID,
                 ChromeFeatureList.SWAP_PIXEL_FORMAT_TO_FIX_CONVERT_FROM_TRANSLUCENT,
                 ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
index 4b5bfce6..7bd82a7f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/UmaUtils.java
@@ -87,7 +87,7 @@
     }
 
     @CalledByNative
-    public static long getMainEntryPointTicks() {
+    public static long getApplicationStartTime() {
         return sApplicationStartTimeMs;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index 469eb7a..b161fde 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -235,7 +235,7 @@
         mActivity = activity;
         mUiConfig = uiConfig;
 
-        Profile profile = Profile.getLastUsedProfile();
+        Profile profile = Profile.getLastUsedRegularProfile();
         OfflinePageBridge offlinePageBridge =
                 SuggestionsDependencyFactory.getInstance().getOfflinePageBridge(profile);
         TileRenderer tileRenderer =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/HomepageManager.java b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/HomepageManager.java
index c749d8a..8aa58321 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/HomepageManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/HomepageManager.java
@@ -15,11 +15,11 @@
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.homepage.HomepagePolicyManager;
-import org.chromium.chrome.browser.homepage.settings.HomepageMetricsEnums.HomeButtonPreferenceState;
-import org.chromium.chrome.browser.homepage.settings.HomepageMetricsEnums.HomepageLocationType;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.settings.homepage.HomepageMetricsEnums.HomeButtonPreferenceState;
+import org.chromium.chrome.browser.settings.homepage.HomepageMetricsEnums.HomepageLocationType;
 import org.chromium.components.embedder_support.util.UrlConstants;
 
 /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeLauncher.java
new file mode 100644
index 0000000..f539775e
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordChangeLauncher.java
@@ -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.
+
+package org.chromium.chrome.browser.password_manager;
+
+import android.os.Bundle;
+
+import org.chromium.base.Log;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantFacade;
+import org.chromium.ui.base.WindowAndroid;
+
+/** Class for starting a password change flow in Autofill Assistant. */
+public class PasswordChangeLauncher {
+    private static final String TAG = "AutofillAssistant";
+
+    /**
+     * Name for the parameter that stores session username. Should be synced with
+     * |kSessionUsernameParameterName| from components/autofill_assistant/browser/controller.cc
+     * TODO(b/151401974): Eliminate duplicate parameter definitions.
+     */
+    private static final String PASSWORD_CHANGE_USERNAME_PARAMETER =
+            AutofillAssistantFacade.INTENT_EXTRA_PREFIX + "PASSWORD_CHANGE_USERNAME";
+
+    @CalledByNative
+    public static void start(WindowAndroid windowAndroid, String origin, String username) {
+        ChromeActivity activity = (ChromeActivity) windowAndroid.getActivity().get();
+        if (activity == null) {
+            Log.v(TAG, "Failed to retrieve ChromeActivity.");
+            return;
+        }
+        Bundle bundleExtras = new Bundle();
+        bundleExtras.putString(PASSWORD_CHANGE_USERNAME_PARAMETER, username);
+        AutofillAssistantFacade.start(activity, bundleExtras, origin);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
index 15d711b..e12cdcd5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/DecoderServiceHost.java
@@ -86,7 +86,7 @@
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             mIRemoteService = IDecoderService.Stub.asInterface(service);
-            mBound = true;
+            assert mIRemoteService != null;
             for (DecoderStatusCallback callback : mCallbacks) {
                 callback.serviceReady();
             }
@@ -96,7 +96,6 @@
         public void onServiceDisconnected(ComponentName className) {
             Log.e(TAG, "Service has unexpectedly disconnected");
             mIRemoteService = null;
-            mBound = false;
         }
     };
 
@@ -204,9 +203,6 @@
     // The callbacks used to notify the clients when the service is ready.
     private final List<DecoderStatusCallback> mCallbacks = new ArrayList<DecoderStatusCallback>();
 
-    // Flag indicating whether we are bound to the service.
-    private boolean mBound;
-
     private final Context mContext;
 
     /**
@@ -237,9 +233,9 @@
      * @param context The context to use.
      */
     public void unbind(Context context) {
-        if (mBound) {
+        if (mIRemoteService != null) {
             context.unbindService(mConnection);
-            mBound = false;
+            mIRemoteService = null;
         }
     }
 
@@ -506,6 +502,15 @@
      * @param params The information about the decoding request.
      */
     private void dispatchDecodeImageRequest(DecoderServiceParams params) {
+        if (mIRemoteService == null) {
+            // If the connection is lost, ignore the request and continue. Further still image
+            // decoding requests will likely be dropped but note that there may be video requests
+            // remaining (which don't require this connection to be open).
+            Log.e(TAG, "Connection to decoder service unexpectedly terminated.");
+            closeRequestWithError(mProcessingRequest.mUri.getPath());
+            return;
+        }
+
         // Obtain a file descriptor to send over to the sandboxed process.
         ParcelFileDescriptor pfd = null;
         Bundle bundle = new Bundle();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageEditor.java
similarity index 98%
rename from chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageEditor.java
rename to chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageEditor.java
index 5efd4a5..b8def92c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageEditor.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import android.os.Bundle;
 import android.text.Editable;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageMetricsEnums.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageMetricsEnums.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageMetricsEnums.java
rename to chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageMetricsEnums.java
index 2d95728..3084079 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageMetricsEnums.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageMetricsEnums.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import androidx.annotation.IntDef;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageSettings.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageSettings.java
similarity index 97%
rename from chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageSettings.java
rename to chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageSettings.java
index 6b98884..92505ee 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/HomepageSettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/HomepageSettings.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import android.os.Bundle;
 
@@ -14,14 +14,14 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.homepage.HomepagePolicyManager;
-import org.chromium.chrome.browser.homepage.settings.RadioButtonGroupHomepagePreference.HomepageOption;
-import org.chromium.chrome.browser.homepage.settings.RadioButtonGroupHomepagePreference.PreferenceValues;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
 import org.chromium.chrome.browser.settings.ManagedPreferenceDelegate;
 import org.chromium.chrome.browser.settings.SettingsUtils;
 import org.chromium.chrome.browser.settings.TextMessagePreference;
+import org.chromium.chrome.browser.settings.homepage.RadioButtonGroupHomepagePreference.HomepageOption;
+import org.chromium.chrome.browser.settings.homepage.RadioButtonGroupHomepagePreference.PreferenceValues;
 import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarVariationManager;
 import org.chromium.components.url_formatter.UrlFormatter;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/RadioButtonGroupHomepagePreference.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/RadioButtonGroupHomepagePreference.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/RadioButtonGroupHomepagePreference.java
rename to chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/RadioButtonGroupHomepagePreference.java
index a30c301..6ea4c0eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/homepage/settings/RadioButtonGroupHomepagePreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/homepage/RadioButtonGroupHomepagePreference.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareButtonController.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareButtonController.java
index 8da6e2a3..a1aeb8e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareButtonController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareButtonController.java
@@ -14,7 +14,6 @@
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
-import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.ButtonData;
@@ -26,6 +25,11 @@
  * whether NTP is shown).
  */
 public class ShareButtonController implements ButtonDataProvider {
+    /**
+     * Default minimum width to show share button.
+     */
+    private static final int MIN_WIDTH = 360;
+
     // Context is used for fetching resources and launching preferences page.
     private final Context mContext;
 
@@ -41,6 +45,8 @@
     private final ObservableSupplier<Boolean> mBottomToolbarVisibilitySupplier;
     private OnClickListener mOnClickListener;
 
+    private Integer mMinimumWidthDp;
+
     /**
      * Creates ShareButtonController object.
      * @param context The Context for retrieving resources, etc.
@@ -99,11 +105,26 @@
     }
 
     private void updateButtonState(Tab tab) {
-        // TODO(crbug.com/1036023) add width constraints.
-        if (!CachedFeatureFlags.isEnabled(ChromeFeatureList.SHARE_BUTTON_IN_TOP_TOOLBAR)
-                || (mBottomToolbarVisibilitySupplier.get()
-                        && BottomToolbarVariationManager.isShareButtonOnBottom())
-                || mShareDelegateSupplier.get() == null) {
+        if (tab == null || tab.getWebContents() == null
+                || !ChromeFeatureList.isEnabled(ChromeFeatureList.SHARE_BUTTON_IN_TOP_TOOLBAR)) {
+            mButtonData.canShow = false;
+            return;
+        }
+
+        if (mMinimumWidthDp == null) {
+            mMinimumWidthDp = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
+                    ChromeFeatureList.SHARE_BUTTON_IN_TOP_TOOLBAR, "minimum_width", MIN_WIDTH);
+        }
+
+        float deviceWidthPX = mContext.getResources().getDisplayMetrics().widthPixels;
+        int deviceWidth =
+                (int) (deviceWidthPX / (mContext.getResources().getDisplayMetrics().density));
+
+        boolean isDeviceWideEnough = deviceWidth > mMinimumWidthDp;
+
+        if ((mBottomToolbarVisibilitySupplier.get()
+                    && BottomToolbarVariationManager.isShareButtonOnBottom())
+                || mShareDelegateSupplier.get() == null || !isDeviceWideEnough) {
             mButtonData.canShow = false;
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
index 0b9ef9e..74c5fde 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/HomeButton.java
@@ -28,10 +28,10 @@
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.homepage.HomepagePolicyManager;
-import org.chromium.chrome.browser.homepage.settings.HomepageSettings;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.settings.SettingsLauncher;
+import org.chromium.chrome.browser.settings.homepage.HomepageSettings;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarConfiguration;
 import org.chromium.ui.widget.ChromeImageButton;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinatorPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinatorPhone.java
index d2935fd..a14d9b8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinatorPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTCoordinatorPhone.java
@@ -11,10 +11,8 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.incognito.IncognitoUtils;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
@@ -25,15 +23,13 @@
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
-import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
-import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
 
 /**
  * The coordinator for the tab switcher mode top toolbar shown on phones, responsible for
  * communication with other UI components and lifecycle. Lazily creates the tab
  * switcher mode top toolbar the first time it's needed.
  */
-class TabSwitcherModeTTCoordinatorPhone implements TemplateUrlServiceObserver {
+class TabSwitcherModeTTCoordinatorPhone {
     private final ViewStub mTabSwitcherToolbarStub;
 
     // TODO(twellington): Create a model to hold all of these properties. Consider using
@@ -51,10 +47,6 @@
     private TabSwitcherModeTTPhone mTabSwitcherModeToolbar;
 
     @Nullable
-    private IncognitoSwitchCoordinator mIncognitoSwitchCoordinator;
-    @Nullable
-    private View mLogo;
-    @Nullable
     private TabModelObserver mTabModelObserver;
 
     TabSwitcherModeTTCoordinatorPhone(ViewStub tabSwitcherToolbarStub) {
@@ -69,25 +61,11 @@
             mTabSwitcherModeToolbar.destroy();
             mTabSwitcherModeToolbar = null;
         }
-        if (mIncognitoSwitchCoordinator != null) {
-            mIncognitoSwitchCoordinator.destroy();
-            mIncognitoSwitchCoordinator = null;
-        }
-        if (StartSurfaceConfiguration.isStartSurfaceEnabled()) {
-            TemplateUrlServiceFactory.get().removeObserver(this);
-        }
         if (mTabModelSelector != null && mTabModelObserver != null) {
             mTabModelSelector.getModel(true).removeObserver(mTabModelObserver);
         }
     }
 
-    @Override
-    public void onTemplateURLServiceChanged() {
-        mLogo.setVisibility(
-                (TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle() ? View.VISIBLE
-                                                                               : View.GONE));
-    }
-
     /**
      * Called when tab switcher mode is entered or exited.
      * @param inTabSwitcherMode Whether or not tab switcher mode should be shown or hidden.
@@ -264,15 +242,6 @@
             incognitoTabModel.addObserver(mTabModelObserver);
             mTabSwitcherModeToolbar.onIncognitoTabsCountChanged(incognitoTabModel.getCount());
         }
-        if (StartSurfaceConfiguration.isStartSurfaceEnabled()) {
-            mIncognitoSwitchCoordinator =
-                    new IncognitoSwitchCoordinator(mTabSwitcherModeToolbar, mTabModelSelector);
-            mLogo = mTabSwitcherModeToolbar.findViewById(R.id.logo);
-            if (TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()) {
-                mLogo.setVisibility(View.VISIBLE);
-            }
-            TemplateUrlServiceFactory.get().addObserver(this);
-        }
 
         assert mIncognitoStateProvider != null;
         mTabSwitcherModeToolbar.setIncognitoStateProvider(mIncognitoStateProvider);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
index 31d7f4fb..8c96d220 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherModeTTPhone.java
@@ -32,7 +32,6 @@
 import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarConfiguration;
 import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarVariationManager;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
-import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
 import org.chromium.components.browser_ui.widget.animation.Interpolators;
@@ -385,11 +384,6 @@
     }
 
     private void setIncognitoToggleVisibility(boolean showIncognitoToggle) {
-        // If StartSurface is enabled, the incognito switch is shown and handled
-        // by the IncognitoSwitchCoordinator in the
-        // TabSwitcherModeTTCoordinatorPhone.
-        if (StartSurfaceConfiguration.isStartSurfaceEnabled()) return;
-
         if (mIncognitoToggleTabLayout == null) {
             if (showIncognitoToggle) inflateIncognitoToggle();
         } else {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
index be1605e..625f9ef 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/HomepagePolicyIntegrationTest.java
@@ -27,14 +27,14 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.homepage.settings.HomepageMetricsEnums.HomeButtonPreferenceState;
-import org.chromium.chrome.browser.homepage.settings.HomepageMetricsEnums.HomepageLocationType;
-import org.chromium.chrome.browser.homepage.settings.HomepageSettings;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
 import org.chromium.chrome.browser.settings.SettingsActivity;
+import org.chromium.chrome.browser.settings.homepage.HomepageMetricsEnums.HomeButtonPreferenceState;
+import org.chromium.chrome.browser.settings.homepage.HomepageMetricsEnums.HomepageLocationType;
+import org.chromium.chrome.browser.settings.homepage.HomepageSettings;
 import org.chromium.chrome.browser.toolbar.HomeButton;
 import org.chromium.chrome.browser.toolbar.ToolbarManager;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
index 8fef4631..a52504c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/partnercustomizations/PartnerHomepageIntegrationTest.java
@@ -27,10 +27,10 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.browser.homepage.settings.HomepageEditor;
-import org.chromium.chrome.browser.homepage.settings.HomepageSettings;
 import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
 import org.chromium.chrome.browser.settings.SettingsActivity;
+import org.chromium.chrome.browser.settings.homepage.HomepageEditor;
+import org.chromium.chrome.browser.settings.homepage.HomepageSettings;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentTest.java
similarity index 99%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentTest.java
index ae2a5ed7..616f7e6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import android.support.test.filters.SmallTest;
 import android.view.View;
@@ -21,13 +21,13 @@
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.homepage.HomepageTestRule;
-import org.chromium.chrome.browser.homepage.settings.HomepageMetricsEnums.HomepageLocationType;
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
 import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.TextMessagePreference;
+import org.chromium.chrome.browser.settings.homepage.HomepageMetricsEnums.HomepageLocationType;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentWithEditorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentWithEditorTest.java
similarity index 98%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentWithEditorTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentWithEditorTest.java
index 5879902..a7acb9d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/homepage/settings/HomepageSettingsFragmentWithEditorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/settings/homepage/HomepageSettingsFragmentWithEditorTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.browser.homepage.settings;
+package org.chromium.chrome.browser.settings.homepage;
 
 import android.support.test.filters.SmallTest;
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/HomeButtonTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/HomeButtonTest.java
index fa6e726..500f103 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/HomeButtonTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/HomeButtonTest.java
@@ -28,9 +28,9 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.homepage.HomepageTestRule;
-import org.chromium.chrome.browser.homepage.settings.HomepageSettings;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.settings.SettingsLauncher;
+import org.chromium.chrome.browser.settings.homepage.HomepageSettings;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
diff --git a/chrome/android/third_party/compositor_animator/BUILD.gn b/chrome/android/third_party/compositor_animator/BUILD.gn
index d1b467310..1fe69c9 100644
--- a/chrome/android/third_party/compositor_animator/BUILD.gn
+++ b/chrome/android/third_party/compositor_animator/BUILD.gn
@@ -6,5 +6,11 @@
 import("//build/config/android/rules.gni")
 
 android_library("compositor_animator_java") {
+  # Turbine does not generate bridge methods in the parent class. Thus each
+  # child class generates its own bridge methods and adds a number of methods.
+  # This target would add about 30 methods if it enabled turbine. We are
+  # hoping to turn this back on once r8 can mitigate these extra methods.
+  enable_turbine = false
+
   sources = [ "java/src/org/chromium/chrome/browser/compositor/animation/FloatProperty.java" ]
 }
diff --git a/chrome/app/android/chrome_main_delegate_android.cc b/chrome/app/android/chrome_main_delegate_android.cc
index fe6940b6..7631584 100644
--- a/chrome/app/android/chrome_main_delegate_android.cc
+++ b/chrome/app/android/chrome_main_delegate_android.cc
@@ -87,8 +87,8 @@
   // Also only record the start time the first time round, since this is the
   // start time of the application, and will be same for all requests.
   if (!browser_runner_) {
-    startup_metric_utils::RecordMainEntryPointTime(
-        chrome::android::GetMainEntryPointTimeTicks());
+    startup_metric_utils::RecordApplicationStartTime(
+        chrome::android::GetApplicationStartTime());
     browser_runner_ = content::BrowserMainRunner::Create();
   }
 
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 80de4b7..d6f0e7d 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -490,23 +490,31 @@
 #endif
 
 #if !defined(CHROME_MULTIPLE_DLL_CHILD)
-void RecordMainStartupMetrics(base::TimeTicks exe_entry_point_ticks) {
-  if (!exe_entry_point_ticks.is_null())
-    startup_metric_utils::RecordExeMainEntryPointTicks(exe_entry_point_ticks);
+void RecordMainStartupMetrics(base::TimeTicks application_start_time) {
+  const base::TimeTicks now = base::TimeTicks::Now();
+
+#if defined(OS_WIN)
+  DCHECK(!application_start_time.is_null());
+  startup_metric_utils::RecordApplicationStartTime(application_start_time);
+#elif defined(OS_ANDROID)
+  // On Android the main entry point time is the time when the Java code starts.
+  // This happens before the shared library containing this code is even loaded.
+  // The Java startup code has recorded that time, but the C++ code can't fetch
+  // it from the Java side until it has initialized the JNI. See
+  // ChromeMainDelegateAndroid.
+#else
+  // On other platforms, |application_start_time| == |now| since the application
+  // starts with ChromeMain().
+  startup_metric_utils::RecordApplicationStartTime(now);
+#endif
+
 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
   // Record the startup process creation time on supported platforms.
   startup_metric_utils::RecordStartupProcessCreationTime(
       base::Process::Current().CreationTime());
 #endif
 
-// On Android the main entry point time is the time when the Java code starts.
-// This happens before the shared library containing this code is even loaded.
-// The Java startup code has recorded that time, but the C++ code can't fetch it
-// from the Java side until it has initialized the JNI. See
-// ChromeMainDelegateAndroid.
-#if !defined(OS_ANDROID)
-  startup_metric_utils::RecordMainEntryPointTime(base::TimeTicks::Now());
-#endif
+  startup_metric_utils::RecordChromeMainEntryTime(now);
 }
 #endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
 
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index d3a7a843..a82a850 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -4525,6 +4525,12 @@
   <message name="IDS_EDU_LOGIN_WELCOME_REAUTH_BODY" desc="Text on the reauthentication screen in EDU account addition flow informing user to ask their parent for permission to reauthenticate the account.">
     To sign in again for access to educational resources, ask a parent to give you permission
   </message>
+  <message name="IDS_EDU_LOGIN_PARENTS_LIST_TITLE" desc="Title for the parent sign-in screen asking the user to ask a parent to sign-in to approve EDU account addition.">
+    Parent sign-in
+  </message>
+  <message name="IDS_EDU_LOGIN_PARENTS_LIST_BODY" desc="Text on the parent sign-in screen asking the user to ask a parent to sign-in to approve EDU account addition.">
+    Ask a parent to sign in to grant permission to add a school account
+  </message>
 
   <!-- TPM firmware auto-update notifications -->
   <message name="IDS_TPM_AUTO_UPDATE_PLANNED_NOTIFICATION_TITLE" desc="Title for the notification informing the user that local data will be deleted.">
diff --git a/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_BODY.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_BODY.png.sha1
new file mode 100644
index 0000000..33e63e37
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_BODY.png.sha1
@@ -0,0 +1 @@
+c1c3e7b4e108aaf5e6cbf7881343710e490b8ac9
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_TITLE.png.sha1
new file mode 100644
index 0000000..33e63e37
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_EDU_LOGIN_PARENTS_LIST_TITLE.png.sha1
@@ -0,0 +1 @@
+c1c3e7b4e108aaf5e6cbf7881343710e490b8ac9
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index d59a6297..8075306 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -2729,7 +2729,7 @@
         Some sites use third-party cookies to load their pages. If a site isn't working, you can try allowing cookies.
       </message>
       <message name="IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE" desc="Text shown in the dialog that allows users to control cookie blocking.">
-        Sites can’t use your browsing activity across different sites to personalize ads
+        Sites can't use cookies that track you across the web
       </message>
       <message name="IDS_COOKIE_CONTROLS_TOOLTIP" desc="Tooltip shown on a page action icon that is shown when cookie blocking is enabled.">
         Third-party cookie blocking
diff --git a/chrome/app/generated_resources_grd/IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE.png.sha1
index cedf0d2..1e54feb 100644
--- a/chrome/app/generated_resources_grd/IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_COOKIE_CONTROLS_BLOCKED_MESSAGE.png.sha1
@@ -1 +1 @@
-35cf61169272da7e8e35f0b4cc77bf41082baf48
\ No newline at end of file
+c0b6ced9a266ce54265096caeef818742a2ed5ac
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 9a31a3b..7269580 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -2997,24 +2997,6 @@
   <message name="IDS_SETTINGS_PERSONALIZE_GOOGLE_SERVICES_TITLE" desc="Title of the personalize Google services section. When clicked, takes the user to the Google Activity Controls.">
     Control how your browsing history is used to personalize Search, ads, and more
   </message>
-  <message name="IDS_SETTINGS_USE_HISTORY_TO_PERSONALIZE_GOOGLE_SERVICES_TITLE" desc="Title of the use your history to personalize Google services section. When clicked, takes the user to the Google Activity Controls.">
-    Use your Chrome browsing history to personalize Search, ads and other Google services
-  </message>
-  <message name="IDS_SETTINGS_SWAA_ON" desc="The sWAA state that shows in the use history to personalize google services row">
-    On
-  </message>
-  <message name="IDS_SETTINGS_SWAA_OFF" desc="The sWAA state that shows in the use history to personalize google services row">
-    Off
-  </message>
-  <message name="IDS_SETTINGS_DATA_ENCRYPTED_HINT" desc="Hint that shows when sWAA is off in the use history to personalize google services row because data is encrypted with custom passphrase">
-    Turned off because you encrypted your sync data with a passphrase
-  </message>
-   <message name="IDS_SETTINGS_HISTORY_SYNC_OFF_HINT" desc="Hint that shows when sWAA is off in the use history to personalize google services row because history sync is off">
-    Turned off because you disabled history sync
-  </message>
-   <message name="IDS_SETTINGS_SWAA_OFF_HINT" desc="Hint that shows when sWAA is off in the use history to personalize google services row because history recording is disabled in Web and App Activity" translateable="false">
-    The usage of Chrome history for personalization is turned off in your Activity controls
-   </message>
 
   <if expr="not chromeos">
     <!-- Import Settings Dialog -->
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index afa8676b..dc5ab97 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -222,8 +222,6 @@
     "browsing_data/browsing_data_important_sites_util.h",
     "browsing_data/browsing_data_indexed_db_helper.cc",
     "browsing_data/browsing_data_indexed_db_helper.h",
-    "browsing_data/browsing_data_local_storage_helper.cc",
-    "browsing_data/browsing_data_local_storage_helper.h",
     "browsing_data/browsing_data_media_license_helper.cc",
     "browsing_data/browsing_data_media_license_helper.h",
     "browsing_data/browsing_data_quota_helper.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 433b4da..7d0aafa 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1371,9 +1371,6 @@
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_TwoPanesSurface[] = {
     {"start_surface_variation", "twopanes"}};
 
-const FeatureEntry::FeatureParam kStartSurfaceAndroid_Toolbar[] = {
-    {"start_surface_variation", "toolbar"}};
-
 const FeatureEntry::FeatureParam kStartSurfaceAndroid_TasksOnly[] = {
     {"start_surface_variation", "tasksonly"}};
 
@@ -1385,8 +1382,6 @@
      base::size(kStartSurfaceAndroid_SingleSurface), nullptr},
     {"Two Panes Surface", kStartSurfaceAndroid_TwoPanesSurface,
      base::size(kStartSurfaceAndroid_TwoPanesSurface), nullptr},
-    {"Start Surface Toolbar", kStartSurfaceAndroid_Toolbar,
-     base::size(kStartSurfaceAndroid_Toolbar), nullptr},
     {"Tasks Only", kStartSurfaceAndroid_TasksOnly,
      base::size(kStartSurfaceAndroid_TasksOnly), nullptr},
     {"Omnibox Only", kStartSurfaceAndroid_OmniboxOnly,
@@ -3980,6 +3975,10 @@
     {"remote-copy-receiver", flag_descriptions::kRemoteCopyReceiverName,
      flag_descriptions::kRemoteCopyReceiverDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(kRemoteCopyReceiver)},
+    {"remote-copy-image-notification",
+     flag_descriptions::kRemoteCopyImageNotificationName,
+     flag_descriptions::kRemoteCopyImageNotificationDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(kRemoteCopyImageNotification)},
     {"remote-copy-progress-notification",
      flag_descriptions::kRemoteCopyProgressNotificationName,
      flag_descriptions::kRemoteCopyProgressNotificationDescription, kOsDesktop,
diff --git a/chrome/browser/android/metrics/uma_utils.cc b/chrome/browser/android/metrics/uma_utils.cc
index 767e0894..2583ad0 100644
--- a/chrome/browser/android/metrics/uma_utils.cc
+++ b/chrome/browser/android/metrics/uma_utils.cc
@@ -18,10 +18,10 @@
 namespace chrome {
 namespace android {
 
-base::TimeTicks GetMainEntryPointTimeTicks() {
+base::TimeTicks GetApplicationStartTime() {
   JNIEnv* env = base::android::AttachCurrentThread();
   return base::TimeTicks::FromUptimeMillis(
-      Java_UmaUtils_getMainEntryPointTicks(env));
+      Java_UmaUtils_getApplicationStartTime(env));
 }
 
 static jboolean JNI_UmaUtils_IsClientInMetricsReportingSample(JNIEnv* env) {
diff --git a/chrome/browser/android/metrics/uma_utils.h b/chrome/browser/android/metrics/uma_utils.h
index 7822aea7..2c51140 100644
--- a/chrome/browser/android/metrics/uma_utils.h
+++ b/chrome/browser/android/metrics/uma_utils.h
@@ -12,7 +12,7 @@
 namespace chrome {
 namespace android {
 
-base::TimeTicks GetMainEntryPointTimeTicks();
+base::TimeTicks GetApplicationStartTime();
 
 // Sets whether UMA reporting is enabled. This will call to Java to update
 // the shared preference that is the source of truth for UMA reporting.
diff --git a/chrome/browser/android/preferences/website_preference_bridge.cc b/chrome/browser/android/preferences/website_preference_bridge.cc
index c82ea0c..9b3b618 100644
--- a/chrome/browser/android/preferences/website_preference_bridge.cc
+++ b/chrome/browser/android/preferences/website_preference_bridge.cc
@@ -21,7 +21,6 @@
 #include "base/stl_util.h"
 #include "chrome/android/chrome_jni_headers/WebsitePreferenceBridge_jni.h"
 #include "chrome/browser/android/search_permissions/search_permissions_service.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/engagement/important_sites_util.h"
 #include "chrome/browser/media/android/cdm/media_drm_license_manager.h"
 #include "chrome/browser/notifications/notification_permission_context.h"
@@ -34,6 +33,7 @@
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/common/pref_names.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/browser/uma_util.h"
@@ -787,7 +787,7 @@
     jboolean fetch_important) {
   Profile* profile = ProfileManager::GetActiveUserProfile();
   auto local_storage_helper =
-      base::MakeRefCounted<BrowsingDataLocalStorageHelper>(profile);
+      base::MakeRefCounted<browsing_data::LocalStorageHelper>(profile);
   local_storage_helper->StartFetching(
       base::Bind(&OnLocalStorageModelInfoLoaded, profile, fetch_important,
                  ScopedJavaGlobalRef<jobject>(java_callback)));
@@ -809,7 +809,7 @@
     const JavaParamRef<jobject>& java_callback) {
   Profile* profile = ProfileManager::GetActiveUserProfile();
   auto local_storage_helper =
-      base::MakeRefCounted<BrowsingDataLocalStorageHelper>(profile);
+      base::MakeRefCounted<browsing_data::LocalStorageHelper>(profile);
   auto origin =
       url::Origin::Create(GURL(ConvertJavaStringToUTF8(env, jorigin)));
   local_storage_helper->DeleteOrigin(
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index c7beb54..ce8fa86 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -225,6 +225,7 @@
         <include name="IDR_EDU_LOGIN_EDU_LOGIN_TEMPLATE_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\edu_login\edu_login_template.js" use_base_dir="false" type="BINDATA" compress="gzip" />
         <include name="IDR_EDU_LOGIN_EDU_LOGIN_CSS_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\edu_login\edu_login_css.js" use_base_dir="false" type ="BINDATA" />
         <include name="IDR_EDU_LOGIN_EDU_LOGIN_WELCOME_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\edu_login\edu_login_welcome.js" use_base_dir="false" type ="BINDATA" compress="gzip" preprocess="true" />
+        <include name="IDR_EDU_LOGIN_EDU_LOGIN_PARENTS_JS" file="${root_gen_dir}\chrome\browser\resources\chromeos\edu_login\edu_login_parents.js" use_base_dir="false" type ="BINDATA" compress="gzip" preprocess="true" />
         <include name="IDR_FAMILY_LINK_LOGO_SVG" file="resources\chromeos\family_link_logo.svg" type="BINDATA" compress="gzip" />
 
         <!-- Chrome OS Account Manager welcome screen resources -->
diff --git a/chrome/browser/browsing_data/browsing_data_local_storage_helper.cc b/chrome/browser/browsing_data/browsing_data_local_storage_helper.cc
deleted file mode 100644
index e43cc4b..0000000
--- a/chrome/browser/browsing_data/browsing_data_local_storage_helper.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
-
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/task/post_task.h"
-#include "chrome/browser/browsing_data/browsing_data_helper.h"
-#include "chrome/browser/profiles/profile.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/browser/storage_usage_info.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-#include "url/url_constants.h"
-
-using content::BrowserContext;
-using content::BrowserThread;
-using content::DOMStorageContext;
-
-namespace {
-
-// Only websafe state is considered browsing data.
-bool HasStorageScheme(const GURL& origin_url) {
-  return BrowsingDataHelper::HasWebScheme(origin_url);
-}
-
-void GetUsageInfoCallback(
-    BrowsingDataLocalStorageHelper::FetchCallback callback,
-    const std::vector<content::StorageUsageInfo>& infos) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(!callback.is_null());
-
-  std::list<content::StorageUsageInfo> result;
-  for (const content::StorageUsageInfo& info : infos) {
-    if (!HasStorageScheme(info.origin.GetURL()))
-      continue;
-    result.push_back(info);
-  }
-
-  base::PostTask(FROM_HERE, {BrowserThread::UI},
-                 base::BindOnce(std::move(callback), result));
-}
-
-}  // namespace
-
-BrowsingDataLocalStorageHelper::BrowsingDataLocalStorageHelper(Profile* profile)
-    : dom_storage_context_(BrowserContext::GetDefaultStoragePartition(profile)
-                               ->GetDOMStorageContext()) {
-  DCHECK(dom_storage_context_);
-}
-
-BrowsingDataLocalStorageHelper::~BrowsingDataLocalStorageHelper() {
-}
-
-void BrowsingDataLocalStorageHelper::StartFetching(FetchCallback callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(!callback.is_null());
-  dom_storage_context_->GetLocalStorageUsage(
-      base::BindOnce(&GetUsageInfoCallback, std::move(callback)));
-}
-
-void BrowsingDataLocalStorageHelper::DeleteOrigin(const url::Origin& origin,
-                                                  base::OnceClosure callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  dom_storage_context_->DeleteLocalStorage(origin, std::move(callback));
-}
-
-//---------------------------------------------------------
-
-CannedBrowsingDataLocalStorageHelper::CannedBrowsingDataLocalStorageHelper(
-    Profile* profile)
-    : BrowsingDataLocalStorageHelper(profile) {
-}
-
-void CannedBrowsingDataLocalStorageHelper::Add(const url::Origin& origin) {
-  if (!HasStorageScheme(origin.GetURL()))
-    return;
-  pending_origins_.insert(origin);
-}
-
-void CannedBrowsingDataLocalStorageHelper::Reset() {
-  pending_origins_.clear();
-}
-
-bool CannedBrowsingDataLocalStorageHelper::empty() const {
-  return pending_origins_.empty();
-}
-
-size_t CannedBrowsingDataLocalStorageHelper::GetCount() const {
-  return pending_origins_.size();
-}
-
-const std::set<url::Origin>& CannedBrowsingDataLocalStorageHelper::GetOrigins()
-    const {
-  return pending_origins_;
-}
-
-void CannedBrowsingDataLocalStorageHelper::StartFetching(
-    FetchCallback callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(!callback.is_null());
-
-  std::list<content::StorageUsageInfo> result;
-  for (const auto& origin : pending_origins_)
-    result.emplace_back(origin, 0, base::Time());
-
-  base::PostTask(FROM_HERE, {BrowserThread::UI},
-                 base::BindOnce(std::move(callback), result));
-}
-
-void CannedBrowsingDataLocalStorageHelper::DeleteOrigin(
-    const url::Origin& origin,
-    base::OnceClosure callback) {
-  pending_origins_.erase(origin);
-  BrowsingDataLocalStorageHelper::DeleteOrigin(origin, std::move(callback));
-}
-
-CannedBrowsingDataLocalStorageHelper::~CannedBrowsingDataLocalStorageHelper() {}
diff --git a/chrome/browser/browsing_data/browsing_data_local_storage_helper_browsertest.cc b/chrome/browser/browsing_data/browsing_data_local_storage_helper_browsertest.cc
index e81fc275..5b04922e 100644
--- a/chrome/browser/browsing_data/browsing_data_local_storage_helper_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_local_storage_helper_browsertest.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/browsing_data/browsing_data_local_storage_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 
 #include <stddef.h>
 
@@ -74,12 +74,12 @@
   }
 };
 
-// This class is notified by BrowsingDataLocalStorageHelper on the UI thread
+// This class is notified by browsing_data::LocalStorageHelper on the UI thread
 // once it finishes fetching the local storage data.
 class StopTestOnCallback {
  public:
   explicit StopTestOnCallback(
-      BrowsingDataLocalStorageHelper* local_storage_helper)
+      browsing_data::LocalStorageHelper* local_storage_helper)
       : local_storage_helper_(local_storage_helper) {
     DCHECK(local_storage_helper_);
   }
@@ -107,12 +107,12 @@
   }
 
  private:
-  BrowsingDataLocalStorageHelper* local_storage_helper_;
+  browsing_data::LocalStorageHelper* local_storage_helper_;
 };
 
 IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest, CallbackCompletes) {
-  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper(
-      new BrowsingDataLocalStorageHelper(browser()->profile()));
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper(
+      new browsing_data::LocalStorageHelper(browser()->profile()));
   CreateLocalStorageFilesForTest();
   StopTestOnCallback stop_test_on_callback(local_storage_helper.get());
   local_storage_helper->StartFetching(base::Bind(
@@ -129,8 +129,8 @@
 #endif
 IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest,
                        MAYBE_DeleteSingleFile) {
-  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper(
-      new BrowsingDataLocalStorageHelper(browser()->profile()));
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper(
+      new browsing_data::LocalStorageHelper(browser()->profile()));
   CreateLocalStorageFilesForTest();
   base::RunLoop run_loop;
   local_storage_helper->DeleteOrigin(
@@ -158,8 +158,8 @@
   const GURL origin1("http://host1:1/");
   const GURL origin2("http://host2:1/");
 
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> helper(
-      new CannedBrowsingDataLocalStorageHelper(browser()->profile()));
+  scoped_refptr<browsing_data::CannedLocalStorageHelper> helper(
+      new browsing_data::CannedLocalStorageHelper(browser()->profile()));
   helper->Add(url::Origin::Create(origin1));
   helper->Add(url::Origin::Create(origin2));
 
@@ -180,8 +180,8 @@
 IN_PROC_BROWSER_TEST_F(BrowsingDataLocalStorageHelperTest, CannedUnique) {
   const GURL origin("http://host1:1/");
 
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> helper(
-      new CannedBrowsingDataLocalStorageHelper(browser()->profile()));
+  scoped_refptr<browsing_data::CannedLocalStorageHelper> helper(
+      new browsing_data::CannedLocalStorageHelper(browser()->profile()));
   helper->Add(url::Origin::Create(origin));
   helper->Add(url::Origin::Create(origin));
 
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index f2b831c..9bca9c55 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -530,7 +530,7 @@
     auto container = std::make_unique<LocalDataContainer>(
         new BrowsingDataCookieHelper(storage_partition),
         new BrowsingDataDatabaseHelper(profile),
-        new BrowsingDataLocalStorageHelper(profile),
+        new browsing_data::LocalStorageHelper(profile),
         /*session_storage_helper=*/nullptr,
         new BrowsingDataAppCacheHelper(storage_partition->GetAppCacheService()),
         new BrowsingDataIndexedDBHelper(storage_partition),
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index eab62d5..2656597 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -28,7 +28,6 @@
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_flash_lso_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_quota_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_service_worker_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_shared_worker_helper.h"
@@ -36,6 +35,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/storage_partition.h"
@@ -1960,7 +1960,7 @@
   auto container = std::make_unique<LocalDataContainer>(
       new BrowsingDataCookieHelper(storage_partition),
       new BrowsingDataDatabaseHelper(profile),
-      new BrowsingDataLocalStorageHelper(profile),
+      new browsing_data::LocalStorageHelper(profile),
       /*session_storage_helper=*/nullptr,
       new BrowsingDataAppCacheHelper(storage_partition->GetAppCacheService()),
       new BrowsingDataIndexedDBHelper(storage_partition),
diff --git a/chrome/browser/browsing_data/local_data_container.cc b/chrome/browser/browsing_data/local_data_container.cc
index 6954591..b0113be 100644
--- a/chrome/browser/browsing_data/local_data_container.cc
+++ b/chrome/browser/browsing_data/local_data_container.cc
@@ -18,8 +18,8 @@
 LocalDataContainer::LocalDataContainer(
     scoped_refptr<BrowsingDataCookieHelper> cookie_helper,
     scoped_refptr<BrowsingDataDatabaseHelper> database_helper,
-    scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper,
-    scoped_refptr<BrowsingDataLocalStorageHelper> session_storage_helper,
+    scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper,
+    scoped_refptr<browsing_data::LocalStorageHelper> session_storage_helper,
     scoped_refptr<BrowsingDataAppCacheHelper> appcache_helper,
     scoped_refptr<BrowsingDataIndexedDBHelper> indexed_db_helper,
     scoped_refptr<BrowsingDataFileSystemHelper> file_system_helper,
diff --git a/chrome/browser/browsing_data/local_data_container.h b/chrome/browser/browsing_data/local_data_container.h
index 7a722c8..d727893 100644
--- a/chrome/browser/browsing_data/local_data_container.h
+++ b/chrome/browser/browsing_data/local_data_container.h
@@ -20,11 +20,11 @@
 #include "chrome/browser/browsing_data/browsing_data_database_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_media_license_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_quota_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_service_worker_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_shared_worker_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 
 class BrowsingDataFlashLSOHelper;
 class CookiesTreeModel;
@@ -67,8 +67,8 @@
   LocalDataContainer(
       scoped_refptr<BrowsingDataCookieHelper> cookie_helper,
       scoped_refptr<BrowsingDataDatabaseHelper> database_helper,
-      scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper,
-      scoped_refptr<BrowsingDataLocalStorageHelper> session_storage_helper,
+      scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper,
+      scoped_refptr<browsing_data::LocalStorageHelper> session_storage_helper,
       scoped_refptr<BrowsingDataAppCacheHelper> appcache_helper,
       scoped_refptr<BrowsingDataIndexedDBHelper> indexed_db_helper,
       scoped_refptr<BrowsingDataFileSystemHelper> file_system_helper,
@@ -126,8 +126,8 @@
   scoped_refptr<BrowsingDataAppCacheHelper> appcache_helper_;
   scoped_refptr<BrowsingDataCookieHelper> cookie_helper_;
   scoped_refptr<BrowsingDataDatabaseHelper> database_helper_;
-  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper_;
-  scoped_refptr<BrowsingDataLocalStorageHelper> session_storage_helper_;
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper_;
+  scoped_refptr<browsing_data::LocalStorageHelper> session_storage_helper_;
   scoped_refptr<BrowsingDataIndexedDBHelper> indexed_db_helper_;
   scoped_refptr<BrowsingDataFileSystemHelper> file_system_helper_;
   scoped_refptr<BrowsingDataQuotaHelper> quota_helper_;
diff --git a/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.cc b/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.cc
index 3fa6511..14fc9479 100644
--- a/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.cc
+++ b/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.cc
@@ -13,12 +13,11 @@
 #include "url/gurl.h"
 
 MockBrowsingDataLocalStorageHelper::MockBrowsingDataLocalStorageHelper(
-    Profile* profile)
-    : BrowsingDataLocalStorageHelper(profile) {
-}
+    content::BrowserContext* context)
+    : browsing_data::LocalStorageHelper(context) {}
 
-MockBrowsingDataLocalStorageHelper::~MockBrowsingDataLocalStorageHelper() {
-}
+MockBrowsingDataLocalStorageHelper::~MockBrowsingDataLocalStorageHelper() =
+    default;
 
 void MockBrowsingDataLocalStorageHelper::StartFetching(FetchCallback callback) {
   ASSERT_FALSE(callback.is_null());
diff --git a/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.h b/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.h
index c01d22b..c5189fa 100644
--- a/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.h
+++ b/chrome/browser/browsing_data/mock_browsing_data_local_storage_helper.h
@@ -10,17 +10,17 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 
-// Mock for BrowsingDataLocalStorageHelper.
+// Mock for browsing_data::LocalStorageHelper.
 // Use AddLocalStorageSamples() or add directly to response_ list, then
 // call Notify().
 class MockBrowsingDataLocalStorageHelper
-    : public BrowsingDataLocalStorageHelper {
+    : public browsing_data::LocalStorageHelper {
  public:
-  explicit MockBrowsingDataLocalStorageHelper(Profile* profile);
+  explicit MockBrowsingDataLocalStorageHelper(content::BrowserContext* context);
 
-  // BrowsingDataLocalStorageHelper implementation.
+  // browsing_data::LocalStorageHelper implementation.
   void StartFetching(FetchCallback callback) override;
   void DeleteOrigin(const url::Origin& origin,
                     base::OnceClosure callback) override;
diff --git a/chrome/browser/browsing_data/site_data_size_collector.cc b/chrome/browser/browsing_data/site_data_size_collector.cc
index 5cf3f93..e6f5290 100644
--- a/chrome/browser/browsing_data/site_data_size_collector.cc
+++ b/chrome/browser/browsing_data/site_data_size_collector.cc
@@ -30,7 +30,7 @@
     const base::FilePath& default_storage_partition_path,
     BrowsingDataCookieHelper* cookie_helper,
     BrowsingDataDatabaseHelper* database_helper,
-    BrowsingDataLocalStorageHelper* local_storage_helper,
+    browsing_data::LocalStorageHelper* local_storage_helper,
     BrowsingDataAppCacheHelper* appcache_helper,
     BrowsingDataIndexedDBHelper* indexed_db_helper,
     BrowsingDataFileSystemHelper* file_system_helper,
diff --git a/chrome/browser/browsing_data/site_data_size_collector.h b/chrome/browser/browsing_data/site_data_size_collector.h
index fa2af8ed..2a39767 100644
--- a/chrome/browser/browsing_data/site_data_size_collector.h
+++ b/chrome/browser/browsing_data/site_data_size_collector.h
@@ -17,9 +17,9 @@
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_flash_lso_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_service_worker_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "content/public/browser/storage_partition.h"
 
 class SiteDataSizeCollector {
@@ -37,7 +37,7 @@
   SiteDataSizeCollector(const base::FilePath& default_storage_partition_path,
                         BrowsingDataCookieHelper* cookie_helper,
                         BrowsingDataDatabaseHelper* database_helper,
-                        BrowsingDataLocalStorageHelper* local_storage_helper,
+                        browsing_data::LocalStorageHelper* local_storage_helper,
                         BrowsingDataAppCacheHelper* appcache_helper,
                         BrowsingDataIndexedDBHelper* indexed_db_helper,
                         BrowsingDataFileSystemHelper* file_system_helper,
@@ -80,7 +80,7 @@
   scoped_refptr<BrowsingDataAppCacheHelper> appcache_helper_;
   scoped_refptr<BrowsingDataCookieHelper> cookie_helper_;
   scoped_refptr<BrowsingDataDatabaseHelper> database_helper_;
-  scoped_refptr<BrowsingDataLocalStorageHelper> local_storage_helper_;
+  scoped_refptr<browsing_data::LocalStorageHelper> local_storage_helper_;
   scoped_refptr<BrowsingDataIndexedDBHelper> indexed_db_helper_;
   scoped_refptr<BrowsingDataFileSystemHelper> file_system_helper_;
   scoped_refptr<BrowsingDataServiceWorkerHelper> service_worker_helper_;
diff --git a/chrome/browser/content_settings/local_shared_objects_container.cc b/chrome/browser/content_settings/local_shared_objects_container.cc
index 5988a53a..dec265b 100644
--- a/chrome/browser/content_settings/local_shared_objects_container.cc
+++ b/chrome/browser/content_settings/local_shared_objects_container.cc
@@ -16,12 +16,12 @@
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_flash_lso_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_service_worker_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_shared_worker_helper.h"
 #include "chrome/browser/browsing_data/canonical_cookie_hash.h"
 #include "chrome/browser/browsing_data/cookies_tree_model.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/common/url_constants.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
@@ -52,7 +52,7 @@
               ->GetFileSystemContext())),
       indexed_dbs_(new CannedBrowsingDataIndexedDBHelper(
           content::BrowserContext::GetDefaultStoragePartition(profile))),
-      local_storages_(new CannedBrowsingDataLocalStorageHelper(profile)),
+      local_storages_(new browsing_data::CannedLocalStorageHelper(profile)),
       service_workers_(new CannedBrowsingDataServiceWorkerHelper(
           content::BrowserContext::GetDefaultStoragePartition(profile)
               ->GetServiceWorkerContext())),
@@ -62,10 +62,9 @@
       cache_storages_(new CannedBrowsingDataCacheStorageHelper(
           content::BrowserContext::GetDefaultStoragePartition(profile)
               ->GetCacheStorageContext())),
-      session_storages_(new CannedBrowsingDataLocalStorageHelper(profile)) {}
+      session_storages_(new browsing_data::CannedLocalStorageHelper(profile)) {}
 
-LocalSharedObjectsContainer::~LocalSharedObjectsContainer() {
-}
+LocalSharedObjectsContainer::~LocalSharedObjectsContainer() = default;
 
 size_t LocalSharedObjectsContainer::GetObjectCount() const {
   size_t count = 0;
diff --git a/chrome/browser/content_settings/local_shared_objects_container.h b/chrome/browser/content_settings/local_shared_objects_container.h
index 4c7f957..9a801cbe 100644
--- a/chrome/browser/content_settings/local_shared_objects_container.h
+++ b/chrome/browser/content_settings/local_shared_objects_container.h
@@ -17,7 +17,6 @@
 class CannedBrowsingDataDatabaseHelper;
 class CannedBrowsingDataFileSystemHelper;
 class CannedBrowsingDataIndexedDBHelper;
-class CannedBrowsingDataLocalStorageHelper;
 class CannedBrowsingDataServiceWorkerHelper;
 class CannedBrowsingDataSharedWorkerHelper;
 class CannedBrowsingDataCacheStorageHelper;
@@ -25,6 +24,10 @@
 class GURL;
 class Profile;
 
+namespace browsing_data {
+class CannedLocalStorageHelper;
+}
+
 class LocalSharedObjectsContainer {
  public:
   explicit LocalSharedObjectsContainer(Profile* profile);
@@ -59,7 +62,7 @@
   CannedBrowsingDataIndexedDBHelper* indexed_dbs() const {
     return indexed_dbs_.get();
   }
-  CannedBrowsingDataLocalStorageHelper* local_storages() const {
+  browsing_data::CannedLocalStorageHelper* local_storages() const {
     return local_storages_.get();
   }
   CannedBrowsingDataServiceWorkerHelper* service_workers() const {
@@ -71,7 +74,7 @@
   CannedBrowsingDataCacheStorageHelper* cache_storages() const {
     return cache_storages_.get();
   }
-  CannedBrowsingDataLocalStorageHelper* session_storages() const {
+  browsing_data::CannedLocalStorageHelper* session_storages() const {
     return session_storages_.get();
   }
 
@@ -81,11 +84,11 @@
   scoped_refptr<CannedBrowsingDataDatabaseHelper> databases_;
   scoped_refptr<CannedBrowsingDataFileSystemHelper> file_systems_;
   scoped_refptr<CannedBrowsingDataIndexedDBHelper> indexed_dbs_;
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> local_storages_;
+  scoped_refptr<browsing_data::CannedLocalStorageHelper> local_storages_;
   scoped_refptr<CannedBrowsingDataServiceWorkerHelper> service_workers_;
   scoped_refptr<CannedBrowsingDataSharedWorkerHelper> shared_workers_;
   scoped_refptr<CannedBrowsingDataCacheStorageHelper> cache_storages_;
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> session_storages_;
+  scoped_refptr<browsing_data::CannedLocalStorageHelper> session_storages_;
 
   DISALLOW_COPY_AND_ASSIGN(LocalSharedObjectsContainer);
 };
diff --git a/chrome/browser/content_settings/tab_specific_content_settings.cc b/chrome/browser/content_settings/tab_specific_content_settings.cc
index bad544c..d6735849 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings.cc
+++ b/chrome/browser/content_settings/tab_specific_content_settings.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/browsing_data/browsing_data_database_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/cookies_tree_model.h"
 #include "chrome/browser/content_settings/chrome_content_settings_utils.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -30,6 +29,7 @@
 #include "chrome/common/pref_names.h"
 #include "chrome/common/render_messages.h"
 #include "chrome/common/renderer_configuration.mojom.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/content_settings/core/browser/content_settings_details.h"
 #include "components/content_settings/core/browser/content_settings_info.h"
 #include "components/content_settings/core/browser/content_settings_registry.h"
@@ -348,7 +348,7 @@
   LocalSharedObjectsContainer& container = blocked_by_policy
                                                ? blocked_local_shared_objects_
                                                : allowed_local_shared_objects_;
-  CannedBrowsingDataLocalStorageHelper* helper =
+  browsing_data::CannedLocalStorageHelper* helper =
       local ? container.local_storages() : container.session_storages();
   helper->Add(url::Origin::Create(url));
 
diff --git a/chrome/browser/dom_distiller/tab_utils.cc b/chrome/browser/dom_distiller/tab_utils.cc
index 363029c3..1f3e6c2 100644
--- a/chrome/browser/dom_distiller/tab_utils.cc
+++ b/chrome/browser/dom_distiller/tab_utils.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "components/dom_distiller/content/browser/distiller_page_web_contents.h"
+#include "components/dom_distiller/content/browser/uma_helper.h"
 #include "components/dom_distiller/core/distiller_page.h"
 #include "components/dom_distiller/core/dom_distiller_service.h"
 #include "components/dom_distiller/core/task_tracker.h"
@@ -173,6 +174,8 @@
       new SourcePageHandleWebContents(old_web_contents_owned.release(), true));
 
   MaybeStartDistillation(std::move(source_page_handle));
+
+  dom_distiller::UMAHelper::LogTimeOnDistillablePage(old_web_contents);
 }
 
 void DistillCurrentPage(content::WebContents* source_web_contents) {
diff --git a/chrome/browser/dom_distiller/tab_utils.h b/chrome/browser/dom_distiller/tab_utils.h
index 7200d8a..09d470e6 100644
--- a/chrome/browser/dom_distiller/tab_utils.h
+++ b/chrome/browser/dom_distiller/tab_utils.h
@@ -15,7 +15,7 @@
 void DistillCurrentPageAndView(content::WebContents* old_web_contents);
 
 // Starts distillation in the |source_web_contents|. The viewer needs to be
-// created separatly.
+// created separately.
 void DistillCurrentPage(content::WebContents* source_web_contents);
 
 // Starts distillation in the |source_web_contents| while navigating the
diff --git a/chrome/browser/dom_distiller/tab_utils_browsertest.cc b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
index a1f6794..7109f9f6 100644
--- a/chrome/browser/dom_distiller/tab_utils_browsertest.cc
+++ b/chrome/browser/dom_distiller/tab_utils_browsertest.cc
@@ -7,6 +7,7 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
 #include "chrome/browser/dom_distiller/tab_utils.h"
@@ -43,6 +44,10 @@
 #else   // Desktop. This test is in chrome/ and is not run on iOS.
 const char* kExpectedArticleTitle = "Test Page Title - Reader Mode";
 #endif  // defined(OS_ANDROID)
+const char* kDistillablePageHistogram =
+    "DomDistiller.Time.ActivelyViewingArticleBeforeDistilling";
+const char* kDistilledPageHistogram =
+    "DomDistiller.Time.ActivelyViewingReaderModePage";
 
 std::unique_ptr<content::WebContents> NewContentsWithSameParamsAs(
     content::WebContents* source_web_contents) {
@@ -177,6 +182,37 @@
   EXPECT_EQ(kExpectedArticleTitle, GetPageTitle(after_web_contents));
 }
 
+// TODO(1061928): Make this test more robust by using a TestMockTimeTaskRunner
+// and a test TickClock. This would require having UMAHelper be an object
+// so that it can hold a TickClock reference.
+IN_PROC_BROWSER_TEST_F(DomDistillerTabUtilsBrowserTest, UMATimesAreLogged) {
+  base::HistogramTester histogram_tester;
+
+  content::WebContents* initial_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // This blocks until the navigation has completely finished.
+  ui_test_utils::NavigateToURL(browser(), article_url());
+
+  // No UMA logged for distillable or distilled yet.
+  histogram_tester.ExpectTotalCount(kDistillablePageHistogram, 0);
+  histogram_tester.ExpectTotalCount(kDistilledPageHistogram, 0);
+
+  DistillCurrentPageAndView(initial_web_contents);
+
+  // UMA should now exist for the distillable page because we distilled it.
+  histogram_tester.ExpectTotalCount(kDistillablePageHistogram, 1);
+
+  // Distilled page UMA isn't logged until we leave that page.
+  histogram_tester.ExpectTotalCount(kDistilledPageHistogram, 0);
+
+  // Go back to the article, check UMA exists for distilled page now.
+  ui_test_utils::NavigateToURL(browser(), article_url());
+  histogram_tester.ExpectTotalCount(kDistilledPageHistogram, 1);
+  // However, there should not be a second distillable histogram.
+  histogram_tester.ExpectTotalCount(kDistillablePageHistogram, 1);
+}
+
 IN_PROC_BROWSER_TEST_F(DomDistillerTabUtilsBrowserTest,
                        DistillAndViewCreatesNewWebContentsAndPreservesOld) {
   content::WebContents* source_web_contents =
diff --git a/chrome/browser/downgrade/user_data_downgrade_unittest.cc b/chrome/browser/downgrade/user_data_downgrade_unittest.cc
index ccaf6db..de0225e 100644
--- a/chrome/browser/downgrade/user_data_downgrade_unittest.cc
+++ b/chrome/browser/downgrade/user_data_downgrade_unittest.cc
@@ -139,11 +139,12 @@
   base::File::Info snapshot_info;
   ASSERT_TRUE(base::GetFileInfo(snapshot_dir, &snapshot_info));
 
-  // Test that data is deleted only if it was created after the deletion time
-  // range start.
-  RemoveDataForProfile(base::Time::Now(), profile_path_default,
+  // Nothing should be deleted from |profile_path_default| since delete_begin
+  // is after the snapshot has been created.
+  RemoveDataForProfile(base::Time::Max(), profile_path_default,
                        ChromeBrowsingDataRemoverDelegate::DATA_TYPE_BOOKMARKS);
-  RemoveDataForProfile(snapshot_info.creation_time, profile_path_1,
+  // Only the bookmarks should be deleted.
+  RemoveDataForProfile(base::Time::Min(), profile_path_1,
                        ChromeBrowsingDataRemoverDelegate::DATA_TYPE_BOOKMARKS);
   EXPECT_TRUE(base::PathExists(
       snapshot_profile_path_default.Append(chrome::kPreferencesFilename)));
@@ -171,8 +172,7 @@
       ChromeBrowsingDataRemoverDelegate::DATA_TYPE_FORM_DATA;
 
   // Delete some data from default profile.
-  RemoveDataForProfile(snapshot_info.creation_time, profile_path_default,
-                       remove_mask);
+  RemoveDataForProfile(base::Time::Min(), profile_path_default, remove_mask);
   for (const auto& item : profile_items) {
     EXPECT_EQ(
         (item.data_types & remove_mask) == 0,
diff --git a/chrome/browser/downgrade/user_data_snapshot_browsertest.cc b/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
index 74177a28..d5270b7 100644
--- a/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
+++ b/chrome/browser/downgrade/user_data_snapshot_browsertest.cc
@@ -394,15 +394,16 @@
         browser()->profile()->GetPrefs()->GetInteger(prefs::kRestoreOnStartup),
         1);
     auto* tab_strip = browser()->tab_strip_model();
-    // There are 2 tabs that need to be preserved, however in the test, there
-    // will be another about:blank tab.
-    if (IsInitialVersion()) {
-      ASSERT_EQ(tab_strip->count(), 2);
-    } else {
-      ASSERT_EQ(tab_strip->count(), 3);
+    // There are 2 tabs that need to be preserved. There might be a 3rd tab,
+    // about:blank that is opened by the test itself. That 3rd tab may or may
+    // not have been opened at this time.
+    ASSERT_GE(tab_strip->count(), 2);
+    ASSERT_LE(tab_strip->count(), 3);
+    if (tab_strip->count() == 3) {
       content::WaitForLoadStop(tab_strip->GetWebContentsAt(2));
       EXPECT_EQ(tab_strip->GetWebContentsAt(2)->GetURL(), GURL("about:blank"));
     }
+
     // embedded_test_server() might return a different hostname.
     content::WaitForLoadStop(tab_strip->GetWebContentsAt(0));
     EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetURL().path(), "/title1.html");
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
index 9479c4e..64f0eafb 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.cc
@@ -7,15 +7,16 @@
 #include <stddef.h>
 
 #include <algorithm>
+#include <map>
 #include <memory>
 
-#include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "base/time/time.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h"
@@ -105,7 +106,7 @@
   // canonicalized credential corresponds to.
   size_t already_processed_ = 0;
   size_t remaining_in_queue_ = 0;
-  base::flat_map<CanonicalizedCredential, size_t> counts_;
+  std::map<CanonicalizedCredential, size_t> counts_;
 
   base::WeakPtrFactory<PasswordCheckProgress> weak_ptr_factory_{this};
 };
@@ -466,6 +467,16 @@
     profile_->GetPrefs()->SetDouble(
         password_manager::prefs::kLastTimePasswordCheckCompleted,
         base::Time::Now().ToDoubleT());
+
+    // In case the check run to completion delay the last Check Status update by
+    // a second. This avoids flickering of the UI if the full check ran from
+    // start to finish almost immediately.
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&PasswordCheckDelegate::NotifyPasswordCheckStatusChanged,
+                       weak_ptr_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(1));
+    return;
   }
 
   // NotifyPasswordCheckStatusChanged() invokes GetPasswordCheckStatus()
@@ -502,9 +513,12 @@
   if (password_check_progress_)
     password_check_progress_->OnProcessed(credential);
 
-  // Trigger an update of the check status, considering that the progress has
-  // changed.
-  NotifyPasswordCheckStatusChanged();
+  // While the check is still running trigger an update of the check status,
+  // considering that the progress has changed.
+  if (bulk_leak_check_service_adapter_.GetBulkLeakCheckState() ==
+      BulkLeakCheckService::State::kRunning) {
+    NotifyPasswordCheckStatusChanged();
+  }
 }
 
 const CredentialWithPassword*
diff --git a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
index 45820b20..4b14e0d 100644
--- a/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/password_check_delegate.h
@@ -169,6 +169,8 @@
               int,
               password_manager::PasswordCredentialLess>
       compromised_credential_id_generator_;
+
+  base::WeakPtrFactory<PasswordCheckDelegate> weak_ptr_factory_{this};
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
index cec4ffd..c5a36f8 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.cc
@@ -220,6 +220,17 @@
       GetDelegate(browser_context())->IsOptedInForAccountStorage())));
 }
 
+// PasswordsPrivateOptInForAccountStorageFunction
+ResponseAction PasswordsPrivateOptInForAccountStorageFunction::Run() {
+  auto parameters =
+      api::passwords_private::OptInForAccountStorage::Params::Create(*args_);
+  EXTENSION_FUNCTION_VALIDATE(parameters.get());
+
+  GetDelegate(browser_context())
+      ->SetAccountStorageOptIn(parameters->opt_in, GetSenderWebContents());
+  return RespondNow(NoArguments());
+}
+
 // PasswordsPrivateGetCompromisedCredentialsFunction:
 PasswordsPrivateGetCompromisedCredentialsFunction::
     ~PasswordsPrivateGetCompromisedCredentialsFunction() = default;
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_api.h b/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
index f143cac..48e1bb6 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_api.h
@@ -195,6 +195,19 @@
   ResponseAction Run() override;
 };
 
+class PasswordsPrivateOptInForAccountStorageFunction
+    : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("passwordsPrivate.optInForAccountStorage",
+                             PASSWORDSPRIVATE_OPTINFORACCOUNTSTORAGE)
+
+ protected:
+  ~PasswordsPrivateOptInForAccountStorageFunction() override = default;
+
+  // ExtensionFunction overrides.
+  ResponseAction Run() override;
+};
+
 class PasswordsPrivateGetCompromisedCredentialsFunction
     : public ExtensionFunction {
  public:
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
index 58e1819..04f9b2f1 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_apitest.cc
@@ -92,8 +92,12 @@
     s_test_delegate_->SetStartPasswordCheckReturnSuccess(result);
   }
 
+  bool IsOptedInForAccountStorage() {
+    return s_test_delegate_->IsOptedInForAccountStorage();
+  }
+
   void SetOptedInForAccountStorage(bool opted_in) {
-    s_test_delegate_->SetOptedInForAccountStorage(opted_in);
+    s_test_delegate_->SetAccountStorageOptIn(opted_in, nullptr);
   }
 
   void ResetPlaintextPassword() { s_test_delegate_->ResetPlaintextPassword(); }
@@ -204,6 +208,16 @@
       << message_;
 }
 
+IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, OptInForAccountStorage) {
+  SetOptedInForAccountStorage(false);
+  EXPECT_TRUE(RunPasswordsSubtest("optInForAccountStorage")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, OptOutForAccountStorage) {
+  SetOptedInForAccountStorage(true);
+  EXPECT_TRUE(RunPasswordsSubtest("optOutForAccountStorage")) << message_;
+}
+
 IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
                        RemoveCompromisedCredentialFails) {
   EXPECT_TRUE(RunPasswordsSubtest("removeCompromisedCredentialFails"))
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
index 510918e5..ca14d66 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h
@@ -111,6 +111,12 @@
   // local/profile storage).
   virtual bool IsOptedInForAccountStorage() = 0;
 
+  // Sets whether the user is opted in to use the Google account storage for
+  // passwords. If |opt_in| is true and the user is not currently opted in,
+  // will trigger a reauth flow.
+  virtual void SetAccountStorageOptIn(bool opt_in,
+                                      content::WebContents* web_contents) = 0;
+
   // Obtains information about compromised credentials. This includes the last
   // time a check was run, as well as all compromised credentials that are
   // present in the password store.
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
index 1e78fd8e..f254b749f 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.cc
@@ -14,6 +14,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h"
+#include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
@@ -28,6 +29,7 @@
 #include "components/password_manager/core/browser/ui/plaintext_reason.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -137,6 +139,9 @@
       password_access_authenticator_(
           base::BindRepeating(&PasswordsPrivateDelegateImpl::OsReauthCall,
                               base::Unretained(this))),
+      account_storage_opt_in_reauthenticator_(
+          base::BindRepeating(&PasswordsPrivateDelegateImpl::InvokeGoogleReauth,
+                              base::Unretained(this))),
       password_account_storage_opt_in_watcher_(
           std::make_unique<
               password_manager::PasswordAccountStorageOptInWatcher>(
@@ -309,6 +314,21 @@
 #endif
 }
 
+void PasswordsPrivateDelegateImpl::InvokeGoogleReauth(
+    content::WebContents* web_contents,
+    PasswordsPrivateDelegateImpl::GoogleReauthCallback callback) {
+  if (auto* client =
+          ChromePasswordManagerClient::FromWebContents(web_contents)) {
+    client->TriggerReauthForAccount(
+        IdentityManagerFactory::GetForProfile(profile_)->GetPrimaryAccountId(
+            signin::ConsentLevel::kNotRequired),
+        std::move(callback));
+    return;
+  }
+  std::move(callback).Run(
+      password_manager::PasswordManagerClient::ReauthSucceeded(false));
+}
+
 Profile* PasswordsPrivateDelegateImpl::GetProfile() {
   return profile_;
 }
@@ -418,6 +438,33 @@
       profile_->GetPrefs(), ProfileSyncServiceFactory::GetForProfile(profile_));
 }
 
+void PasswordsPrivateDelegateImpl::SetAccountStorageOptIn(
+    bool opt_in,
+    content::WebContents* web_contents) {
+  if (opt_in == IsOptedInForAccountStorage())
+    return;
+  if (!opt_in) {
+    password_manager_util::SetAccountStorageOptIn(
+        profile_->GetPrefs(),
+        ProfileSyncServiceFactory::GetForProfile(profile_), false);
+    return;
+  }
+  account_storage_opt_in_reauthenticator_.Run(
+      web_contents,
+      base::BindOnce(
+          &PasswordsPrivateDelegateImpl::SetAccountStorageOptInCallback,
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PasswordsPrivateDelegateImpl::SetAccountStorageOptInCallback(
+    password_manager::PasswordManagerClient::ReauthSucceeded reauth_succeeded) {
+  if (reauth_succeeded) {
+    password_manager_util::SetAccountStorageOptIn(
+        profile_->GetPrefs(),
+        ProfileSyncServiceFactory::GetForProfile(profile_), true);
+  }
+}
+
 std::vector<api::passwords_private::CompromisedCredential>
 PasswordsPrivateDelegateImpl::GetCompromisedCredentials() {
   return password_check_delegate_.GetCompromisedCredentials();
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
index b437b46..dd1cdf25 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h
@@ -13,6 +13,7 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/strings/string16.h"
 #include "build/build_config.h"
@@ -26,6 +27,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/password_manager/core/browser/password_access_authenticator.h"
 #include "components/password_manager/core/browser/password_account_storage_opt_in_watcher.h"
+#include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/reauth_purpose.h"
 #include "components/password_manager/core/browser/ui/export_progress_status.h"
 #include "extensions/browser/extension_function.h"
@@ -66,6 +68,8 @@
   api::passwords_private::ExportProgressStatus GetExportProgressStatus()
       override;
   bool IsOptedInForAccountStorage() override;
+  void SetAccountStorageOptIn(bool opt_in,
+                              content::WebContents* web_contents) override;
   std::vector<api::passwords_private::CompromisedCredential>
   GetCompromisedCredentials() override;
   void GetPlaintextCompromisedPassword(
@@ -96,6 +100,14 @@
 
   IdGenerator<std::string>& GetPasswordIdGeneratorForTesting();
 
+  // TODO(crbug.com/1049141): Move the strong alias out of PasswordManagerClient
+  // to avoid leaking implementation details here.
+  using GoogleReauthCallback = base::OnceCallback<void(
+      password_manager::PasswordManagerClient::ReauthSucceeded)>;
+  using GoogleAccountAuthenticator =
+      base::RepeatingCallback<void(content::WebContents*,
+                                   GoogleReauthCallback)>;
+
 #if defined(UNIT_TEST)
   // Use this in tests to mock the OS-level reauthentication.
   void set_os_reauth_call(
@@ -104,6 +116,12 @@
     password_access_authenticator_.set_os_reauth_call(
         std::move(os_reauth_call));
   }
+  // Use this in tests to mock the Google account reauthentication.
+  void set_account_storage_opt_in_reauthenticator(
+      GoogleAccountAuthenticator account_storage_opt_in_reauthenticator) {
+    account_storage_opt_in_reauthenticator_ =
+        std::move(account_storage_opt_in_reauthenticator);
+  }
 #endif  // defined(UNIT_TEST)
 
  private:
@@ -129,10 +147,20 @@
 
   void OnAccountStorageOptInStateChanged();
 
+  // Callback for the reauth flow that is triggered upon the user opting in to
+  // account password storage. Will opt in the user if the reauth succeeded.
+  void SetAccountStorageOptInCallback(
+      password_manager::PasswordManagerClient::ReauthSucceeded
+          reauth_succeeded);
+
   // Triggers an OS-dependent UI to present OS account login challenge and
   // returns true if the user passed that challenge.
   bool OsReauthCall(password_manager::ReauthPurpose purpose);
 
+  // Triggers a Google account reauthentication UI.
+  void InvokeGoogleReauth(content::WebContents* web_contents,
+                          GoogleReauthCallback callback);
+
   // Not owned by this class.
   Profile* profile_;
 
@@ -144,6 +172,8 @@
 
   password_manager::PasswordAccessAuthenticator password_access_authenticator_;
 
+  GoogleAccountAuthenticator account_storage_opt_in_reauthenticator_;
+
   std::unique_ptr<password_manager::PasswordAccountStorageOptInWatcher>
       password_account_storage_opt_in_watcher_;
 
@@ -178,6 +208,8 @@
   // NativeWindow for the window where the API was called.
   content::WebContents* web_contents_;
 
+  base::WeakPtrFactory<PasswordsPrivateDelegateImpl> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(PasswordsPrivateDelegateImpl);
 };
 
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc
index 20cb0b0..64de82d2 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl_unittest.cc
@@ -18,12 +18,14 @@
 #include "base/test/gmock_move_support.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate_impl.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h"
 #include "chrome/browser/extensions/api/passwords_private/passwords_private_event_router_factory.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/common/extensions/api/passwords_private.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/autofill/core/common/password_form.h"
@@ -31,8 +33,12 @@
 #include "components/password_manager/core/browser/password_list_sorter.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
+#include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/reauth_purpose.h"
 #include "components/password_manager/core/browser/test_password_store.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#include "components/sync/driver/test_sync_service.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/test_event_router.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -122,6 +128,16 @@
   return form;
 }
 
+std::unique_ptr<KeyedService> BuildTestSyncService(content::BrowserContext*) {
+  auto sync_service = std::make_unique<syncer::TestSyncService>();
+  CoreAccountInfo fake_info;
+  fake_info.account_id = CoreAccountId("id");
+  fake_info.gaia = "gaia";
+  fake_info.email = "foo@bar.com";
+  sync_service->SetAuthenticatedAccountInfo(fake_info);
+  return sync_service;
+}
+
 }  // namespace
 
 class PasswordsPrivateDelegateImplTest : public testing::Test {
@@ -136,6 +152,9 @@
   // PasswordsPrivateEventRouter.
   void SetUpRouters();
 
+  void SetGoogleReauthResponse(PasswordsPrivateDelegateImpl* delegate,
+                               bool should_succeed);
+
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  protected:
@@ -146,6 +165,8 @@
       CreateAndUseTestPasswordStore(&profile_);
   ui::TestClipboard* test_clipboard_ =
       ui::TestClipboard::CreateForCurrentThread();
+  base::MockCallback<PasswordsPrivateDelegateImpl::GoogleAccountAuthenticator>
+      mock_google_authenticator_;
 
  private:
   base::HistogramTester histogram_tester_;
@@ -178,6 +199,22 @@
       &profile_, base::BindRepeating(&BuildPasswordsPrivateEventRouter));
 }
 
+void PasswordsPrivateDelegateImplTest::SetGoogleReauthResponse(
+    PasswordsPrivateDelegateImpl* delegate,
+    bool should_succeed) {
+  ON_CALL(mock_google_authenticator_, Run)
+      .WillByDefault(
+          [should_succeed](
+              content::WebContents*,
+              PasswordsPrivateDelegateImpl::GoogleReauthCallback callback) {
+            std::move(callback).Run(
+                password_manager::PasswordManagerClient::ReauthSucceeded(
+                    should_succeed));
+          });
+  delegate->set_account_storage_opt_in_reauthenticator(
+      mock_google_authenticator_.Get());
+}
+
 TEST_F(PasswordsPrivateDelegateImplTest, GetSavedPasswordsList) {
   PasswordsPrivateDelegateImpl delegate(&profile_);
 
@@ -289,6 +326,47 @@
       1);
 }
 
+TEST_F(PasswordsPrivateDelegateImplTest, TestShouldOptInIfReauthSucceeds) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      password_manager::features::kEnablePasswordsAccountStorage);
+
+  PasswordsPrivateDelegateImpl delegate(&profile_);
+
+  SetGoogleReauthResponse(&delegate, true);
+
+  auto* test_sync_service = static_cast<syncer::SyncService*>(
+      ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+          &profile_, base::BindRepeating(&BuildTestSyncService)));
+
+  password_manager_util::SetAccountStorageOptIn(profile_.GetPrefs(),
+                                                test_sync_service, false);
+  delegate.SetAccountStorageOptIn(true, nullptr);
+
+  EXPECT_TRUE(password_manager_util::IsOptedInForAccountStorage(
+      profile_.GetPrefs(), test_sync_service));
+}
+
+TEST_F(PasswordsPrivateDelegateImplTest, TestShouldOptOut) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      password_manager::features::kEnablePasswordsAccountStorage);
+
+  PasswordsPrivateDelegateImpl delegate(&profile_);
+
+  auto* test_sync_service = static_cast<syncer::SyncService*>(
+      ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+          &profile_, base::BindRepeating(&BuildTestSyncService)));
+
+  password_manager_util::SetAccountStorageOptIn(profile_.GetPrefs(),
+                                                test_sync_service, true);
+  delegate.SetAccountStorageOptIn(false, nullptr);
+
+  EXPECT_CALL(mock_google_authenticator_, Run).Times(0);
+  EXPECT_FALSE(password_manager_util::IsOptedInForAccountStorage(
+      profile_.GetPrefs(), test_sync_service));
+}
+
 TEST_F(PasswordsPrivateDelegateImplTest, TestCopyPasswordCallbackResultFail) {
   SetUpPasswordStore({CreateSampleForm()});
 
diff --git a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
index 3d72c27..7412ecd 100644
--- a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
+++ b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.cc
@@ -151,6 +151,12 @@
   return is_opted_in_for_account_storage_;
 }
 
+void TestPasswordsPrivateDelegate::SetAccountStorageOptIn(
+    bool opt_in,
+    content::WebContents* web_contents) {
+  is_opted_in_for_account_storage_ = opt_in;
+}
+
 std::vector<api::passwords_private::CompromisedCredential>
 TestPasswordsPrivateDelegate::GetCompromisedCredentials() {
   api::passwords_private::CompromisedCredential credential;
diff --git a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
index 17b26e2..f3c27bd 100644
--- a/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
+++ b/chrome/browser/extensions/api/passwords_private/test_passwords_private_delegate.h
@@ -40,6 +40,8 @@
   api::passwords_private::ExportProgressStatus GetExportProgressStatus()
       override;
   bool IsOptedInForAccountStorage() override;
+  void SetAccountStorageOptIn(bool opt_in,
+                              content::WebContents* web_contents) override;
   std::vector<api::passwords_private::CompromisedCredential>
   GetCompromisedCredentials() override;
   void GetPlaintextCompromisedPassword(
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 9a7ee97..6e337da 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -3393,6 +3393,11 @@
     "expiry_milestone": 84
   },
   {
+    "name": "remote-copy-image-notification",
+    "owners": [ "//chrome/browser/sharing/OWNERS" ],
+    "expiry_milestone": 85
+  },
+  {
     "name": "remote-copy-progress-notification",
     "owners": [ "//chrome/browser/sharing/OWNERS" ],
     "expiry_milestone": 85
@@ -3418,6 +3423,11 @@
     "expiry_milestone": 90
   },
   {
+    "name": "safe-browsing-available",
+    "owners": [ "//ios/chrome/browser/safe_browsing/OWNERS" ],
+    "expiry_milestone": 85
+  },
+  {
     "name": "safety-tips",
     "owners": [ "jdeblasio", "estark", "meacer" ],
     "expiry_milestone": 84
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c5724aa..47544d0 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3738,6 +3738,12 @@
     "Enables the remote copy feature to handle messages by writing content to "
     "the clipboard and showing a notification to the user.";
 
+const char kRemoteCopyImageNotificationName[] =
+    "Enables image notifications for the remote copy feature";
+const char kRemoteCopyImageNotificationDescription[] =
+    "Enables image notifications to be shown for the remote copy feature "
+    "when receiving a message.";
+
 const char kRemoteCopyProgressNotificationName[] =
     "Enables progress notifications for the remote copy feature";
 const char kRemoteCopyProgressNotificationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 034cb3c..f905ee2 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2184,6 +2184,9 @@
 extern const char kRemoteCopyReceiverName[];
 extern const char kRemoteCopyReceiverDescription[];
 
+extern const char kRemoteCopyImageNotificationName[];
+extern const char kRemoteCopyImageNotificationDescription[];
+
 extern const char kRemoteCopyProgressNotificationName[];
 extern const char kRemoteCopyProgressNotificationDescription[];
 
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
index c832fcc..d14c61c 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlags.java
@@ -65,7 +65,6 @@
             put(ChromeFeatureList.TAB_GROUPS_ANDROID, false);
             put(ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID, false);
             put(ChromeFeatureList.DUET_TABSTRIP_INTEGRATION_ANDROID, false);
-            put(ChromeFeatureList.SHARE_BUTTON_IN_TOP_TOOLBAR, false);
             put(ChromeFeatureList.CLOSE_TAB_SUGGESTIONS, false);
             put(ChromeFeatureList.INSTANT_START, false);
             put(ChromeFeatureList.TAB_GROUPS_CONTINUATION_ANDROID, false);
diff --git a/chrome/browser/media/feeds/media_feeds_contents_observer.cc b/chrome/browser/media/feeds/media_feeds_contents_observer.cc
index 98e561b..aee74b2 100644
--- a/chrome/browser/media/feeds/media_feeds_contents_observer.cc
+++ b/chrome/browser/media/feeds/media_feeds_contents_observer.cc
@@ -61,7 +61,7 @@
       return;
     }
 
-    service->SaveMediaFeed(*url);
+    service->DiscoverMediaFeed(*url);
   }
 
   if (test_closure_)
diff --git a/chrome/browser/media/feeds/media_feeds_store.mojom b/chrome/browser/media/feeds/media_feeds_store.mojom
index 6b2abaa..7eaa7fa 100644
--- a/chrome/browser/media/feeds/media_feeds_store.mojom
+++ b/chrome/browser/media/feeds/media_feeds_store.mojom
@@ -14,6 +14,9 @@
 
   // The URL for the discovered feed.
   url.mojom.Url url;
+
+  // The last time this feed was discovered.
+  mojo_base.mojom.Time last_discovery_time;
 };
 
 // The type of the feed item. This enum is committed to storage so do not
diff --git a/chrome/browser/media/history/media_history_feeds_table.cc b/chrome/browser/media/history/media_history_feeds_table.cc
index 0c14b44..212c878 100644
--- a/chrome/browser/media/history/media_history_feeds_table.cc
+++ b/chrome/browser/media/history/media_history_feeds_table.cc
@@ -29,6 +29,7 @@
                                        "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                        "origin_id INTEGER NOT NULL UNIQUE,"
                                        "url TEXT NOT NULL, "
+                                       "last_discovery_time_s INTEGER, "
                                        "CONSTRAINT fk_origin "
                                        "FOREIGN KEY (origin_id) "
                                        "REFERENCES origin(id) "
@@ -55,23 +56,57 @@
   return sql::INIT_OK;
 }
 
-bool MediaHistoryFeedsTable::SaveFeed(const GURL& url) {
+bool MediaHistoryFeedsTable::DiscoverFeed(const GURL& url) {
   DCHECK_LT(0, DB()->transaction_nesting());
   if (!CanAccessDatabase())
     return false;
 
-  sql::Statement statement(DB()->GetCachedStatement(
-      SQL_FROM_HERE,
-      base::StringPrintf("INSERT OR REPLACE INTO %s "
-                         "(origin_id, url) VALUES "
-                         "((SELECT id FROM origin WHERE origin = ?), ?)",
-                         kTableName)
-          .c_str()));
-  statement.BindString(0, MediaHistoryOriginTable::GetOriginForStorage(
-                              url::Origin::Create(url)));
-  statement.BindString(1, url.spec());
+  const auto origin =
+      MediaHistoryOriginTable::GetOriginForStorage(url::Origin::Create(url));
+  const auto now = base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
 
-  return statement.Run();
+  base::Optional<GURL> feed_url;
+  base::Optional<int64_t> feed_id;
+
+  {
+    // Check if we already have a feed for the current origin;
+    sql::Statement statement(DB()->GetCachedStatement(
+        SQL_FROM_HERE,
+        "SELECT id, url FROM mediaFeed WHERE origin_id = (SELECT id FROM "
+        "origin WHERE origin = ?)"));
+    statement.BindString(0, origin);
+
+    while (statement.Step()) {
+      DCHECK(!feed_id);
+      DCHECK(!feed_url);
+
+      feed_id = statement.ColumnInt64(0);
+      feed_url = GURL(statement.ColumnString(1));
+    }
+  }
+
+  if (!feed_url || url != feed_url) {
+    // If the feed does not exist or exists and has a different URL then we
+    // should replace the feed.
+    sql::Statement statement(DB()->GetCachedStatement(
+        SQL_FROM_HERE,
+        "INSERT OR REPLACE INTO mediaFeed "
+        "(origin_id, url, last_discovery_time_s) VALUES "
+        "((SELECT id FROM origin WHERE origin = ?), ?, ?)"));
+    statement.BindString(0, origin);
+    statement.BindString(1, url.spec());
+    statement.BindInt64(2, now);
+    return statement.Run() && DB()->GetLastChangeCount() == 1;
+  } else {
+    // If the feed already exists in the database with the same URL we should
+    // just update the last discovery time so we don't delete the old entry.
+    sql::Statement statement(DB()->GetCachedStatement(
+        SQL_FROM_HERE,
+        "UPDATE mediaFeed SET last_discovery_time_s = ? WHERE id = ?"));
+    statement.BindInt64(0, now);
+    statement.BindInt64(1, *feed_id);
+    return statement.Run() && DB()->GetLastChangeCount() == 1;
+  }
 }
 
 std::vector<media_feeds::mojom::MediaFeedPtr>
@@ -80,17 +115,19 @@
   if (!CanAccessDatabase())
     return feeds;
 
-  sql::Statement statement(
-      DB()->GetUniqueStatement(base::StringPrintf("SELECT id, url "
-                                                  "FROM %s",
-                                                  kTableName)
-                                   .c_str()));
+  sql::Statement statement(DB()->GetUniqueStatement(
+      base::StringPrintf("SELECT id, url, last_discovery_time_s "
+                         "FROM %s",
+                         kTableName)
+          .c_str()));
 
   while (statement.Step()) {
     media_feeds::mojom::MediaFeedPtr feed(media_feeds::mojom::MediaFeed::New());
 
     feed->id = statement.ColumnInt64(0);
     feed->url = GURL(statement.ColumnString(1));
+    feed->last_discovery_time = base::Time::FromDeltaSinceWindowsEpoch(
+        base::TimeDelta::FromSeconds(statement.ColumnInt64(2)));
 
     feeds.push_back(std::move(feed));
   }
diff --git a/chrome/browser/media/history/media_history_feeds_table.h b/chrome/browser/media/history/media_history_feeds_table.h
index a48381c..9840388 100644
--- a/chrome/browser/media/history/media_history_feeds_table.h
+++ b/chrome/browser/media/history/media_history_feeds_table.h
@@ -35,7 +35,7 @@
   sql::InitStatus CreateTableIfNonExistent() override;
 
   // Saves a newly discovered feed in the database.
-  bool SaveFeed(const GURL& url);
+  bool DiscoverFeed(const GURL& url);
 
   // Returns the feed rows in the database.
   std::vector<media_feeds::mojom::MediaFeedPtr> GetRows();
diff --git a/chrome/browser/media/history/media_history_keyed_service.cc b/chrome/browser/media/history/media_history_keyed_service.cc
index 669cd2b..f3233cc 100644
--- a/chrome/browser/media/history/media_history_keyed_service.cc
+++ b/chrome/browser/media/history/media_history_keyed_service.cc
@@ -207,9 +207,9 @@
   store_->GetForRead()->GetURLsInTableForTest(table, std::move(callback));
 }
 
-void MediaHistoryKeyedService::SaveMediaFeed(const GURL& url) {
+void MediaHistoryKeyedService::DiscoverMediaFeed(const GURL& url) {
   if (auto* store = store_->GetForWrite())
-    store->SaveMediaFeed(url);
+    store->DiscoverMediaFeed(url);
 }
 
 void MediaHistoryKeyedService::PostTaskToDBForTest(base::OnceClosure callback) {
diff --git a/chrome/browser/media/history/media_history_keyed_service.h b/chrome/browser/media/history/media_history_keyed_service.h
index bdbf2d8..14e3d99 100644
--- a/chrome/browser/media/history/media_history_keyed_service.h
+++ b/chrome/browser/media/history/media_history_keyed_service.h
@@ -81,7 +81,7 @@
       const std::vector<media_session::MediaImage>& artwork);
 
   // Saves a newly discovered media feed in the media history store.
-  void SaveMediaFeed(const GURL& url);
+  void DiscoverMediaFeed(const GURL& url);
 
   // Gets the media items in |feed_id|.
   void GetItemsForMediaFeedForDebug(
diff --git a/chrome/browser/media/history/media_history_store.cc b/chrome/browser/media/history/media_history_store.cc
index 137de2d2..6ea7f716 100644
--- a/chrome/browser/media/history/media_history_store.cc
+++ b/chrome/browser/media/history/media_history_store.cc
@@ -91,7 +91,7 @@
 
   std::set<GURL> GetURLsInTableForTest(const std::string& table);
 
-  void SaveMediaFeed(const GURL& url);
+  void DiscoverMediaFeed(const GURL& url);
 
   void ReplaceMediaFeedItems(
       const int64_t feed_id,
@@ -472,7 +472,7 @@
   return urls;
 }
 
-void MediaHistoryStoreInternal::SaveMediaFeed(const GURL& url) {
+void MediaHistoryStoreInternal::DiscoverMediaFeed(const GURL& url) {
   DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
   if (!initialization_successful_)
     return;
@@ -486,7 +486,7 @@
     return;
 
   if (!(CreateOriginId(url::Origin::Create(url)) &&
-        feeds_table_->SaveFeed(url))) {
+        feeds_table_->DiscoverFeed(url))) {
     DB()->RollbackTransaction();
     return;
   }
@@ -661,10 +661,10 @@
       std::move(callback));
 }
 
-void MediaHistoryStore::SaveMediaFeed(const GURL& url) {
+void MediaHistoryStore::DiscoverMediaFeed(const GURL& url) {
   db_->db_task_runner_->PostTask(
       FROM_HERE,
-      base::BindOnce(&MediaHistoryStoreInternal::SaveMediaFeed, db_, url));
+      base::BindOnce(&MediaHistoryStoreInternal::DiscoverMediaFeed, db_, url));
 }
 
 void MediaHistoryStore::PostTaskToDBForTest(base::OnceClosure callback) {
diff --git a/chrome/browser/media/history/media_history_store.h b/chrome/browser/media/history/media_history_store.h
index f1461740..ad83536 100644
--- a/chrome/browser/media/history/media_history_store.h
+++ b/chrome/browser/media/history/media_history_store.h
@@ -101,7 +101,7 @@
       const std::vector<media_session::MediaImage>& artwork);
 
   // Saves a newly discovered media feed in the media history store.
-  void SaveMediaFeed(const GURL& url);
+  void DiscoverMediaFeed(const GURL& url);
 
   void ReplaceMediaFeedItems(
       const int64_t feed_id,
diff --git a/chrome/browser/media/history/media_history_store_unittest.cc b/chrome/browser/media/history/media_history_store_unittest.cc
index 84faf22..e942074 100644
--- a/chrome/browser/media/history/media_history_store_unittest.cc
+++ b/chrome/browser/media/history/media_history_store_unittest.cc
@@ -523,8 +523,8 @@
   EXPECT_EQ(origins, GetOriginRowsSync(otr_service()));
 }
 
-TEST_P(MediaHistoryStoreUnitTest, SaveMediaFeed_Noop) {
-  service()->SaveMediaFeed(GURL("https://www.google.com/feed"));
+TEST_P(MediaHistoryStoreUnitTest, DiscoverMediaFeed_Noop) {
+  service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
   WaitForDB();
 
   {
@@ -698,13 +698,13 @@
   ASSERT_TRUE(GetDB().DoesTableExist("mediaFeedItem"));
 }
 
-TEST_P(MediaHistoryStoreFeedsTest, SaveMediaFeed) {
+TEST_P(MediaHistoryStoreFeedsTest, DiscoverMediaFeed) {
   GURL url_a("https://www.google.com/feed");
   GURL url_b("https://www.google.co.uk/feed");
   GURL url_c("https://www.google.com/feed2");
 
-  service()->SaveMediaFeed(url_a);
-  service()->SaveMediaFeed(url_b);
+  service()->DiscoverMediaFeed(url_a);
+  service()->DiscoverMediaFeed(url_b);
   WaitForDB();
 
   {
@@ -727,7 +727,7 @@
     EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
   }
 
-  service()->SaveMediaFeed(url_c);
+  service()->DiscoverMediaFeed(url_c);
   WaitForDB();
 
   {
@@ -752,7 +752,7 @@
 }
 
 TEST_P(MediaHistoryStoreFeedsTest, ReplaceMediaFeedItems) {
-  service()->SaveMediaFeed(GURL("https://www.google.com/feed"));
+  service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
   WaitForDB();
 
   // If we are read only we should use -1 as a placeholder feed id because the
@@ -796,7 +796,7 @@
 }
 
 TEST_P(MediaHistoryStoreFeedsTest, ReplaceMediaFeedItems_WithEmpty) {
-  service()->SaveMediaFeed(GURL("https://www.google.com/feed"));
+  service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
   WaitForDB();
 
   // If we are read only we should use -1 as a placeholder feed id because the
@@ -836,8 +836,8 @@
 }
 
 TEST_P(MediaHistoryStoreFeedsTest, ReplaceMediaFeedItems_MultipleFeeds) {
-  service()->SaveMediaFeed(GURL("https://www.google.com/feed"));
-  service()->SaveMediaFeed(GURL("https://www.google.co.uk/feed"));
+  service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
+  service()->DiscoverMediaFeed(GURL("https://www.google.co.uk/feed"));
   WaitForDB();
 
   // If we are read only we should use -1 as a placeholder feed id because the
@@ -882,7 +882,7 @@
 }
 
 TEST_P(MediaHistoryStoreFeedsTest, ReplaceMediaFeedItems_BadType) {
-  service()->SaveMediaFeed(GURL("https://www.google.com/feed"));
+  service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
   WaitForDB();
 
   // If we are read only we should use -1 as a placeholder feed id because the
@@ -921,4 +921,91 @@
   }
 }
 
+TEST_P(MediaHistoryStoreFeedsTest, RediscoverMediaFeed) {
+  GURL feed_url("https://www.google.com/feed");
+  service()->DiscoverMediaFeed(feed_url);
+  WaitForDB();
+
+  // If we are read only we should use -1 as a placeholder feed id because the
+  // feed will not have been stored. This is so we can run the rest of the test
+  // to ensure a no-op.
+  int feed_id = -1;
+  base::Time feed_last_time;
+
+  if (!IsReadOnly()) {
+    auto feeds = GetMediaFeedsSync(service());
+    feed_id = feeds[0]->id;
+    feed_last_time = feeds[0]->last_discovery_time;
+
+    EXPECT_LT(base::Time(), feed_last_time);
+    EXPECT_GT(base::Time::Now(), feed_last_time);
+    EXPECT_EQ(feed_url, feeds[0]->url);
+  }
+
+  service()->ReplaceMediaFeedItems(feed_id, GetExpectedItems());
+  WaitForDB();
+
+  {
+    // The media items should be stored.
+    auto items = GetItemsForMediaFeedSync(service(), feed_id);
+
+    if (IsReadOnly()) {
+      EXPECT_TRUE(items.empty());
+    } else {
+      EXPECT_EQ(GetExpectedItems(), items);
+    }
+
+    // The OTR service should have the same data.
+    EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
+  }
+
+  // Rediscovering the same feed should not replace the feed.
+  service()->DiscoverMediaFeed(feed_url);
+  WaitForDB();
+
+  if (!IsReadOnly()) {
+    auto feeds = GetMediaFeedsSync(service());
+
+    EXPECT_LE(feed_last_time, feeds[0]->last_discovery_time);
+    EXPECT_EQ(feed_id, feeds[0]->id);
+    EXPECT_EQ(feed_url, feeds[0]->url);
+  }
+
+  {
+    // The media items should be stored.
+    auto items = GetItemsForMediaFeedSync(service(), feed_id);
+
+    if (IsReadOnly()) {
+      EXPECT_TRUE(items.empty());
+    } else {
+      EXPECT_EQ(GetExpectedItems(), items);
+    }
+
+    // The OTR service should have the same data.
+    EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
+  }
+
+  // Finding a new URL should replace the feed.
+  GURL new_url("https://www.google.com/feed2");
+  service()->DiscoverMediaFeed(new_url);
+  WaitForDB();
+
+  if (!IsReadOnly()) {
+    auto feeds = GetMediaFeedsSync(service());
+
+    EXPECT_LE(feed_last_time, feeds[0]->last_discovery_time);
+    EXPECT_LT(feed_id, feeds[0]->id);
+    EXPECT_EQ(new_url, feeds[0]->url);
+  }
+
+  {
+    // The media items should be deleted.
+    auto items = GetItemsForMediaFeedSync(service(), feed_id);
+    EXPECT_TRUE(items.empty());
+
+    // The OTR service should have the same data.
+    EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
+  }
+}
+
 }  // namespace media_history
diff --git a/chrome/browser/metrics/startup_metrics_browsertest.cc b/chrome/browser/metrics/startup_metrics_browsertest.cc
index f93a345..32ced89 100644
--- a/chrome/browser/metrics/startup_metrics_browsertest.cc
+++ b/chrome/browser/metrics/startup_metrics_browsertest.cc
@@ -23,10 +23,10 @@
     "Startup.FirstWebContents.MainNavigationFinished",
     "Startup.FirstWebContents.MainNavigationStart",
     "Startup.FirstWebContents.NonEmptyPaint2",
+    "Startup.FirstWebContents.NonEmptyPaint3",
     "Startup.FirstWebContents.RenderProcessHostInit.ToNonEmptyPaint",
-    "Startup.LoadTime.ExeMainToDllMain2",
-    "Startup.LoadTime.ProcessCreateToDllMain2",
-    "Startup.LoadTime.ProcessCreateToExeMain2",
+    "Startup.LoadTime.ApplicationStartToChromeMain",
+    "Startup.LoadTime.ProcessCreateToApplicationStart",
 
 #if defined(OS_WIN)
     "Startup.BrowserMessageLoopStartHardFaultCount",
@@ -38,9 +38,6 @@
 
 // Verify that startup histograms are logged on browser startup.
 IN_PROC_BROWSER_TEST_F(StartupMetricsTest, ReportsValues) {
-  // This is usually done from the constructor of ChromeMainDelegate.
-  startup_metric_utils::RecordExeMainEntryPointTicks(base::TimeTicks::Now());
-
   // This is usually done from ChromeBrowserMainParts::MainMessageLoopRun().
   startup_metric_utils::RecordBrowserMainMessageLoopStart(
       base::TimeTicks::Now(), false /* is_first_run */);
diff --git a/chrome/browser/password_manager/credential_leak_password_change_controller_android.cc b/chrome/browser/password_manager/credential_leak_password_change_controller_android.cc
index 22a4401..bc117eca 100644
--- a/chrome/browser/password_manager/credential_leak_password_change_controller_android.cc
+++ b/chrome/browser/password_manager/credential_leak_password_change_controller_android.cc
@@ -6,6 +6,7 @@
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
+#include "chrome/android/chrome_jni_headers/PasswordChangeLauncher_jni.h"
 #include "chrome/browser/ui/android/passwords/credential_leak_dialog_password_change_view_android.h"
 #include "chrome/common/url_constants.h"
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
@@ -47,6 +48,13 @@
       password_manager::GetLeakDialogType(leak_type_),
       ShouldCheckPasswords() ? LeakDialogDismissalReason::kClickedCheckPasswords
                              : LeakDialogDismissalReason::kClickedOk);
+  if (window_android_) {
+    JNIEnv* env = base::android::AttachCurrentThread();
+    Java_PasswordChangeLauncher_start(
+        env, window_android_->GetJavaObject(),
+        base::android::ConvertUTF8ToJavaString(env, origin_.spec()),
+        base::android::ConvertUTF16ToJavaString(env, username_));
+  }
   delete this;
 }
 
diff --git a/chrome/browser/password_manager/credential_manager_browsertest.cc b/chrome/browser/password_manager/credential_manager_browsertest.cc
index 7f35db1..c9b4530 100644
--- a/chrome/browser/password_manager/credential_manager_browsertest.cc
+++ b/chrome/browser/password_manager/credential_manager_browsertest.cc
@@ -30,6 +30,8 @@
 
 namespace {
 
+using password_manager::MatchesFormExceptStore;
+
 class CredentialManagerBrowserTest : public PasswordManagerBrowserTestBase {
  public:
   CredentialManagerBrowserTest() {}
@@ -980,7 +982,8 @@
   // Now make them equal to be able to check the equality of other fields.
   signin_form.date_last_used =
       stored[signin_form.signon_realm][0].date_last_used;
-  EXPECT_EQ(signin_form, stored[signin_form.signon_realm][0]);
+  EXPECT_THAT(signin_form,
+              MatchesFormExceptStore(stored[signin_form.signon_realm][0]));
 }
 
 IN_PROC_BROWSER_TEST_F(CredentialManagerBrowserTest, CredentialsAutofilled) {
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 6a3973b..b141005 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -466,8 +466,14 @@
 // TODO(wjmaclean): Are there any attributes we can/should test with respect to
 // the extension's loaded html?
 // TODO(https://crbug.com/1034972): Re-enable. Flaky on all platforms.
+// Temporarily re-enabling on Linux to collect diagnostic data.
+#if defined(OS_LINUX)
+#define MAYBE_PdfExtensionLoadedInGuest PdfExtensionLoadedInGuest
+#else
+#define MAYBE_PdfExtensionLoadedInGuest DISABLED_PdfExtensionLoadedInGuest
+#endif
 IN_PROC_BROWSER_TEST_F(PDFExtensionTestWithTestGuestViewManager,
-                       DISABLED_PdfExtensionLoadedInGuest) {
+                       MAYBE_PdfExtensionLoadedInGuest) {
   // Load test HTML, and verify the text area has focus.
   GURL main_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
   ui_test_utils::NavigateToURL(browser(), main_url);
diff --git a/chrome/browser/resources/chromeos/edu_login/BUILD.gn b/chrome/browser/resources/chromeos/edu_login/BUILD.gn
index b890332..c3c2c25 100644
--- a/chrome/browser/resources/chromeos/edu_login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/edu_login/BUILD.gn
@@ -11,6 +11,7 @@
     ":app",
     ":browser_proxy",
     ":edu_login_button",
+    ":edu_login_parents",
     ":edu_login_template",
     ":edu_login_util",
     ":edu_login_welcome",
@@ -19,9 +20,12 @@
 
 js_library("app") {
   deps = [
+    ":edu_login_parents",
+    ":edu_login_util",
     ":edu_login_welcome",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager.m",
+    "//ui/webui/resources/js:assert.m",
   ]
 }
 
@@ -59,6 +63,15 @@
   ]
 }
 
+js_library("edu_login_parents") {
+  deps = [
+    ":edu_login_button",
+    ":edu_login_template",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:icon.m",
+  ]
+}
+
 polymer_modulizer("app") {
   js_file = "app.js"
   html_file = "app.html"
@@ -89,11 +102,18 @@
   html_type = "v3-ready"
 }
 
+polymer_modulizer("edu_login_parents") {
+  js_file = "edu_login_parents.js"
+  html_file = "edu_login_parents.html"
+  html_type = "v3-ready"
+}
+
 group("polymer3_elements") {
   public_deps = [
     ":app_module",
     ":edu_login_button_module",
     ":edu_login_css_module",
+    ":edu_login_parents_module",
     ":edu_login_template_module",
     ":edu_login_welcome_module",
   ]
diff --git a/chrome/browser/resources/chromeos/edu_login/app.html b/chrome/browser/resources/chromeos/edu_login/app.html
index c37a6a5e..0aa2bb5 100644
--- a/chrome/browser/resources/chromeos/edu_login/app.html
+++ b/chrome/browser/resources/chromeos/edu_login/app.html
@@ -1,4 +1,7 @@
 <cr-view-manager id="viewManager">
   <edu-login-welcome id="[[Steps.WELCOME]]" slot="view">
   </edu-login-welcome>
+  <edu-login-parents id="[[Steps.PARENTS]]" slot="view"
+      selected-parent="{{selectedParent_}}">
+  </edu-login-parents>
 </cr-view-manager>
diff --git a/chrome/browser/resources/chromeos/edu_login/app.js b/chrome/browser/resources/chromeos/edu_login/app.js
index 7056d1a..450f5816 100644
--- a/chrome/browser/resources/chromeos/edu_login/app.js
+++ b/chrome/browser/resources/chromeos/edu_login/app.js
@@ -3,9 +3,12 @@
 // found in the LICENSE file.
 
 import './edu_login_welcome.js';
+import './edu_login_parents.js';
 import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.m.js';
 
+import {assert} from 'chrome://resources/js/assert.m.js';
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {ParentAccount} from './edu_login_util.js';
 
 /** @enum {string} */
 const Steps = {
@@ -39,10 +42,17 @@
       type: Number,
       value: 0,
     },
+
+    /**
+     * Selected parent account for approving EDU login flow.
+     * @type {?ParentAccount}
+     */
+    selectedParent_: Object,
   },
 
   listeners: {
     'go-next': 'onGoNext_',
+    'go-back': 'onGoBack_',
   },
 
   /** @override */
@@ -52,9 +62,15 @@
 
   /** Switches to the next view. */
   onGoNext_() {
-    if (this.stepIndex_ < stepsArray.length - 1) {
-      ++this.stepIndex_;
-    }
+    assert(this.stepIndex_ < stepsArray.length - 1);
+    ++this.stepIndex_;
+    this.switchViewAtIndex_(this.stepIndex_);
+  },
+
+  /** Switches to the previous view. */
+  onGoBack_() {
+    assert(this.stepIndex_ > 0);
+    --this.stepIndex_;
     this.switchViewAtIndex_(this.stepIndex_);
   },
 
diff --git a/chrome/browser/resources/chromeos/edu_login/edu_login_parents.html b/chrome/browser/resources/chromeos/edu_login/edu_login_parents.html
new file mode 100644
index 0000000..b2023136
--- /dev/null
+++ b/chrome/browser/resources/chromeos/edu_login/edu_login_parents.html
@@ -0,0 +1,76 @@
+<style include="edu-login-css">
+  .account-list-item {
+    align-items: center;
+    cursor: pointer;
+    display: flex;
+    min-height: 64px;
+    outline: none;
+    transition: background 200ms;
+  }
+
+  .main-padding.account-list-item {
+    padding-bottom: 0;
+    padding-top: 0;
+  }
+
+  .account-list-item:hover,
+  .account-list-item:focus {
+    background: var(--google-grey-100);
+  }
+
+  .account-list-item.selected {
+    background: var(--google-grey-200);
+  }
+
+  .account-info {
+    padding-inline-start: 24px;
+  }
+
+  .profile-icon {
+    --profile-icon-size: 36px;
+    background: center / cover no-repeat;
+    border-radius: 50%;
+    flex-shrink: 0;
+    height: var(--profile-icon-size);
+    width: var(--profile-icon-size);
+  }
+</style>
+
+<edu-login-template>
+  <span slot="main">
+    <div class="main-padding">
+      <if expr="_google_chrome">
+        <img class="google-full-logo"
+            src="chrome://theme/IDR_LOGO_GOOGLE_COLOR_90" alt="">
+      </if>
+      <h1>$i18n{parentsListTitle}</h1>
+      <p id="parentsListBody" aria-hidden="true" class="secondary">
+        $i18n{parentsListBody}
+      </p>
+    </div>
+    <div tabindex="0" role="listbox" aria-labelledby="parentsListBody">
+      <template is="dom-repeat" items="[[parents_]]">
+        <div role="option" tabindex="0" aria-selected="false"
+            id="account-list-item-[[index]]"
+            aria-label$="[[item.displayName]] [[item.email]]"
+            class$="account-list-item main-padding
+            [[getSelectedClass_(selectedParent, item)]]"
+            on-tap="selectParent_" on-keydown="onParentSelectionKeydown_">
+          <div class="profile-icon" style="background-image:
+                  [[getIconImageSet_(item.profileImage)]]">
+          </div>
+          <div class="account-info text-elide">
+            <span>[[item.displayName]]</span>
+            <div class="secondary">[[item.email]]</div>
+          </div>
+        </div>
+      </template>
+    </div>
+  </span>
+  <span slot="buttons">
+    <edu-login-button button-type="back"></edu-login-button>
+    <edu-login-button button-type="next"
+        disabled="[[isNextDisabled_(selectedParent)]]">
+    </edu-login-button>
+  </span>
+</edu-login-template>
diff --git a/chrome/browser/resources/chromeos/edu_login/edu_login_parents.js b/chrome/browser/resources/chromeos/edu_login/edu_login_parents.js
new file mode 100644
index 0000000..f0210ec
--- /dev/null
+++ b/chrome/browser/resources/chromeos/edu_login/edu_login_parents.js
@@ -0,0 +1,103 @@
+// 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 './edu_login_css.js';
+import './edu_login_template.js';
+import './edu_login_button.js';
+
+import {getImage} from 'chrome://resources/js/icon.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {EduAccountLoginBrowserProxyImpl} from './browser_proxy.js';
+import {ParentAccount} from './edu_login_util.js';
+
+Polymer({
+  is: 'edu-login-parents',
+
+  _template: html`{__html_template__}`,
+
+  properties: {
+    /**
+     * Selected parent account for approving EDU login flow.
+     * @type {?ParentAccount}
+     */
+    selectedParent: {
+      type: Object,
+      value: null,
+      notify: true,
+    },
+
+    /**
+     * Array of user's parents.
+     * @private {!Array<!ParentAccount>}
+     */
+    parents_: {
+      type: Array,
+      value() {
+        return [];
+      },
+    },
+  },
+
+  /** @override */
+  ready() {
+    // TODO(anastasiian): handle fetching error.
+    EduAccountLoginBrowserProxyImpl.getInstance().getParents().then(result => {
+      this.parents_ = result;
+    });
+  },
+
+  /**
+   * @param {ParentAccount} selectedParent
+   * @param {ParentAccount} item
+   * @return {string} class name
+   * @private
+   */
+  getSelectedClass_(selectedParent, item) {
+    return item === selectedParent ? 'selected' : '';
+  },
+
+  /**
+   * @param {string} iconUrl
+   * @return {string} A CSS image-set for multiple scale factors.
+   * @private
+   */
+  getIconImageSet_(iconUrl) {
+    return getImage(iconUrl);
+  },
+
+  /**
+   * @param {ParentAccount} selectedParent
+   * @return {boolean}
+   * @private
+   */
+  isNextDisabled_(selectedParent) {
+    return selectedParent === null;
+  },
+
+  /**
+   * Sets selected parent.
+   * @param {Event} e
+   * @private
+   */
+  onParentSelectionKeydown_(e) {
+    if (e.key === 'Enter' || e.key === ' ') {  // Enter or Space key
+      this.selectParent_(e);
+    }
+  },
+
+  /**
+   * @param {Event} e
+   * @private
+   */
+  selectParent_(e) {
+    this.shadowRoot.querySelectorAll('.account-list-item')
+        .forEach(el => el.setAttribute('aria-selected', false));
+    this.$$(`#account-list-item-${e.model.index}`)
+        .setAttribute('aria-selected', true);
+
+    this.selectedParent = e.model.item;
+    this.fire('go-next');
+  },
+});
diff --git a/chrome/browser/resources/chromeos/edu_login/edu_login_util.js b/chrome/browser/resources/chromeos/edu_login/edu_login_util.js
index 03c93f00..7260eaf 100644
--- a/chrome/browser/resources/chromeos/edu_login/edu_login_util.js
+++ b/chrome/browser/resources/chromeos/edu_login/edu_login_util.js
@@ -7,7 +7,7 @@
  * @typedef {{
  *   email: string,
  *   displayName: string,
- *   profileImageUrl: string,
+ *   profileImage: string,
  *   obfuscatedGaiaId: string
  * }}
  */
diff --git a/chrome/browser/resources/media/media_feeds.html b/chrome/browser/resources/media/media_feeds.html
index c4fb7d7..faeee94 100644
--- a/chrome/browser/resources/media/media_feeds.html
+++ b/chrome/browser/resources/media/media_feeds.html
@@ -86,6 +86,9 @@
         <th sort-key="url">
           Url
         </th>
+        <th sort-key="lastDiscoveryTime">
+          Last Discovery Time
+        </th>
       </tr>
     </thead>
     <tbody id="feed-table-body">
@@ -96,6 +99,7 @@
     <tr>
       <td class="id-cell"></td>
       <td class="url-cell"></td>
+      <td class="last-discovery-time-cell"></td>
     </tr>
   </template>
 </body>
diff --git a/chrome/browser/resources/media/media_feeds.js b/chrome/browser/resources/media/media_feeds.js
index e174ec5..e1d8ed23 100644
--- a/chrome/browser/resources/media/media_feeds.js
+++ b/chrome/browser/resources/media/media_feeds.js
@@ -32,11 +32,32 @@
 
   td[0].textContent = rowInfo.id;
   td[1].textContent = rowInfo.url.url;
+  td[2].textContent = convertMojoTimeToJS(rowInfo.lastDiscoveryTime).toString();
 
   return document.importNode(template.content, true);
 }
 
 /**
+ * Converts a mojo time to a JS time.
+ * @param {!mojoBase.mojom.Time} mojoTime
+ * @return {Date}
+ */
+function convertMojoTimeToJS(mojoTime) {
+  // The new Date().getTime() returns the number of milliseconds since the
+  // UNIX epoch (1970-01-01 00::00:00 UTC), while |internalValue| of the
+  // device.mojom.Geoposition represents the value of microseconds since the
+  // Windows FILETIME epoch (1601-01-01 00:00:00 UTC). So add the delta when
+  // sets the |internalValue|. See more info in //base/time/time.h.
+  const windowsEpoch = Date.UTC(1601, 0, 1, 0, 0, 0, 0);
+  const unixEpoch = Date.UTC(1970, 0, 1, 0, 0, 0, 0);
+  // |epochDeltaInMs| equals to base::Time::kTimeTToMicrosecondsOffset.
+  const epochDeltaInMs = unixEpoch - windowsEpoch;
+  const timeInMs = Number(mojoTime.internalValue) / 1000;
+
+  return new Date(timeInMs - epochDeltaInMs);
+}
+
+/**
  * Remove all rows from the feeds table.
  */
 function clearTable() {
@@ -65,6 +86,9 @@
     return a.url.url > b.url.url ? 1 : -1;
   } else if (sortKey == 'id') {
     return a.id > b.id;
+  } else if (sortKey == 'lastDiscoveryTime') {
+    return (
+        a.lastDiscoveryTime.internalValue > b.lastDiscoveryTime.internalValue);
   }
 
   assertNotReached('Unsupported sort key: ' + sortKey);
diff --git a/chrome/browser/resources/settings/autofill_page/BUILD.gn b/chrome/browser/resources/settings/autofill_page/BUILD.gn
index 2acf1d8..a6d0bdf 100644
--- a/chrome/browser/resources/settings/autofill_page/BUILD.gn
+++ b/chrome/browser/resources/settings/autofill_page/BUILD.gn
@@ -99,6 +99,7 @@
   deps = [
     ":password_manager_proxy",
     "..:plural_string_proxy",
+    "../prefs:prefs_behavior",
     "//ui/webui/resources/js:i18n_behavior",
   ]
 }
@@ -291,6 +292,7 @@
   deps = [
     ":password_manager_proxy.m",
     "..:plural_string_proxy.m",
+    "../prefs:prefs_behavior.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
@@ -523,12 +525,12 @@
   js_file = "password_check.js"
   html_file = "password_check.html"
   html_type = "dom-module"
-  auto_imports = [
-    "chrome/browser/resources/settings/autofill_page/password_manager_proxy.html|PasswordManagerImpl,PasswordManagerProxy",
-    "chrome/browser/resources/settings/people_page/sync_browser_proxy.html|SyncBrowserProxyImpl,SyncPrefs,SyncStatus",
-    "chrome/browser/resources/settings/plural_string_proxy.html|PluralStringProxyImpl",
-    "ui/webui/resources/html/assert.html|assert",
-  ]
+  auto_imports = settings_auto_imports + [
+                   "chrome/browser/resources/settings/autofill_page/password_manager_proxy.html|PasswordManagerImpl,PasswordManagerProxy",
+                   "chrome/browser/resources/settings/people_page/sync_browser_proxy.html|SyncBrowserProxyImpl,SyncPrefs,SyncStatus",
+                   "chrome/browser/resources/settings/plural_string_proxy.html|PluralStringProxyImpl",
+                   "ui/webui/resources/html/assert.html|assert",
+                 ]
   namespace_rewrites = settings_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_page.html b/chrome/browser/resources/settings/autofill_page/autofill_page.html
index 30f4dbf..f8faf55 100644
--- a/chrome/browser/resources/settings/autofill_page/autofill_page.html
+++ b/chrome/browser/resources/settings/autofill_page/autofill_page.html
@@ -56,7 +56,7 @@
             associated-control="[[$$('#passwordManagerButton')]]"
             page-title="$i18n{checkPasswords}"
             learn-more-url="$i18n{passwordCheckLearnMoreURL}">
-          <settings-password-check></settings-password-check>
+          <settings-password-check prefs="{{prefs}}"></settings-password-check>
         </settings-subpage>
       </template>
       <template is="dom-if" route-path="/payments">
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.html b/chrome/browser/resources/settings/autofill_page/password_check.html
index bae8450..e58224f 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.html
+++ b/chrome/browser/resources/settings/autofill_page/password_check.html
@@ -14,6 +14,8 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="chrome://resources/html/cr/ui/focus_without_ink.html">
+<link rel="import" href="../prefs/prefs.html">
+<link rel="import" href="../prefs/prefs_behavior.html">
 <link rel="import" href="password_check_edit_dialog.html">
 <link rel="import" href="password_check_list_item.html">
 <link rel="import" href="password_manager_proxy.html">
@@ -106,7 +108,7 @@
       </cr-button>
     </div>
     <div id="noCompromisedCredentials"
-        hidden$="[[hasLeakedCredentials_(leakedPasswords)]]">
+        hidden$="[[!showNoCompromisedPasswordsLabel_]]">
       <div class="settings-box secondary">
         $i18n{noCompromisedCredentials}
       </div>
diff --git a/chrome/browser/resources/settings/autofill_page/password_check.js b/chrome/browser/resources/settings/autofill_page/password_check.js
index 85b53f7f..af0b38c 100644
--- a/chrome/browser/resources/settings/autofill_page/password_check.js
+++ b/chrome/browser/resources/settings/autofill_page/password_check.js
@@ -11,6 +11,7 @@
 
   behaviors: [
     I18nBehavior,
+    PrefsBehavior,
     WebUIListenerBehavior,
   ],
 
@@ -79,6 +80,13 @@
      * @private {?PasswordManagerProxy.CompromisedCredential}
      */
     activePassword_: Object,
+
+    /** @private */
+    showNoCompromisedPasswordsLabel_: {
+      type: Boolean,
+      computed:
+          'computeShowNoCompromisedPasswordsLabel(syncStatus_, prefs.*, status_, leakedPasswords)',
+    }
   },
 
   /**
@@ -281,9 +289,13 @@
       case CheckState.CANCELED:
         return this.i18n('checkPasswordsCanceled');
       case CheckState.RUNNING:
+        // Returns the progress of a running check. Ensures that both numbers
+        // are at least 1.
         return this.i18n(
-            'checkPasswordsProgress', this.status_.alreadyProcessed || 0,
-            this.status_.remainingInQueue + this.status_.alreadyProcessed);
+            'checkPasswordsProgress', (this.status_.alreadyProcessed || 0) + 1,
+            Math.max(
+                this.status_.remainingInQueue + this.status_.alreadyProcessed,
+                1));
       case CheckState.OFFLINE:
         return this.i18n('checkPasswordsErrorOffline');
       case CheckState.SIGNED_OUT:
@@ -520,5 +532,26 @@
     this.leakedPasswords = resultList.concat(addedResults);
   },
 
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeShowNoCompromisedPasswordsLabel() {
+    // Check if user isn't signed in.
+    if (!this.syncStatus_ || !this.syncStatus_.signedIn) {
+      return false;
+    }
+
+    // Check if breach detection is turned off in settings.
+    if (!this.prefs ||
+        !this.getPref('profile.password_manager_leak_detection').value) {
+      return false;
+    }
+
+    // Return true if there was a successful check and no compromised passwords
+    // were found.
+    return !this.hasLeakedCredentials_(this.leakedPasswords) &&
+        this.showsTimestamp_(this.status_);
+  },
 });
 })();
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_section.html b/chrome/browser/resources/settings/autofill_page/passwords_section.html
index cf97434..031a935 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_section.html
+++ b/chrome/browser/resources/settings/autofill_page/passwords_section.html
@@ -111,7 +111,7 @@
     </settings-toggle-button>
     <template is="dom-if" if="[[enablePasswordCheck_]]">
       <div id="checkPasswordsBannerContainer" class="settings-box"
-          hidden$="[[haveCheckedPasswordsBefore_]]">
+          hidden$="[[!shouldShowBanner_]]">
         <picture>
           <source
               srcset="chrome://settings/images/password_check_neutral_dark.svg"
@@ -121,7 +121,7 @@
         </picture>
       </div>
       <div id="checkPasswordsButton" class="settings-box continuation"
-          hidden$="[[haveCheckedPasswordsBefore_]]">
+          hidden$="[[!shouldShowBanner_]]">
         <div class="start settings-box-text">
           <div>$i18n{checkPasswords}</div>
           <div class="secondary">$i18n{checkPasswordsDescription}</div>
@@ -132,7 +132,7 @@
       </div>
       <div class="settings-box two-line" id="checkPasswordsLinkRow"
           on-click="onCheckPasswordsClick_" actionable
-          hidden$="[[!haveCheckedPasswordsBefore_]]">
+          hidden$="[[shouldShowBanner_]]">
         <iron-icon icon="cr:warning" id="checkPasswordWarningIcon"
             hidden$="[[!hasLeakedCredentials_]]"></iron-icon>
         <div class="start settings-box-text">
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_section.js b/chrome/browser/resources/settings/autofill_page/passwords_section.js
index 729664ec..7f97f2c4 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_section.js
+++ b/chrome/browser/resources/settings/autofill_page/passwords_section.js
@@ -94,7 +94,30 @@
     },
 
     /** @private */
-    haveCheckedPasswordsBefore_: Boolean,
+    signedIn_: {
+      type: Boolean,
+      value: true,
+      computed: 'computeSignedIn_(syncStatus_.signedIn)',
+    },
+
+    /** @private */
+    hasNeverCheckedPasswords_: {
+      type: Boolean,
+      value: false,
+    },
+
+    /** @private */
+    hasStoredPasswords_: {
+      type: Boolean,
+      value: false,
+    },
+
+    shouldShowBanner_: {
+      type: Boolean,
+      value: true,
+      computed: 'computeShouldShowBanner_(' +
+          'signedIn_, hasNeverCheckedPasswords_, hasStoredPasswords_)',
+    },
 
     /** @private */
     hasLeakedCredentials_: {
@@ -234,6 +257,7 @@
       // given entry and is stable with regard to mutations to the list, it is
       // sufficient to just use this id to create a item uid.
       this.updateList('savedPasswords', item => item.entry.id, newList);
+      this.hasStoredPasswords_ = list.length > 0;
     };
 
     const setPasswordExceptionsListener = list => {
@@ -254,7 +278,7 @@
     // TODO(https://crbug.com/1047726) Remove code duplication with
     // password_check.js
     const statusChangeListener = status => {
-      this.haveCheckedPasswordsBefore_ = !!status.elapsedTimeSinceLastCheck;
+      this.hasNeverCheckedPasswords_ = !status.elapsedTimeSinceLastCheck;
     };
 
     this.setIsOptedInForAccountStorageListener_ =
@@ -412,6 +436,23 @@
    * @return {boolean}
    * @private
    */
+  computeSignedIn_() {
+    return !!this.syncStatus_ && !!this.syncStatus_.signedIn;
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  computeShouldShowBanner_() {
+    return this.signedIn_ && this.hasStoredPasswords_ &&
+        this.hasNeverCheckedPasswords_;
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
   computeHidePasswordsLink_() {
     return !!this.syncStatus_ && !!this.syncStatus_.signedIn &&
         !!this.syncPrefs_ && !!this.syncPrefs_.encryptAllData;
diff --git a/chrome/browser/resources/settings/people_page/sync_browser_proxy.js b/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
index 9cb6cb1..05544df 100644
--- a/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
+++ b/chrome/browser/resources/settings/people_page/sync_browser_proxy.js
@@ -100,11 +100,6 @@
   };
 
   /**
-   * @typedef {{requestSucceeded: boolean, historyRecordingEnabled: boolean}}
-   */
-  let HistoryRecordingEnabled;
-
-  /**
    * Key to be used with localStorage.
    * @type {string}
    */
@@ -224,13 +219,6 @@
      * manager in passwords section on page load.
      */
     sendSyncPrefsChanged() {}
-
-    /**
-     * Fetches if history recording is enabled and can be used to provide
-     * personalized experience.
-     * @return {!Promise<!HistoryRecordingEnabled>}
-     */
-    queryIsHistoryRecordingEnabled() {}
   }
 
   /**
@@ -338,11 +326,6 @@
     sendSyncPrefsChanged() {
       chrome.send('SyncPrefsDispatch');
     }
-
-    /** @override */
-    queryIsHistoryRecordingEnabled() {
-      return cr.sendWithPromise('GetIsHistoryRecordingEnabledAndCanBeUsed');
-    }
   }
 
   cr.addSingletonGetter(SyncBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index 1e6482ea..963f2d5 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -16,7 +16,6 @@
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="sync_account_control.html">
 <link rel="import" href="sync_encryption_options.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
 <link rel="import" href="sync_browser_proxy.html">
 <link rel="import" href="../privacy_page/personalization_options.html">
 <link rel="import" href="../router.html">
@@ -84,32 +83,6 @@
         width: 40px;
       }
 
-      #history-usage-state {
-        text-align: end;
-        vertical-align: middle;
-        width: 36px;
-      }
-
-      #history-usage-row[disabled] {
-        pointer-events: none;
-      }
-
-      #history-usage-row[disabled] > #history-usage-state {
-        text-align: center;
-      }
-
-      #history-usage-row > cr-icon-button.icon-external {
-        margin-inline-start: 1px;
-      }
-
-      #history-usage-state paper-spinner-lite {
-        --paper-spinner-color: var(--google-grey-500);
-        --paper-spinner-stroke-width: 2px;
-        height: var(--cr-icon-size);
-        vertical-align: middle;
-        width: var(--cr-icon-size);
-    }
-
       #toast {
         left: 0;
         z-index: 1;
@@ -193,31 +166,13 @@
             </cr-icon-button>
           </div>
 
-          <a id="history-usage-row"
-              class="inherit-color no-outline list-item" tabindex="-1"
+          <a class="inherit-color no-outline list-item" tabindex="-1"
               target="_blank" href="$i18n{activityControlsUrl}"
-              on-click="onActivityControlsTap_"
-              disabled$="[[hideActivityControlsUrl_]]">
+              on-click="onActivityControlsTap_">
             <div class="start settings-box-text">
               $i18n{personalizeGoogleServicesTitle}
-              <div id="history-usage-off-hint"
-                  class="secondary" hidden$="[[!isSwaaOff_(Swaa_)]]">
-                [[getHistoryUsageOffHint_(Swaa_, syncPrefs.encryptAllData,
-                      syncPrefs.typedUrlsSynced)]]
-              </div>
             </div>
-            <div id="history-usage-state"
-                hidden$="[[!syncSetupFriendlySettings_]]">
-              <paper-spinner-lite
-                  class="last" hidden$="[[!isSwaaFetching_(Swaa_)]]" active>
-              </paper-spinner-lite>
-              <div class="secondary" hidden$="[[!isSwaaFetched_(Swaa_)]]">
-                [[getSwaaStateText_(Swaa_)]]
-              </div>
-            </div>
-            <cr-icon-button class="icon-external"
-                hidden$="[[hideActivityControlsUrl_]]">
-            </cr-icon-button>
+            <cr-icon-button class="icon-external"></cr-icon-button>
           </a>
 
           <a id="syncDashboardLink"
diff --git a/chrome/browser/resources/settings/people_page/sync_page.js b/chrome/browser/resources/settings/people_page/sync_page.js
index 2be922e3..2f2ebe0e 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.js
+++ b/chrome/browser/resources/settings/people_page/sync_page.js
@@ -5,32 +5,6 @@
 (function() {
 
 /**
- * These values are persisted to logs and should not be renumbered or re-used.
- * See SyncSetupSettignsDisplayedSwaaState in
- * tools/metrics/histograms/enums.xml.
- * @enum {number}
- */
-const displayedSwaaState = {
-  ON: 0,
-  OFF_ENCRYPTION_ON: 1,
-  OFF_HISTORY_SYNC_OFF: 2,
-  OFF_WEB_AND_APP_ACTIVITY: 3,
-  NONE: 4,
-};
-
-/**
- * All possible states for the sWAA bit.
- * @enum {string}
- */
-const SwaaState = {
-  NOT_FETCHED: 'not-fetched',
-  FETCHING: 'fetching',
-  FAILED: 'failed',
-  ON: 'On',
-  OFF: 'Off',
-};
-
-/**
  * @fileoverview
  * 'settings-sync-page' is the settings page containing sync settings.
  */
@@ -40,7 +14,6 @@
   behaviors: [
     WebUIListenerBehavior,
     settings.RouteObserverBehavior,
-    I18nBehavior,
   ],
 
   properties: {
@@ -142,47 +115,15 @@
     syncSetupFriendlySettings_: {
       type: Boolean,
       value() {
-        return loadTimeData.valueExists('syncSetupFriendlySettings') &&
-            loadTimeData.getBoolean('syncSetupFriendlySettings');
+        return loadTimeData.getBoolean('syncSetupFriendlySettings');
       }
     },
-
-    /**
-     * Whether history is not synced or data is encrypted.
-     * @private
-     */
-    historyNotSyncedOrEncrypted_: {
-      type: Boolean,
-      computed: 'computeHistoryNotSyncedOrEncrypted_(' +
-          'syncPrefs.encryptAllData, syncPrefs.typedUrlsSynced)',
-    },
-
-    /** @private */
-    hideActivityControlsUrl_: {
-      type: Boolean,
-      computed: 'computeHideActivityControlsUrl_(historyNotSyncedOrEncrypted_)',
-    },
-
-    /** @private */
-    Swaa_: {
-      type: String,
-      value: SwaaState.NOT_FETCHED,
-    },
   },
 
-  observers: ['fetchSwaa_(syncSectionDisabled_, historyNotSyncedOrEncrypted_)'],
-
   /** @private {?settings.SyncBrowserProxy} */
   browserProxy_: null,
 
   /**
-   * The visibility changed callback is used to refetch the |Swaa_| bit when
-   * the page is foregrounded.
-   * @private {?Function}
-   */
-  visibilityChangedCallback_: null,
-
-  /**
    * The beforeunload callback is used to show the 'Leave site' dialog. This
    * makes sure that the user has the chance to go back and confirm the sync
    * opt-in before leaving.
@@ -282,134 +223,6 @@
   },
 
   /**
-   * @return {boolean} Returns true if History sync is off or data is encrypted.
-   * @private
-   */
-  computeHistoryNotSyncedOrEncrypted_() {
-    return !!(this.syncPrefs) &&
-        (!this.syncPrefs.typedUrlsSynced || this.syncPrefs.encryptAllData);
-  },
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  computeHideActivityControlsUrl_() {
-    return !!this.syncSetupFriendlySettings_ &&
-        !!this.historyNotSyncedOrEncrypted_;
-  },
-
-
-  /**
-   * Compute and fetch the sWAA bit for sync users. sWAA is 'OFF' if sync
-   * history is off or data is encrypted with custom passphrase. Otherwise,
-   * a query to |Web and App Activity| is needed.
-   * @private
-   */
-  fetchSwaa_() {
-    const router = settings.Router.getInstance();
-    if (router.getCurrentRoute() !== router.getRoutes().SYNC) {
-      return;
-    }
-
-    if (!this.syncSetupFriendlySettings_) {
-      return;
-    }
-
-    if (!this.syncPrefs || this.syncPrefs.encryptAllData === undefined ||
-        this.syncPrefs.typedUrlsSynced === undefined) {
-      return;
-    }
-
-    if (this.syncSectionDisabled_) {
-      this.Swaa_ = SwaaState.NOT_FETCHED;
-      return;
-    }
-
-    if (this.historyNotSyncedOrEncrypted_) {
-      this.Swaa_ = SwaaState.OFF;
-      this.recordDisplayedSwaa();
-      return;
-    }
-
-    if (this.Swaa_ === SwaaState.FETCHING) {
-      return;
-    }
-
-    this.Swaa_ = SwaaState.FETCHING;
-    const updateSwaa = historyRecordingEnabled => {
-      this.Swaa_ = historyRecordingEnabled.requestSucceeded ?
-          (historyRecordingEnabled.historyRecordingEnabled ? SwaaState.ON :
-                                                             SwaaState.OFF) :
-          SwaaState.FAILED;
-      this.recordDisplayedSwaa();
-    };
-    this.browserProxy_.queryIsHistoryRecordingEnabled().then(updateSwaa);
-  },
-
-  /** @private */
-  recordDisplayedSwaa() {
-    let currentDisplayedSwaa;
-    if (this.Swaa_ === SwaaState.FAILED) {
-      currentDisplayedSwaa = displayedSwaaState.NONE;
-    } else if (this.Swaa_ === SwaaState.ON) {
-      currentDisplayedSwaa = displayedSwaaState.ON;
-    } else {
-      assert(this.Swaa_ === SwaaState.OFF);
-      if (this.syncPrefs.encryptAllData) {
-        currentDisplayedSwaa = displayedSwaaState.OFF_ENCRYPTION_ON;
-      } else if (!this.syncPrefs.typedUrlsSynced) {
-        currentDisplayedSwaa = displayedSwaaState.OFF_HISTORY_SYNC_OFF;
-      } else {
-        currentDisplayedSwaa = displayedSwaaState.OFF_WEB_AND_APP_ACTIVITY;
-      }
-    }
-    chrome.metricsPrivate.recordEnumerationValue(
-        'Settings.SyncSetup.DisplayedSwaaState', currentDisplayedSwaa,
-        Object.keys(displayedSwaaState).length);
-  },
-
-  /**
-   * Refetch sWAA when the page is forgrounded, to guarantee the value shown is
-   * most up-to-date.
-   * @private
-   */
-  visibilityHandler_() {
-    if (document.visibilityState === 'visible') {
-      this.fetchSwaa_();
-    }
-  },
-
-  /**
-   * Return hint to explain the sWAA state. It is displayed as secondary text in
-   * the history usage row.
-   * @private
-   */
-  getHistoryUsageOffHint_() {
-    if (this.Swaa_ === SwaaState.OFF) {
-      if (this.syncPrefs.encryptAllData) {
-        return this.i18n('dataEncryptedHint');
-      }
-
-      if (!this.syncPrefs.typedUrlsSynced) {
-        return this.i18n('historySyncOffHint');
-      }
-      return this.i18n('SwaaOffHint');
-    }
-    return '';
-  },
-
-  /**
-   * @private
-   */
-  getSwaaStateText_() {
-    if (!this.isSwaaFetched_()) {
-      return '';
-    }
-    return this.i18n(this.Swaa_ === SwaaState.ON ? 'SwaaOn' : 'SwaaOff');
-  },
-
-  /**
    * @return {boolean}
    * @private
    */
@@ -495,36 +308,10 @@
     return expectedPageStatus == this.pageStatus_;
   },
 
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isSwaaFetching_() {
-    return this.Swaa_ === SwaaState.FETCHING;
-  },
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isSwaaFetched_() {
-    return this.Swaa_ === SwaaState.ON || this.Swaa_ === SwaaState.OFF;
-  },
-
-  /**
-   * @return {boolean}
-   * @private
-   */
-  isSwaaOff_() {
-    return this.Swaa_ === SwaaState.OFF;
-  },
-
   /** @private */
   onNavigateToPage_() {
     const router = settings.Router.getInstance();
     assert(router.getCurrentRoute() == router.getRoutes().SYNC);
-    this.Swaa_ = SwaaState.NOT_FETCHED;
-    this.fetchSwaa_();
     if (this.beforeunloadCallback_) {
       return;
     }
@@ -551,11 +338,6 @@
 
     this.unloadCallback_ = this.onNavigateAwayFromPage_.bind(this);
     window.addEventListener('unload', this.unloadCallback_);
-
-    this.visibilityChangedCallback_ = this.visibilityHandler_.bind(this);
-    window.addEventListener('focus', this.visibilityChangedCallback_);
-    document.addEventListener(
-        'visibilitychange', this.visibilityChangedCallback_);
   },
 
   /** @private */
@@ -577,13 +359,6 @@
       window.removeEventListener('unload', this.unloadCallback_);
       this.unloadCallback_ = null;
     }
-
-    if (this.visibilityChangedCallback_) {
-      window.removeEventListener('focus', this.visibilityChangedCallback_);
-      document.removeEventListener(
-          'visibilitychange', this.visibilityChangedCallback_);
-      this.visibilityChangedCallback_ = null;
-    }
   },
 
   /**
@@ -594,8 +369,13 @@
     this.syncPrefs = syncPrefs;
     this.pageStatus_ = settings.PageStatus.CONFIGURE;
 
-    if (this.Swaa_ === SwaaState.FAILED) {
-      this.fetchSwaa_();
+    // Hide the new passphrase box if (a) full data encryption is enabled,
+    // (b) encrypting all data is not allowed (so far, only applies to
+    // supervised accounts), or (c) the user is a supervised account.
+    if (this.syncPrefs.encryptAllData ||
+        !this.syncPrefs.encryptAllDataAllowed ||
+        (this.syncStatus && this.syncStatus.supervisedUser)) {
+      this.creatingNewPassphrase_ = false;
     }
   },
 
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index a644d11..679803a 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -27,6 +27,7 @@
 js_library("collapse_radio_button") {
   deps = [
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button_behavior",
+    "//ui/webui/resources/cr_elements/policy:cr_policy_indicator",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/privacy_page/collapse_radio_button.html b/chrome/browser/resources/settings/privacy_page/collapse_radio_button.html
index cb861aca..3820469 100644
--- a/chrome/browser/resources/settings/privacy_page/collapse_radio_button.html
+++ b/chrome/browser/resources/settings/privacy_page/collapse_radio_button.html
@@ -3,6 +3,8 @@
 <link rel="import" href="chrome://resources/cr_elements/cr_expand_button/cr_expand_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_behavior.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button_style_css.html">
+<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator.html">
+<link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_indicator_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html">
 <link rel="import" href="../settings_shared_css.html">
 
@@ -13,9 +15,17 @@
         display: block;
       }
 
-      #radioCollapse {
-        display: flex;
-        padding: 0;
+      :host([disabled]) {
+        opacity: 1;
+      }
+
+      cr-policy-indicator,
+      :host([disabled]) cr-expand-button {
+        pointer-events: auto;
+      }
+
+      :host([disabled]) .disc-wrapper {
+        opacity: var(--cr-disabled-opacity);
       }
 
       iron-collapse {
@@ -23,13 +33,21 @@
         margin-inline-start: var(--cr-section-indent-width);
       }
 
+      .separator {
+        margin-inline-end: 0;
+      }
+
       #labelWrapper {
         padding-bottom: 6px;
         padding-top: 6px;
       }
-    </style>
 
-    <div id="radioCollapse" class="settings-box first">
+      #radioCollapse {
+        align-items: center;
+        display: flex;
+      }
+    </style>
+    <div id="radioCollapse">
       <div aria-checked$="[[getAriaChecked_(checked)]]"
           aria-disabled$="[[getAriaDisabled_(disabled)]]"
           aria-labelledby="label"
@@ -51,8 +69,14 @@
           <slot name="sub-label"></slot>
         </div>
       </div>
-      <div class="separator"></div>
-      <cr-expand-button expanded="{{expanded}}"></cr-expand-button>
+      <cr-policy-indicator id="policyIndicator"
+          on-click="onIndicatorClick_"
+          on-aria-label="[[label]]"
+          indicator-type="[[policyIndicatorType]]">
+      </cr-policy-indicator>
+      <div hidden$="[[noCollapse]]" class="separator"></div>
+      <cr-expand-button hidden$="[[noCollapse]]" expanded="{{expanded}}">
+      </cr-expand-button>
     </div>
 
     <iron-collapse opened="[[expanded]]">
diff --git a/chrome/browser/resources/settings/privacy_page/collapse_radio_button.js b/chrome/browser/resources/settings/privacy_page/collapse_radio_button.js
index d717320..0e33605 100644
--- a/chrome/browser/resources/settings/privacy_page/collapse_radio_button.js
+++ b/chrome/browser/resources/settings/privacy_page/collapse_radio_button.js
@@ -16,6 +16,17 @@
       value: false,
     },
 
+    /**
+     * Which indicator type to show (or NONE).
+     * @type {CrPolicyIndicatorType}
+     */
+    policyIndicatorType: {
+      type: String,
+      value: CrPolicyIndicatorType.NONE,
+    },
+
+    noCollapse: Boolean,
+
     label: String,
 
     subLabel: {
@@ -30,4 +41,14 @@
   onCheckedChanged_() {
     this.expanded = this.checked;
   },
+
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onIndicatorClick_(e) {
+    // Prevent interacting with the indicator changing anything when disabled.
+    e.preventDefault();
+    e.stopPropagation();
+  },
 });
diff --git a/chrome/browser/sharing/shared_clipboard/feature_flags.cc b/chrome/browser/sharing/shared_clipboard/feature_flags.cc
index eb7a304..6bc97a07 100644
--- a/chrome/browser/sharing/shared_clipboard/feature_flags.cc
+++ b/chrome/browser/sharing/shared_clipboard/feature_flags.cc
@@ -15,6 +15,9 @@
 const base::FeatureParam<std::string> kRemoteCopyAllowedOrigins = {
     &kRemoteCopyReceiver, "RemoteCopyAllowedOrigins", ""};
 
+const base::Feature kRemoteCopyImageNotification{
+    "RemoteCopyImageNotification", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kRemoteCopyProgressNotification{
     "RemoteCopyProgressNotification", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) ||
diff --git a/chrome/browser/sharing/shared_clipboard/feature_flags.h b/chrome/browser/sharing/shared_clipboard/feature_flags.h
index ad317c4..c07172a 100644
--- a/chrome/browser/sharing/shared_clipboard/feature_flags.h
+++ b/chrome/browser/sharing/shared_clipboard/feature_flags.h
@@ -22,6 +22,9 @@
 // List of allowed origins to fetch images from, comma separated.
 extern const base::FeatureParam<std::string> kRemoteCopyAllowedOrigins;
 
+// Feature to enable image notifications for remote copy messages.
+extern const base::Feature kRemoteCopyImageNotification;
+
 // Feature to enable progress notifications for remote copy messages.
 extern const base::Feature kRemoteCopyProgressNotification;
 #endif  // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) ||
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 5869167..d7eb9d9 100644
--- a/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc
+++ b/chrome/browser/sharing/shared_clipboard/remote_copy_message_handler.cc
@@ -440,6 +440,11 @@
   LogRemoteCopyDecodeImageTime(timer_.Elapsed());
   LogRemoteCopyReceivedImageSizeAfterDecode(image.computeByteSize());
 
+  if (!base::FeatureList::IsEnabled(kRemoteCopyImageNotification)) {
+    WriteImageAndShowNotification(image, image);
+    return;
+  }
+
   double scale = std::min(
       static_cast<double>(kNotificationImageMaxWidthPx) / image.width(),
       static_cast<double>(kNotificationImageMaxHeightPx) / image.height());
@@ -518,14 +523,18 @@
     const std::string& notification_id) {
   TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::ShowNotification");
 
+  bool use_image_notification =
+      base::FeatureList::IsEnabled(kRemoteCopyImageNotification) &&
+      !image.drawsNothing();
+
   message_center::RichNotificationData rich_notification_data;
-  if (!image.drawsNothing())
+  if (use_image_notification)
     rich_notification_data.image = gfx::Image::CreateFrom1xBitmap(image);
   rich_notification_data.vector_small_image = &kSendTabToSelfIcon;
 
   message_center::NotificationType type =
-      image.drawsNothing() ? message_center::NOTIFICATION_TYPE_SIMPLE
-                           : message_center::NOTIFICATION_TYPE_IMAGE;
+      use_image_notification ? message_center::NOTIFICATION_TYPE_IMAGE
+                             : message_center::NOTIFICATION_TYPE_SIMPLE;
 
   ui::Accelerator paste_accelerator(ui::VKEY_V, ui::EF_PLATFORM_ACCELERATOR);
 
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 b2cf72ec..329ac4fb 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
@@ -235,8 +235,8 @@
 TEST_F(RemoteCopyMessageHandlerTest, ImageNotificationWithoutProgressFlag) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeaturesAndParameters(
-      {{kRemoteCopyReceiver,
-        {{kRemoteCopyAllowedOrigins.name, kTestImageUrl}}}},
+      {{kRemoteCopyReceiver, {{kRemoteCopyAllowedOrigins.name, kTestImageUrl}}},
+       {kRemoteCopyImageNotification, {}}},
       {kRemoteCopyProgressNotification});
 
   base::RunLoop run_loop;
@@ -267,10 +267,50 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(RemoteCopyMessageHandlerTest,
+       NoImageAndNoProgressNotificationWhenDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeaturesAndParameters(
+      {{kRemoteCopyReceiver,
+        {{kRemoteCopyAllowedOrigins.name, kTestImageUrl}}}},
+      {kRemoteCopyImageNotification, kRemoteCopyProgressNotification});
+
+  base::RunLoop run_loop;
+  ClipboardObserver observer(run_loop.QuitClosure());
+  ui::ClipboardMonitor::GetInstance()->AddObserver(&observer);
+
+  message_handler_->OnMessage(CreateMessageWithImage(kTestImageUrl),
+                              base::DoNothing());
+
+  // There should be no progress notification with the flag disabled.
+  EXPECT_FALSE(HasProgressNotification());
+
+  // Let tasks run until the image is decoded, written to the clipboard and the
+  // simple notification is shown (the image notification feature is disabled).
+  run_loop.Run();
+  ui::ClipboardMonitor::GetInstance()->RemoveObserver(&observer);
+
+  // After finishing the transfer there should be no progress notification.
+  EXPECT_FALSE(HasProgressNotification());
+
+  // Expect the image to be in the clipboard now.
+  SkBitmap image = GetClipboardImage();
+  EXPECT_TRUE(gfx::BitmapsAreEqual(*image_, image));
+
+  // Expect a simple notification.
+  auto notification = GetNotification();
+  EXPECT_TRUE(notification.image().IsEmpty());
+
+  // Calling GetDefaultStoragePartition creates tasks that need to run before
+  // the ScopedFeatureList is destroyed. See crbug.com/1060869
+  task_environment_.RunUntilIdle();
+}
+
 TEST_F(RemoteCopyMessageHandlerTest, ImageNotificationWithProgressFlag) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeaturesAndParameters(
       {{kRemoteCopyReceiver, {{kRemoteCopyAllowedOrigins.name, kTestImageUrl}}},
+       {kRemoteCopyImageNotification, {}},
        {kRemoteCopyProgressNotification, {}}},
       {});
 
@@ -335,6 +375,7 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeaturesAndParameters(
       {{kRemoteCopyReceiver, {{kRemoteCopyAllowedOrigins.name, kTestImageUrl}}},
+       {kRemoteCopyImageNotification, {}},
        {kRemoteCopyProgressNotification, {}}},
       {});
 
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 5361f2b..2373b2dd 100644
--- a/chrome/browser/sync/test/integration/password_manager_sync_test.cc
+++ b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/password_manager/account_storage/account_password_store_factory.h"
 #include "chrome/browser/password_manager/password_manager_test_base.h"
 #include "chrome/browser/password_manager/password_store_factory.h"
+#include "chrome/browser/sync/test/integration/encryption_helper.h"
 #include "chrome/browser/sync/test/integration/passwords_helper.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
 #include "chrome/browser/sync/test/integration/secondary_account_helper.h"
@@ -34,6 +35,7 @@
 namespace {
 
 using testing::ElementsAre;
+using testing::IsEmpty;
 
 MATCHER_P2(MatchesLogin, username, password, "") {
   return arg->username_value == base::UTF8ToUTF16(username) &&
@@ -103,6 +105,8 @@
 
     ASSERT_TRUE(embedded_test_server()->Start());
     host_resolver()->AddRule("*", "127.0.0.1");
+
+    encryption_helper::SetKeystoreNigoriInFakeServer(GetFakeServer());
   }
 
   void TearDownOnMainThread() override {
@@ -110,6 +114,46 @@
     SyncTest::TearDownOnMainThread();
   }
 
+  void SetupSyncTransportWithPasswordAccountStorage() {
+    // Setup Sync for a secondary account (i.e. in transport mode).
+    secondary_account_helper::SignInSecondaryAccount(
+        GetProfile(0), &test_url_loader_factory_, "user@email.com");
+    ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+    ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureEnabled());
+
+    // Let the user opt in to the passwords account storage, and wait for it to
+    // become active.
+    password_manager_util::SetAccountStorageOptIn(GetProfile(0)->GetPrefs(),
+                                                  GetSyncService(0), true);
+    PasswordSyncActiveChecker(GetSyncService(0)).Wait();
+    ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS));
+  }
+
+  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.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());
+  }
+
+  void AddLocalPassword(const std::string& username,
+                        const std::string& password) {
+    scoped_refptr<password_manager::PasswordStore> password_store =
+        passwords_helper::GetPasswordStore(0);
+    password_store->AddLogin(CreateTestPasswordForm(username, password));
+  }
+
   // Synchronously reads all credentials from the profile password store and
   // returns them.
   std::vector<std::unique_ptr<autofill::PasswordForm>>
@@ -142,6 +186,19 @@
     observer.Wait();
   }
 
+  void FillAndSubmitPasswordForm(content::WebContents* web_contents,
+                                 const std::string& username,
+                                 const std::string& password) {
+    NavigationObserver observer(web_contents);
+    std::string fill_and_submit = base::StringPrintf(
+        "document.getElementById('username_field').value = '%s';"
+        "document.getElementById('password_field').value = '%s';"
+        "document.getElementById('input_submit_button').click()",
+        username.c_str(), password.c_str());
+    ASSERT_TRUE(content::ExecJs(web_contents, fill_and_submit));
+    observer.Wait();
+  }
+
  private:
   base::test::ScopedFeatureList feature_list_;
 
@@ -159,32 +216,13 @@
   content::WebContents* web_contents = nullptr;
   GetNewTab(GetBrowser(0), &web_contents);
 
-  // Setup Sync for a secondary account (i.e. in transport mode).
-  secondary_account_helper::SignInSecondaryAccount(
-      GetProfile(0), &test_url_loader_factory_, "user@email.com");
-  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
-  ASSERT_FALSE(GetSyncService(0)->IsSyncFeatureEnabled());
-
-  // Let the user opt in to the passwords account storage, and wait for it to
-  // become active.
-  password_manager_util::SetAccountStorageOptIn(GetProfile(0)->GetPrefs(),
-                                                GetSyncService(0), true);
-  PasswordSyncActiveChecker(GetSyncService(0)).Wait();
-  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::PASSWORDS));
+  SetupSyncTransportWithPasswordAccountStorage();
 
   // Part 1: Save a password; it should go into the account store by default.
   {
-    // Navigate to a page with a password form.
+    // Navigate to a page with a password form, fill it out, and submit it.
     NavigateToFile(web_contents, "/password/password_form.html");
-
-    // Fill out and submit the password form.
-    NavigationObserver observer(web_contents);
-    std::string fill_and_submit =
-        "document.getElementById('username_field').value = 'accountuser';"
-        "document.getElementById('password_field').value = 'accountpass';"
-        "document.getElementById('input_submit_button').click()";
-    ASSERT_TRUE(content::ExecJs(web_contents, fill_and_submit));
-    observer.Wait();
+    FillAndSubmitPasswordForm(web_contents, "accountuser", "accountpass");
 
     // Save the password and check the store.
     BubbleObserver bubble_observer(web_contents);
@@ -203,7 +241,7 @@
       GetProfile(0)->GetPrefs(), GetSyncService(0),
       autofill::PasswordForm::Store::kProfileStore);
   {
-    // Navigate to a page with a password form.
+    // Navigate to a page with a password form, fill it out, and submit it.
     // TODO(crbug.com/1058339): If we use the same URL as in part 1 here, then
     // the test fails because the *account* data gets filled and submitted
     // again. This is because the password manager is "smart" and prefers
@@ -216,15 +254,7 @@
     // autofill on pageload, see PasswordManagerBrowserTestWithAutofillDisabled.
     // NavigateToFile(web_contents, "/password/password_form.html");
     NavigateToFile(web_contents, "/password/simple_password.html");
-
-    // Fill out and submit the password form.
-    NavigationObserver observer(web_contents);
-    std::string fill_and_submit =
-        "document.getElementById('username_field').value = 'localuser';"
-        "document.getElementById('password_field').value = 'localpass';"
-        "document.getElementById('input_submit_button').click()";
-    ASSERT_TRUE(content::ExecJs(web_contents, fill_and_submit));
-    observer.Wait();
+    FillAndSubmitPasswordForm(web_contents, "localuser", "localpass");
 
     // Save the password and check the store.
     BubbleObserver bubble_observer(web_contents);
@@ -237,6 +267,189 @@
                 ElementsAre(MatchesLogin("localuser", "localpass")));
   }
 }
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest, UpdateInProfileStore) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  AddLocalPassword("user", "localpass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit a different password.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "newpass");
+
+  // There should be an update bubble; accept it.
+  BubbleObserver bubble_observer(web_contents);
+  ASSERT_TRUE(bubble_observer.IsUpdatePromptShownAutomatically());
+  bubble_observer.AcceptUpdatePrompt();
+
+  // The updated password should be in the profile store, while the account
+  // store should still be empty.
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(), IsEmpty());
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest, UpdateInAccountStore) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  AddPasswordToFakeServer("user", "accountpass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit a different password.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "newpass");
+
+  // There should be an update bubble; accept it.
+  BubbleObserver bubble_observer(web_contents);
+  ASSERT_TRUE(bubble_observer.IsUpdatePromptShownAutomatically());
+  bubble_observer.AcceptUpdatePrompt();
+
+  // The updated password should be in the account store, while the profile
+  // store should still be empty.
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(), IsEmpty());
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       UpdateMatchingCredentialInBothStores) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  AddPasswordToFakeServer("user", "pass");
+  AddLocalPassword("user", "pass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit a different password.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "newpass");
+
+  // There should be an update bubble; accept it.
+  BubbleObserver bubble_observer(web_contents);
+  ASSERT_TRUE(bubble_observer.IsUpdatePromptShownAutomatically());
+  bubble_observer.AcceptUpdatePrompt();
+
+  // The updated password should be in both stores.
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       UpdateMismatchingCredentialInBothStores) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  AddPasswordToFakeServer("user", "accountpass");
+  AddLocalPassword("user", "localpass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit a different password.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "newpass");
+
+  // There should be an update bubble; accept it.
+  BubbleObserver bubble_observer(web_contents);
+  ASSERT_TRUE(bubble_observer.IsUpdatePromptShownAutomatically());
+  bubble_observer.AcceptUpdatePrompt();
+
+  // The updated password should be in both stores.
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "newpass")));
+}
+
+// Tests that if credentials for the same username, but with different passwords
+// exist in the two stores, and one of them is used to successfully log in, the
+// other one is silently updated to match.
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       AutoUpdateFromAccountToProfileOnSuccessfulUse) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  // Add credentials for the same username, but with different passwords, to the
+  // two stores.
+  AddPasswordToFakeServer("user", "accountpass");
+  AddLocalPassword("user", "localpass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  // Now we have credentials for the same user, but with different passwords, in
+  // the two stores.
+  ASSERT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "localpass")));
+  ASSERT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "accountpass")));
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit the version of the credentials from the profile
+  // store.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "localpass");
+
+  // Now the credential should of course still be in the profile store...
+  ASSERT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "localpass")));
+  // ...but also the one in the account store should have been silently updated
+  // to match.
+  EXPECT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "localpass")));
+}
+
+// Tests that if credentials for the same username, but with different passwords
+// exist in the two stores, and one of them is used to successfully log in, the
+// other one is silently updated to match.
+IN_PROC_BROWSER_TEST_F(PasswordManagerSyncTest,
+                       AutoUpdateFromProfileToAccountOnSuccessfulUse) {
+  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
+
+  // Add credentials for the same username, but with different passwords, to the
+  // two stores.
+  AddPasswordToFakeServer("user", "accountpass");
+  AddLocalPassword("user", "localpass");
+
+  SetupSyncTransportWithPasswordAccountStorage();
+
+  // Now we have credentials for the same user, but with different passwords, in
+  // the two stores.
+  ASSERT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "localpass")));
+  ASSERT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "accountpass")));
+
+  content::WebContents* web_contents = nullptr;
+  GetNewTab(GetBrowser(0), &web_contents);
+
+  // Go to a form and submit the version of the credentials from the account
+  // store.
+  NavigateToFile(web_contents, "/password/simple_password.html");
+  FillAndSubmitPasswordForm(web_contents, "user", "accountpass");
+
+  // Now the credential should of course still be in the account store...
+  ASSERT_THAT(GetAllLoginsFromAccountPasswordStore(),
+              ElementsAre(MatchesLogin("user", "accountpass")));
+  // ...but also the one in the profile store should have been updated to match.
+  EXPECT_THAT(GetAllLoginsFromProfilePasswordStore(),
+              ElementsAre(MatchesLogin("user", "accountpass")));
+}
 #endif  // !defined(OS_CHROMEOS)
 
 }  // namespace
diff --git a/chrome/browser/sync/test/integration/passwords_helper.cc b/chrome/browser/sync/test/integration/passwords_helper.cc
index 99af7e98..f4117f09 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.cc
+++ b/chrome/browser/sync/test/integration/passwords_helper.cc
@@ -333,6 +333,13 @@
 }
 
 void InjectKeystoreEncryptedServerPassword(
+    const autofill::PasswordForm& form,
+    fake_server::FakeServer* fake_server) {
+  InjectKeystoreEncryptedServerPassword(SpecificsDataFromPasswordForm(form),
+                                        fake_server);
+}
+
+void InjectKeystoreEncryptedServerPassword(
     const sync_pb::PasswordSpecificsData& password_data,
     fake_server::FakeServer* fake_server) {
   InjectEncryptedServerPassword(
diff --git a/chrome/browser/sync/test/integration/passwords_helper.h b/chrome/browser/sync/test/integration/passwords_helper.h
index 52be73e0..56fc0c2c 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.h
+++ b/chrome/browser/sync/test/integration/passwords_helper.h
@@ -116,6 +116,10 @@
     fake_server::FakeServer* fake_server);
 // As above, but using standard Keystore encryption.
 void InjectKeystoreEncryptedServerPassword(
+    const autofill::PasswordForm& form,
+    fake_server::FakeServer* fake_server);
+// As above, but using standard Keystore encryption and PasswordSpecificsData.
+void InjectKeystoreEncryptedServerPassword(
     const sync_pb::PasswordSpecificsData& password_data,
     fake_server::FakeServer* fake_server);
 
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
index eeb2a13..dec75f3 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
@@ -60,7 +60,8 @@
   MOCK_METHOD1(HideNotification, void(const std::string&));
   MOCK_METHOD1(RemoveItem, void(const std::string&));
   MOCK_CONST_METHOD0(GetTaskRunner, scoped_refptr<base::SequencedTaskRunner>());
-  MOCK_METHOD1(LogMediaSessionActionButtonPressed, void(const std::string&));
+  MOCK_METHOD2(LogMediaSessionActionButtonPressed,
+               void(const std::string&, MediaSessionAction));
 };
 
 class MockMediaNotificationView
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc
index 3fe7349..7207356 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_notification_provider_unittest.cc
@@ -38,7 +38,9 @@
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
     return nullptr;
   }
-  MOCK_METHOD1(LogMediaSessionActionButtonPressed, void(const std::string& id));
+  MOCK_METHOD2(LogMediaSessionActionButtonPressed,
+               void(const std::string& id,
+                    media_session::mojom::MediaSessionAction action));
 };
 
 class MockMediaNotificationView
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.cc b/chrome/browser/ui/global_media_controls/media_notification_service.cc
index 7d812c7..46818e97 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.cc
@@ -19,13 +19,19 @@
 #include "components/media_message_center/media_notification_item.h"
 #include "components/media_message_center/media_notification_util.h"
 #include "components/media_message_center/media_session_notification_item.h"
+#include "components/ukm/content/source_url_recorder.h"
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/media_session_service.h"
 #include "media/base/media_switches.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 
 namespace {
 
+// The maximum number of actions we will record to UKM for a specific source.
+constexpr int kMaxActionsRecordedToUKM = 100;
+
 constexpr int kAutoDismissTimerInMinutesDefault = 60;  // minutes
 
 constexpr const char kAutoDismissTimerInMinutesParamName[] = "timer_in_minutes";
@@ -427,7 +433,8 @@
 }
 
 void MediaNotificationService::LogMediaSessionActionButtonPressed(
-    const std::string& id) {
+    const std::string& id,
+    media_session::mojom::MediaSessionAction action) {
   auto it = sessions_.find(id);
   if (it == sessions_.end())
     return;
@@ -438,6 +445,17 @@
 
   base::UmaHistogramBoolean("Media.GlobalMediaControls.UserActionFocus",
                             IsWebContentsFocused(web_contents));
+
+  ukm::UkmRecorder* recorder = ukm::UkmRecorder::Get();
+  ukm::SourceId source_id =
+      ukm::GetSourceIdForWebContentsDocument(web_contents);
+
+  if (++actions_recorded_to_ukm_[source_id] > kMaxActionsRecordedToUKM)
+    return;
+
+  ukm::builders::Media_GlobalMediaControls_ActionButtonPressed(source_id)
+      .SetMediaSessionAction(static_cast<int64_t>(action))
+      .Record(recorder);
 }
 
 void MediaNotificationService::OnContainerClicked(const std::string& id) {
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.h b/chrome/browser/ui/global_media_controls/media_notification_service.h
index c72036aa..1c3c1a3d 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.h
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.h
@@ -25,6 +25,7 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 #include "services/media_session/public/mojom/media_controller.mojom-forward.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 
 namespace content {
 class WebContents;
@@ -63,7 +64,9 @@
   void HideNotification(const std::string& id) override;
   void RemoveItem(const std::string& id) override;
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
-  void LogMediaSessionActionButtonPressed(const std::string& id) override;
+  void LogMediaSessionActionButtonPressed(
+      const std::string& id,
+      media_session::mojom::MediaSessionAction action) override;
 
   // MediaNotificationContainerObserver implementation.
   void OnContainerExpanded(bool expanded) override {}
@@ -270,6 +273,10 @@
 
   base::ObserverList<MediaNotificationServiceObserver> observers_;
 
+  // Tracks the number of times we have recorded an action for a specific
+  // source. We use this to cap the number of UKM recordings per site.
+  std::map<ukm::SourceId, int> actions_recorded_to_ukm_;
+
   base::WeakPtrFactory<MediaNotificationService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/page_info/page_info.cc b/chrome/browser/ui/page_info/page_info.cc
index b193efe6..698151c8 100644
--- a/chrome/browser/ui/page_info/page_info.cc
+++ b/chrome/browser/ui/page_info/page_info.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/browsing_data/browsing_data_database_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/local_shared_objects_container.h"
 #include "chrome/browser/history/history_service_factory.h"
@@ -48,6 +47,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/theme_resources.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/content_settings/core/browser/content_settings_registry.h"
 #include "components/content_settings/core/browser/content_settings_utils.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index bc10974..e2bc74f 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -56,8 +56,8 @@
 #include "chrome/grit/theme_resources.h"
 #include "components/bookmarks/common/bookmark_pref_names.h"
 #include "components/dom_distiller/content/browser/distillable_page_utils.h"
+#include "components/dom_distiller/content/browser/uma_helper.h"
 #include "components/dom_distiller/core/dom_distiller_features.h"
-#include "components/dom_distiller/core/uma_helper.h"
 #include "components/dom_distiller/core/url_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/signin_metrics.h"
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index 87d6a35..adfbdf3 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -1163,7 +1163,9 @@
       case autofill::PopupItemId::POPUP_ITEM_ID_CREDIT_CARD_SIGNIN_PROMO:
       case autofill::PopupItemId::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY:
       case autofill::PopupItemId::POPUP_ITEM_ID_HIDE_AUTOFILL_SUGGESTIONS:
-      case autofill::PopupItemId::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN:
+      case autofill::PopupItemId::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN:
+      case autofill::PopupItemId::
+          POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE:
       case autofill::PopupItemId::POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS:
       case autofill::PopupItemId::POPUP_ITEM_ID_USE_VIRTUAL_CARD:
         // This is a footer, so this suggestion will be processed later. Don't
diff --git a/chrome/browser/ui/views/collected_cookies_views.cc b/chrome/browser/ui/views/collected_cookies_views.cc
index 6abfaca..eb923bf 100644
--- a/chrome/browser/ui/views/collected_cookies_views.cc
+++ b/chrome/browser/ui/views/collected_cookies_views.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/browsing_data/browsing_data_database_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/cookies_tree_model.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/content_settings/local_shared_objects_container.h"
@@ -26,6 +25,7 @@
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/cookie_info_view.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/strings/grit/components_strings.h"
diff --git a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
index 00a8452..35a95ca 100644
--- a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
+++ b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.cc
@@ -7,19 +7,39 @@
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/dom_distiller/content/browser/uma_helper.h"
 #include "components/dom_distiller/core/dom_distiller_features.h"
-#include "components/dom_distiller/core/uma_helper.h"
 #include "components/dom_distiller/core/url_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/ukm/content/source_url_recorder.h"
+#include "content/public/browser/navigation_details.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
 #include "ui/base/l10n/l10n_util.h"
 
+using dom_distiller::UMAHelper;
 using dom_distiller::url_utils::IsDistilledPage;
 
+namespace {
+UMAHelper::ReaderModePageType GetPageType(content::WebContents* contents) {
+  // Determine if the current web contents is a distilled page.
+  UMAHelper::ReaderModePageType page_type =
+      UMAHelper::ReaderModePageType::kNone;
+  if (IsDistilledPage(contents->GetLastCommittedURL())) {
+    page_type = UMAHelper::ReaderModePageType::kDistilled;
+  } else {
+    base::Optional<dom_distiller::DistillabilityResult> distillability =
+        dom_distiller::GetLatestResult(contents);
+    if (distillability && distillability.value().is_distillable)
+      page_type = UMAHelper::ReaderModePageType::kDistillable;
+  }
+  return page_type;
+}
+
+}  // namespace
+
 ReaderModeIconView::ReaderModeIconView(
     CommandUpdater* command_updater,
     IconLabelBubbleView::Delegate* icon_label_bubble_delegate,
@@ -44,14 +64,37 @@
     AnimateInkDrop(views::InkDropState::HIDDEN, nullptr);
 }
 
+void ReaderModeIconView::ReadyToCommitNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!navigation_handle->IsInMainFrame())
+    return;
+  // When navigation is about to happen, ensure timers are appropriately stopped
+  // and reset.
+  UMAHelper::UpdateTimersOnNavigation(GetWebContents(),
+                                      GetPageType(GetWebContents()));
+}
+
+void ReaderModeIconView::DocumentAvailableInMainFrame() {
+  UMAHelper::StartTimerIfNeeded(GetWebContents(),
+                                GetPageType(GetWebContents()));
+}
+
 void ReaderModeIconView::UpdateImpl() {
   content::WebContents* contents = GetWebContents();
   if (!contents) {
     SetVisible(false);
     return;
   }
+  UMAHelper::ReaderModePageType page_type = GetPageType(contents);
 
-  if (IsDistilledPage(contents->GetLastCommittedURL())) {
+  // WebContentsObserver::web_contents() is not updated until the call to
+  // Observe() below, so it should still contain the old contents. This will
+  // be used to ensure the timers for UMA are updated for the old web contents.
+  content::WebContents* old_contents = web_contents();
+  if (contents != old_contents)
+    UMAHelper::UpdateTimersOnContentsChange(contents, old_contents);
+
+  if (page_type == UMAHelper::ReaderModePageType::kDistilled) {
     SetVisible(true);
     SetActive(true);
   } else {
@@ -65,17 +108,12 @@
     }
     // If the currently active web contents has changed since last time, stop
     // observing the old web contents and start observing the new one.
-    // (WebContentsObserver::web_contents() is not updated until the call to
-    // Observe() below, so it should still contain the old contents.)
-    content::WebContents* old_contents = web_contents();
     if (old_contents != contents) {
       if (old_contents)
         dom_distiller::RemoveObserver(old_contents, this);
       dom_distiller::AddObserver(contents, this);
     }
-    base::Optional<dom_distiller::DistillabilityResult> distillability =
-        dom_distiller::GetLatestResult(contents);
-    SetVisible(distillability && distillability.value().is_distillable);
+    SetVisible(page_type == UMAHelper::ReaderModePageType::kDistillable);
     SetActive(false);
   }
 
@@ -131,4 +169,8 @@
         .Record(ukm::UkmRecorder::Get());
   }
   Update();
+  // Once we know the type of page we are on (distillable or not), we can
+  // update the timers.
+  UMAHelper::ReaderModePageType page_type = GetPageType(contents);
+  UMAHelper::StartTimerIfNeeded(contents, page_type);
 }
diff --git a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
index 8e69531c..fe21e93 100644
--- a/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
+++ b/chrome/browser/ui/views/reader_mode/reader_mode_icon_view.h
@@ -33,11 +33,16 @@
   ~ReaderModeIconView() override;
 
  protected:
+  // content:WebContentsObserver overrides:
   // Detect when navigation to the distilled page completes. This is required to
   // correctly update the icon's inkdrop.
   void DidFinishNavigation(
       content::NavigationHandle* navigation_handle) override;
+  void ReadyToCommitNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DocumentAvailableInMainFrame() override;
 
+  // PageActionIconView overrides:
   void UpdateImpl() override;
   const gfx::VectorIcon& GetVectorIcon() const override;
   base::string16 GetTextForTooltipAndAccessibleName() const override;
@@ -48,6 +53,7 @@
   // intentionally does not display a bubble when activated.
   views::BubbleDialogDelegateView* GetBubble() const override;
 
+  // dom_distiller::DistillabilityObserver overrides:
   void OnResult(const dom_distiller::DistillabilityResult& result) override;
 
  private:
diff --git a/chrome/browser/ui/views/sharing/remote_copy_browsertest.cc b/chrome/browser/ui/views/sharing/remote_copy_browsertest.cc
index f05a199..d8291d2 100644
--- a/chrome/browser/ui/views/sharing/remote_copy_browsertest.cc
+++ b/chrome/browser/ui/views/sharing/remote_copy_browsertest.cc
@@ -184,9 +184,11 @@
     EXPECT_TRUE(server_->Start());
 
     url::Origin allowlist_origin = url::Origin::Create(server_->base_url());
-    feature_list_.InitAndEnableFeatureWithParameters(
-        kRemoteCopyReceiver,
-        {{kRemoteCopyAllowedOrigins.name, allowlist_origin.Serialize()}});
+    feature_list_.InitWithFeaturesAndParameters(
+        {{kRemoteCopyReceiver,
+          {{kRemoteCopyAllowedOrigins.name, allowlist_origin.Serialize()}}},
+         {kRemoteCopyImageNotification, {}}},
+        {});
   }
 };
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc b/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
index 70c961a..c9cfb83 100644
--- a/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/calculator/size_calculator.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/browsing_data/browsing_data_file_system_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_flash_lso_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_indexed_db_helper.h"
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
 #include "chrome/browser/browsing_data/browsing_data_service_worker_helper.h"
 #include "chrome/browser/chromeos/crostini/crostini_features.h"
 #include "chrome/browser/chromeos/file_manager/path_util.h"
@@ -28,6 +27,7 @@
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/storage_manager/arc_storage_manager.h"
 #include "components/browsing_data/content/conditional_cache_counting_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 #include "components/user_manager/user_manager.h"
 #include "content/public/browser/storage_partition.h"
 
@@ -172,7 +172,7 @@
         storage_partition->GetPath(),
         new BrowsingDataCookieHelper(storage_partition),
         new BrowsingDataDatabaseHelper(profile_),
-        new BrowsingDataLocalStorageHelper(profile_),
+        new browsing_data::LocalStorageHelper(profile_),
         new BrowsingDataAppCacheHelper(storage_partition->GetAppCacheService()),
         new BrowsingDataIndexedDBHelper(storage_partition),
         BrowsingDataFileSystemHelper::Create(
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc
index aaed3dc..57cd1e34 100644
--- a/chrome/browser/ui/webui/settings/people_handler.cc
+++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -16,7 +16,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "chrome/browser/history/web_history_service_factory.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -32,14 +31,12 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/singleton_tabs.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/autofill/core/common/autofill_prefs.h"
-#include "components/browsing_data/core/history_notice_utils.h"
 #include "components/prefs/pref_service.h"
 #include "components/signin/core/browser/signin_error_controller.h"
 #include "components/signin/public/base/signin_metrics.h"
@@ -54,9 +51,7 @@
 #include "components/sync/driver/sync_service_utils.h"
 #include "components/sync/driver/sync_user_settings.h"
 #include "components/unified_consent/unified_consent_metrics.h"
-#include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_view_host.h"
-#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -300,11 +295,6 @@
       "SyncPrefsDispatch",
       base::BindRepeating(&PeopleHandler::HandleSyncPrefsDispatch,
                           base::Unretained(this)));
-  web_ui()->RegisterMessageCallback(
-      "GetIsHistoryRecordingEnabledAndCanBeUsed",
-      base::BindRepeating(
-          &PeopleHandler::HandleGetIsHistoryRecordingEnabledAndCanBeUsed,
-          base::Unretained(this)));
 #if defined(OS_CHROMEOS)
   web_ui()->RegisterMessageCallback(
       "AttemptUserExit",
@@ -470,61 +460,6 @@
     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
 }
 
-void PeopleHandler::HandleGetIsHistoryRecordingEnabledAndCanBeUsed(
-    const base::ListValue* args) {
-  AllowJavascript();
-  std::string webui_callback_id;
-  CHECK(args->GetString(0, &webui_callback_id));
-
-  DCHECK(base::FeatureList::IsEnabled(features::kSyncSetupFriendlySettings));
-  syncer::SyncService* sync_service = GetSyncService();
-  if (!sync_service) {
-    OnQueryHistoryRecordingCompletion(webui_callback_id, nullptr,
-                                      base::nullopt);
-    return;
-  }
-
-  if (sync_service->GetUserSettings()->IsUsingSecondaryPassphrase()) {
-    OnQueryHistoryRecordingCompletion(webui_callback_id, nullptr, false);
-    return;
-  }
-
-  std::unique_ptr<history::WebHistoryService::Request> request =
-      browsing_data::CreateQueryWebAndAppActivityRequest(
-          IdentityManagerFactory::GetForProfile(profile_),
-          content::BrowserContext::GetDefaultStoragePartition(profile_)
-              ->GetURLLoaderFactoryForBrowserProcess(),
-          base::BindOnce(&PeopleHandler::OnQueryHistoryRecordingCompletion,
-                         weak_factory_.GetWeakPtr(), webui_callback_id));
-  DCHECK(request);
-  auto* request_ptr = request.get();
-  web_and_app_activity_requests_.insert(std::move(request));
-  request_ptr->Start();
-}
-
-void PeopleHandler::OnQueryHistoryRecordingCompletion(
-    const std::string& webui_callback_id,
-    history::WebHistoryService::Request* request,
-    const base::Optional<bool>& history_recording_enabled) {
-  if (request) {
-    auto it =
-        std::find_if(web_and_app_activity_requests_.begin(),
-                     web_and_app_activity_requests_.end(),
-                     [request](const auto& r) { return r.get() == request; });
-    DCHECK(web_and_app_activity_requests_.end() != it);
-    web_and_app_activity_requests_.erase(it);
-  }
-
-  if (!IsJavascriptAllowed())
-    return;
-
-  std::unique_ptr<base::DictionaryValue> status(new base::DictionaryValue);
-  status->SetBoolean("requestSucceeded", history_recording_enabled.has_value());
-  status->SetBoolean("historyRecordingEnabled",
-                     history_recording_enabled.value_or(false));
-  ResolveJavascriptCallback(base::Value(webui_callback_id), *status);
-}
-
 void PeopleHandler::HandleGetStoredAccounts(const base::ListValue* args) {
   AllowJavascript();
   CHECK_EQ(1U, args->GetSize());
diff --git a/chrome/browser/ui/webui/settings/people_handler.h b/chrome/browser/ui/webui/settings/people_handler.h
index 84ec3ce..50093e1 100644
--- a/chrome/browser/ui/webui/settings/people_handler.h
+++ b/chrome/browser/ui/webui/settings/people_handler.h
@@ -16,7 +16,6 @@
 #include "build/buildflag.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
-#include "components/history/core/browser/web_history_service.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/signin/public/base/signin_buildflags.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -155,8 +154,6 @@
   void HandleSetEncryption(const base::ListValue* args);
   void HandleShowSetupUI(const base::ListValue* args);
   void HandleSyncPrefsDispatch(const base::ListValue* args);
-  void HandleGetIsHistoryRecordingEnabledAndCanBeUsed(
-      const base::ListValue* args);
 #if defined(OS_CHROMEOS)
   void HandleAttemptUserExit(const base::ListValue* args);
   void HandleTurnOnSync(const base::ListValue* args);
@@ -180,11 +177,6 @@
       signin_metrics::AccessPoint access_point);
 #endif
 
-  void OnQueryHistoryRecordingCompletion(
-      const std::string& webui_callback_id,
-      history::WebHistoryService::Request* request,
-      const base::Optional<bool>& history_recording_enabled);
-
   void HandleGetStoredAccounts(const base::ListValue* args);
   void HandleStartSyncingWithEmail(const base::ListValue* args);
   base::Value GetStoredAccountsList();
@@ -228,11 +220,6 @@
   // Used to listen for pref changes to allow or disallow signin.
   PrefChangeRegistrar profile_pref_registrar_;
 
-  // Pending web and app activity requests to query whether history recording
-  // is enabled or not.
-  std::set<std::unique_ptr<history::WebHistoryService::Request>>
-      web_and_app_activity_requests_;
-
   // Manages observer lifetimes.
   ScopedObserver<signin::IdentityManager, signin::IdentityManager::Observer>
       identity_manager_observer_{this};
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 50546fbd..b808956 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
-#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/management_ui.h"
 #include "chrome/browser/ui/webui/policy_indicator_localized_strings_provider.h"
 #include "chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.h"
diff --git a/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
index 9cf16c5d6..9a461686 100644
--- a/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/shared_settings_localized_strings_provider.cc
@@ -194,6 +194,8 @@
       {"syncSetupCancelDialogTitle",
        IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_TITLE},
       {"syncSetupCancelDialogBody", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_BODY},
+      {"personalizeGoogleServicesTitle",
+       IDS_SETTINGS_PERSONALIZE_GOOGLE_SERVICES_TITLE},
   };
   AddLocalizedStringsBulk(html_source, kLocalizedStrings);
 
@@ -228,23 +230,10 @@
           base::ASCIIToUTF16(chrome::kSyncEncryptionHelpURL)));
 #endif
   if (base::FeatureList::IsEnabled(features::kSyncSetupFriendlySettings)) {
-    static constexpr webui::LocalizedString
-        kSyncSetupFriendlySettingsStrings[] = {
-            {"personalizeGoogleServicesTitle",
-             IDS_SETTINGS_USE_HISTORY_TO_PERSONALIZE_GOOGLE_SERVICES_TITLE},
-            {"SwaaOn", IDS_SETTINGS_SWAA_ON},
-            {"SwaaOff", IDS_SETTINGS_SWAA_OFF},
-            {"dataEncryptedHint", IDS_SETTINGS_DATA_ENCRYPTED_HINT},
-            {"historySyncOffHint", IDS_SETTINGS_HISTORY_SYNC_OFF_HINT},
-            {"SwaaOffHint", IDS_SETTINGS_SWAA_OFF_HINT},
-            {"manageSyncedDataTitle",
-             IDS_SETTINGS_NEW_MANAGE_SYNCED_DATA_TITLE_UNIFIED_CONSENT},
-        };
-    AddLocalizedStringsBulk(html_source, kSyncSetupFriendlySettingsStrings);
-  } else {
     html_source->AddLocalizedString(
-        "personalizeGoogleServicesTitle",
-        IDS_SETTINGS_PERSONALIZE_GOOGLE_SERVICES_TITLE);
+        "manageSyncedDataTitle",
+        IDS_SETTINGS_NEW_MANAGE_SYNCED_DATA_TITLE_UNIFIED_CONSENT);
+  } else {
     html_source->AddLocalizedString(
         "manageSyncedDataTitle",
         IDS_SETTINGS_MANAGE_SYNCED_DATA_TITLE_UNIFIED_CONSENT);
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 5b77ab9..1827416b 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -735,8 +735,8 @@
   }
 
   // Each call to HandleGetAllSites() above added a callback to the profile's
-  // BrowsingDataLocalStorageHelper, so make sure these aren't stuck waiting to
-  // run at the end of the test.
+  // browsing_data::LocalStorageHelper, so make sure these aren't stuck waiting
+  // to run at the end of the test.
   base::RunLoop run_loop;
   run_loop.RunUntilIdle();
 }
diff --git a/chrome/browser/ui/webui/signin/inline_login_ui.cc b/chrome/browser/ui/webui/signin/inline_login_ui.cc
index 2cd45a5..69f57dde 100644
--- a/chrome/browser/ui/webui/signin/inline_login_ui.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_ui.cc
@@ -49,6 +49,10 @@
                              IDS_EDU_LOGIN_WELCOME_REAUTH_TITLE);
   source->AddLocalizedString("welcomeReauthBody",
                              IDS_EDU_LOGIN_WELCOME_REAUTH_BODY);
+  source->AddLocalizedString("parentsListTitle",
+                             IDS_EDU_LOGIN_PARENTS_LIST_TITLE);
+  source->AddLocalizedString("parentsListBody",
+                             IDS_EDU_LOGIN_PARENTS_LIST_BODY);
 }
 #endif  // defined(OS_CHROMEOS)
 
@@ -88,6 +92,8 @@
   source->AddResourcePath("edu_login_util.js", IDR_EDU_LOGIN_EDU_LOGIN_UTIL_JS);
   source->AddResourcePath("edu_login_welcome.js",
                           IDR_EDU_LOGIN_EDU_LOGIN_WELCOME_JS);
+  source->AddResourcePath("edu_login_parents.js",
+                          IDR_EDU_LOGIN_EDU_LOGIN_PARENTS_JS);
 
   source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER);
   source->AddResourcePath("test_loader.html", IDR_WEBUI_HTML_TEST_LOADER);
diff --git a/chrome/browser/upboarding/query_tiles/internal/BUILD.gn b/chrome/browser/upboarding/query_tiles/internal/BUILD.gn
index 716370d..6ad84b2 100644
--- a/chrome/browser/upboarding/query_tiles/internal/BUILD.gn
+++ b/chrome/browser/upboarding/query_tiles/internal/BUILD.gn
@@ -9,8 +9,12 @@
 
 source_set("internal") {
   sources = [
+    "image_data_store.cc",
+    "image_data_store.h",
     "image_decoder.cc",
     "image_decoder.h",
+    "image_info_store.cc",
+    "image_info_store.h",
     "image_loader.cc",
     "image_loader.h",
     "proto_conversion.cc",
diff --git a/chrome/browser/upboarding/query_tiles/internal/image_data_store.cc b/chrome/browser/upboarding/query_tiles/internal/image_data_store.cc
new file mode 100644
index 0000000..579bd37
--- /dev/null
+++ b/chrome/browser/upboarding/query_tiles/internal/image_data_store.cc
@@ -0,0 +1,58 @@
+// 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/upboarding/query_tiles/internal/image_data_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace upboarding {
+
+namespace {
+
+// An image data storage based on leveldb.
+class ImageDataStoreImpl : public ImageDataStore {
+ public:
+  ImageDataStoreImpl() = default;
+  ~ImageDataStoreImpl() override = default;
+
+ private:
+  // ImageDataStore implementation.
+  void Init(SuccessCallback callback) override { NOTIMPLEMENTED(); }
+
+  void Update(std::unique_ptr<ImageData> data,
+              SuccessCallback callback) override {
+    NOTIMPLEMENTED();
+  }
+
+  void GetImageData(const std::string& image_id,
+                    ImageDataCallback callback) override {
+    NOTIMPLEMENTED();
+  }
+
+  void Delete(std::vector<std::string> image_ids,
+              SuccessCallback callback) override {
+    NOTIMPLEMENTED();
+  }
+};
+
+}  // namespace
+
+ImageData::ImageData(const std::string& id, std::string data)
+    : id_(id), data_(std::move(data)) {}
+
+ImageData::~ImageData() = default;
+
+void ImageData::TakeData(std::string* output) {
+  DCHECK(output);
+  output->swap(data_);
+}
+
+// static
+std::unique_ptr<ImageDataStore> ImageDataStore::Create() {
+  return std::make_unique<ImageDataStoreImpl>();
+}
+
+}  // namespace upboarding
diff --git a/chrome/browser/upboarding/query_tiles/internal/image_data_store.h b/chrome/browser/upboarding/query_tiles/internal/image_data_store.h
new file mode 100644
index 0000000..5f6801a
--- /dev/null
+++ b/chrome/browser/upboarding/query_tiles/internal/image_data_store.h
@@ -0,0 +1,71 @@
+// 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_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_DATA_STORE_H_
+#define CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_DATA_STORE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+
+namespace upboarding {
+
+// Contains decoded image data.
+// Serialized to ImageData protobuf in image.proto.
+class ImageData {
+ public:
+  ImageData(const std::string& id, std::string data);
+  ImageData(const ImageData&) = delete;
+  ImageData& operator=(const ImageData&) = delete;
+  ~ImageData();
+
+  const std::string& id() const { return id_; }
+
+  // Transfers the ownership of |data_|.
+  void TakeData(std::string* output);
+
+ private:
+  // Unique id of the image.
+  std::string id_;
+
+  // Raw bytes of the image.
+  std::string data_;
+};
+
+// Storage to save deoded query tile images' raw data.
+// Only supports loads one image at a time.
+class ImageDataStore {
+ public:
+  using SuccessCallback = base::OnceCallback<void(bool /*success*/)>;
+  using ImageDataCallback =
+      base::OnceCallback<void(std::unique_ptr<ImageData>)>;
+
+  static std::unique_ptr<ImageDataStore> Create();
+
+  ImageDataStore() = default;
+  ImageDataStore(const ImageDataStore&) = delete;
+  ImageDataStore& operator=(const ImageDataStore&) = delete;
+  virtual ~ImageDataStore() = default;
+
+  // Initializes the store.
+  virtual void Init(SuccessCallback callback) = 0;
+
+  // Updates one image.
+  virtual void Update(std::unique_ptr<ImageData> data,
+                      SuccessCallback callback) = 0;
+
+  // Loads one image data into memory.
+  virtual void GetImageData(const std::string& image_id,
+                            ImageDataCallback callback) = 0;
+
+  // Deletes images from the store.
+  virtual void Delete(std::vector<std::string> image_ids,
+                      SuccessCallback callback) = 0;
+};
+
+}  // namespace upboarding
+
+#endif  // CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_DATA_STORE_H_
diff --git a/chrome/browser/upboarding/query_tiles/internal/image_info_store.cc b/chrome/browser/upboarding/query_tiles/internal/image_info_store.cc
new file mode 100644
index 0000000..07226356
--- /dev/null
+++ b/chrome/browser/upboarding/query_tiles/internal/image_info_store.cc
@@ -0,0 +1,40 @@
+// 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/upboarding/query_tiles/internal/image_info_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+
+namespace upboarding {
+namespace {
+
+class ImageInfoStoreImpl : public ImageInfoStore {
+ public:
+  ImageInfoStoreImpl() = default;
+  ~ImageInfoStoreImpl() override = default;
+
+ private:
+  void InitAndLoad(Store::LoadCallback callback) override { NOTIMPLEMENTED(); }
+
+  void Update(const std::string& key,
+              const ImageInfo& entry,
+              Store::UpdateCallback callback) override {
+    NOTIMPLEMENTED();
+  }
+
+  void Delete(const std::string& key, Store::DeleteCallback callback) override {
+    NOTIMPLEMENTED();
+  }
+};
+
+}  // namespace
+
+// static
+std::unique_ptr<ImageInfoStore> ImageInfoStore::Create() {
+  return std::make_unique<ImageInfoStoreImpl>();
+}
+
+}  // namespace upboarding
diff --git a/chrome/browser/upboarding/query_tiles/internal/image_info_store.h b/chrome/browser/upboarding/query_tiles/internal/image_info_store.h
new file mode 100644
index 0000000..75b3e9d
--- /dev/null
+++ b/chrome/browser/upboarding/query_tiles/internal/image_info_store.h
@@ -0,0 +1,45 @@
+// 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_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_INFO_STORE_H_
+#define CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_INFO_STORE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "chrome/browser/upboarding/query_tiles/internal/store.h"
+#include "url/gurl.h"
+
+namespace upboarding {
+
+// Contains information for a query tile image. This doesn't include the raw
+// bytes of the image.
+// Serialized to ImageInfo protobuf in image.proto.
+struct ImageInfo {
+  // Unique image id.
+  std::string id;
+
+  // URL of the image.
+  GURL url;
+
+  // The most recent update time, image will be expired after a certain period
+  // of time,
+  base::Time last_update;
+};
+
+// Store to save query tiles image info.
+class ImageInfoStore : public Store<ImageInfo> {
+ public:
+  ImageInfoStore() = default;
+  ~ImageInfoStore() override = default;
+
+  static std::unique_ptr<ImageInfoStore> Create();
+};
+
+}  // namespace upboarding
+
+#endif  // CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_IMAGE_INFO_STORE_H_
diff --git a/chrome/browser/upboarding/query_tiles/internal/image_loader.h b/chrome/browser/upboarding/query_tiles/internal/image_loader.h
index d9b6968..de141fc 100644
--- a/chrome/browser/upboarding/query_tiles/internal/image_loader.h
+++ b/chrome/browser/upboarding/query_tiles/internal/image_loader.h
@@ -20,8 +20,7 @@
 // on disk.
 class ImageLoader {
  public:
-  using UpdateCallback = base::OnceCallback<bool>;
-  using DeleteCallback = base::OnceCallback<bool>;
+  using SuccessCallback = base::OnceCallback<bool>;
   using BitmapCallback = base::OnceCallback<std::unique_ptr<SkBitmap>>;
   using Id = std::string;
 
@@ -32,9 +31,9 @@
   // immediately fetch the image, then invoke the callback.
   virtual void Update(const Id& id,
                       const GURL& url,
-                      UpdateCallback callback) = 0;
+                      SuccessCallback callback) = 0;
   // Deletes an image cache for a specific tile.
-  virtual void Delete(const Id& id, DeleteCallback callback) = 0;
+  virtual void Delete(const Id& id, SuccessCallback callback) = 0;
 
   // Gets the bitmap for a specific tile. Callback will be invoked after
   // reading the data from disk or the fetch is done.
diff --git a/chrome/browser/upboarding/query_tiles/internal/store.h b/chrome/browser/upboarding/query_tiles/internal/store.h
index 5eb44b8..d5c40ba 100644
--- a/chrome/browser/upboarding/query_tiles/internal/store.h
+++ b/chrome/browser/upboarding/query_tiles/internal/store.h
@@ -5,10 +5,12 @@
 #ifndef CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_STORE_H_
 #define CHROME_BROWSER_UPBOARDING_QUERY_TILES_INTERNAL_STORE_H_
 
+#include <map>
 #include <memory>
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/macros.h"
 
 namespace upboarding {
diff --git a/chrome/browser/upboarding/query_tiles/proto/BUILD.gn b/chrome/browser/upboarding/query_tiles/proto/BUILD.gn
index fa5632a..822c8fb8 100644
--- a/chrome/browser/upboarding/query_tiles/proto/BUILD.gn
+++ b/chrome/browser/upboarding/query_tiles/proto/BUILD.gn
@@ -5,5 +5,8 @@
 import("//third_party/protobuf/proto_library.gni")
 
 proto_library("proto") {
-  sources = [ "query_tile_entry.proto" ]
+  sources = [
+    "image.proto",
+    "query_tile_entry.proto",
+  ]
 }
diff --git a/chrome/browser/upboarding/query_tiles/proto/image.proto b/chrome/browser/upboarding/query_tiles/proto/image.proto
new file mode 100644
index 0000000..516c79c
--- /dev/null
+++ b/chrome/browser/upboarding/query_tiles/proto/image.proto
@@ -0,0 +1,33 @@
+// 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.
+
+syntax = "proto3";
+
+option optimize_for = LITE_RUNTIME;
+
+package upboarding.query_tiles.proto;
+
+// Information of a query tile image, doesn't contain image raw bytes.
+// Next tag: 4
+message ImageInfo {
+  // Unique id of the image.
+  string id = 1;
+
+  // URL of the image.
+  string url = 2;
+
+  // The last update time in milliseconds since epoch, image will be expired
+  // after a certain amount of time.
+  int64 last_update_time_ms = 3;
+}
+
+// Contains the decoded image bytes.
+// Next tag: 3
+message ImageData {
+  // Unique id of the image.
+  string id = 1;
+
+  // Decoded image data.
+  bytes data = 2;
+}
\ No newline at end of file
diff --git a/chrome/browser/upboarding/query_tiles/proto/query_tile_entry.proto b/chrome/browser/upboarding/query_tiles/proto/query_tile_entry.proto
index 4a2bed33..b9b5865 100644
--- a/chrome/browser/upboarding/query_tiles/proto/query_tile_entry.proto
+++ b/chrome/browser/upboarding/query_tiles/proto/query_tile_entry.proto
@@ -9,15 +9,15 @@
 package upboarding.query_tiles.proto;
 
 // The QuertTileEntry is the schema to represent data in query tile entry.
-// Next tag:7
+// Next tag: 7
 message QueryTileEntry {
   // Metadata about the image.
-  // Next tag:3
+  // Next tag: 3
   message ImageMetadata {
     // Unique id of the query tile image.
     string id = 1;
 
-    // Origin URL of the image.
+    // URL of the image.
     string url = 2;
   }
 
diff --git a/chrome/common/extensions/api/passwords_private.idl b/chrome/common/extensions/api/passwords_private.idl
index fa922fd..e0def107 100644
--- a/chrome/common/extensions/api/passwords_private.idl
+++ b/chrome/common/extensions/api/passwords_private.idl
@@ -257,6 +257,9 @@
     // Requests the account-storage opt-in state of the current user.
     static void isOptedInForAccountStorage(OptInCallback callback);
 
+    // Triggers the opt-in or opt-out flow for the account storage.
+    static void optInForAccountStorage(boolean optIn);
+
     // Requests the latest compromised credentials.
     static void getCompromisedCredentials(
         CompromisedCredentialsCallback callback);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f4a9a3e6..3a01e00 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3077,7 +3077,6 @@
     "../browser/browsing_data/browsing_data_file_system_helper_unittest.cc",
     "../browser/browsing_data/browsing_data_helper_unittest.cc",
     "../browser/browsing_data/browsing_data_indexed_db_helper_unittest.cc",
-    "../browser/browsing_data/browsing_data_local_storage_helper_unittest.cc",
     "../browser/browsing_data/browsing_data_media_license_helper_unittest.cc",
     "../browser/browsing_data/browsing_data_quota_helper_unittest.cc",
     "../browser/browsing_data/browsing_data_service_worker_helper_unittest.cc",
diff --git a/chrome/test/base/chrome_test_launcher.cc b/chrome/test/base/chrome_test_launcher.cc
index bfedb561..f359888 100644
--- a/chrome/test/base/chrome_test_launcher.cc
+++ b/chrome/test/base/chrome_test_launcher.cc
@@ -21,6 +21,7 @@
 #include "base/strings/string_util.h"
 #include "base/test/test_file_util.h"
 #include "base/test/test_switches.h"
+#include "base/time/time.h"
 #include "chrome/app/chrome_main_delegate.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_switches.h"
@@ -135,7 +136,7 @@
 #if !defined(OS_ANDROID)
 content::ContentMainDelegate*
 ChromeTestLauncherDelegate::CreateContentMainDelegate() {
-  return new ChromeMainDelegate();
+  return new ChromeMainDelegate(base::TimeTicks::Now());
 }
 #endif
 
diff --git a/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view.Nexus_5X-23.png.sha1 b/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view.Nexus_5X-23.png.sha1
deleted file mode 100644
index 3a2dedc..0000000
--- a/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view.Nexus_5X-23.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-3ca70f365224a2acf417e16eb8f70957c82a0dc1
\ No newline at end of file
diff --git a/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view_one_selected_tab.Nexus_5X-23.png.sha1 b/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view_one_selected_tab.Nexus_5X-23.png.sha1
deleted file mode 100644
index c3e55b8..0000000
--- a/chrome/test/data/android/render_tests/TabSelectionEditorTest.list_view_one_selected_tab.Nexus_5X-23.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-828512f08172f235986d1a27f671c70528d51907
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/passwords_private/test.js b/chrome/test/data/extensions/api_test/passwords_private/test.js
index 2e49a266..ceaf22f 100644
--- a/chrome/test/data/extensions/api_test/passwords_private/test.js
+++ b/chrome/test/data/extensions/api_test/passwords_private/test.js
@@ -190,8 +190,8 @@
   },
 
   function isNotOptedInForAccountStorage() {
-    var callback = function(opted_in) {
-      chrome.test.assertEq(opted_in, false);
+    var callback = function(optedIn) {
+      chrome.test.assertEq(optedIn, false);
       // Ensure that the callback is invoked.
       chrome.test.succeed();
     };
@@ -200,8 +200,8 @@
   },
 
   function isOptedInForAccountStorage() {
-    var callback = function(opted_in) {
-      chrome.test.assertEq(opted_in, true);
+    var callback = function(optedIn) {
+      chrome.test.assertEq(optedIn, true);
       // Ensure that the callback is invoked.
       chrome.test.succeed();
     };
@@ -209,6 +209,22 @@
     chrome.passwordsPrivate.isOptedInForAccountStorage(callback);
   },
 
+  function optInForAccountStorage() {
+    chrome.passwordsPrivate.optInForAccountStorage(true);
+    chrome.passwordsPrivate.isOptedInForAccountStorage(function(optedIn) {
+      chrome.test.assertEq(optedIn, true);
+      chrome.test.succeed();
+    });
+  },
+
+  function optOutForAccountStorage() {
+    chrome.passwordsPrivate.optInForAccountStorage(false);
+    chrome.passwordsPrivate.isOptedInForAccountStorage(function(optedIn) {
+      chrome.test.assertEq(optedIn, false);
+      chrome.test.succeed();
+    });
+  },
+
   function getCompromisedCredentials() {
     chrome.passwordsPrivate.getCompromisedCredentials(
         compromisedCredentials => {
diff --git a/chrome/test/data/webui/chromeos/edu_login/edu_login_browsertest.js b/chrome/test/data/webui/chromeos/edu_login/edu_login_browsertest.js
index 75ba0150..f31a071e 100644
--- a/chrome/test/data/webui/chromeos/edu_login/edu_login_browsertest.js
+++ b/chrome/test/data/webui/chromeos/edu_login/edu_login_browsertest.js
@@ -66,3 +66,32 @@
 TEST_F('EduLoginButtonTest', 'BackButtonRtlIcon', function() {
   this.runMochaTest(edu_login_button_tests.TestNames.BackButtonRtlIcon);
 });
+
+// eslint-disable-next-line no-var
+var EduLoginParentsTest = class extends EduLoginTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://chrome-signin/test_loader.html?module=chromeos/edu_login/edu_login_parents_test.js';
+  }
+
+  /** @override */
+  get suiteName() {
+    return edu_login_parents_tests.suiteName;
+  }
+};
+
+TEST_F('EduLoginParentsTest', 'Initialize', function() {
+  this.runMochaTest(edu_login_parents_tests.TestNames.Initialize);
+});
+
+TEST_F('EduLoginParentsTest', 'NextButton', function() {
+  this.runMochaTest(edu_login_parents_tests.TestNames.NextButton);
+});
+
+TEST_F('EduLoginParentsTest', 'GoNext', function() {
+  this.runMochaTest(edu_login_parents_tests.TestNames.GoNext);
+});
+
+TEST_F('EduLoginParentsTest', 'SelectedParent', function() {
+  this.runMochaTest(edu_login_parents_tests.TestNames.SelectedParent);
+});
diff --git a/chrome/test/data/webui/chromeos/edu_login/edu_login_parents_test.js b/chrome/test/data/webui/chromeos/edu_login/edu_login_parents_test.js
new file mode 100644
index 0000000..66faac77
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/edu_login/edu_login_parents_test.js
@@ -0,0 +1,138 @@
+// 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 'chrome://chrome-signin/edu_login_parents.js';
+
+import {EduAccountLoginBrowserProxyImpl} from 'chrome://chrome-signin/browser_proxy.js';
+import {ParentAccount} from 'chrome://chrome-signin/edu_login_util.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {TestBrowserProxy} from '../../test_browser_proxy.m.js';
+
+window.edu_login_parents_tests = {};
+edu_login_parents_tests.suiteName = 'EduLoginParentsTest';
+
+/** @enum {string} */
+edu_login_parents_tests.TestNames = {
+  Initialize: 'Expect getParents call on initialize',
+  NextButton: 'Next button is enabled only when parent is selected',
+  GoNext: 'go-next event should be fired when parent is selected',
+  SelectedParent: 'Selected parent',
+};
+
+/**
+ * @param {string} email
+ * @param {string} displayName
+ * @param {string} profileImage
+ * @param {string} obfuscatedGaiaId
+ * @return {ParentAccount}
+ */
+function getFakeParent(email, displayName, profileImage, obfuscatedGaiaId) {
+  return {
+    email: email,
+    displayName: displayName,
+    profileImage: profileImage,
+    obfuscatedGaiaId: obfuscatedGaiaId,
+  };
+}
+
+/** @return {Array<ParentAccount>} */
+function getFakeParentsList() {
+  return [
+    getFakeParent('parent1@gmail.com', 'Parent 1', '', 'parent1gaia'),
+    getFakeParent('parent2@gmail.com', 'Parent 2', '', 'parent2gaia'),
+  ];
+}
+
+suite(edu_login_parents_tests.suiteName, function() {
+  let parentsComponent;
+  let testBrowserProxy;
+
+  /** @implements {EduAccountLoginBrowserProxy} */
+  class TestEduAccountLoginBrowserProxy extends TestBrowserProxy {
+    constructor() {
+      super(['getParents']);
+    }
+
+    /** @override */
+    getParents() {
+      this.methodCalled('getParents');
+      return Promise.resolve(getFakeParentsList());
+    }
+  }
+
+  /** @return {NodeList} */
+  function getAccountListItems() {
+    return parentsComponent.shadowRoot.querySelectorAll('.account-list-item');
+  }
+
+  setup(function() {
+    PolymerTest.clearBody();
+    testBrowserProxy = new TestEduAccountLoginBrowserProxy();
+    EduAccountLoginBrowserProxyImpl.instance_ = testBrowserProxy;
+    parentsComponent = document.createElement('edu-login-parents');
+    document.body.appendChild(parentsComponent);
+    flush();
+  });
+
+  test(assert(edu_login_parents_tests.TestNames.Initialize), function() {
+    assertEquals(1, testBrowserProxy.getCallCount('getParents'));
+  });
+
+  test(assert(edu_login_parents_tests.TestNames.NextButton), function() {
+    testBrowserProxy.whenCalled('getParents').then(function() {
+      flush();
+      assertTrue(
+          parentsComponent.$$('edu-login-button[button-type="next"]').disabled);
+      // Select the first parent from the list.
+      getAccountListItems()[0].click();
+      assertFalse(
+          parentsComponent.$$('edu-login-button[button-type="next"]').disabled);
+    });
+  });
+
+  test(assert(edu_login_parents_tests.TestNames.GoNext), function() {
+    let goNextCalls = 0;
+    parentsComponent.addEventListener('go-next', function() {
+      goNextCalls++;
+    });
+    testBrowserProxy.whenCalled('getParents').then(function() {
+      flush();
+      assertEquals(0, goNextCalls);
+      const accountListItems = getAccountListItems();
+      // Select the first parent from the list.
+      accountListItems[0].click();
+      assertEquals(1, goNextCalls);
+      // If user goes back and selects the same item -'go-next' should be fired.
+      accountListItems[0].click();
+      assertEquals(2, goNextCalls);
+      // If user goes back and selects another item - 'go-next' should be fired.
+      accountListItems[1].click();
+      assertEquals(3, goNextCalls);
+    });
+  });
+
+  test(assert(edu_login_parents_tests.TestNames.SelectedParent), function() {
+    testBrowserProxy.whenCalled('getParents').then(function() {
+      flush();
+      assertDeepEquals(getFakeParentsList(), parentsComponent.parents_);
+      assertEquals(null, parentsComponent.selectedParent);
+      const accountListItems = getAccountListItems();
+      accountListItems.forEach(element => {
+        // No option should be selected.
+        assertEquals('false', element.getAttribute('aria-selected'));
+      });
+      // Select the first parent from the list.
+      accountListItems[0].click();
+      assertDeepEquals(
+          getFakeParentsList()[0], parentsComponent.selectedParent);
+      accountListItems.forEach((element, index) => {
+        assertEquals(
+            index === 0 ? 'true' : 'false',
+            element.getAttribute('aria-selected'));
+      });
+    });
+  });
+});
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 78f841d..6927c3b 100644
--- a/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
+++ b/chrome/test/data/webui/media/media_feeds_webui_browsertest.js
@@ -35,7 +35,9 @@
     let feedHeaders =
         Array.from(document.querySelector('#feed-table-header').children);
 
-    assertDeepEquals(['ID', 'Url'], feedHeaders.map(x => x.textContent.trim()));
+    assertDeepEquals(
+        ['ID', 'Url', 'Last Discovery Time'],
+        feedHeaders.map(x => x.textContent.trim()));
   });
 
   mocha.run();
diff --git a/chrome/test/data/webui/settings/collapse_radio_button_tests.js b/chrome/test/data/webui/settings/collapse_radio_button_tests.js
index e829e4a..8ffe9a2f 100644
--- a/chrome/test/data/webui/settings/collapse_radio_button_tests.js
+++ b/chrome/test/data/webui/settings/collapse_radio_button_tests.js
@@ -57,4 +57,42 @@
     Polymer.dom.flush();
     assertFalse(collapse.opened);
   });
+
+  test('expansionHiddenWhenNoCollapseSet', function() {
+    assertTrue(
+        test_util.isChildVisible(collapseRadioButton, 'cr-expand-button'));
+    assertTrue(test_util.isChildVisible(collapseRadioButton, '.separator'));
+
+    collapseRadioButton.noCollapse = true;
+    Polymer.dom.flush();
+    assertFalse(
+        test_util.isChildVisible(collapseRadioButton, 'cr-expand-button'));
+    assertFalse(test_util.isChildVisible(collapseRadioButton, '.separator'));
+  });
+
+  test('openOnExpandHitWhenDisabled', function() {
+    collapseRadioButton.checked = false;
+    collapseRadioButton.disabled = true;
+    const collapse = collapseRadioButton.$$('iron-collapse');
+
+    Polymer.dom.flush();
+    assertFalse(collapse.opened);
+    collapseRadioButton.$$('cr-expand-button').click();
+
+    Polymer.dom.flush();
+    assertTrue(collapse.opened);
+  });
+
+  test('displayPolicyIndicator', function() {
+    assertFalse(
+        test_util.isChildVisible(collapseRadioButton, '#policyIndicator'));
+    assertEquals(
+        collapseRadioButton.policyIndicatorType, CrPolicyIndicatorType.NONE);
+
+    collapseRadioButton.policyIndicatorType =
+        CrPolicyIndicatorType.DEVICE_POLICY;
+    Polymer.dom.flush();
+    assertTrue(
+        test_util.isChildVisible(collapseRadioButton, '#policyIndicator'));
+  });
 });
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 71d3347..0976326e 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -2069,6 +2069,7 @@
   browsePreload: 'chrome://settings/privacy_page/collapse_radio_button.html',
 
   extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
+    '../test_util.js',
     'collapse_radio_button_tests.js',
   ]),
 };
diff --git a/chrome/test/data/webui/settings/password_check_test.js b/chrome/test/data/webui/settings/password_check_test.js
index 1bee10c..bb66b20f 100644
--- a/chrome/test/data/webui/settings/password_check_test.js
+++ b/chrome/test/data/webui/settings/password_check_test.js
@@ -310,13 +310,33 @@
     });
 
     // Test verifies that if no compromised credentials found than list is not
-    // shown TODO(https://crbug.com/1047726): add additional checks after
-    // UI is implemented
+    // shown
     test('testNoCompromisedCredentials', function() {
-      const checkPasswordSection = createCheckPasswordSection();
-      assertTrue(checkPasswordSection.$.passwordCheckBody.hidden);
-      assertFalse(checkPasswordSection.$.noCompromisedCredentials.hidden);
-      validateLeakedPasswordsList(checkPasswordSection, []);
+      const data = passwordManager.data;
+      data.checkStatus = autofill_test_util.makePasswordCheckStatus(
+          /*state=*/ PasswordCheckState.IDLE,
+          /*checked=*/ 4,
+          /*remaining=*/ 0,
+          /*lastCheck=*/ 'Just now');
+      data.leakedCredentials = [];
+
+      const section = createCheckPasswordSection();
+      assertFalse(isElementVisible(section.$.noCompromisedCredentials));
+      cr.webUIListenerCallback(
+          'sync-prefs-changed', sync_test_util.getSyncAllPrefs());
+      sync_test_util.simulateSyncStatus({signedIn: true});
+
+      // Initialize with dummy data breach detection settings
+      section.prefs = {
+        profile: {password_manager_leak_detection: {value: true}}
+      };
+
+      return passwordManager.whenCalled('getCompromisedCredentials')
+          .then(() => {
+            Polymer.dom.flush();
+            assertFalse(isElementVisible(section.$.passwordCheckBody));
+            assertTrue(isElementVisible(section.$.noCompromisedCredentials));
+          });
     });
 
     // Test verifies that compromised credentials are displayed in a proper way
@@ -392,8 +412,8 @@
       passwordManager.data.checkStatus =
           autofill_test_util.makePasswordCheckStatus(
               /*state=*/ PasswordCheckState.RUNNING,
-              /*checked=*/ 1,
-              /*remaining=*/ 1);
+              /*checked=*/ 0,
+              /*remaining=*/ 2);
 
       const section = createCheckPasswordSection();
       return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
@@ -409,8 +429,8 @@
         passwordManager.lastCallback.addPasswordCheckStatusListener(
             autofill_test_util.makePasswordCheckStatus(
                 /*state=*/ PasswordCheckState.RUNNING,
-                /*checked=*/ 2,
-                /*remaining=*/ 0));
+                /*checked=*/ 1,
+                /*remaining=*/ 1));
 
         Polymer.dom.flush();
         assertTrue(isElementVisible(section.$.title));
@@ -537,8 +557,8 @@
       passwordManager.data.checkStatus =
           autofill_test_util.makePasswordCheckStatus(
               /*state=*/ PasswordCheckState.RUNNING,
-              /*checked=*/ 1,
-              /*remaining=*/ 3);
+              /*checked=*/ 0,
+              /*remaining=*/ 4);
 
       const section = createCheckPasswordSection();
       return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
@@ -551,13 +571,33 @@
       });
     });
 
+    // Verifies that in case the backend could not obtain the number of checked
+    // and remaining credentials the UI does not surface 0s to the user.
+    test('runningProgressHandlesZeroCaseFromBackend', function() {
+      passwordManager.data.checkStatus =
+          autofill_test_util.makePasswordCheckStatus(
+              /*state=*/ PasswordCheckState.RUNNING,
+              /*checked=*/ 0,
+              /*remaining=*/ 0);
+
+      const section = createCheckPasswordSection();
+      return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
+        Polymer.dom.flush();
+        const title = section.$.title;
+        assertTrue(isElementVisible(title));
+        expectEquals(
+            section.i18n('checkPasswordsProgress', 1, 1), title.innerText);
+        expectFalse(isElementVisible(section.$.subtitle));
+      });
+    });
+
     // While running, show progress and already found leak count.
     test('testShowProgressAndLeaksWhileRunning', function() {
       const data = passwordManager.data;
       data.checkStatus = autofill_test_util.makePasswordCheckStatus(
           /*state=*/ PasswordCheckState.RUNNING,
-          /*checked=*/ 2,
-          /*remaining=*/ 3);
+          /*checked=*/ 1,
+          /*remaining=*/ 4);
       data.leakedCredentials = [
         autofill_test_util.makeCompromisedCredential(
             'one.com', 'test4', 'LEAKED'),
diff --git a/chrome/test/data/webui/settings/passwords_section_test.js b/chrome/test/data/webui/settings/passwords_section_test.js
index 6208974..fcbc520f 100644
--- a/chrome/test/data/webui/settings/passwords_section_test.js
+++ b/chrome/test/data/webui/settings/passwords_section_test.js
@@ -812,31 +812,88 @@
       assertFalse(passwordsSection.$.manageLink.hidden);
     });
 
-    test('showPasswordCheckBannerWhenNotCheckedBefore', function() {
-      // Suppose no check done initially.
-      assertEquals(
-          passwordManager.data.checkStatus.elapsedTimeSinceLastCheck,
-          undefined);
-      const passwordsSection =
-          elementFactory.createPasswordsSection(passwordManager, [], []);
-      return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
-        Polymer.dom.flush();
-        assertFalse(
-            passwordsSection.$$('#checkPasswordsBannerContainer').hidden);
-        assertFalse(passwordsSection.$$('#checkPasswordsButton').hidden);
-        assertTrue(passwordsSection.$$('#checkPasswordsLinkRow').hidden);
-      });
-    });
+    test(
+        'showPasswordCheckBannerWhenNotCheckedBeforeAndSignedInAndHavePasswords',
+        function() {
+          // Suppose no check done initially, non-empty list of passwords,
+          // signed in.
+          assertEquals(
+              passwordManager.data.checkStatus.elapsedTimeSinceLastCheck,
+              undefined);
+          const passwordList = [
+            autofill_test_util.createPasswordEntry('site1.com', 'luigi', 1),
+          ];
+          const passwordsSection = elementFactory.createPasswordsSection(
+              passwordManager, passwordList, []);
+          return passwordManager.whenCalled('getPasswordCheckStatus')
+              .then(() => {
+                Polymer.dom.flush();
+                assertFalse(
+                    passwordsSection.$$('#checkPasswordsBannerContainer')
+                        .hidden);
+                assertFalse(
+                    passwordsSection.$$('#checkPasswordsButton').hidden);
+                assertTrue(
+                    passwordsSection.$$('#checkPasswordsLinkRow').hidden);
+              });
+        });
+
+    test(
+        'showPasswordCheckLinkButtonWithoutWarningWhenNotSignedIn', function() {
+          // Suppose no check done initially, non-empty list of passwords,
+          // signed out.
+          assertEquals(
+              passwordManager.data.checkStatus.elapsedTimeSinceLastCheck,
+              undefined);
+          const passwordList = [
+            autofill_test_util.createPasswordEntry('site1.com', 'luigi', 1),
+          ];
+          const passwordsSection = elementFactory.createPasswordsSection(
+              passwordManager, passwordList, []);
+          sync_test_util.simulateSyncStatus({signedIn: false});
+          return passwordManager.whenCalled('getPasswordCheckStatus')
+              .then(() => {
+                Polymer.dom.flush();
+                assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer')
+                               .hidden);
+                assertTrue(passwordsSection.$$('#checkPasswordsButton').hidden);
+                assertFalse(
+                    passwordsSection.$$('#checkPasswordsLinkRow').hidden);
+              });
+        });
+
+    test(
+        'showPasswordCheckLinkButtonWithoutWarningWhenNoPasswords', function() {
+          // Suppose no check done initially, empty list of passwords, signed
+          // in.
+          assertEquals(
+              passwordManager.data.checkStatus.elapsedTimeSinceLastCheck,
+              undefined);
+          const passwordsSection =
+              elementFactory.createPasswordsSection(passwordManager, [], []);
+          return passwordManager.whenCalled('getPasswordCheckStatus')
+              .then(() => {
+                Polymer.dom.flush();
+                assertTrue(passwordsSection.$$('#checkPasswordsBannerContainer')
+                               .hidden);
+                assertTrue(passwordsSection.$$('#checkPasswordsButton').hidden);
+                assertFalse(
+                    passwordsSection.$$('#checkPasswordsLinkRow').hidden);
+              });
+        });
 
     test(
         'showPasswordCheckLinkButtonWithoutWarningWhenNoCredentialsLeaked',
         function() {
-          // Suppose no leaks detected initially.
+          // Suppose no leaks initially, non-empty list of passwords, signed in.
           passwordManager.data.leakedCredentials = [];
           passwordManager.data.checkStatus.elapsedTimeSinceLastCheck =
               '5 min ago';
-          const passwordsSection =
-              elementFactory.createPasswordsSection(passwordManager, [], []);
+          const passwordList = [
+            autofill_test_util.createPasswordEntry('site1.com', 'luigi', 1),
+          ];
+          const passwordsSection = elementFactory.createPasswordsSection(
+              passwordManager, passwordList, []);
           return passwordManager.whenCalled('getPasswordCheckStatus')
               .then(() => {
                 Polymer.dom.flush();
@@ -857,7 +914,7 @@
     test(
         'showPasswordCheckLinkButtonWithWarningWhenSomeCredentialsLeaked',
         function() {
-          // Suppose two leaks detected initially.
+          // Suppose no leaks initially, non-empty list of passwords, signed in.
           passwordManager.data.leakedCredentials = [
             autofill_test_util.makeCompromisedCredential(
                 'one.com', 'test4', 'LEAKED'),
@@ -866,8 +923,11 @@
           ];
           passwordManager.data.checkStatus.elapsedTimeSinceLastCheck =
               '5 min ago';
-          const passwordsSection =
-              elementFactory.createPasswordsSection(passwordManager, [], []);
+          const passwordList = [
+            autofill_test_util.createPasswordEntry('site1.com', 'luigi', 1),
+          ];
+          const passwordsSection = elementFactory.createPasswordsSection(
+              passwordManager, passwordList, []);
           return passwordManager.whenCalled('getPasswordCheckStatus')
               .then(() => {
                 Polymer.dom.flush();
@@ -886,11 +946,19 @@
         });
 
     test('makeWarningAppearWhenLeaksDetected', function() {
-      // Suppose no leaks detected initially.
+      // Suppose no leaks detected initially, non-empty list of passwords,
+      // signed in.
+      assertEquals(
+          passwordManager.data.checkStatus.elapsedTimeSinceLastCheck,
+          undefined);
       passwordManager.data.leakedCredentials = [];
       passwordManager.data.checkStatus.elapsedTimeSinceLastCheck = '5 min ago';
-      const passwordsSection =
-          elementFactory.createPasswordsSection(passwordManager, [], []);
+      const passwordList = [
+        autofill_test_util.createPasswordEntry('one.com', 'test4', 1),
+        autofill_test_util.createPasswordEntry('two.com', 'test3', 1),
+      ];
+      const passwordsSection = elementFactory.createPasswordsSection(
+          passwordManager, passwordList, []);
       return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
         Polymer.dom.flush();
         assertTrue(
@@ -930,6 +998,30 @@
         assertFalse(passwordsSection.$$('#checkPasswordLeakCount').hidden);
       });
     });
+
+    test('makeBannerDisappearWhenSignedOut', function() {
+      // Suppose no leaks detected initially, non-empty list of passwords,
+      // signed in.
+      const passwordList = [
+        autofill_test_util.createPasswordEntry('one.com', 'test4', 1),
+        autofill_test_util.createPasswordEntry('two.com', 'test3', 1),
+      ];
+      const passwordsSection = elementFactory.createPasswordsSection(
+          passwordManager, passwordList, []);
+      return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
+        Polymer.dom.flush();
+        assertFalse(
+            passwordsSection.$$('#checkPasswordsBannerContainer').hidden);
+        assertFalse(passwordsSection.$$('#checkPasswordsButton').hidden);
+        assertTrue(passwordsSection.$$('#checkPasswordsLinkRow').hidden);
+        sync_test_util.simulateSyncStatus({signedIn: false});
+        Polymer.dom.flush();
+        assertTrue(
+            passwordsSection.$$('#checkPasswordsBannerContainer').hidden);
+        assertTrue(passwordsSection.$$('#checkPasswordsButton').hidden);
+        assertFalse(passwordsSection.$$('#checkPasswordsLinkRow').hidden);
+      });
+    });
   });
   // #cr_define_end
 });
diff --git a/chrome/test/data/webui/settings/people_page_sync_page_test.js b/chrome/test/data/webui/settings/people_page_sync_page_test.js
index 9e54dde..ab62d62 100644
--- a/chrome/test/data/webui/settings/people_page_sync_page_test.js
+++ b/chrome/test/data/webui/settings/people_page_sync_page_test.js
@@ -46,11 +46,6 @@
     suiteSetup(function() {
       loadTimeData.overrideValues({
         syncSetupFriendlySettings: true,
-        SwaaOn: 'On',
-        SwaaOff: 'Off',
-        SwaaOffHint: 'SwaaOffHint',
-        historySyncOffHint: 'historySyncOffHint',
-        dataEncryptedHint: 'dataEncryptedHint',
         signinAllowed: true
       });
     });
@@ -565,87 +560,6 @@
       assertTrue(dashboardLink.hidden);
     });
 
-    test('Swaa', async function() {
-      function verifyResults(
-          hidden, Swaa, SwaaOffHint, hideActivityControlsUrl) {
-        const SwaaText = syncPage.$$('#history-usage-state .secondary');
-        const historyUsageOffHint = syncPage.$$('#history-usage-off-hint');
-        assertEquals(SwaaText.hidden, hidden);
-        assertEquals(historyUsageOffHint.hidden, Swaa !== 'Off');
-        assertEquals(
-            syncPage.$$('#history-usage-row')
-                .querySelector('.icon-external')
-                .hidden,
-            hideActivityControlsUrl);
-
-        if (!hidden) {
-          assertEquals(SwaaText.textContent.trim(), Swaa);
-          if (Swaa === 'Off') {
-            assertEquals(historyUsageOffHint.textContent.trim(), SwaaOffHint);
-          }
-        }
-      }
-
-      /** @param {Object=} syncPrefOverrides */
-      function setSyncPrefs(syncPrefOverrides = {}) {
-        const defaults = sync_test_util.getSyncAllPrefs();
-        const syncPrefs = Object.assign({}, defaults, syncPrefOverrides);
-        cr.webUIListenerCallback('sync-prefs-changed', syncPrefs);
-        Polymer.dom.flush();
-      }
-
-      const syncSection = syncPage.$$('#sync-section');
-      assertTrue(syncSection.hidden);
-      syncPage.syncStatus = {
-        signedIn: true,
-        disabled: false,
-        hasError: false,
-        statusAction: settings.StatusAction.NO_ACTION,
-      };
-      Polymer.dom.flush();
-      assertFalse(syncSection.hidden);
-      await browserProxy.whenCalled('queryIsHistoryRecordingEnabled');
-      verifyResults(
-          /*hidden=*/ false, 'On', '', /*hideActivityControlsUrl=*/ false);
-
-      // Data encrypted with custom passphrase.
-      setSyncPrefs({encryptAllData: true});
-      verifyResults(
-          /*hidden=*/ false, 'Off', 'dataEncryptedHint',
-          /*hideActivityControlsUrl=*/ true);
-
-      // sWAA off.
-      browserProxy.setHistoryRecordingEnabled({
-        requestSucceeded: true,
-        historyRecordingEnabled: /*hideActivityControlsUrl=*/ false
-      });
-      setSyncPrefs();
-      await browserProxy.whenCalled('queryIsHistoryRecordingEnabled');
-      verifyResults(
-          /*hidden=*/ false, 'Off', 'SwaaOffHint',
-          /*hideActivityControlsUrl=*/ false);
-
-      // Turn history sync off.
-      setSyncPrefs({syncAllDataTypes: false, typedUrlsSynced: false});
-      verifyResults(
-          /*hidden=*/ false, 'Off', 'historySyncOffHint',
-          /*hideActivityControlsUrl=*/ true);
-
-      // Verify hint is updated.
-      setSyncPrefs({encryptAllData: true});
-      verifyResults(
-          /*hidden=*/ false, 'Off', 'dataEncryptedHint',
-          /*hideActivityControlsUrl=*/ true);
-
-      // Failed to fetch |historyRecordingEnabled|.
-      browserProxy.setHistoryRecordingEnabled(
-          {requestSucceeded: false, historyRecordingEnabled: false});
-      setSyncPrefs();
-      await browserProxy.whenCalled('queryIsHistoryRecordingEnabled');
-      verifyResults(
-          /*hidden=*/ true, '', '', /*hideActivityControlsUrl=*/ false);
-    });
-
     // ##################################
     // TESTS THAT ARE SKIPPED ON CHROMEOS
     // ##################################
diff --git a/chrome/test/data/webui/settings/test_sync_browser_proxy.js b/chrome/test/data/webui/settings/test_sync_browser_proxy.js
index 05e4e898..fb7a4a0 100644
--- a/chrome/test/data/webui/settings/test_sync_browser_proxy.js
+++ b/chrome/test/data/webui/settings/test_sync_browser_proxy.js
@@ -23,7 +23,6 @@
       'sendSyncPrefsChanged',
       'startSignIn',
       'startSyncingWithEmail',
-      'queryIsHistoryRecordingEnabled',
     ];
 
     if (cr.isChromeOS) {
@@ -35,12 +34,6 @@
     /** @private {number} */
     this.impressionCount_ = 0;
 
-    /** @type {!HistoryRecordingEnabled} */
-    this.historyRecordingEnabled_ = {
-      requestSucceeded: true,
-      historyRecordingEnabled: true
-    };
-
     /** @type {!settings.PageStatus} */
     this.encryptionResponse = settings.PageStatus.CONFIGURE;
   }
@@ -118,19 +111,6 @@
   sendSyncPrefsChanged() {
     this.methodCalled('sendSyncPrefsChanged');
   }
-
-  /**
-   * @param {!HistoryRecordingEnabled} historyRecordingEnabled
-   */
-  setHistoryRecordingEnabled(historyRecordingEnabled) {
-    this.historyRecordingEnabled_ = historyRecordingEnabled;
-  }
-
-  /** @override */
-  queryIsHistoryRecordingEnabled() {
-    this.methodCalled('queryIsHistoryRecordingEnabled');
-    return Promise.resolve(this.historyRecordingEnabled_);
-  }
 }
 
 if (cr.isChromeOS) {
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 43c278c..b1255c750 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -181,6 +181,8 @@
       "persisted_data_unittest.cc",
       "prefs_unittest.cc",
       "run_all_unittests.cc",
+      "test/integration_tests.cc",
+      "test/integration_tests.h",
       "updater_unittest.cc",
     ]
 
@@ -197,17 +199,26 @@
     ]
 
     if (is_win) {
+      sources += [ "test/integration_tests_win.cc" ]
+
       deps += [ "//chrome/updater/win:updater_tests" ]
 
       data_deps = [ "//chrome/updater/win:updater" ]
     }
 
     if (is_mac) {
+      sources += [ "test/integration_tests_mac.cc" ]
+
       deps += [
         "//chrome/updater/mac:updater_bundle",
         "//chrome/updater/mac:updater_setup_tests",
         "//chrome/updater/mac:updater_tests",
       ]
+
+      data_deps = [
+        "//chrome/updater/mac:updater_bundle",
+        "//chrome/updater/mac:updater_setup",
+      ]
     }
 
     if (is_win || is_mac) {
diff --git a/chrome/updater/run_all_unittests.cc b/chrome/updater/run_all_unittests.cc
index b03b357..6929486 100644
--- a/chrome/updater/run_all_unittests.cc
+++ b/chrome/updater/run_all_unittests.cc
@@ -10,7 +10,7 @@
 int main(int argc, char** argv) {
   base::TestSuite test_suite(argc, argv);
   chrome::RegisterPathProvider();
-  return base::LaunchUnitTests(
+  return base::LaunchUnitTestsSerially(
       argc, argv,
       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
 }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
new file mode 100644
index 0000000..a763b94d
--- /dev/null
+++ b/chrome/updater/test/integration_tests.cc
@@ -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.
+
+#include "chrome/updater/test/integration_tests.h"
+
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace updater {
+
+namespace test {
+
+class IntegrationTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    Clean();
+    ExpectClean();
+  }
+
+  void TearDown() override {
+    ExpectClean();
+    Clean();
+  }
+};
+
+// TODO(crbug.com/1063064): Fix the test on Windows.
+#if defined(OS_WIN)
+#define MAYBE_InstallUninstall DISABLED_InstallUninstall
+#else
+#define MAYBE_InstallUninstall InstallUninstall
+#endif
+TEST_F(IntegrationTest, MAYBE_InstallUninstall) {
+  Install();
+  ExpectInstalled();
+  Uninstall();
+}
+
+}  // namespace test
+
+}  // namespace updater
diff --git a/chrome/updater/test/integration_tests.h b/chrome/updater/test/integration_tests.h
new file mode 100644
index 0000000..44786b6
--- /dev/null
+++ b/chrome/updater/test/integration_tests.h
@@ -0,0 +1,37 @@
+// 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_UPDATER_TEST_INTEGRATION_TESTS_H_
+#define CHROME_UPDATER_TEST_INTEGRATION_TESTS_H_
+
+namespace updater {
+
+namespace test {
+
+// Removes traces of the updater from the system. It is best to run this at the
+// start of each test in case a previous crash or timeout on the machine running
+// the test left the updater in an installed or partially installed state.
+void Clean();
+
+// Expect that the system is in a clean state, i.e. no updater is installed and
+// no traces of an updater exist. Should be run at the start and end of each
+// test.
+void ExpectClean();
+
+// Expect that the updater is installed on the system.
+void ExpectInstalled();
+
+// Install the updater.
+void Install();
+
+// Uninstall the updater. If the updater was installed during the test, it
+// should be uninstalled before the end of the test to avoid having an actual
+// live updater on the machine that ran the test.
+void Uninstall();
+
+}  // namespace test
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_TEST_INTEGRATION_TESTS_H_
diff --git a/chrome/updater/test/integration_tests_mac.cc b/chrome/updater/test/integration_tests_mac.cc
new file mode 100644
index 0000000..f757f41
--- /dev/null
+++ b/chrome/updater/test/integration_tests_mac.cc
@@ -0,0 +1,99 @@
+// 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 "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "chrome/updater/updater_version.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace updater {
+
+namespace test {
+
+namespace {
+
+base::FilePath GetExecutablePath() {
+  base::FilePath test_executable;
+  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
+    return base::FilePath();
+  return test_executable.DirName()
+      .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING ".App"))
+      .Append(FILE_PATH_LITERAL("Contents"))
+      .Append(FILE_PATH_LITERAL("MacOS"))
+      .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING));
+}
+
+base::FilePath GetInstallerPath() {
+  base::FilePath test_executable;
+  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
+    return base::FilePath();
+  return test_executable.DirName().Append("updater_setup");
+}
+
+bool Run(base::CommandLine command_line, int* exit_code) {
+  auto process = base::LaunchProcess(command_line, {});
+  if (!process.IsValid())
+    return false;
+  if (!process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(60),
+                                      exit_code))
+    return false;
+  return true;
+}
+
+}  // namespace
+
+void Clean() {
+  EXPECT_TRUE(base::DeleteFile(base::mac::GetUserLibraryPath()
+                                   .AppendASCII(COMPANY_SHORTNAME_STRING)
+                                   .AppendASCII(PRODUCT_FULLNAME_STRING),
+                               true));
+  // TODO(crbug.com/1062288): Delete the service launchd entry.
+  // TODO(crbug.com/1062288): Delete the update task launchd entry.
+}
+
+void ExpectClean() {
+  // Files must not exist on the file system.
+  EXPECT_FALSE(base::PathExists(base::mac::GetUserLibraryPath()
+                                    .AppendASCII(COMPANY_SHORTNAME_STRING)
+                                    .AppendASCII(PRODUCT_FULLNAME_STRING)));
+  // TODO(crbug.com/1062288): Check that service Launchd entry does not exist.
+  // TODO(crbug.com/1062288): Check that update task Launchd entry does not
+  // exist.
+}
+
+void ExpectInstalled() {
+  // Files must exist on the file system.
+  EXPECT_TRUE(base::PathExists(base::mac::GetUserLibraryPath()
+                                   .AppendASCII(COMPANY_SHORTNAME_STRING)
+                                   .AppendASCII(PRODUCT_FULLNAME_STRING)));
+  // TODO(crbug.com/1062288): Check that service Launchd entry exists.
+  // TODO(crbug.com/1062288): Check that update task Launchd entry exists.
+}
+
+void Install() {
+  base::FilePath path = GetInstallerPath();
+  ASSERT_FALSE(path.empty());
+  int exit_code = -1;
+  ASSERT_TRUE(Run(base::CommandLine(path), &exit_code));
+  EXPECT_EQ(0, exit_code);
+}
+
+void Uninstall() {
+  base::FilePath path = GetExecutablePath();
+  ASSERT_FALSE(path.empty());
+  base::CommandLine command_line(path);
+  command_line.AppendSwitch("uninstall");
+  int exit_code = -1;
+  ASSERT_TRUE(Run(command_line, &exit_code));
+  EXPECT_EQ(0, exit_code);
+}
+
+}  // namespace test
+
+}  // namespace updater
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
new file mode 100644
index 0000000..efe49a3
--- /dev/null
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -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.
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "chrome/updater/updater_version.h"
+#include "chrome/updater/util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace updater {
+
+namespace test {
+
+namespace {
+
+base::FilePath GetInstallerPath() {
+  base::FilePath test_executable;
+  if (!base::PathService::Get(base::FILE_EXE, &test_executable))
+    return base::FilePath();
+  return test_executable.DirName().AppendASCII("UpdaterSetup.exe");
+}
+
+bool Run(base::CommandLine command_line, int* exit_code) {
+  auto process = base::LaunchProcess(command_line, {});
+  if (!process.IsValid())
+    return false;
+  if (!process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(60),
+                                      exit_code))
+    return false;
+  return true;
+}
+
+base::FilePath GetProductPath() {
+  base::FilePath app_data_dir;
+  if (!base::PathService::Get(base::DIR_LOCAL_APP_DATA, &app_data_dir))
+    return base::FilePath();
+  return app_data_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
+      .AppendASCII(PRODUCT_FULLNAME_STRING);
+}
+
+base::FilePath GetExecutablePath() {
+  return GetProductPath().AppendASCII("updater.exe");
+}
+
+}  // namespace
+
+void Clean() {
+  // TODO(crbug.com/1062288): Delete the Client / ClientState registry keys.
+  // TODO(crbug.com/1062288): Delete the COM server items.
+  // TODO(crbug.com/1062288): Delete the COM service items.
+  // TODO(crbug.com/1062288): Delete the COM interfaces.
+  // TODO(crbug.com/1062288): Delete the UpdateApps task.
+  EXPECT_TRUE(base::DeleteFile(GetProductPath(), true));
+}
+
+void ExpectClean() {
+  // TODO(crbug.com/1062288): Assert there are no Client / ClientState registry
+  // keys.
+  // TODO(crbug.com/1062288): Assert there are no COM server items.
+  // TODO(crbug.com/1062288): Assert there are no COM service items.
+  // TODO(crbug.com/1062288): Assert there are no COM interfaces.
+  // TODO(crbug.com/1062288): Assert there are no UpdateApps tasks.
+
+  // Files must not exist on the file system.
+
+  // EXPECT_FALSE(base::PathExists(GetProductPath()));
+}
+
+void ExpectInstalled() {
+  // TODO(crbug.com/1062288): Assert there are Client / ClientState registry
+  // keys.
+  // TODO(crbug.com/1062288): Assert there are COM server items.
+  // TODO(crbug.com/1062288): Assert there are COM service items. (Maybe.)
+  // TODO(crbug.com/1062288): Assert there are COM interfaces.
+  // TODO(crbug.com/1062288): Assert there are UpdateApps tasks.
+
+  // Files must exist on the file system.
+  EXPECT_TRUE(base::PathExists(GetProductPath()));
+}
+
+void Install() {
+  int exit_code = -1;
+  ASSERT_TRUE(Run(base::CommandLine(GetInstallerPath()), &exit_code));
+  EXPECT_EQ(0, exit_code);
+}
+
+void Uninstall() {
+  base::FilePath path = GetExecutablePath();
+  ASSERT_FALSE(path.empty());
+  base::CommandLine command_line(path);
+  command_line.AppendSwitch("uninstall");
+  int exit_code = -1;
+  ASSERT_TRUE(Run(command_line, &exit_code));
+  EXPECT_EQ(0, exit_code);
+}
+
+}  // namespace test
+
+}  // namespace updater
diff --git a/chromecast/browser/accessibility/touch_exploration_controller.cc b/chromecast/browser/accessibility/touch_exploration_controller.cc
index faaae8d2..b2898fa4 100644
--- a/chromecast/browser/accessibility/touch_exploration_controller.cc
+++ b/chromecast/browser/accessibility/touch_exploration_controller.cc
@@ -195,6 +195,12 @@
   if (side_gesture_pass_through_ && type == ui::ET_TOUCH_PRESSED &&
       FindEdgesWithinInset(location, gesture_start_width_,
                            gesture_start_height_) != NO_EDGE) {
+    // If we are already in pass-through, ignore additional presses
+    // or the other fingers will clobber our initial press.
+    if (state_ == ONE_FINGER_PASSTHROUGH) {
+      return DiscardEvent(continuation);
+    }
+
     SET_STATE(ONE_FINGER_PASSTHROUGH);
     initial_press_ = std::make_unique<ui::TouchEvent>(touch_event);
     passthrough_offset_ = gfx::Vector2dF(0, 0);
diff --git a/chromeos/constants/chromeos_switches.cc b/chromeos/constants/chromeos_switches.cc
index 3cdb92f9..add7294 100644
--- a/chromeos/constants/chromeos_switches.cc
+++ b/chromeos/constants/chromeos_switches.cc
@@ -561,12 +561,6 @@
   return base::CommandLine::ForCurrentProcess()->HasSwitch(kShelfHoverPreviews);
 }
 
-bool ShouldShowScrollableShelf() {
-  // TODO(manucornet): Remove this method and simplify conditions depending
-  // on it since it's now always true.
-  return true;
-}
-
 bool ShouldTetherHostScansIgnoreWiredConnections() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(
       kTetherHostScansIgnoreWiredConnections);
diff --git a/chromeos/constants/chromeos_switches.h b/chromeos/constants/chromeos_switches.h
index bca1f4f..3327821 100644
--- a/chromeos/constants/chromeos_switches.h
+++ b/chromeos/constants/chromeos_switches.h
@@ -230,9 +230,6 @@
 // on the shelf.
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool ShouldShowShelfHoverPreviews();
 
-// Returns true if we should show a scrollable list of apps in the main shelf.
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool ShouldShowScrollableShelf();
-
 // Returns true if the Chromebook should ignore its wired connections when
 // deciding whether to run scans for tethering hosts. Should be used only for
 // testing.
diff --git a/chromeos/profiles/airmont.afdo.newest.txt b/chromeos/profiles/airmont.afdo.newest.txt
index a4159b4..642a171 100644
--- a/chromeos/profiles/airmont.afdo.newest.txt
+++ b/chromeos/profiles/airmont.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-airmont-82-4079.0-1584355719-benchmark-82.0.4085.10-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-airmont-82-4079.0-1584355719-benchmark-82.0.4085.12-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/profiles/broadwell.afdo.newest.txt b/chromeos/profiles/broadwell.afdo.newest.txt
index 198fb031..e7d57ab 100644
--- a/chromeos/profiles/broadwell.afdo.newest.txt
+++ b/chromeos/profiles/broadwell.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-broadwell-82-4044.42-1584352428-benchmark-82.0.4085.10-r1-redacted.afdo.xz
\ No newline at end of file
+chromeos-chrome-amd64-broadwell-82-4044.42-1584352428-benchmark-82.0.4085.12-r1-redacted.afdo.xz
\ No newline at end of file
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.cc b/chromeos/services/assistant/assistant_manager_service_impl.cc
index c5f8690e..53bab7e 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.cc
+++ b/chromeos/services/assistant/assistant_manager_service_impl.cc
@@ -709,11 +709,6 @@
     it->OnOpenUrlResponse(gurl, is_background);
 }
 
-void AssistantManagerServiceImpl::OnPlaybackStateChange(
-    const MediaStatus& status) {
-  media_session_->NotifyMediaSessionMetadataChanged(status);
-}
-
 void AssistantManagerServiceImpl::OnShowNotification(
     const action::Notification& notification) {
   ENSURE_MAIN_THREAD(&AssistantManagerServiceImpl::OnShowNotification,
@@ -1338,10 +1333,9 @@
   });
 }
 
-void AssistantManagerServiceImpl::MediaSessionChanged(
-    const base::Optional<base::UnguessableToken>& request_id) {
-  if (request_id.has_value())
-    media_session_audio_focus_id_ = std::move(request_id.value());
+void AssistantManagerServiceImpl::OnPlaybackStateChange(
+    const MediaStatus& status) {
+  media_session_->NotifyMediaSessionMetadataChanged(status);
 }
 
 void AssistantManagerServiceImpl::MediaSessionInfoChanged(
@@ -1356,6 +1350,12 @@
   UpdateMediaState();
 }
 
+void AssistantManagerServiceImpl::MediaSessionChanged(
+    const base::Optional<base::UnguessableToken>& request_id) {
+  if (request_id.has_value())
+    media_session_audio_focus_id_ = std::move(request_id.value());
+}
+
 // TODO(dmblack): Handle non-firing (e.g. paused or scheduled) timers.
 void AssistantManagerServiceImpl::OnAlarmTimerStateChanged() {
   ENSURE_MAIN_THREAD(&AssistantManagerServiceImpl::OnAlarmTimerStateChanged);
diff --git a/chromeos/services/assistant/assistant_manager_service_impl.h b/chromeos/services/assistant/assistant_manager_service_impl.h
index b1c2b43..c67fb2d6 100644
--- a/chromeos/services/assistant/assistant_manager_service_impl.h
+++ b/chromeos/services/assistant/assistant_manager_service_impl.h
@@ -165,8 +165,6 @@
       const std::vector<action::Suggestion>& suggestions) override;
   void OnShowText(const std::string& text) override;
   void OnOpenUrl(const std::string& url, bool in_background) override;
-  void OnPlaybackStateChange(
-      const assistant_client::MediaStatus& status) override;
   void OnShowNotification(const action::Notification& notification) override;
   void OnOpenAndroidApp(const action::AndroidAppInfo& app_info,
                         const action::InteractionInfo& interaction) override;
@@ -216,6 +214,10 @@
   }
   CrosPlatformApi* platform_api() { return platform_api_.get(); }
 
+  // assistant_client::MediaManager::Listener overrides:
+  void OnPlaybackStateChange(
+      const assistant_client::MediaStatus& status) override;
+
   // media_session::mojom::MediaControllerObserver overrides:
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr info) override;
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 51f5d2e..81e7c48 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -222,6 +222,7 @@
       "//components/autofill/content/browser:unit_tests",
       "//components/autofill/content/renderer:unit_tests",
       "//components/autofill/core/common/mojom:unit_tests",
+      "//components/browsing_data/content:unit_tests",
       "//components/captive_portal/content:unit_tests",
       "//components/cast_certificate:unit_tests",
       "//components/cast_channel:unit_tests",
diff --git a/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
index 7adcb7a5..b70b30a 100644
--- a/components/autofill/core/browser/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/autofill_external_delegate_unittest.cc
@@ -766,7 +766,8 @@
 TEST_F(AutofillExternalDelegateUnitTest, ShouldShowGooglePayIcon) {
   IssueOnQuery(kQueryId);
 
-  auto element_icons = testing::ElementsAre(std::string(), "googlePay");
+  auto element_icons =
+      testing::ElementsAre(std::string(), testing::StartsWith("googlePay"));
   EXPECT_CALL(autofill_client_,
               ShowAutofillPopup(_, _, SuggestionVectorIconsAre(element_icons),
                                 false, PopupType::kPersonalInformation, _));
diff --git a/components/autofill/core/browser/ui/popup_item_ids.h b/components/autofill/core/browser/ui/popup_item_ids.h
index 2e2372e..085863e1 100644
--- a/components/autofill/core/browser/ui/popup_item_ids.h
+++ b/components/autofill/core/browser/ui/popup_item_ids.h
@@ -27,10 +27,11 @@
   POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY = -13,
   POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY = -14,
   POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS = -15,
-  POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN = -16,
+  POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN = -16,
   POPUP_ITEM_ID_HIDE_AUTOFILL_SUGGESTIONS = -17,
   POPUP_ITEM_ID_USE_VIRTUAL_CARD = -18,
   POPUP_ITEM_ID_LOADING_SPINNER = -20,
+  POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE = -21,
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/common/save_password_progress_logger.cc b/components/autofill/core/common/save_password_progress_logger.cc
index cb4d60e2..8d98ef9 100644
--- a/components/autofill/core/common/save_password_progress_logger.cc
+++ b/components/autofill/core/common/save_password_progress_logger.cc
@@ -504,6 +504,12 @@
       return "Server predictions";
     case STRING_USERNAME_FIRST_FLOW_VOTE:
       return "Username first flow vote";
+    case STRING_POSSIBLE_USERNAME_USED:
+      return "Possible username is used";
+    case STRING_POSSIBLE_USERNAME_NOT_USED:
+      return "Possible username is not used";
+    case STRING_LOCALLY_SAVED_PREDICTION:
+      return "Locally saved prediction";
     case SavePasswordProgressLogger::STRING_INVALID:
       return "INVALID";
       // Intentionally no default: clause here -- all IDs need to get covered.
diff --git a/components/autofill/core/common/save_password_progress_logger.h b/components/autofill/core/common/save_password_progress_logger.h
index e4656f0..59825fd 100644
--- a/components/autofill/core/common/save_password_progress_logger.h
+++ b/components/autofill/core/common/save_password_progress_logger.h
@@ -160,6 +160,9 @@
     STRING_NAVIGATION_NTP,
     STRING_SERVER_PREDICTIONS,
     STRING_USERNAME_FIRST_FLOW_VOTE,
+    STRING_POSSIBLE_USERNAME_USED,
+    STRING_POSSIBLE_USERNAME_NOT_USED,
+    STRING_LOCALLY_SAVED_PREDICTION,
     STRING_INVALID,  // Represents a string returned in a case of an error.
     STRING_MAX = STRING_INVALID
   };
diff --git a/components/autofill_assistant/browser/basic_interactions.cc b/components/autofill_assistant/browser/basic_interactions.cc
index 4f737d55f..687420a 100644
--- a/components/autofill_assistant/browser/basic_interactions.cc
+++ b/components/autofill_assistant/browser/basic_interactions.cc
@@ -416,7 +416,10 @@
 }
 
 bool BasicInteractions::EndAction(const EndActionProto& proto) {
-  CHECK(end_action_callback_) << "Failed to EndAction: no callback set";
+  if (!end_action_callback_) {
+    DVLOG(2) << "Failed to EndAction: no callback set";
+    return false;
+  }
   std::move(end_action_callback_)
       .Run(proto.status(), delegate_->GetUserModel());
   return true;
diff --git a/components/autofill_assistant/browser/basic_interactions_unittest.cc b/components/autofill_assistant/browser/basic_interactions_unittest.cc
index 18e74fbe..0c99874 100644
--- a/components/autofill_assistant/browser/basic_interactions_unittest.cc
+++ b/components/autofill_assistant/browser/basic_interactions_unittest.cc
@@ -237,8 +237,7 @@
 
 TEST_F(BasicInteractionsTest, EndActionWithoutCallbackFails) {
   EndActionProto proto;
-  ASSERT_DEATH(basic_interactions_.EndAction(proto),
-               "Failed to EndAction: no callback set");
+  EXPECT_FALSE(basic_interactions_.EndAction(proto));
 }
 
 TEST_F(BasicInteractionsTest, EndActionWithCallbackSucceeds) {
diff --git a/components/browsing_data/content/BUILD.gn b/components/browsing_data/content/BUILD.gn
index 179a40a..e32ecc0 100644
--- a/components/browsing_data/content/BUILD.gn
+++ b/components/browsing_data/content/BUILD.gn
@@ -6,6 +6,8 @@
   sources = [
     "conditional_cache_counting_helper.cc",
     "conditional_cache_counting_helper.h",
+    "local_storage_helper.cc",
+    "local_storage_helper.h",
   ]
 
   configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
@@ -19,3 +21,16 @@
     "//net",
   ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "local_storage_helper_unittest.cc" ]
+
+  deps = [
+    ":content",
+    "//base",
+    "//content/test:test_support",
+    "//testing/gtest",
+    "//url",
+  ]
+}
diff --git a/components/browsing_data/content/DEPS b/components/browsing_data/content/DEPS
index 9093044..27dbf2d 100644
--- a/components/browsing_data/content/DEPS
+++ b/components/browsing_data/content/DEPS
@@ -2,6 +2,7 @@
   "+components/content_settings/core/browser",
   "+components/content_settings/core/common",
   "+content/public/browser",
+  "+content/public/test",
   "+mojo/public/cpp/bindings",
   "+net",
   "+services/network/public/cpp",
diff --git a/components/browsing_data/content/local_storage_helper.cc b/components/browsing_data/content/local_storage_helper.cc
new file mode 100644
index 0000000..5dabe4263
--- /dev/null
+++ b/components/browsing_data/content/local_storage_helper.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/browsing_data/content/local_storage_helper.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/storage_usage_info.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+using content::BrowserContext;
+using content::BrowserThread;
+
+namespace browsing_data {
+
+namespace {
+
+// Only websafe state is considered browsing data.
+bool HasStorageScheme(const GURL& origin_url) {
+  return base::Contains(url::GetWebStorageSchemes(), origin_url.scheme());
+}
+
+void GetUsageInfoCallback(LocalStorageHelper::FetchCallback callback,
+                          const std::vector<content::StorageUsageInfo>& infos) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!callback.is_null());
+
+  std::list<content::StorageUsageInfo> result;
+  for (const content::StorageUsageInfo& info : infos) {
+    if (HasStorageScheme(info.origin.GetURL()))
+      result.push_back(info);
+  }
+
+  base::PostTask(FROM_HERE, {BrowserThread::UI},
+                 base::BindOnce(std::move(callback), result));
+}
+
+}  // namespace
+
+LocalStorageHelper::LocalStorageHelper(BrowserContext* context)
+    : dom_storage_context_(BrowserContext::GetDefaultStoragePartition(context)
+                               ->GetDOMStorageContext()) {
+  DCHECK(dom_storage_context_);
+}
+
+LocalStorageHelper::~LocalStorageHelper() = default;
+
+void LocalStorageHelper::StartFetching(FetchCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!callback.is_null());
+  dom_storage_context_->GetLocalStorageUsage(
+      base::BindOnce(&GetUsageInfoCallback, std::move(callback)));
+}
+
+void LocalStorageHelper::DeleteOrigin(const url::Origin& origin,
+                                      base::OnceClosure callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  dom_storage_context_->DeleteLocalStorage(origin, std::move(callback));
+}
+
+//---------------------------------------------------------
+
+CannedLocalStorageHelper::CannedLocalStorageHelper(BrowserContext* context)
+    : LocalStorageHelper(context) {}
+
+void CannedLocalStorageHelper::Add(const url::Origin& origin) {
+  if (!HasStorageScheme(origin.GetURL()))
+    return;
+  pending_origins_.insert(origin);
+}
+
+void CannedLocalStorageHelper::Reset() {
+  pending_origins_.clear();
+}
+
+bool CannedLocalStorageHelper::empty() const {
+  return pending_origins_.empty();
+}
+
+size_t CannedLocalStorageHelper::GetCount() const {
+  return pending_origins_.size();
+}
+
+const std::set<url::Origin>& CannedLocalStorageHelper::GetOrigins() const {
+  return pending_origins_;
+}
+
+void CannedLocalStorageHelper::StartFetching(FetchCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DCHECK(!callback.is_null());
+
+  std::list<content::StorageUsageInfo> result;
+  for (const auto& origin : pending_origins_)
+    result.emplace_back(origin, 0, base::Time());
+
+  base::PostTask(FROM_HERE, {BrowserThread::UI},
+                 base::BindOnce(std::move(callback), result));
+}
+
+void CannedLocalStorageHelper::DeleteOrigin(const url::Origin& origin,
+                                            base::OnceClosure callback) {
+  pending_origins_.erase(origin);
+  LocalStorageHelper::DeleteOrigin(origin, std::move(callback));
+}
+
+CannedLocalStorageHelper::~CannedLocalStorageHelper() = default;
+
+}  // namespace browsing_data
diff --git a/chrome/browser/browsing_data/browsing_data_local_storage_helper.h b/components/browsing_data/content/local_storage_helper.h
similarity index 68%
rename from chrome/browser/browsing_data/browsing_data_local_storage_helper.h
rename to components/browsing_data/content/local_storage_helper.h
index 09368b8..2d33dc22 100644
--- a/chrome/browser/browsing_data/browsing_data_local_storage_helper.h
+++ b/components/browsing_data/content/local_storage_helper.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_BROWSING_DATA_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
-#define CHROME_BROWSER_BROWSING_DATA_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+#ifndef COMPONENTS_BROWSING_DATA_CONTENT_LOCAL_STORAGE_HELPER_H_
+#define COMPONENTS_BROWSING_DATA_CONTENT_LOCAL_STORAGE_HELPER_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -21,17 +21,20 @@
 #include "content/public/browser/storage_usage_info.h"
 #include "url/origin.h"
 
-class Profile;
+namespace content {
+class BrowserContext;
+}
+
+namespace browsing_data {
 
 // This class fetches local storage information and provides a
 // means to delete the data associated with an origin.
-class BrowsingDataLocalStorageHelper
-    : public base::RefCounted<BrowsingDataLocalStorageHelper> {
+class LocalStorageHelper : public base::RefCounted<LocalStorageHelper> {
  public:
   using FetchCallback =
       base::OnceCallback<void(const std::list<content::StorageUsageInfo>&)>;
 
-  explicit BrowsingDataLocalStorageHelper(Profile* profile);
+  explicit LocalStorageHelper(content::BrowserContext* context);
 
   // Starts the fetching process, which will notify its completion via
   // callback. This must be called only in the UI thread.
@@ -44,22 +47,21 @@
                             base::OnceClosure callback);
 
  protected:
-  friend class base::RefCounted<BrowsingDataLocalStorageHelper>;
-  virtual ~BrowsingDataLocalStorageHelper();
+  friend class base::RefCounted<LocalStorageHelper>;
+  virtual ~LocalStorageHelper();
 
-  content::DOMStorageContext* dom_storage_context_;  // Owned by the profile
+  content::DOMStorageContext* dom_storage_context_;  // Owned by the context
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(BrowsingDataLocalStorageHelper);
+  DISALLOW_COPY_AND_ASSIGN(LocalStorageHelper);
 };
 
-// This class is a thin wrapper around BrowsingDataLocalStorageHelper that does
+// This class is a thin wrapper around LocalStorageHelper that does
 // not fetch its information from the local storage context, but gets them
 // passed by a call when accessed.
-class CannedBrowsingDataLocalStorageHelper
-    : public BrowsingDataLocalStorageHelper {
+class CannedLocalStorageHelper : public LocalStorageHelper {
  public:
-  explicit CannedBrowsingDataLocalStorageHelper(Profile* profile);
+  explicit CannedLocalStorageHelper(content::BrowserContext* context);
 
   // Add a local storage to the set of canned local storages that is returned
   // by this helper.
@@ -77,17 +79,19 @@
   // Returns the set of origins that use local storage.
   const std::set<url::Origin>& GetOrigins() const;
 
-  // BrowsingDataLocalStorageHelper implementation.
+  // LocalStorageHelper implementation.
   void StartFetching(FetchCallback callback) override;
   void DeleteOrigin(const url::Origin& origin,
                     base::OnceClosure callback) override;
 
  private:
-  ~CannedBrowsingDataLocalStorageHelper() override;
+  ~CannedLocalStorageHelper() override;
 
   std::set<url::Origin> pending_origins_;
 
-  DISALLOW_COPY_AND_ASSIGN(CannedBrowsingDataLocalStorageHelper);
+  DISALLOW_COPY_AND_ASSIGN(CannedLocalStorageHelper);
 };
 
-#endif  // CHROME_BROWSER_BROWSING_DATA_BROWSING_DATA_LOCAL_STORAGE_HELPER_H_
+}  // namespace browsing_data
+
+#endif  // COMPONENTS_BROWSING_DATA_CONTENT_LOCAL_STORAGE_HELPER_H_
diff --git a/chrome/browser/browsing_data/browsing_data_local_storage_helper_unittest.cc b/components/browsing_data/content/local_storage_helper_unittest.cc
similarity index 73%
rename from chrome/browser/browsing_data/browsing_data_local_storage_helper_unittest.cc
rename to components/browsing_data/content/local_storage_helper_unittest.cc
index fa1ad468..1a1c841 100644
--- a/chrome/browser/browsing_data/browsing_data_local_storage_helper_unittest.cc
+++ b/components/browsing_data/content/local_storage_helper_unittest.cc
@@ -2,12 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/browsing_data/browsing_data_local_storage_helper.h"
+#include "components/browsing_data/content/local_storage_helper.h"
 
 #include "base/bind_helpers.h"
-#include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_browser_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace browsing_data {
 
 namespace {
 
@@ -16,12 +19,12 @@
 };
 
 TEST_F(CannedBrowsingDataLocalStorageTest, Empty) {
-  TestingProfile profile;
+  content::TestBrowserContext context;
 
   const GURL origin("http://host1:1/");
 
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> helper(
-      new CannedBrowsingDataLocalStorageHelper(&profile));
+  scoped_refptr<CannedLocalStorageHelper> helper(
+      new CannedLocalStorageHelper(&context));
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin));
@@ -31,14 +34,14 @@
 }
 
 TEST_F(CannedBrowsingDataLocalStorageTest, Delete) {
-  TestingProfile profile;
+  content::TestBrowserContext context;
 
   const GURL origin1("http://host1:9000");
   const GURL origin2("http://example.com");
   const GURL origin3("http://foo.example.com");
 
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> helper(
-      new CannedBrowsingDataLocalStorageHelper(&profile));
+  scoped_refptr<CannedLocalStorageHelper> helper(
+      new CannedLocalStorageHelper(&context));
 
   EXPECT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
@@ -52,13 +55,13 @@
 }
 
 TEST_F(CannedBrowsingDataLocalStorageTest, IgnoreExtensionsAndDevTools) {
-  TestingProfile profile;
+  content::TestBrowserContext context;
 
   const GURL origin1("chrome-extension://abcdefghijklmnopqrstuvwxyz/");
   const GURL origin2("devtools://abcdefghijklmnopqrstuvwxyz/");
 
-  scoped_refptr<CannedBrowsingDataLocalStorageHelper> helper(
-      new CannedBrowsingDataLocalStorageHelper(&profile));
+  scoped_refptr<CannedLocalStorageHelper> helper(
+      new CannedLocalStorageHelper(&context));
 
   ASSERT_TRUE(helper->empty());
   helper->Add(url::Origin::Create(origin1));
@@ -68,3 +71,5 @@
 }
 
 }  // namespace
+
+}  // namespace browsing_data
diff --git a/components/browsing_data/core/BUILD.gn b/components/browsing_data/core/BUILD.gn
index a1b573f1..28f8cd2a 100644
--- a/components/browsing_data/core/BUILD.gn
+++ b/components/browsing_data/core/BUILD.gn
@@ -39,12 +39,10 @@
     "//components/password_manager/core/browser",
     "//components/pref_registry",
     "//components/prefs",
-    "//components/signin/public/identity_manager",
     "//components/strings",
     "//components/sync",
     "//components/version_info",
     "//components/webdata/common",
-    "//services/network/public/cpp",
     "//ui/base",
   ]
 }
diff --git a/components/browsing_data/core/DEPS b/components/browsing_data/core/DEPS
index cf7d954..3b68fd56 100644
--- a/components/browsing_data/core/DEPS
+++ b/components/browsing_data/core/DEPS
@@ -7,7 +7,6 @@
   "+components/password_manager/core/browser",
   "+components/pref_registry",
   "+components/prefs",
-  "+components/signin/public/identity_manager",
   "+components/strings/grit/components_strings.h",
   "+components/sync/base",
   "+components/sync/driver",
@@ -15,6 +14,5 @@
   "+components/version_info",
   "+components/webdata/common",
   "+net",
-  "+services/network/public",
   "+ui/base",
 ]
diff --git a/components/browsing_data/core/history_notice_utils.cc b/components/browsing_data/core/history_notice_utils.cc
index e454a5d3..ef252ed 100644
--- a/components/browsing_data/core/history_notice_utils.cc
+++ b/components/browsing_data/core/history_notice_utils.cc
@@ -10,40 +10,13 @@
 #include "base/single_thread_task_runner.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "components/history/core/browser/web_history_service.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_user_settings.h"
 #include "components/version_info/version_info.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace {
-constexpr net::PartialNetworkTrafficAnnotationTag
-    kHistoryRecordingEnabledAnnotation =
-        net::DefinePartialNetworkTrafficAnnotation("history_recording_enabled",
-                                                   "web_history_service",
-                                                   R"(
-        semantics {
-          description:
-            "Queries history.google.com to find out if user has the 'Include "
-            "Chrome browsing history and activity from websites and apps that "
-            "use Google services' option enabled in the Activity controls of "
-            "their Google account. This is done for users who sync their "
-            "browsing history without a custom passphrase in order to show "
-            "information about history.google.com on the history page, "
-            "the settings sync setup page and in the Clear Browsing Data "
-            "dialog."
-          trigger:
-            "This request is sent when user opens the history page or the "
-            "settings sync setup page or the Clear Browsing Data dialog and "
-            "history sync without a custom passphrase is (re)enabled."
-          data:
-            "An OAuth2 token authenticating the user."
-        }
-        policy {
-          chrome_policy {
-            SyncDisabled {
-              SyncDisabled: true
-            }
-          }
-        })");
 
 // Merges several asynchronous boolean callbacks into one that returns a boolean
 // product of their responses. Deletes itself when done.
@@ -85,51 +58,42 @@
     const syncer::SyncService* sync_service,
     history::WebHistoryService* history_service,
     base::OnceCallback<void(bool)> callback) {
-  IsHistoryRecordingEnabledAndCanBeUsed(
-      sync_service, history_service,
-      base::BindOnce(
-          [](base::OnceCallback<void(bool)> callback,
-             const base::Optional<bool>& history_recording_enabled) {
-            std::move(callback).Run(history_recording_enabled.value_or(false));
-          },
-          std::move(callback)));
-}
-
-std::unique_ptr<history::WebHistoryService::Request>
-CreateQueryWebAndAppActivityRequest(
-    signin::IdentityManager* identity_manager,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    base::OnceCallback<void(history::WebHistoryService::Request*,
-                            const base::Optional<bool>&)> callback) {
-  DCHECK(identity_manager);
-  DCHECK(url_loader_factory);
-
-  return history::WebHistoryService::CreateQueryWebAndAppActivityRequest(
-      identity_manager, url_loader_factory, std::move(callback),
-      kHistoryRecordingEnabledAnnotation);
-}
-
-void IsHistoryRecordingEnabledAndCanBeUsed(
-    const syncer::SyncService* sync_service,
-    history::WebHistoryService* history_service,
-    base::OnceCallback<void(const base::Optional<bool>&)> callback) {
   if (!sync_service || !sync_service->IsSyncFeatureActive() ||
       !sync_service->GetActiveDataTypes().Has(
           syncer::HISTORY_DELETE_DIRECTIVES) ||
+      sync_service->GetUserSettings()->IsUsingSecondaryPassphrase() ||
       !history_service) {
-    std::move(callback).Run(base::nullopt);
-    return;
-  }
-
-  if (sync_service->GetUserSettings()->IsUsingSecondaryPassphrase()) {
-    // The user has a custom passphrase. The data is encrypted and can not be
-    // used.
     std::move(callback).Run(false);
     return;
   }
-
+  net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
+      net::DefinePartialNetworkTrafficAnnotation("history_notice_utils_notice",
+                                                 "web_history_service", R"(
+      semantics {
+        description:
+          "Queries history.google.com to find out if user has the 'Include "
+          "Chrome browsing history and activity from websites and apps that "
+          "use Google services' option enabled in the Activity controls of "
+          "their Google account. This is done for users who sync their "
+          "browsing history without a custom passphrase in order to show "
+          "information about history.google.com on the history page and in "
+          "the Clear Browsing Data dialog."
+        trigger:
+          "This request is sent when user opens the history page or the "
+          "Clear Browsing Data dialog and history sync without a custom "
+          "passphrase is (re)enabled."
+        data:
+          "An OAuth2 token authenticating the user."
+      }
+      policy {
+        chrome_policy {
+          SyncDisabled {
+            SyncDisabled: true
+          }
+        }
+      })");
   history_service->QueryWebAndAppActivity(std::move(callback),
-                                          kHistoryRecordingEnabledAnnotation);
+                                          partial_traffic_annotation);
 }
 
 void ShouldPopupDialogAboutOtherFormsOfBrowsingHistory(
@@ -176,13 +140,8 @@
             }
           })");
   history_service->QueryWebAndAppActivity(
-      base::BindOnce(
-          [](base::OnceCallback<void(bool)> callback,
-             const base::Optional<bool>& history_recording_enabled) {
-            std::move(callback).Run(history_recording_enabled.value_or(false));
-          },
-          base::BindOnce(&MergeBooleanCallbacks::RunCallback,
-                         base::Unretained(merger))),
+      base::BindOnce(&MergeBooleanCallbacks::RunCallback,
+                     base::Unretained(merger)),
       partial_traffic_annotation);
   history_service->QueryOtherFormsOfBrowsingHistory(
       channel,
diff --git a/components/browsing_data/core/history_notice_utils.h b/components/browsing_data/core/history_notice_utils.h
index f5c575b9..ff51743 100644
--- a/components/browsing_data/core/history_notice_utils.h
+++ b/components/browsing_data/core/history_notice_utils.h
@@ -8,10 +8,10 @@
 #include <string>
 
 #include "base/callback_forward.h"
-#include "base/optional.h"
-#include "components/history/core/browser/web_history_service.h"
-#include "components/signin/public/identity_manager/identity_manager.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace history {
+class WebHistoryService;
+}
 
 namespace syncer {
 class SyncService;
@@ -23,38 +23,6 @@
 
 namespace browsing_data {
 
-// Returns a request that can be used to query |Web and App Activity|. It can be
-// made independently from the history sync state and its lifetime needs to be
-// managed by the caller.
-//
-// Once the request is completed, |callback| is called with the following
-// arguments:
-//   * a pointer to the request associated with the response.
-//   * a base::Optional<bool> that indicated whether the user has enabled
-//     'Include Chrome browsing history and activity from websites and apps that
-//     use Google services' in the |Web and App Activity| for their Google
-//     Account. This argument is base::nullopt if the request to fetch the
-//     |Web and App Activity| information failed.
-std::unique_ptr<history::WebHistoryService::Request>
-CreateQueryWebAndAppActivityRequest(
-    signin::IdentityManager* identity_manager,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    base::OnceCallback<void(history::WebHistoryService::Request*,
-                            const base::Optional<bool>&)> callback);
-
-// The response is returned in the |callback|. It can be:
-// * nullopt: If we fail to query the |Web And App Activity| or history sync is
-//   not fully active yet.
-// * True: If the user has enabled 'Include Chrome browsing history and activity
-//   from websites and apps that use Google services' in the
-//   |Web and App Activity| of their Google Account, data is not encrypted with
-//   custom passphrase and history sync is active.
-// * False: Otherwise.
-void IsHistoryRecordingEnabledAndCanBeUsed(
-    const syncer::SyncService* sync_service,
-    history::WebHistoryService* history_service,
-    base::OnceCallback<void(const base::Optional<bool>&)> callback);
-
 // Whether the Clear Browsing Data UI should show a notice about the existence
 // of other forms of browsing history stored in user's account. The response
 // is returned in a |callback|.
diff --git a/components/cast_channel/cast_message_util_unittest.cc b/components/cast_channel/cast_message_util_unittest.cc
index 9515808..65ee3dcb 100644
--- a/components/cast_channel/cast_message_util_unittest.cc
+++ b/components/cast_channel/cast_message_util_unittest.cc
@@ -10,7 +10,7 @@
 #include "third_party/openscreen/src/cast/common/channel/proto/cast_channel.pb.h"
 
 using base::test::IsJson;
-using base::test::ParseJsonDeprecated;
+using base::test::ParseJson;
 
 namespace cast_channel {
 
@@ -38,8 +38,7 @@
     }
   )";
 
-  LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
+  LaunchSessionResponse response = GetLaunchSessionResponse(ParseJson(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kOk, response.result);
   EXPECT_TRUE(response.receiver_status);
 }
@@ -52,8 +51,7 @@
     }
   )";
 
-  LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
+  LaunchSessionResponse response = GetLaunchSessionResponse(ParseJson(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kError, response.result);
   EXPECT_FALSE(response.receiver_status);
 }
@@ -68,8 +66,7 @@
     }
   )";
 
-  LaunchSessionResponse response =
-      GetLaunchSessionResponse(*ParseJsonDeprecated(payload));
+  LaunchSessionResponse response = GetLaunchSessionResponse(ParseJson(payload));
   EXPECT_EQ(LaunchSessionResponse::Result::kUnknown, response.result);
   EXPECT_FALSE(response.receiver_status);
 }
@@ -127,8 +124,8 @@
        "requestId": 123,
     })";
 
-  CastMessage message = CreateMediaRequest(*ParseJsonDeprecated(body), 123,
-                                           "theSourceId", "theDestinationId");
+  CastMessage message = CreateMediaRequest(ParseJson(body), 123, "theSourceId",
+                                           "theDestinationId");
   ASSERT_TRUE(IsCastMessageValid(message));
   EXPECT_EQ(kMediaNamespace, message.namespace_());
   EXPECT_EQ("theSourceId", message.source_id());
@@ -147,7 +144,7 @@
     })";
 
   CastMessage message =
-      CreateSetVolumeRequest(*ParseJsonDeprecated(body), 123, "theSourceId");
+      CreateSetVolumeRequest(ParseJson(body), 123, "theSourceId");
   ASSERT_TRUE(IsCastMessageValid(message));
   EXPECT_EQ(kReceiverNamespace, message.namespace_());
   EXPECT_EQ("theSourceId", message.source_id());
diff --git a/components/dom_distiller/content/browser/BUILD.gn b/components/dom_distiller/content/browser/BUILD.gn
index 584e8849..77f0757 100644
--- a/components/dom_distiller/content/browser/BUILD.gn
+++ b/components/dom_distiller/content/browser/BUILD.gn
@@ -18,6 +18,8 @@
     "distiller_page_web_contents.h",
     "dom_distiller_viewer_source.cc",
     "dom_distiller_viewer_source.h",
+    "uma_helper.cc",
+    "uma_helper.h",
   ]
 
   public_deps = [
@@ -50,9 +52,13 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "dom_distiller_viewer_source_unittest.cc" ]
+  sources = [
+    "dom_distiller_viewer_source_unittest.cc",
+    "uma_helper_unittest.cc",
+  ]
   deps = [
     ":browser",
+    "//base/test:test_support",
     "//content/test:test_support",
     "//testing/gtest",
   ]
diff --git a/components/dom_distiller/content/browser/distillability_driver.h b/components/dom_distiller/content/browser/distillability_driver.h
index 21fa749..f14eb90 100644
--- a/components/dom_distiller/content/browser/distillability_driver.h
+++ b/components/dom_distiller/content/browser/distillability_driver.h
@@ -12,6 +12,7 @@
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "components/dom_distiller/content/browser/distillable_page_utils.h"
+#include "components/dom_distiller/content/browser/uma_helper.h"
 #include "components/dom_distiller/content/common/mojom/distillability_service.mojom.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -33,6 +34,8 @@
     return latest_result_;
   }
 
+  UMAHelper::DistillabilityDriverTimer& GetTimer() { return timer_; }
+
  private:
   explicit DistillabilityDriver(content::WebContents* web_contents);
   friend class content::WebContentsUserData<DistillabilityDriver>;
@@ -48,6 +51,13 @@
   // new page, accounting for same-document navigation.
   base::Optional<DistillabilityResult> latest_result_;
 
+  // For UMA metrics on durations spent in distilled or distillable pages.
+  // Because each DistillabilityDriver is associated with just one WebContents,
+  // it can be used to track the amount of time spent actively viewing that
+  // WebContents when the page is distillable or distilled, creating useful
+  // metrics for the ReaderMode experiment.
+  UMAHelper::DistillabilityDriverTimer timer_;
+
   base::WeakPtrFactory<DistillabilityDriver> weak_factory_{this};
 
   WEB_CONTENTS_USER_DATA_KEY_DECL();
diff --git a/components/dom_distiller/content/browser/uma_helper.cc b/components/dom_distiller/content/browser/uma_helper.cc
new file mode 100644
index 0000000..fb3da6ab
--- /dev/null
+++ b/components/dom_distiller/content/browser/uma_helper.cc
@@ -0,0 +1,175 @@
+// 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 "components/dom_distiller/content/browser/uma_helper.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
+#include "components/dom_distiller/content/browser/distillability_driver.h"
+#include "content/public/browser/visibility.h"
+#include "content/public/browser/web_contents.h"
+
+namespace dom_distiller {
+
+void UMAHelper::DistillabilityDriverTimer::Start(bool is_distilled_page) {
+  if (HasStarted())
+    DCHECK_EQ(is_distilled_page_, is_distilled_page);
+  is_distilled_page_ = is_distilled_page;
+  if (active_time_start_ != base::Time())
+    return;
+  active_time_start_ = base::Time::Now();
+}
+
+void UMAHelper::DistillabilityDriverTimer::Resume() {
+  DCHECK(HasStarted());
+  if (active_time_start_ != base::Time())
+    return;
+  active_time_start_ = base::Time::Now();
+}
+
+void UMAHelper::DistillabilityDriverTimer::Pause() {
+  // Return early if already paused.
+  if (active_time_start_ == base::Time())
+    return;
+  total_active_time_ += base::Time::Now() - active_time_start_;
+  active_time_start_ = base::Time();
+}
+
+void UMAHelper::DistillabilityDriverTimer::Reset() {
+  active_time_start_ = base::Time();
+  total_active_time_ = base::TimeDelta();
+  is_distilled_page_ = false;
+}
+
+bool UMAHelper::DistillabilityDriverTimer::HasStarted() {
+  return active_time_start_ != base::Time() ||
+         total_active_time_ != base::TimeDelta();
+}
+
+bool UMAHelper::DistillabilityDriverTimer::IsTimingDistilledPage() {
+  return HasStarted() && is_distilled_page_;
+}
+
+base::TimeDelta UMAHelper::DistillabilityDriverTimer::GetElapsedTime() {
+  // If the timer is unpaused, add in the current time too.
+  if (active_time_start_ != base::Time())
+    return total_active_time_ + (base::Time::Now() - active_time_start_);
+  return total_active_time_;
+}
+
+// static
+void UMAHelper::RecordReaderModeEntry(ReaderModeEntryPoint entry_point) {
+  // Use histograms instead of user actions because order doesn't matter.
+  base::UmaHistogramEnumeration("DomDistiller.ReaderMode.EntryPoint",
+                                entry_point);
+}
+
+// static
+void UMAHelper::RecordReaderModeExit(ReaderModeEntryPoint exit_point) {
+  // Use histograms instead of user actions because order doesn't matter.
+  base::UmaHistogramEnumeration("DomDistiller.ReaderMode.ExitPoint",
+                                exit_point);
+}
+
+// static
+void UMAHelper::UpdateTimersOnContentsChange(
+    content::WebContents* web_contents,
+    content::WebContents* old_contents) {
+  if (old_contents && old_contents != web_contents) {
+    // Pause the timer on the the driver at the old contents.
+    DistillabilityDriver::CreateForWebContents(old_contents);
+    DistillabilityDriver* old_driver =
+        DistillabilityDriver::FromWebContents(old_contents);
+    CHECK(old_driver);
+    if (old_driver->GetTimer().HasStarted()) {
+      old_driver->GetTimer().Pause();
+    }
+  }
+
+  CHECK(web_contents);
+  DistillabilityDriver::CreateForWebContents(web_contents);
+  DistillabilityDriver* driver =
+      DistillabilityDriver::FromWebContents(web_contents);
+  CHECK(driver);
+
+  // If we were already timing the new page, pause or resume as necessary.
+  if (!driver->GetTimer().HasStarted())
+    return;
+
+  if (web_contents->GetVisibility() != content::Visibility::VISIBLE) {
+    // Pause any running timer if the web contents are no longer visible.
+    driver->GetTimer().Pause();
+    return;
+  }
+  // Resume the driver's timer when contents have come back into focus.
+  driver->GetTimer().Resume();
+}
+
+// static
+void UMAHelper::StartTimerIfNeeded(content::WebContents* web_contents,
+                                   ReaderModePageType page_type) {
+  CHECK(web_contents);
+  DistillabilityDriver::CreateForWebContents(web_contents);
+  DistillabilityDriver* driver =
+      DistillabilityDriver::FromWebContents(web_contents);
+  CHECK(driver);
+
+  if (page_type == ReaderModePageType::kDistilled) {
+    // If this is a distilled page, ensure the timer is running.
+    driver->GetTimer().Start(/* is_distilled_page */ true);
+  } else if (page_type == ReaderModePageType::kDistillable) {
+    // If we are on a distillable page, ensure the timer is running.
+    driver->GetTimer().Start(false);
+  }
+}
+
+// static
+void UMAHelper::UpdateTimersOnNavigation(content::WebContents* web_contents,
+                                         ReaderModePageType page_type) {
+  CHECK(web_contents);
+  DistillabilityDriver::CreateForWebContents(web_contents);
+  DistillabilityDriver* driver =
+      DistillabilityDriver::FromWebContents(web_contents);
+  CHECK(driver);
+
+  if (!driver->GetTimer().HasStarted())
+    return;
+
+  // Stop timing distilled pages when a user navigates away. (Note that
+  // distillable pages are logged only when reader mode is triggered, so there
+  // is no need to log time on a distillable page at navigation.
+  if (driver->GetTimer().IsTimingDistilledPage())
+    LogTimeOnDistilledPage(driver->GetTimer().GetElapsedTime());
+  driver->GetTimer().Reset();
+}
+
+// static
+void UMAHelper::LogTimeOnDistillablePage(content::WebContents* web_contents) {
+  CHECK(web_contents);
+  DistillabilityDriver::CreateForWebContents(web_contents);
+  DistillabilityDriver* driver =
+      DistillabilityDriver::FromWebContents(web_contents);
+  CHECK(driver);
+
+  // TODO(crbug.com/1061928): Check that the timer has started before logging.
+  // This is currently causing tests to fail, but they can begin passing if they
+  // add DistillabilityObservers and wait for DistillabilityResults before
+  // trying to toggle from a distillable to a distilled page.
+  // DCHECK(driver->GetTimer().HasStarted());
+
+  // We shouldn't log time on a distillable page if this is a distilled page.
+  DCHECK(!driver->GetTimer().IsTimingDistilledPage());
+
+  base::UmaHistogramLongTimes(
+      "DomDistiller.Time.ActivelyViewingArticleBeforeDistilling",
+      driver->GetTimer().GetElapsedTime());
+  driver->GetTimer().Reset();
+}
+
+void UMAHelper::LogTimeOnDistilledPage(base::TimeDelta time) {
+  base::UmaHistogramLongTimes("DomDistiller.Time.ActivelyViewingReaderModePage",
+                              time);
+}
+
+}  // namespace dom_distiller
diff --git a/components/dom_distiller/content/browser/uma_helper.h b/components/dom_distiller/content/browser/uma_helper.h
new file mode 100644
index 0000000..c144ff41
--- /dev/null
+++ b/components/dom_distiller/content/browser/uma_helper.h
@@ -0,0 +1,103 @@
+// 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 COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_UMA_HELPER_H_
+#define COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_UMA_HELPER_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace dom_distiller {
+
+// A utility class for logging UMA metrics.
+class UMAHelper {
+ public:
+  // Must agree with ReaderModeEntryPoint in enums.xml.
+  enum class ReaderModeEntryPoint {
+    kOmniboxIcon = 0,
+    kMenuOption = 1,
+    kMaxValue = kMenuOption,
+  };
+
+  // A page can be distillable (an article), distilled (a reader mode
+  // page), or neither (some other webpage).
+  enum class ReaderModePageType {
+    kDistillable,
+    kDistilled,
+    kNone,
+  };
+
+  // A class to time the active time of a page, so that metrics for time spent
+  // on distillable pages and distilled pages can be logged. There should be
+  // exactly one of these associated with each DistillabilityDriver.
+  class DistillabilityDriverTimer {
+   public:
+    DistillabilityDriverTimer() = default;
+
+    ~DistillabilityDriverTimer() {
+      // The timer has the same life as a DistillabilityDriver, which is
+      // destroyed when a WebContents is destroyed. Thus if we are being
+      // destroyed and on a distilled page, go ahead and log the total time to
+      // UMA. This may happen if the user closes the tab or window, for example.
+      if (IsTimingDistilledPage())
+        UMAHelper::LogTimeOnDistilledPage(GetElapsedTime());
+    }
+
+    // Starts if not already started.
+    void Start(bool is_distilled_page);
+    // Starts up again.
+    void Resume();
+    // Pauses if not already paused.
+    void Pause();
+    // Resets the timer and whether it is tracking a distilled page.
+    void Reset();
+    // Returns true if the timer is not zeroed, whether it is paused or
+    // currently running.
+    bool HasStarted();
+    // If the timer is not zeroed and the timer is associated with a distilled
+    // page.
+    bool IsTimingDistilledPage();
+    // The amount of active time so far.
+    base::TimeDelta GetElapsedTime();
+
+   private:
+    base::TimeDelta total_active_time_;
+    base::Time active_time_start_;
+    bool is_distilled_page_ = false;
+  };
+
+  static void RecordReaderModeEntry(ReaderModeEntryPoint entry_point);
+  static void RecordReaderModeExit(ReaderModeEntryPoint exit_point);
+
+  // Timers at the old contents may need to be stopped / paused.
+  static void UpdateTimersOnContentsChange(content::WebContents* web_contents,
+                                           content::WebContents* old_contents);
+  // Starts the timer if the page time is one which should be timed.
+  static void StartTimerIfNeeded(content::WebContents* web_contents,
+                                 ReaderModePageType page_type);
+  // Timers may need to be paused on navigation.
+  static void UpdateTimersOnNavigation(content::WebContents* web_contents,
+                                       ReaderModePageType page_type);
+
+  // Logs the time spent on a distillable page. Should be called just before
+  // when switching to a distilled page. We are only interested in time on
+  // distillable pages when the user then decides to distill the article.
+  static void LogTimeOnDistillablePage(content::WebContents* web_contents);
+
+  // Logs the time spent on a distilled (reader mode) page. Should be called
+  // when leaving a distilled page, i.e. if the tab is closed or if the user
+  // navigates to another page.
+  static void LogTimeOnDistilledPage(base::TimeDelta time);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(UMAHelper);
+};
+
+}  // namespace dom_distiller
+
+#endif  // COMPONENTS_DOM_DISTILLER_CONTENT_BROWSER_UMA_HELPER_H_
diff --git a/components/dom_distiller/content/browser/uma_helper_unittest.cc b/components/dom_distiller/content/browser/uma_helper_unittest.cc
new file mode 100644
index 0000000..417154f
--- /dev/null
+++ b/components/dom_distiller/content/browser/uma_helper_unittest.cc
@@ -0,0 +1,116 @@
+// 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 "components/dom_distiller/content/browser/uma_helper.h"
+#include <string>
+
+#include "base/macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace dom_distiller {
+
+namespace {
+const std::string kDistilledPageHistogram =
+    "DomDistiller.Time.ActivelyViewingReaderModePage";
+}  // namespace
+
+class UMAHelperTest : public testing::Test {
+ public:
+  void FastForwardBy(int milliseconds) {
+    task_environment_.FastForwardBy(
+        base::TimeDelta::FromMilliseconds(milliseconds));
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(UMAHelperTest, TestTimerBasics) {
+  UMAHelper::DistillabilityDriverTimer timer;
+  ASSERT_FALSE(timer.HasStarted());
+  timer.Start(false);
+  ASSERT_FALSE(timer.IsTimingDistilledPage());
+  ASSERT_TRUE(timer.HasStarted());
+
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 100);
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 200);
+
+  // After pausing, the timer should still be running (active), and the
+  // value should be unchanged.
+  timer.Pause();
+  ASSERT_TRUE(timer.HasStarted());
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 200);
+  // Paused timer shouldn't increase value when time changes.
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 200);
+
+  // Starting the timer again will cause it to move forward again as time
+  // changes.
+  timer.Start(false);
+  ASSERT_TRUE(timer.HasStarted());
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 200);
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 300);
+
+  // Pause again, but this time continue with Resume instead of Start. This
+  // should have the same effect.
+  timer.Pause();
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 300);
+  timer.Resume();
+  FastForwardBy(100);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 400);
+
+  // Calling start or pause multiple times in a row does not break anything.
+  timer.Start(false);
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 400);
+  FastForwardBy(100);
+  timer.Start(false);
+  timer.Start(false);
+  timer.Resume();
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 500);
+  timer.Pause();
+  timer.Pause();
+  ASSERT_EQ(timer.GetElapsedTime().InMilliseconds(), 500);
+
+  // Reset the timer.
+  timer.Reset();
+  ASSERT_FALSE(timer.HasStarted());
+}
+
+TEST_F(UMAHelperTest, TestTimerForDistilledPage) {
+  UMAHelper::DistillabilityDriverTimer* timer =
+      new UMAHelper::DistillabilityDriverTimer();
+  base::HistogramTester histogram_tester;
+
+  timer->Start(true);
+  ASSERT_TRUE(timer->HasStarted());
+  ASSERT_TRUE(timer->IsTimingDistilledPage());
+  FastForwardBy(100);
+  ASSERT_EQ(timer->GetElapsedTime().InMilliseconds(), 100);
+  timer->Start(true);
+  ASSERT_EQ(timer->GetElapsedTime().InMilliseconds(), 100);
+
+  // Destroy the timer. Since it was running and on a distilled page, expect
+  // logging to have happened.
+  delete timer;
+  histogram_tester.ExpectTimeBucketCount(
+      kDistilledPageHistogram, base::TimeDelta::FromMilliseconds(100), 1);
+
+  // Nothing is logged if it wasn't destroyed while on a distilled page.
+  timer = new UMAHelper::DistillabilityDriverTimer();
+  timer->Start(false);
+  FastForwardBy(200);
+  delete timer;
+  histogram_tester.ExpectTimeBucketCount(
+      kDistilledPageHistogram, base::TimeDelta::FromMilliseconds(200), 0);
+}
+
+}  // namespace dom_distiller
diff --git a/components/dom_distiller/core/BUILD.gn b/components/dom_distiller/core/BUILD.gn
index a956479..d70bbd2 100644
--- a/components/dom_distiller/core/BUILD.gn
+++ b/components/dom_distiller/core/BUILD.gn
@@ -43,8 +43,6 @@
     "pref_names.h",
     "task_tracker.cc",
     "task_tracker.h",
-    "uma_helper.cc",
-    "uma_helper.h",
     "url_constants.cc",
     "url_constants.h",
     "url_utils.cc",
diff --git a/components/dom_distiller/core/uma_helper.cc b/components/dom_distiller/core/uma_helper.cc
deleted file mode 100644
index 66fefc3c..0000000
--- a/components/dom_distiller/core/uma_helper.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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 "components/dom_distiller/core/uma_helper.h"
-
-#include "base/metrics/histogram_functions.h"
-
-namespace dom_distiller {
-
-// static
-void UMAHelper::RecordReaderModeEntry(ReaderModeEntryPoint entry_point) {
-  // Use histograms instead of user actions because order doesn't matter.
-  base::UmaHistogramEnumeration("DomDistiller.ReaderMode.EntryPoint",
-                                entry_point);
-}
-
-// static
-void UMAHelper::RecordReaderModeExit(ReaderModeEntryPoint exit_point) {
-  // Use histograms instead of user actions because order doesn't matter.
-  base::UmaHistogramEnumeration("DomDistiller.ReaderMode.ExitPoint",
-                                exit_point);
-}
-
-}  // namespace dom_distiller
diff --git a/components/dom_distiller/core/uma_helper.h b/components/dom_distiller/core/uma_helper.h
deleted file mode 100644
index cd85416..0000000
--- a/components/dom_distiller/core/uma_helper.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 COMPONENTS_DOM_DISTILLER_CORE_UMA_HELPER_H_
-#define COMPONENTS_DOM_DISTILLER_CORE_UMA_HELPER_H_
-
-#include "base/macros.h"
-
-namespace dom_distiller {
-
-// A utility class for logging UMA metrics.
-class UMAHelper {
- public:
-  // Must agree with ReaderModeEntryPoint in enums.xml.
-  enum class ReaderModeEntryPoint {
-    kOmniboxIcon = 0,
-    kMenuOption = 1,
-    kMaxValue = kMenuOption,
-  };
-
-  static void RecordReaderModeEntry(ReaderModeEntryPoint entry_point);
-  static void RecordReaderModeExit(ReaderModeEntryPoint exit_point);
-
- private:
-  DISALLOW_IMPLICIT_CONSTRUCTORS(UMAHelper);
-};
-
-}  // namespace dom_distiller
-
-#endif  // COMPONENTS_DOM_DISTILLER_CORE_UMA_HELPER_H_
diff --git a/components/exo/wayland/clients/client_base.cc b/components/exo/wayland/clients/client_base.cc
index aa6c0ccc..843bc27 100644
--- a/components/exo/wayland/clients/client_base.cc
+++ b/components/exo/wayland/clients/client_base.cc
@@ -374,6 +374,7 @@
       LOG(ERROR) << "Invalid value for " << switches::kTransform;
       return false;
     }
+    has_transform = true;
   }
 
   use_drm = command_line.HasSwitch(switches::kUseDrm);
@@ -429,6 +430,7 @@
   fullscreen_ = params.fullscreen;
   transparent_background_ = params.transparent_background;
   y_invert_ = params.y_invert;
+  has_transform_ = params.has_transform;
 
   display_.reset(wl_display_connect(nullptr));
   if (!display_) {
@@ -751,7 +753,29 @@
                                 int32_t subpixel,
                                 const char* make,
                                 const char* model,
-                                int32_t transform) {}
+                                int32_t transform) {
+  if (has_transform_)
+    return;
+  // |transform| describes the display transform. In order to take advantage of
+  // hardware overlays, content needs to be rotated in the opposite direction to
+  // show right-side up on the display.
+  switch (transform) {
+    case WL_OUTPUT_TRANSFORM_90:
+      transform_ = WL_OUTPUT_TRANSFORM_270;
+      break;
+    case WL_OUTPUT_TRANSFORM_270:
+      transform_ = WL_OUTPUT_TRANSFORM_90;
+      break;
+    case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+      transform_ = WL_OUTPUT_TRANSFORM_FLIPPED_270;
+      break;
+    case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+      transform_ = WL_OUTPUT_TRANSFORM_FLIPPED_90;
+      break;
+    default:
+      transform_ = transform;
+  }
+}
 
 void ClientBase::HandleMode(void* data,
                             struct wl_output* wl_output,
diff --git a/components/exo/wayland/clients/client_base.h b/components/exo/wayland/clients/client_base.h
index 4e4b192..0b4c2c26 100644
--- a/components/exo/wayland/clients/client_base.h
+++ b/components/exo/wayland/clients/client_base.h
@@ -48,6 +48,7 @@
     size_t height = 256;
     int scale = 1;
     int transform = WL_OUTPUT_TRANSFORM_NORMAL;
+    bool has_transform = false;
     bool fullscreen = false;
     bool transparent_background = false;
     bool use_drm = false;
@@ -179,6 +180,7 @@
   gfx::Size size_ = gfx::Size(256, 256);
   int scale_ = 1;
   int transform_ = WL_OUTPUT_TRANSFORM_NORMAL;
+  bool has_transform_ = false;
   gfx::Size surface_size_ = gfx::Size(256, 256);
   bool fullscreen_ = false;
   bool use_memfd_ = false;
diff --git a/components/history/core/browser/web_history_service.cc b/components/history/core/browser/web_history_service.cc
index 341280f..bfd7569 100644
--- a/components/history/core/browser/web_history_service.cc
+++ b/components/history/core/browser/web_history_service.cc
@@ -64,19 +64,6 @@
 
 const char kSyncProtoMimeType[] = "application/octet-stream";
 
-const char kQueryWebAndAppActivityStateHistogramName[] =
-    "WebHistory.QueryWebAndAppActivity.State";
-
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class QueryWebAndAppActivityState {
-  kOn = 0,
-  kOff = 1,
-  kRequestFailed = 2,
-  kUnexpectedResponse = 3,
-  kMaxValue = kUnexpectedResponse,
-};
-
 // The maximum number of retries for the SimpleURLLoader requests.
 const size_t kMaxRetries = 1;
 
@@ -540,30 +527,6 @@
   request->Start();
 }
 
-// static
-std::unique_ptr<history::WebHistoryService::Request>
-WebHistoryService::CreateQueryWebAndAppActivityRequest(
-    signin::IdentityManager* identity_manager,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    QueryWebAndAppActivityWithRequestCallback callback,
-    const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
-  CompletionCallback completion_callback = base::BindOnce(
-      [](QueryWebAndAppActivityWithRequestCallback callback,
-         WebHistoryService::Request* request, bool success) {
-        std::move(callback).Run(
-            request,
-            WebHistoryService::ReportQueryWebAndAppActivity(request, success));
-      },
-      std::move(callback));
-
-  GURL url(kQueryWebAndAppActivityUrl);
-  std::unique_ptr<history::WebHistoryService::Request> request =
-      base::WrapUnique(new RequestImpl(identity_manager, url_loader_factory,
-                                       url, std::move(completion_callback),
-                                       partial_traffic_annotation));
-  return request;
-}
-
 void WebHistoryService::QueryOtherFormsOfBrowsingHistory(
     version_info::Channel channel,
     QueryOtherFormsOfBrowsingHistoryCallback callback,
@@ -666,7 +629,18 @@
       std::move(pending_web_and_app_activity_requests_[request]);
   pending_web_and_app_activity_requests_.erase(request);
 
-  std::move(callback).Run(ReportQueryWebAndAppActivity(request, success));
+  std::unique_ptr<base::DictionaryValue> response_value;
+  bool web_and_app_activity_enabled = false;
+
+  if (success) {
+    response_value = ReadResponse(request);
+    if (response_value) {
+      response_value->GetBoolean("history_recording_enabled",
+                                 &web_and_app_activity_enabled);
+    }
+  }
+
+  std::move(callback).Run(web_and_app_activity_enabled);
 }
 
 void WebHistoryService::QueryOtherFormsOfBrowsingHistoryCompletionCallback(
@@ -687,29 +661,4 @@
   std::move(callback).Run(has_other_forms_of_browsing_history);
 }
 
-// static
-base::Optional<bool> WebHistoryService::ReportQueryWebAndAppActivity(
-    WebHistoryService::Request* request,
-    bool success) {
-  if (success) {
-    std::unique_ptr<base::DictionaryValue> response_value;
-    response_value = ReadResponse(request);
-    bool web_and_app_activity_enabled = false;
-    if (response_value &&
-        response_value->GetBoolean("history_recording_enabled",
-                                   &web_and_app_activity_enabled)) {
-      UMA_HISTOGRAM_ENUMERATION(kQueryWebAndAppActivityStateHistogramName,
-                                web_and_app_activity_enabled
-                                    ? QueryWebAndAppActivityState::kOn
-                                    : QueryWebAndAppActivityState::kOff);
-      return web_and_app_activity_enabled;
-    }
-  }
-  UMA_HISTOGRAM_ENUMERATION(
-      kQueryWebAndAppActivityStateHistogramName,
-      success ? QueryWebAndAppActivityState::kUnexpectedResponse
-              : QueryWebAndAppActivityState::kRequestFailed);
-  return base::nullopt;
-}
-
 }  // namespace history
diff --git a/components/history/core/browser/web_history_service.h b/components/history/core/browser/web_history_service.h
index 18b0036..a7acd29 100644
--- a/components/history/core/browser/web_history_service.h
+++ b/components/history/core/browser/web_history_service.h
@@ -89,12 +89,7 @@
   using AudioWebHistoryCallback =
       base::OnceCallback<void(bool success, bool new_enabled_value)>;
 
-  using QueryWebAndAppActivityCallback = base::OnceCallback<void(
-      const base::Optional<bool>& history_recording_enabled)>;
-
-  using QueryWebAndAppActivityWithRequestCallback = base::OnceCallback<void(
-      WebHistoryService::Request* request,
-      const base::Optional<bool>& history_recording_enabled)>;
+  using QueryWebAndAppActivityCallback = base::OnceCallback<void(bool success)>;
 
   using QueryOtherFormsOfBrowsingHistoryCallback =
       base::OnceCallback<void(bool success)>;
@@ -157,18 +152,6 @@
       const net::PartialNetworkTrafficAnnotationTag&
           partial_traffic_annotation);
 
-  // Returns a request to query whether web and app activity is enabled on the
-  // server. The request can be made independently from sync state. The caller
-  // must make sure that the |identity_manager| outlives the returned request
-  // object.
-  static std::unique_ptr<history::WebHistoryService::Request>
-  CreateQueryWebAndAppActivityRequest(
-      signin::IdentityManager* identity_manager,
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      QueryWebAndAppActivityWithRequestCallback callback,
-      const net::PartialNetworkTrafficAnnotationTag&
-          partial_traffic_annotation);
-
   // Used for tests.
   size_t GetNumberOfPendingAudioHistoryRequests();
 
@@ -228,18 +211,13 @@
   // completed. Unpacks the response and calls |callback|, which is the original
   // callback that was passed to QueryOtherFormsOfBrowsingHistory().
   void QueryOtherFormsOfBrowsingHistoryCompletionCallback(
-      WebHistoryService::QueryOtherFormsOfBrowsingHistoryCallback callback,
+      WebHistoryService::QueryWebAndAppActivityCallback callback,
       WebHistoryService::Request* request,
       bool success);
 
  private:
   friend class WebHistoryServiceTest;
 
-  // Extracts from the request's response if history recording is enabled.
-  static base::Optional<bool> ReportQueryWebAndAppActivity(
-      WebHistoryService::Request* request,
-      bool success);
-
   // Stores pointer to IdentityManager instance. It must outlive the
   // WebHistoryService and can be null during tests.
   signin::IdentityManager* identity_manager_;
diff --git a/components/media_message_center/media_notification_controller.h b/components/media_message_center/media_notification_controller.h
index 7fdbcdb..344bf9e 100644
--- a/components/media_message_center/media_notification_controller.h
+++ b/components/media_message_center/media_notification_controller.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/component_export.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
 
 template <typename T>
 class scoped_refptr;
@@ -37,7 +38,9 @@
 
   // Notifies the MediaNotificationController that a media button was pressed on
   // the MediaNotificationView.
-  virtual void LogMediaSessionActionButtonPressed(const std::string& id) = 0;
+  virtual void LogMediaSessionActionButtonPressed(
+      const std::string& id,
+      media_session::mojom::MediaSessionAction action) = 0;
 
  protected:
   virtual ~MediaNotificationController() = default;
diff --git a/components/media_message_center/media_notification_view_impl_unittest.cc b/components/media_message_center/media_notification_view_impl_unittest.cc
index 9f331c3..4815360f 100644
--- a/components/media_message_center/media_notification_view_impl_unittest.cc
+++ b/components/media_message_center/media_notification_view_impl_unittest.cc
@@ -80,7 +80,9 @@
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
     return nullptr;
   }
-  MOCK_METHOD1(LogMediaSessionActionButtonPressed, void(const std::string& id));
+  MOCK_METHOD2(LogMediaSessionActionButtonPressed,
+               void(const std::string& id,
+                    media_session::mojom::MediaSessionAction action));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationController);
@@ -449,7 +451,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, NextTrackButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+                                _, MediaSessionAction::kNextTrack));
   EnableAction(MediaSessionAction::kNextTrack);
 
   EXPECT_EQ(0, media_controller()->next_track_count());
@@ -462,7 +465,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, PlayButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(),
+              LogMediaSessionActionButtonPressed(_, MediaSessionAction::kPlay));
   EnableAction(MediaSessionAction::kPlay);
 
   EXPECT_EQ(0, media_controller()->resume_count());
@@ -475,7 +479,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, PauseButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+                                _, MediaSessionAction::kPause));
   EnableAction(MediaSessionAction::kPause);
   EXPECT_CALL(container(), OnMediaSessionInfoChanged(_));
 
@@ -496,7 +501,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, PreviousTrackButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+                                _, MediaSessionAction::kPreviousTrack));
   EnableAction(MediaSessionAction::kPreviousTrack);
 
   EXPECT_EQ(0, media_controller()->previous_track_count());
@@ -509,7 +515,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, SeekBackwardButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+                                _, MediaSessionAction::kSeekBackward));
   EnableAction(MediaSessionAction::kSeekBackward);
 
   EXPECT_EQ(0, media_controller()->seek_backward_count());
@@ -522,7 +529,8 @@
 }
 
 TEST_F(MAYBE_MediaNotificationViewImplTest, SeekForwardButtonClick) {
-  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(
+                                _, MediaSessionAction::kSeekForward));
   EnableAction(MediaSessionAction::kSeekForward);
 
   EXPECT_EQ(0, media_controller()->seek_forward_count());
diff --git a/components/media_message_center/media_session_notification_item.cc b/components/media_message_center/media_session_notification_item.cc
index 3cfe476..88d5f4c 100644
--- a/components/media_message_center/media_session_notification_item.cc
+++ b/components/media_message_center/media_session_notification_item.cc
@@ -155,7 +155,7 @@
   if (frozen_)
     return;
 
-  controller_->LogMediaSessionActionButtonPressed(request_id_);
+  controller_->LogMediaSessionActionButtonPressed(request_id_, action);
   media_session::PerformMediaSessionAction(action, media_controller_remote_);
 }
 
diff --git a/components/new_or_sad_tab_strings.grdp b/components/new_or_sad_tab_strings.grdp
index 270a8ed..d33b2dc 100644
--- a/components/new_or_sad_tab_strings.grdp
+++ b/components/new_or_sad_tab_strings.grdp
@@ -174,7 +174,7 @@
         Block third-party cookies
       </message>
       <message name="IDS_NEW_TAB_OTR_THIRD_PARTY_COOKIE_SUBLABEL" desc="A sub-label below the Block 3rd-party cookie checkbox." formatter_data="android_java">
-        When on, sites can’t use your browsing activity across different sites to personalize ads. Features on some sites may break.
+        When on, sites can't use cookies that track you across the web. Features on some sites may break.
       </message>
 
 </grit-part>
diff --git a/components/new_or_sad_tab_strings_grdp/IDS_NEW_TAB_OTR_THIRD_PARTY_COOKIE_SUBLABEL.png.sha1 b/components/new_or_sad_tab_strings_grdp/IDS_NEW_TAB_OTR_THIRD_PARTY_COOKIE_SUBLABEL.png.sha1
index a2ca154..47d8a4f 100644
--- a/components/new_or_sad_tab_strings_grdp/IDS_NEW_TAB_OTR_THIRD_PARTY_COOKIE_SUBLABEL.png.sha1
+++ b/components/new_or_sad_tab_strings_grdp/IDS_NEW_TAB_OTR_THIRD_PARTY_COOKIE_SUBLABEL.png.sha1
@@ -1 +1 @@
-5e55b66b9d0bbf98a85ff7ecfc246ed18d79f986
\ No newline at end of file
+9bb944c2ad3e4319e18666958944710b6bbb801a
\ No newline at end of file
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
index ae1666c..0fccdcc3 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc
@@ -50,8 +50,7 @@
 }
 
 UserInitiatedInfo CreateUserInitiatedInfo(
-    content::NavigationHandle* navigation_handle,
-    PageLoadTracker* committed_load) {
+    content::NavigationHandle* navigation_handle) {
   if (!navigation_handle->IsRendererInitiated())
     return UserInitiatedInfo::BrowserInitiated();
 
@@ -181,7 +180,7 @@
 void MetricsWebContentsObserver::WillStartNavigationRequestImpl(
     content::NavigationHandle* navigation_handle) {
   UserInitiatedInfo user_initiated_info(
-      CreateUserInitiatedInfo(navigation_handle, committed_load_.get()));
+      CreateUserInitiatedInfo(navigation_handle));
   std::unique_ptr<PageLoadTracker> last_aborted =
       NotifyAbortedProvisionalLoadsNewNavigation(navigation_handle,
                                                  user_initiated_info);
@@ -198,7 +197,7 @@
     chain_size = last_aborted->aborted_chain_size() + 1;
   }
 
-  if (!ShouldTrackNavigation(navigation_handle))
+  if (!ShouldTrackMainFrameNavigation(navigation_handle))
     return;
 
   // Pass in the last committed url to the PageLoadTracker. If the MWCO has
@@ -412,15 +411,15 @@
   // ensure it is set consistently for all navigations.
   has_navigated_ = true;
 
-  std::unique_ptr<PageLoadTracker> finished_nav(
+  std::unique_ptr<PageLoadTracker> navigation_handle_tracker(
       std::move(provisional_loads_[navigation_handle]));
   provisional_loads_.erase(navigation_handle);
 
   // Ignore same-document navigations.
   if (navigation_handle->HasCommitted() &&
       navigation_handle->IsSameDocument()) {
-    if (finished_nav)
-      finished_nav->StopTracking();
+    if (navigation_handle_tracker)
+      navigation_handle_tracker->StopTracking();
     if (committed_load_)
       committed_load_->DidCommitSameDocumentNavigation(navigation_handle);
     return;
@@ -431,41 +430,35 @@
   if (!navigation_handle->HasCommitted() &&
       navigation_handle->GetNetErrorCode() == net::ERR_ABORTED &&
       navigation_handle->GetResponseHeaders()) {
-    if (finished_nav) {
-      finished_nav->DidInternalNavigationAbort(navigation_handle);
-      finished_nav->StopTracking();
+    if (navigation_handle_tracker) {
+      navigation_handle_tracker->DidInternalNavigationAbort(navigation_handle);
+      navigation_handle_tracker->StopTracking();
     }
     return;
   }
 
-  const bool should_track =
-      finished_nav && ShouldTrackNavigation(navigation_handle);
+  if (navigation_handle->HasCommitted()) {
+    // A new navigation is committing, so finalize and destroy the tracker for
+    // the currently committed navigation.
+    FinalizeCurrentlyCommittedLoad(navigation_handle,
+                                   navigation_handle_tracker.get());
+    committed_load_.reset();
+  }
 
-  if (finished_nav && !should_track)
-    finished_nav->StopTracking();
+  if (!navigation_handle_tracker)
+    return;
+
+  if (!ShouldTrackMainFrameNavigation(navigation_handle)) {
+    navigation_handle_tracker->StopTracking();
+    return;
+  }
 
   if (navigation_handle->HasCommitted()) {
-    UserInitiatedInfo user_initiated_info =
-        finished_nav
-            ? finished_nav->user_initiated_info()
-            : CreateUserInitiatedInfo(navigation_handle, committed_load_.get());
-
-    // Notify other loads that they may have been aborted by this committed
-    // load. is_certainly_browser_timestamp is set to false because
-    // NavigationStart() could be set in either the renderer or browser process.
-    NotifyPageEndAllLoadsWithTimestamp(
-        EndReasonForPageTransition(navigation_handle->GetPageTransition()),
-        user_initiated_info, navigation_handle->NavigationStart(), false);
-
-    if (should_track) {
-      HandleCommittedNavigationForTrackedLoad(navigation_handle,
-                                              std::move(finished_nav));
-    } else {
-      committed_load_.reset();
-    }
-  } else if (should_track) {
+    HandleCommittedNavigationForTrackedLoad(
+        navigation_handle, std::move(navigation_handle_tracker));
+  } else {
     HandleFailedNavigationForTrackedLoad(navigation_handle,
-                                         std::move(finished_nav));
+                                         std::move(navigation_handle_tracker));
   }
 }
 
@@ -500,15 +493,6 @@
 void MetricsWebContentsObserver::HandleCommittedNavigationForTrackedLoad(
     content::NavigationHandle* navigation_handle,
     std::unique_ptr<PageLoadTracker> tracker) {
-  if (!IsNavigationUserInitiated(navigation_handle) &&
-      (navigation_handle->GetPageTransition() &
-       ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0 &&
-      committed_load_) {
-    // TODO(bmcquade): consider carrying the user_gesture bit forward to the
-    // redirected navigation.
-    committed_load_->NotifyClientRedirectTo(*tracker);
-  }
-
   committed_load_ = std::move(tracker);
   committed_load_->Commit(navigation_handle);
   DCHECK(committed_load_->did_commit());
@@ -517,6 +501,34 @@
     observer.OnCommit(committed_load_.get());
 }
 
+void MetricsWebContentsObserver::FinalizeCurrentlyCommittedLoad(
+    content::NavigationHandle* newly_committed_navigation,
+    PageLoadTracker* newly_committed_navigation_tracker) {
+  UserInitiatedInfo user_initiated_info =
+      newly_committed_navigation_tracker
+          ? newly_committed_navigation_tracker->user_initiated_info()
+          : CreateUserInitiatedInfo(newly_committed_navigation);
+
+  // Notify other loads that they may have been aborted by this committed
+  // load. is_certainly_browser_timestamp is set to false because
+  // NavigationStart() could be set in either the renderer or browser process.
+  NotifyPageEndAllLoadsWithTimestamp(
+      EndReasonForPageTransition(
+          newly_committed_navigation->GetPageTransition()),
+      user_initiated_info, newly_committed_navigation->NavigationStart(),
+      /*is_certainly_browser_timestamp=*/false);
+
+  if (committed_load_) {
+    bool is_non_user_initiated_client_redirect =
+        !IsNavigationUserInitiated(newly_committed_navigation) &&
+        (newly_committed_navigation->GetPageTransition() &
+         ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0;
+    if (is_non_user_initiated_client_redirect) {
+      committed_load_->NotifyClientRedirectTo(newly_committed_navigation);
+    }
+  }
+}
+
 void MetricsWebContentsObserver::NavigationStopped() {
   // TODO(csharrison): Use a more user-initiated signal for STOP.
   NotifyPageEndAllLoads(END_STOP, UserInitiatedInfo::NotUserInitiated());
@@ -617,7 +629,8 @@
     PageEndReason page_end_reason,
     UserInitiatedInfo user_initiated_info) {
   NotifyPageEndAllLoadsWithTimestamp(page_end_reason, user_initiated_info,
-                                     base::TimeTicks::Now(), true);
+                                     base::TimeTicks::Now(),
+                                     /*is_certainly_browser_timestamp=*/true);
 }
 
 void MetricsWebContentsObserver::NotifyPageEndAllLoadsWithTimestamp(
@@ -743,7 +756,7 @@
                   std::move(cpu_timing), std::move(new_deferred_resource_data));
 }
 
-bool MetricsWebContentsObserver::ShouldTrackNavigation(
+bool MetricsWebContentsObserver::ShouldTrackMainFrameNavigation(
     content::NavigationHandle* navigation_handle) const {
   DCHECK(navigation_handle->IsInMainFrame());
   DCHECK(!navigation_handle->HasCommitted() ||
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.h b/components/page_load_metrics/browser/metrics_web_contents_observer.h
index 6b36c86..fe68929 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.h
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.h
@@ -195,6 +195,10 @@
       content::NavigationHandle* navigation_handle,
       std::unique_ptr<PageLoadTracker> tracker);
 
+  void FinalizeCurrentlyCommittedLoad(
+      content::NavigationHandle* newly_committed_navigation,
+      PageLoadTracker* newly_committed_navigation_tracker);
+
   // Return a PageLoadTracker (either provisional or committed) that matches the
   // given request attributes, or nullptr if there are no matching
   // PageLoadTrackers.
@@ -225,8 +229,9 @@
       content::NavigationHandle* new_navigation,
       UserInitiatedInfo user_initiated_info);
 
-  // Whether metrics should be tracked for the navigation.
-  bool ShouldTrackNavigation(
+  // Whether metrics should be tracked, and a PageLoadTracker should be created,
+  // for the given main frame navigation.
+  bool ShouldTrackMainFrameNavigation(
       content::NavigationHandle* navigation_handle) const;
 
   void OnBrowserFeatureUsage(content::RenderFrameHost* render_frame_host,
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc b/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
index 6d4a16b..2cc2d86 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer_unittest.cc
@@ -22,12 +22,13 @@
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/render_frame_host_test_support.h"
 #include "content/public/test/test_renderer_host.h"
-#include "content/public/test/web_contents_tester.h"
 #include "net/base/net_errors.h"
+#include "net/cert/cert_status_flags.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
+#include "url/url_constants.h"
 
 using content::NavigationSimulator;
 
@@ -87,9 +88,10 @@
     content::SetBrowserClientForTesting(original_browser_client_);
     RenderViewHostTestHarness::TearDown();
   }
+
   void NavigateToUntrackedUrl() {
-    content::WebContentsTester::For(web_contents())
-        ->NavigateAndCommit(GURL(url::kAboutBlankURL));
+    content::NavigationSimulator::NavigateAndCommitFromBrowser(
+        web_contents(), GURL(url::kAboutBlankURL));
   }
 
   // Returns the mock timer used for buffering updates in the
@@ -240,12 +242,10 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(1);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
   ASSERT_TRUE(observed_committed_urls_from_on_start().empty());
   ASSERT_FALSE(is_first_navigation_in_web_contents().has_value());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(1u, observed_committed_urls_from_on_start().size());
   ASSERT_TRUE(observed_committed_urls_from_on_start().at(0).is_empty());
   ASSERT_TRUE(is_first_navigation_in_web_contents().has_value());
@@ -256,7 +256,8 @@
   ASSERT_EQ(1, CountUpdatedTimingReported());
   ASSERT_EQ(0, CountCompleteTimingReported());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   ASSERT_FALSE(is_first_navigation_in_web_contents().value());
   ASSERT_EQ(1, CountCompleteTimingReported());
   ASSERT_EQ(0, CountEmptyCompleteTimingReported());
@@ -271,10 +272,10 @@
 
 TEST_F(MetricsWebContentsObserverTest,
        DISABLED_MainFrameNavigationInternalAbort) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndFail(
-      GURL(kDefaultTestUrl), net::ERR_ABORTED,
+  auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
+      GURL(kDefaultTestUrl), web_contents());
+  navigation->FailWithResponseHeaders(
+      net::ERR_ABORTED,
       base::MakeRefCounted<net::HttpResponseHeaders>("some_headers"));
   ASSERT_EQ(1u, observed_aborted_urls().size());
   ASSERT_EQ(kDefaultTestUrl, observed_aborted_urls().front().spec());
@@ -287,9 +288,8 @@
   timing.response_start = base::TimeDelta::FromMilliseconds(10);
   timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(20);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
 
   ASSERT_EQ(1, CountUpdatedTimingReported());
@@ -322,7 +322,8 @@
   EXPECT_TRUE(updated_timings().back()->paint_timing->first_paint);
 
   // Navigate again to see if the timing updated for a subframe message.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   ASSERT_EQ(1, CountCompleteTimingReported());
   ASSERT_EQ(2, CountUpdatedTimingReported());
@@ -339,13 +340,13 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(1);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(0, CountUpdatedTimingReported());
   SimulateTimingUpdate(timing);
   ASSERT_EQ(1, CountUpdatedTimingReported());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrlAnchor));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrlAnchor));
   // Send the same timing update. The original tracker for kDefaultTestUrl
   // should dedup the update, and the tracker for kDefaultTestUrlAnchor should
   // have been destroyed as a result of its being a same page navigation, so
@@ -356,7 +357,8 @@
   ASSERT_EQ(0, CountCompleteTimingReported());
 
   // Navigate again to force histogram logging.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   // A same page navigation shouldn't trigger logging UMA for the original.
   ASSERT_EQ(1, CountUpdatedTimingReported());
@@ -370,20 +372,21 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(1);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
   embedder_interface_->set_is_ntp(true);
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   ASSERT_EQ(0, CountUpdatedTimingReported());
   ASSERT_EQ(0, CountCompleteTimingReported());
 
   // Ensure that NTP and other untracked loads are still accounted for as part
   // of keeping track of the first navigation in the WebContents.
   embedder_interface_->set_is_ntp(false);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_TRUE(is_first_navigation_in_web_contents().has_value());
   ASSERT_FALSE(is_first_navigation_in_web_contents().value());
 
@@ -396,14 +399,13 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.navigation_start = base::Time::FromDoubleT(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
   GURL about_blank_url = GURL("about:blank");
-  web_contents_tester->NavigateAndCommit(about_blank_url);
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+                                                             about_blank_url);
   SimulateTimingUpdate(timing);
   ASSERT_EQ(0, CountUpdatedTimingReported());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(0, CountUpdatedTimingReported());
   ASSERT_EQ(0, CountCompleteTimingReported());
 
@@ -421,10 +423,8 @@
   mojom::PageLoadTiming timing;
   page_load_metrics::InitPageLoadTimingForTest(&timing);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
   ASSERT_EQ(0, CountUpdatedTimingReported());
   NavigateToUntrackedUrl();
@@ -447,10 +447,8 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing);
   timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(1);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
   ASSERT_EQ(0, CountUpdatedTimingReported());
   NavigateToUntrackedUrl();
@@ -474,10 +472,8 @@
   timing.navigation_start = base::Time::FromDoubleT(1);
   timing.parse_timing->parse_stop = base::TimeDelta::FromMilliseconds(1);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
   ASSERT_EQ(0, CountUpdatedTimingReported());
   NavigateToUntrackedUrl();
@@ -503,9 +499,8 @@
   page_load_metrics::InitPageLoadTimingForTest(&timing2);
   timing2.navigation_start = base::Time::FromDoubleT(100);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
 
   SimulateTimingUpdate(timing);
   ASSERT_EQ(1, CountUpdatedTimingReported());
@@ -539,9 +534,8 @@
   SimulateTimingUpdate(timing);
 
   // Navigate again to force histogram logging.
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   ASSERT_EQ(0, CountCompleteTimingReported());
   ASSERT_EQ(0, CountUpdatedTimingReported());
   CheckErrorEvent(ERR_IPC_WITH_NO_RELEVANT_LOAD, 1);
@@ -615,9 +609,8 @@
 }
 
 TEST_F(MetricsWebContentsObserverTest, FlushMetricsOnAppEnterBackground) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
 
   histogram_tester_.ExpectTotalCount(
       internal::kPageLoadCompletedAfterAppBackground, 0);
@@ -633,7 +626,8 @@
 
   // Navigate again, which forces completion callbacks on the previous
   // navigation to be invoked.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   // Verify that, even though the page load completed, no complete timings were
   // reported, because the TestPageLoadMetricsObserver's
@@ -652,47 +646,51 @@
 }
 
 TEST_F(MetricsWebContentsObserverTest, StopObservingOnCommit) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
   ASSERT_TRUE(completed_filtered_urls().empty());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_TRUE(completed_filtered_urls().empty());
 
   // kFilteredCommitUrl should stop observing in OnCommit, and thus should not
   // reach OnComplete().
-  web_contents_tester->NavigateAndCommit(GURL(kFilteredCommitUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kFilteredCommitUrl));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl)}),
             completed_filtered_urls());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl)}),
             completed_filtered_urls());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl), GURL(kDefaultTestUrl2)}),
             completed_filtered_urls());
 }
 
 TEST_F(MetricsWebContentsObserverTest, StopObservingOnStart) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
   ASSERT_TRUE(completed_filtered_urls().empty());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_TRUE(completed_filtered_urls().empty());
 
   // kFilteredCommitUrl should stop observing in OnStart, and thus should not
   // reach OnComplete().
-  web_contents_tester->NavigateAndCommit(GURL(kFilteredStartUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kFilteredStartUrl));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl)}),
             completed_filtered_urls());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl)}),
             completed_filtered_urls());
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(std::vector<GURL>({GURL(kDefaultTestUrl), GURL(kDefaultTestUrl2)}),
             completed_filtered_urls());
 }
@@ -705,9 +703,8 @@
   timing.navigation_start = base::Time::FromDoubleT(1);
   timing.response_start = base::TimeDelta::FromMilliseconds(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
 
   ASSERT_EQ(1, CountUpdatedTimingReported());
@@ -750,7 +747,8 @@
   EXPECT_TRUE(updated_timings().back()->paint_timing->first_paint);
 
   // Navigate again to see if the timing updated for a subframe message.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   ASSERT_EQ(1, CountCompleteTimingReported());
   ASSERT_EQ(2, CountUpdatedTimingReported());
@@ -773,9 +771,8 @@
   // currently can't inject the navigation start offset, so we must ensure that
   // subframe first paint + navigation start offset < main frame first paint.
   timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(100000);
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdateWithoutFiringDispatchTimer(timing, main_rfh());
 
   EXPECT_TRUE(GetMostRecentTimer()->IsRunning());
@@ -862,12 +859,11 @@
   timing.interactive_timing->first_input_delay =
       base::TimeDelta::FromMilliseconds(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -893,12 +889,11 @@
   timing.interactive_timing->first_input_timestamp =
       base::TimeDelta::FromMilliseconds(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -924,12 +919,11 @@
   timing.interactive_timing->longest_input_delay =
       base::TimeDelta::FromMilliseconds(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -955,12 +949,11 @@
   timing.interactive_timing->longest_input_timestamp =
       base::TimeDelta::FromMilliseconds(10);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -993,12 +986,11 @@
   timing.interactive_timing->longest_input_timestamp =
       base::TimeDelta::FromMilliseconds(2000);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1033,12 +1025,11 @@
   timing.interactive_timing->longest_input_timestamp =
       base::TimeDelta::FromMilliseconds(500);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1074,9 +1065,8 @@
   timing.interactive_timing->first_input_timestamp =
       base::TimeDelta::FromMinutes(100);
 
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
 
   content::RenderFrameHostTester* rfh_tester =
@@ -1097,7 +1087,8 @@
   SimulateTimingUpdate(subframe_timing, subframe);
 
   // Navigate again to confirm the timing updated for a subframe message.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1117,11 +1108,9 @@
 // FirstInputDelay and FirstInputTimestamp come from the main frame.
 TEST_F(MetricsWebContentsObserverTest,
        FirstInputDelayAndTimingMainframeFirstDeliveredSecond) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
   // We need to navigate before we can navigate the subframe.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
 
   content::RenderFrameHostTester* rfh_tester =
       content::RenderFrameHostTester::For(main_rfh());
@@ -1151,11 +1140,13 @@
   timing.interactive_timing->first_input_timestamp =
       base::TimeDelta::FromMilliseconds(90);
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(timing);
 
   // Navigate again to confirm the timing updated for the mainframe message.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1171,11 +1162,9 @@
 }
 
 TEST_F(MetricsWebContentsObserverTest, DISABLED_LongestInputInMainFrame) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
   // We need to navigate before we can navigate the subframe.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
 
   content::RenderFrameHostTester* rfh_tester =
       content::RenderFrameHostTester::For(main_rfh());
@@ -1201,7 +1190,8 @@
       base::TimeDelta::FromMilliseconds(100);
   main_frame_timing.interactive_timing->longest_input_timestamp =
       base::TimeDelta::FromMilliseconds(2000);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(main_frame_timing);
 
   // Second subframe.
@@ -1217,7 +1207,8 @@
   SimulateTimingUpdate(subframe2_timing, subframe2);
 
   // Navigate again to confirm all timings are updated.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1238,16 +1229,14 @@
 //
 // Delivery order: Main Frame -> Subframe1 -> Subframe2.
 TEST_F(MetricsWebContentsObserverTest, LongestInputInSubframe) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-
   mojom::PageLoadTiming main_frame_timing;
   PopulatePageLoadTiming(&main_frame_timing);
   main_frame_timing.interactive_timing->longest_input_delay =
       base::TimeDelta::FromMilliseconds(100);
   main_frame_timing.interactive_timing->longest_input_timestamp =
       base::TimeDelta::FromMilliseconds(2000);
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdate(main_frame_timing);
 
   content::RenderFrameHostTester* rfh_tester =
@@ -1279,7 +1268,8 @@
   SimulateTimingUpdate(subframe2_timing, subframe2);
 
   // Navigate again to confirm all timings are updated.
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
 
   const mojom::InteractiveTiming& interactive_timing =
       *complete_timings().back()->interactive_timing;
@@ -1301,9 +1291,8 @@
   mojom::PageLoadTiming timing;
   PopulatePageLoadTiming(&timing);
   timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(1000);
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   SimulateTimingUpdateWithoutFiringDispatchTimer(timing, main_rfh());
 
   // Throw in a cpu timing update, shouldn't affect the page timing results.
@@ -1331,9 +1320,8 @@
 
 // Make sure the dispatch of CPU occurs immediately.
 TEST_F(MetricsWebContentsObserverTest, DispatchCpuMetricsImmediately) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
 
   mojom::CpuTiming timing;
   timing.task_time = base::TimeDelta::FromMilliseconds(1000);
@@ -1353,8 +1341,8 @@
 
 TEST_F(MetricsWebContentsObserverTest, OnLoadedResource_MainFrame) {
   GURL main_resource_url(kDefaultTestUrl);
-  content::WebContentsTester::For(web_contents())
-      ->NavigateAndCommit(main_resource_url);
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+                                                             main_resource_url);
 
   auto navigation_simulator =
       content::NavigationSimulator::CreateRendererInitiated(
@@ -1386,9 +1374,8 @@
 }
 
 TEST_F(MetricsWebContentsObserverTest, OnLoadedResource_Subresource) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   GURL loaded_resource_url("http://www.other.com/");
   observer()->ResourceLoadComplete(
       web_contents()->GetMainFrame(), content::GlobalRequestID(),
@@ -1422,9 +1409,8 @@
 
 TEST_F(MetricsWebContentsObserverTest,
        OnLoadedResource_IgnoreNonHttpOrHttpsScheme) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   GURL loaded_resource_url("data:text/html,Hello world");
   observer()->ResourceLoadComplete(
       web_contents()->GetMainFrame(), content::GlobalRequestID(),
@@ -1435,9 +1421,8 @@
 }
 
 TEST_F(MetricsWebContentsObserverTest, RecordFeatureUsage) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(main_rfh()->GetLastCommittedURL().spec(), GURL(kDefaultTestUrl));
 
   std::vector<blink::mojom::WebFeature> web_features;
@@ -1486,9 +1471,8 @@
 
 TEST_F(MetricsWebContentsObserverBackForwardCacheTest,
        RecordFeatureUsageWithBackForwardCache) {
-  content::WebContentsTester* web_contents_tester =
-      content::WebContentsTester::For(web_contents());
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl));
   ASSERT_EQ(main_rfh()->GetLastCommittedURL().spec(), GURL(kDefaultTestUrl));
 
   std::vector<blink::mojom::WebFeature> web_features1{
@@ -1496,7 +1480,8 @@
   mojom::PageLoadFeatures features1(web_features1, {}, {});
   MetricsWebContentsObserver::RecordFeatureUsage(main_rfh(), features1);
 
-  web_contents_tester->NavigateAndCommit(GURL(kDefaultTestUrl2));
+  content::NavigationSimulator::NavigateAndCommitFromBrowser(
+      web_contents(), GURL(kDefaultTestUrl2));
   content::NavigationSimulator::GoBack(web_contents());
 
   std::vector<blink::mojom::WebFeature> web_features2{
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index a6bc3758..f136027 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -415,15 +415,15 @@
 }
 
 void PageLoadTracker::NotifyClientRedirectTo(
-    const PageLoadTracker& destination) {
+    content::NavigationHandle* destination) {
   if (metrics_update_dispatcher_.timing().paint_timing->first_paint) {
     base::TimeTicks first_paint_time =
         navigation_start() +
         metrics_update_dispatcher_.timing().paint_timing->first_paint.value();
     base::TimeDelta first_paint_to_navigation;
-    if (destination.navigation_start() > first_paint_time)
+    if (destination->NavigationStart() > first_paint_time)
       first_paint_to_navigation =
-          destination.navigation_start() - first_paint_time;
+          destination->NavigationStart() - first_paint_time;
     PAGE_LOAD_HISTOGRAM(internal::kClientRedirectFirstPaintToNavigation,
                         first_paint_to_navigation);
   } else {
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index 555bf9c..4913899 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -258,7 +258,7 @@
     visibility_tracker_ = tracker;
   }
 
-  void NotifyClientRedirectTo(const PageLoadTracker& destination);
+  void NotifyClientRedirectTo(content::NavigationHandle* destination);
 
   void OnLoadedResource(
       const ExtraRequestCompleteInfo& extra_request_complete_info);
diff --git a/components/password_manager/core/browser/credential_manager_impl_unittest.cc b/components/password_manager/core/browser/credential_manager_impl_unittest.cc
index f38893c..8d639276 100644
--- a/components/password_manager/core/browser/credential_manager_impl_unittest.cc
+++ b/components/password_manager/core/browser/credential_manager_impl_unittest.cc
@@ -470,13 +470,14 @@
 
   RunAllPendingTasks();
   TestPasswordStore::PasswordMap passwords = store_->stored_passwords();
-  EXPECT_THAT(passwords["https://example.com/"], ElementsAre(form_));
+  EXPECT_THAT(passwords["https://example.com/"],
+              ElementsAre(MatchesFormExceptStore(form_)));
   federated.date_created =
       passwords["federation://example.com/google.com"][0].date_created;
   federated.date_last_used =
       passwords["federation://example.com/google.com"][0].date_last_used;
   EXPECT_THAT(passwords["federation://example.com/google.com"],
-              ElementsAre(federated));
+              ElementsAre(MatchesFormExceptStore(federated)));
 }
 
 TEST_F(CredentialManagerImplTest, CredentialManagerStoreOverwrite) {
@@ -508,7 +509,8 @@
   TestPasswordStore::PasswordMap passwords = store_->stored_passwords();
   EXPECT_EQ(1U, passwords.size());
   EXPECT_EQ(2U, passwords[form_.signon_realm].size());
-  EXPECT_EQ(origin_path_form_, passwords[form_.signon_realm][0]);
+  EXPECT_THAT(origin_path_form_,
+              MatchesFormExceptStore(passwords[form_.signon_realm][0]));
   EXPECT_EQ(base::ASCIIToUTF16("Totally new password."),
             passwords[form_.signon_realm][1].password_value);
   EXPECT_EQ(base::ASCIIToUTF16("New Name"),
@@ -827,9 +829,10 @@
        CredentialManagerOnRequestCredentialWithPSLCredential) {
   store_->AddLogin(subdomain_form_);
   subdomain_form_.is_public_suffix_match = true;
-  EXPECT_CALL(*client_,
-              PromptUserToChooseCredentialsPtr(
-                  UnorderedElementsAre(Pointee(subdomain_form_)), _, _));
+  EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(
+                            UnorderedElementsAre(Pointee(
+                                MatchesFormExceptStore(subdomain_form_))),
+                            _, _));
   EXPECT_CALL(*client_, NotifyUserAutoSigninPtr()).Times(0);
 
   ExpectCredentialType(CredentialMediationRequirement::kOptional, true,
@@ -843,10 +846,12 @@
   store_->AddLogin(origin_path_form_);
   store_->AddLogin(subdomain_form_);
 
-  EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(
-                            UnorderedElementsAre(Pointee(origin_path_form_),
-                                                 Pointee(form_)),
-                            _, _));
+  EXPECT_CALL(*client_,
+              PromptUserToChooseCredentialsPtr(
+                  UnorderedElementsAre(
+                      Pointee(MatchesFormExceptStore(origin_path_form_)),
+                      Pointee(MatchesFormExceptStore(form_))),
+                  _, _));
 
   ExpectCredentialType(CredentialMediationRequirement::kOptional, true,
                        std::vector<GURL>(),
@@ -894,11 +899,13 @@
       "federation://" + federated.origin.host() + "/google.com";
   store_->AddLogin(federated);
 
-  EXPECT_CALL(*client_, PromptUserToChooseCredentialsPtr(
-                            UnorderedElementsAre(Pointee(form_),
-                                                 Pointee(origin_path_form_),
-                                                 Pointee(federated)),
-                            _, _));
+  EXPECT_CALL(*client_,
+              PromptUserToChooseCredentialsPtr(
+                  UnorderedElementsAre(
+                      Pointee(MatchesFormExceptStore(form_)),
+                      Pointee(MatchesFormExceptStore(origin_path_form_)),
+                      Pointee(MatchesFormExceptStore(federated))),
+                  _, _));
 
   bool called = false;
   CredentialManagerError error;
@@ -1113,8 +1120,8 @@
   store_->AddLogin(form_);
 
   std::vector<GURL> federations;
-  EXPECT_CALL(*client_,
-              NotifyUserCouldBeAutoSignedInPtr(testing::Pointee(form_)))
+  EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(
+                            Pointee(MatchesFormExceptStore(form_))))
       .Times(1);
 
   ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true,
@@ -1128,8 +1135,8 @@
   store_->AddLogin(form_);
 
   std::vector<GURL> federations;
-  EXPECT_CALL(*client_,
-              NotifyUserCouldBeAutoSignedInPtr(testing::Pointee(form_)))
+  EXPECT_CALL(*client_, NotifyUserCouldBeAutoSignedInPtr(
+                            Pointee(MatchesFormExceptStore(form_))))
       .Times(1);
 
   ExpectZeroClickSignInFailure(CredentialMediationRequirement::kSilent, true,
@@ -1560,7 +1567,8 @@
   blacklisted.origin = form_.origin;
   blacklisted.signon_realm = form_.signon_realm;
   blacklisted.date_created = passwords[form_.signon_realm][0].date_created;
-  EXPECT_THAT(passwords[form_.signon_realm], testing::ElementsAre(blacklisted));
+  EXPECT_THAT(passwords[form_.signon_realm],
+              ElementsAre(MatchesFormExceptStore(blacklisted)));
 }
 
 TEST_F(CredentialManagerImplTest, BlacklistFederatedCredential) {
@@ -1590,7 +1598,7 @@
   blacklisted.date_created =
       passwords[blacklisted.signon_realm][0].date_created;
   EXPECT_THAT(passwords[blacklisted.signon_realm],
-              testing::ElementsAre(blacklisted));
+              ElementsAre(MatchesFormExceptStore(blacklisted)));
 }
 
 TEST_F(CredentialManagerImplTest, RespectBlacklistingPasswordCredential) {
@@ -1644,9 +1652,11 @@
   form_.username_value = base::ASCIIToUTF16("username_value");
   store_->AddLogin(form_);
 
-  EXPECT_CALL(*client_,
-              PasswordWasAutofilled(ElementsAre(Pointee(form_)), _,
-                                    Pointee(ElementsAre(Pointee(federated)))));
+  EXPECT_CALL(
+      *client_,
+      PasswordWasAutofilled(
+          ElementsAre(Pointee(MatchesFormExceptStore(form_))), _,
+          Pointee(ElementsAre(Pointee(MatchesFormExceptStore(federated))))));
 
   bool called = false;
   CredentialManagerError error;
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 326e575..eb057a2 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -199,12 +199,23 @@
   return suggestion;
 }
 
-autofill::Suggestion CreateAccountStorageOptInEntry() {
-  // TODO(crbug.com/1024332): Add proper (translated) string.
+// Entry for opting in to password account storage and then filling.
+autofill::Suggestion CreateEntryToOptInToAccountStorageThenFill() {
+  // TODO(crbug.com/1062344): Add proper (translated) string.
   autofill::Suggestion suggestion(
       base::ASCIIToUTF16("Use passwords stored in your Google account"));
   suggestion.frontend_id =
-      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN;
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN;
+  return suggestion;
+}
+
+// Entry for opting in to password account storage and then generating password.
+autofill::Suggestion CreateEntryToOptInToAccountStorageThenGenerate() {
+  // TODO(crbug.com/1062344): Add proper (translated) string.
+  autofill::Suggestion suggestion(base::ASCIIToUTF16(
+      "Use your Google account to generate a strong password"));
+  suggestion.frontend_id =
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE;
   return suggestion;
 }
 
@@ -233,27 +244,42 @@
 }
 
 std::vector<autofill::Suggestion> ReplaceUnlockButtonWithLoadingIndicator(
-    base::span<const autofill::Suggestion> suggestions) {
+    base::span<const autofill::Suggestion> suggestions,
+    autofill::PopupItemId unlock_item) {
+  DCHECK(
+      unlock_item == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN ||
+      unlock_item ==
+          autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE);
   std::vector<autofill::Suggestion> new_suggestions;
   new_suggestions.push_back(CreateLoadingSpinner());
   std::copy_if(suggestions.begin(), suggestions.end(),
                std::back_inserter(new_suggestions),
-               [](const autofill::Suggestion& suggestion) {
-                 return suggestion.frontend_id !=
-                        autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN;
+               [unlock_item](const autofill::Suggestion& suggestion) {
+                 return suggestion.frontend_id != unlock_item;
                });
   return new_suggestions;
 }
 
 std::vector<autofill::Suggestion> ReplaceLoaderWithUnlock(
-    base::span<const autofill::Suggestion> suggestions) {
+    base::span<const autofill::Suggestion> suggestions,
+    autofill::PopupItemId unlock_item) {
   std::vector<autofill::Suggestion> new_suggestions;
   new_suggestions.reserve(suggestions.size());
   for (const auto& suggestion : suggestions) {
     if (suggestion.frontend_id != autofill::POPUP_ITEM_ID_LOADING_SPINNER)
       new_suggestions.push_back(suggestion);
   }
-  new_suggestions.push_back(CreateAccountStorageOptInEntry());
+  switch (unlock_item) {
+    case autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE:
+      new_suggestions.push_back(
+          CreateEntryToOptInToAccountStorageThenGenerate());
+      break;
+    case autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN:
+      new_suggestions.push_back(CreateEntryToOptInToAccountStorageThenFill());
+      break;
+    default:
+      NOTREACHED();
+  }
   return new_suggestions;
 }
 
@@ -286,12 +312,38 @@
   ClearPreviewedForm();
   if (identifier == autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY ||
       identifier == autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY ||
-      identifier == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN)
+      identifier == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN ||
+      identifier ==
+          autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE)
     return;
   bool success = PreviewSuggestion(GetUsernameFromSuggestion(value));
   DCHECK(success);
 }
 
+void PasswordAutofillManager::OnUnlockItemAccepted(
+    autofill::PopupItemId unlock_item) {
+  DCHECK(
+      unlock_item == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN ||
+      unlock_item ==
+          autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE);
+
+  signin::IdentityManager* identity_manager =
+      password_client_->GetIdentityManager();
+  if (!identity_manager)
+    return;
+  CoreAccountId account_id =
+      identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kNotRequired);
+  if (account_id.empty())
+    return;
+  UpdatePopup(ReplaceUnlockButtonWithLoadingIndicator(
+      autofill_client_->GetPopupSuggestions(), unlock_item));
+  autofill_client_->PinPopupViewUntilUpdate();
+  password_client_->TriggerReauthForAccount(
+      account_id,
+      base::BindOnce(&PasswordAutofillManager::OnUnlockReauthCompleted,
+                     weak_ptr_factory_.GetWeakPtr(), unlock_item));
+}
+
 void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value,
                                                   int identifier,
                                                   int position) {
@@ -317,22 +369,12 @@
       password_client_->GetMetricsRecorder()->RecordPageLevelUserAction(
           UserAction::kShowAllPasswordsWhileSomeAreSuggested);
     }
-  } else if (identifier ==
-             autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN) {
-    signin::IdentityManager* identity_manager =
-        password_client_->GetIdentityManager();
-    if (!identity_manager)
-      return;
-    CoreAccountId account_id = identity_manager->GetPrimaryAccountId(
-        signin::ConsentLevel::kNotRequired);
-    if (account_id.empty())
-      return;
-    UpdatePopup(ReplaceUnlockButtonWithLoadingIndicator(
-        autofill_client_->GetPopupSuggestions()));
-    autofill_client_->PinPopupViewUntilUpdate();
-    password_client_->TriggerReauthForAccount(
-        account_id, base::BindOnce(&PasswordAutofillManager::OnReauthCompleted,
-                                   weak_ptr_factory_.GetWeakPtr()));
+  } else if (
+      identifier == autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN ||
+      identifier ==
+          autofill::
+              POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE) {
+    OnUnlockItemAccepted(static_cast<autofill::PopupItemId>(identifier));
     return;  // Do not hide the popup while loading data.
   } else {
     metrics_util::LogPasswordDropdownItemSelected(
@@ -472,9 +514,8 @@
     ShowPasswordSuggestions show_password_suggestions) {
   std::vector<autofill::Suggestion> suggestions;
   bool show_account_storage_optin =
-      !offers_generation && password_client_ &&
-      password_client_->GetPasswordFeatureManager()
-          ->ShouldShowAccountStorageOptIn();
+      password_client_ && password_client_->GetPasswordFeatureManager()
+                              ->ShouldShowAccountStorageOptIn();
 
   if (!fill_data_ && !show_account_storage_optin) {
     // Probably the credential was deleted in the mean time.
@@ -489,15 +530,19 @@
   }
 
   // Add password generation entry, if available.
-  if (offers_generation)
-    suggestions.push_back(CreateGenerationEntry());
+  if (offers_generation) {
+    suggestions.push_back(show_account_storage_optin
+                              ? CreateEntryToOptInToAccountStorageThenGenerate()
+                              : CreateGenerationEntry());
+  }
 
   // Add "Manage all passwords" link to settings.
   MaybeAppendManualFallback(autofill_client_->GetSyncService(), &suggestions);
 
-  // Add button to opt into using the account storage for passwords.
+  // Add button to opt into using the account storage for passwords and then
+  // suggest.
   if (show_account_storage_optin)
-    suggestions.push_back(CreateAccountStorageOptInEntry());
+    suggestions.push_back(CreateEntryToOptInToAccountStorageThenFill());
 
   return suggestions;
 }
@@ -513,6 +558,9 @@
             metrics_util::ShowAllSavedPasswordsContext::kPassword);
         continue;
       case autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY:
+        // TODO(crbug.com/1062709): Revisit metrics for the "opt in and
+        // generate" button.
+      case autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE:
         dropdown_state = metrics_util::PasswordDropdownState::kStandardGenerate;
         continue;
     }
@@ -622,13 +670,19 @@
     page_favicon_ = result.image;
 }
 
-void PasswordAutofillManager::OnReauthCompleted(
+void PasswordAutofillManager::OnUnlockReauthCompleted(
+    autofill::PopupItemId unlock_item,
     PasswordManagerClient::ReauthSucceeded reauth_succeeded) {
   if (reauth_succeeded) {
     password_client_->GetPasswordFeatureManager()->SetAccountStorageOptIn(true);
+    if (unlock_item ==
+        autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE) {
+      password_client_->GeneratePassword();
+    }
     return;
   }
-  UpdatePopup(ReplaceLoaderWithUnlock(autofill_client_->GetPopupSuggestions()));
+  UpdatePopup(ReplaceLoaderWithUnlock(autofill_client_->GetPopupSuggestions(),
+                                      unlock_item));
 }
 
 }  //  namespace password_manager
diff --git a/components/password_manager/core/browser/password_autofill_manager.h b/components/password_manager/core/browser/password_autofill_manager.h
index 40d0e780d..4fc1cca 100644
--- a/components/password_manager/core/browser/password_autofill_manager.h
+++ b/components/password_manager/core/browser/password_autofill_manager.h
@@ -160,10 +160,16 @@
   // store is canceled on navigation.
   void OnFaviconReady(const favicon_base::FaviconImageResult& result);
 
-  // Checks the result of the reauth triggered by UnlockAccountStoreAfterReauth
-  // and either unlocks the account store if the reauth succeeded or resets the
-  // suggestions to show the unlock button again.
-  void OnReauthCompleted(
+  // Replaces |unlock_item| with a loading symbol and triggers a reauth flow to
+  // opt in for passwords account storage, with OnUnlockReauthCompleted as
+  // callback.
+  void OnUnlockItemAccepted(autofill::PopupItemId unlock_item);
+
+  // If reauth failed, resets the suggestions to show the |unlock_item| again.
+  // Otherwise, unlocks the account store and either triggers generation or
+  // filling based on the |unlock_item| that was clicked.
+  void OnUnlockReauthCompleted(
+      autofill::PopupItemId unlock_item,
       PasswordManagerClient::ReauthSucceeded reauth_succeeded);
 
   std::unique_ptr<autofill::PasswordFormFillData> fill_data_;
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 8d217028..adba7c0 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -64,7 +64,10 @@
 using autofill::SuggestionVectorValuesAre;
 using favicon_base::FaviconImageCallback;
 using testing::_;
+using testing::AllOf;
 using testing::ElementsAreArray;
+using testing::Eq;
+using testing::Field;
 using testing::Invoke;
 using testing::NiceMock;
 using testing::Return;
@@ -195,7 +198,9 @@
   return 1;
 }
 
-std::vector<autofill::Suggestion> CreateTestSuggestions(bool has_opt_in) {
+std::vector<autofill::Suggestion> CreateTestSuggestions(
+    bool has_opt_in_and_fill,
+    bool has_opt_in_and_generate) {
   std::vector<Suggestion> suggestions;
   suggestions.push_back(
       Suggestion(/*label=*/"User1", /*value=*/"PW1", /*icon=*/"",
@@ -205,10 +210,17 @@
         /*label=*/"Show all pwds", /*value=*/"", /*icon=*/"",
         /*fronend_id=*/autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY));
   }
-  if (has_opt_in) {
+  if (has_opt_in_and_fill) {
     suggestions.push_back(Suggestion(
-        /*label=*/"Unlock passwords", /*value=*/"", /*icon=*/"",
-        /*fronend_id=*/autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN));
+        /*label=*/"Unlock passwords and fill", /*value=*/"", /*icon=*/"",
+        /*fronend_id=*/
+        autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN));
+  }
+  if (has_opt_in_and_generate) {
+    suggestions.push_back(Suggestion(
+        /*label=*/"Unlock passwords and generate", /*value=*/"", /*icon=*/"",
+        /*fronend_id=*/
+        autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE));
   }
   return suggestions;
 }
@@ -383,7 +395,7 @@
 }
 
 // Test that the popup is updated once remote suggestions are unlocked.
-TEST_F(PasswordAutofillManagerTest, ShowUnlockButton) {
+TEST_F(PasswordAutofillManagerTest, ShowOptInAndFillButton) {
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -397,15 +409,16 @@
           SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
               {autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
                autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY,
-               autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN}))),
+               autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN}))),
           /*autoselect_first_suggestion=*/false, PopupType::kPasswords, _));
   password_autofill_manager_->OnShowPasswordSuggestions(
       base::i18n::RIGHT_TO_LEFT, base::string16(),
       autofill::SHOW_ALL | autofill::IS_PASSWORD_FIELD, gfx::RectF());
 }
 
-// Test that the popup is updated once remote suggestions are unlocked.
-TEST_F(PasswordAutofillManagerTest, ClickOnUnlockPutsPopupInWaitingState) {
+// Test that the popup is updated once "opt in and fill" is clicked.
+TEST_F(PasswordAutofillManagerTest,
+       ClickOnOptInAndFillPutsPopupInWaitingState) {
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -428,14 +441,47 @@
   EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate);
   EXPECT_CALL(client, TriggerReauthForAccount(kAliceId, _));
   EXPECT_CALL(autofill_client, GetPopupSuggestions())
-      .WillOnce(Return(CreateTestSuggestions(/*has_opt_in=*/true)));
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false)));
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN,
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
       1);
 }
 
-// Test that the popup is updated once remote suggestions are unlocked.
-TEST_F(PasswordAutofillManagerTest, FailedUnlockUpdatesPopup) {
+// Test that the popup is updated once "opt in and generate" is clicked.
+TEST_F(PasswordAutofillManagerTest,
+       ClickOnOptInAndGeneratePutsPopupInWaitingState) {
+  TestPasswordManagerClient client;
+  NiceMock<MockAutofillClient> autofill_client;
+  InitializePasswordAutofillManager(&client, &autofill_client);
+  client.SetAccountStorageOptIn(false);
+  const CoreAccountId kAliceId = client.identity_test_env()
+                                     .SetUnconsentedPrimaryAccount(kAliceEmail)
+                                     .account_id;
+  testing::Mock::VerifyAndClearExpectations(&autofill_client);
+
+  // Accepting a suggestion should trigger a call to update the popup. The first
+  // update removes the unlock button
+  EXPECT_CALL(
+      autofill_client,
+      UpdatePopup(
+          SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
+              {autofill::POPUP_ITEM_ID_LOADING_SPINNER,
+               autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+               autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY}))),
+          PopupType::kPasswords));
+  EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate);
+  EXPECT_CALL(client, TriggerReauthForAccount(kAliceId, _));
+  EXPECT_CALL(autofill_client, GetPopupSuggestions())
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true)));
+  password_autofill_manager_->DidAcceptSuggestion(
+      test_username_,
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE, 1);
+}
+
+// Test that the popup is updated once "opt in and fill" is clicked.
+TEST_F(PasswordAutofillManagerTest, FailedOptInAndFillUpdatesPopup) {
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -450,14 +496,16 @@
   // Accepting a suggestion should trigger a call to update the popup.
   // First the popup enters the waiting state.
   EXPECT_CALL(autofill_client, GetPopupSuggestions)
-      .WillOnce(Return(CreateTestSuggestions(/*has_opt_in=*/true)));
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false)));
   EXPECT_CALL(autofill_client, UpdatePopup);
 
   // As soon as the waiting state is pending, the next update resets the popup.
   EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate).WillOnce([&] {
     testing::Mock::VerifyAndClear(&autofill_client);
     EXPECT_CALL(autofill_client, GetPopupSuggestions)
-        .WillOnce(Return(CreateTestSuggestions(/*has_opt_in=*/false)));
+        .WillOnce(Return(CreateTestSuggestions(
+            /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ false)));
     EXPECT_CALL(client, TriggerReauthForAccount(kAliceId, _))
         .WillOnce([](const auto& unused, auto reauth_callback) {
           std::move(reauth_callback).Run(ReauthSucceeded(false));
@@ -468,17 +516,63 @@
             SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
                 {autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
                  autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY,
-                 autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN}))),
+                 autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN}))),
             PopupType::kPasswords));
   });
 
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN,
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
       1);
 }
 
-// Test that the popup is updated once remote suggestions are unlocked.
-TEST_F(PasswordAutofillManagerTest, SuccessfullUnlockTriggersOptIn) {
+// Test that the popup is updated once "opt in and generate" is clicked.
+TEST_F(PasswordAutofillManagerTest, FailedOptInAndGenerateUpdatesPopup) {
+  TestPasswordManagerClient client;
+  NiceMock<MockAutofillClient> autofill_client;
+  InitializePasswordAutofillManager(&client, &autofill_client);
+  client.SetAccountStorageOptIn(false);
+  const CoreAccountId kAliceId = client.identity_test_env()
+                                     .SetUnconsentedPrimaryAccount(kAliceEmail)
+                                     .account_id;
+  testing::Mock::VerifyAndClearExpectations(&autofill_client);
+  EXPECT_CALL(*client.GetPasswordFeatureManager(), SetAccountStorageOptIn)
+      .Times(0);
+
+  // Accepting a suggestion should trigger a call to update the popup.
+  // First the popup enters the waiting state.
+  EXPECT_CALL(autofill_client, GetPopupSuggestions)
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true)));
+  EXPECT_CALL(autofill_client, UpdatePopup);
+
+  // As soon as the waiting state is pending, the next update resets the popup.
+  EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate).WillOnce([&] {
+    testing::Mock::VerifyAndClear(&autofill_client);
+    EXPECT_CALL(autofill_client, GetPopupSuggestions)
+        .WillOnce(Return(CreateTestSuggestions(
+            /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ false)));
+    EXPECT_CALL(client, TriggerReauthForAccount(kAliceId, _))
+        .WillOnce([](const auto& unused, auto reauth_callback) {
+          std::move(reauth_callback).Run(ReauthSucceeded(false));
+        });
+    EXPECT_CALL(
+        autofill_client,
+        UpdatePopup(
+            SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
+                {autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
+                 autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY,
+                 autofill::
+                     POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE}))),
+            PopupType::kPasswords));
+  });
+
+  password_autofill_manager_->DidAcceptSuggestion(
+      test_username_,
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE, 1);
+}
+
+// Test that the popup is updated once "opt in and fill" is clicked.
+TEST_F(PasswordAutofillManagerTest, SuccessfullOptInAndFillTriggersOptIn) {
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -490,7 +584,8 @@
 
   // Accepting a suggestion should trigger a call to update the popup.
   EXPECT_CALL(autofill_client, GetPopupSuggestions)
-      .WillOnce(Return(CreateTestSuggestions(/*has_opt_in=*/true)));
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/true, /*has_opt_in_and_generate*/ false)));
   EXPECT_CALL(autofill_client, UpdatePopup);
   EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate);
 
@@ -502,12 +597,45 @@
               SetAccountStorageOptIn(true));
 
   password_autofill_manager_->DidAcceptSuggestion(
-      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN,
+      test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN,
       1);
 }
 
-// Test that the popup is updated once remote suggestions are unlocked.
-TEST_F(PasswordAutofillManagerTest, AddOnFillDataAfterUnlockPopuplatesPopup) {
+// Test that the popup is updated once "opt in and generate" is clicked.
+TEST_F(PasswordAutofillManagerTest,
+       SuccessfullOptInAndGenerateTriggersOptInAndGeneration) {
+  TestPasswordManagerClient client;
+  NiceMock<MockAutofillClient> autofill_client;
+  InitializePasswordAutofillManager(&client, &autofill_client);
+  client.SetAccountStorageOptIn(false);
+  const CoreAccountId kAliceId = client.identity_test_env()
+                                     .SetUnconsentedPrimaryAccount(kAliceEmail)
+                                     .account_id;
+  testing::Mock::VerifyAndClearExpectations(&autofill_client);
+
+  // Accepting a suggestion should trigger a call to update the popup.
+  EXPECT_CALL(autofill_client, GetPopupSuggestions)
+      .WillOnce(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ true)));
+  EXPECT_CALL(autofill_client, UpdatePopup);
+  EXPECT_CALL(autofill_client, PinPopupViewUntilUpdate);
+
+  EXPECT_CALL(client, TriggerReauthForAccount(kAliceId, _))
+      .WillOnce([](const auto& id, auto reauth_callback) {
+        std::move(reauth_callback).Run(ReauthSucceeded(true));
+      });
+  EXPECT_CALL(*client.GetPasswordFeatureManager(),
+              SetAccountStorageOptIn(true));
+  EXPECT_CALL(client, GeneratePassword());
+
+  password_autofill_manager_->DidAcceptSuggestion(
+      test_username_,
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE, 1);
+}
+
+// Test that the popup is updated once "opt in and fill" is clicked".
+TEST_F(PasswordAutofillManagerTest,
+       AddOnFillDataAfterOptInAndFillPopulatesPopup) {
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -523,7 +651,8 @@
   new_data.additional_logins[additional_username] = additional;
   EXPECT_CALL(autofill_client, GetPopupSuggestions())
       .Times(2)
-      .WillRepeatedly(Return(CreateTestSuggestions(/*has_opt_in=*/false)));
+      .WillRepeatedly(Return(CreateTestSuggestions(
+          /*has_opt_in_and_fill=*/false, /*has_opt_in_and_generate*/ false)));
   EXPECT_CALL(
       autofill_client,
       UpdatePopup(
@@ -1030,6 +1159,44 @@
           /*show_password_suggestions=*/false));
 }
 
+// Test that if the "opt in and generate" button gets displayed, the regular
+// generation button does not.
+TEST_F(PasswordAutofillManagerTest,
+       MaybeShowPasswordSuggestionsWithAccountPasswordsEnabled) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage);
+
+  TestPasswordManagerClient client;
+  client.SetAccountStorageOptIn(false);
+
+  NiceMock<MockAutofillClient> autofill_client;
+  InitializePasswordAutofillManager(&client, &autofill_client);
+
+  autofill::PasswordFormFillData data = CreateTestFormFillData();
+
+  favicon::MockFaviconService favicon_service;
+  EXPECT_CALL(client, GetFaviconService()).WillOnce(Return(&favicon_service));
+  EXPECT_CALL(favicon_service, GetFaviconImageForPageURL(data.origin, _, _));
+  password_autofill_manager_->OnAddPasswordFillData(data);
+
+  auto opt_in_and_generate_id = static_cast<int>(
+      autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE);
+  auto regular_generate_id =
+      static_cast<int>(autofill::POPUP_ITEM_ID_GENERATE_PASSWORD_ENTRY);
+  EXPECT_CALL(autofill_client,
+              ShowAutofillPopup(
+                  _, _,
+                  AllOf(Contains(Field(&autofill::Suggestion::frontend_id,
+                                       Eq(opt_in_and_generate_id))),
+                        Not(Contains(Field(&autofill::Suggestion::frontend_id,
+                                           Eq(regular_generate_id))))),
+                  _, _, _));
+
+  password_autofill_manager_->MaybeShowPasswordSuggestionsWithGeneration(
+      gfx::RectF(), base::i18n::RIGHT_TO_LEFT,
+      /*show_password_suggestions=*/true);
+}
+
 TEST_F(PasswordAutofillManagerTest, DisplayAccountSuggestionsIndicatorIcon) {
   base::test::ScopedFeatureList features;
   features.InitAndEnableFeature(features::kEnablePasswordsAccountStorage);
diff --git a/components/password_manager/core/browser/password_feature_manager_impl.cc b/components/password_manager/core/browser/password_feature_manager_impl.cc
index bfdcc5d..216eeee 100644
--- a/components/password_manager/core/browser/password_feature_manager_impl.cc
+++ b/components/password_manager/core/browser/password_feature_manager_impl.cc
@@ -21,7 +21,8 @@
 bool PasswordFeatureManagerImpl::IsGenerationEnabled() const {
   switch (password_manager_util::GetPasswordSyncState(sync_service_)) {
     case NOT_SYNCING:
-      return false;
+      return password_manager_util::ShouldShowAccountStorageOptIn(
+          pref_service_, sync_service_);
     case SYNCING_WITH_CUSTOM_PASSPHRASE:
     case SYNCING_NORMAL_ENCRYPTION:
     case ACCOUNT_PASSWORDS_ACTIVE_NORMAL_ENCRYPTION:
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc
index fb965bc..0636796f 100644
--- a/components/password_manager/core/browser/password_form_manager.cc
+++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -104,6 +104,17 @@
   return nullptr;
 }
 
+void LogUsingPossibleUsername(PasswordManagerClient* client,
+                              bool is_used,
+                              const char* message) {
+  if (!password_manager_util::IsLoggingActive(client))
+    return;
+  BrowserSavePasswordProgressLogger logger(client->GetLogManager());
+  logger.LogString(is_used ? Logger::STRING_POSSIBLE_USERNAME_USED
+                           : Logger::STRING_POSSIBLE_USERNAME_NOT_USED,
+                   message);
+}
+
 }  // namespace
 
 PasswordFormManager::PasswordFormManager(
@@ -901,19 +912,25 @@
 
 bool PasswordFormManager::UsePossibleUsername(
     const PossibleUsernameData* possible_username) {
-  if (!possible_username)
+  if (!possible_username) {
+    LogUsingPossibleUsername(client_, /*is_used*/ false, "Null");
     return false;
+  }
 
   // The username form and password forms signon realms must be the same.
-  if (GetSignonRealm(observed_form_.url) != possible_username->signon_realm)
+  if (GetSignonRealm(observed_form_.url) != possible_username->signon_realm) {
+    LogUsingPossibleUsername(client_, /*is_used*/ false, "Different domains");
     return false;
+  }
 
   // The username candidate field should not be in |observed_form_|, otherwise
   // that is a task of FormParser to choose it from |observed_form_|.
   if (possible_username->driver_id == driver_id_) {
     for (const auto& field : observed_form_.fields) {
-      if (field.unique_renderer_id == possible_username->renderer_id)
+      if (field.unique_renderer_id == possible_username->renderer_id) {
+        LogUsingPossibleUsername(client_, /*is_used*/ false, "Same form");
         return false;
+      }
     }
   }
 
@@ -921,10 +938,15 @@
   const PasswordFieldPrediction* field_prediction = FindFieldPrediction(
       possible_username->form_predictions, possible_username->renderer_id);
   if (field_prediction) {
-    if (field_prediction->type == SINGLE_USERNAME)
+    if (field_prediction->type == SINGLE_USERNAME) {
+      LogUsingPossibleUsername(client_, /*is_used*/ true, "Server predictions");
       return true;
-    if (field_prediction->type == NOT_USERNAME)
+    }
+    if (field_prediction->type == NOT_USERNAME) {
+      LogUsingPossibleUsername(client_, /*is_used*/ false,
+                               "Server predictions");
       return false;
+    }
   }
 
 #if defined(OS_ANDROID)
@@ -941,15 +963,22 @@
     auto field_signature = field_prediction->signature;
     autofill::ServerFieldType type =
         field_info_manager->GetFieldType(form_signature, field_signature);
-    if (type == SINGLE_USERNAME)
+    if (type == SINGLE_USERNAME) {
+      LogUsingPossibleUsername(client_, /*is_used*/ true, "Local prediction");
       return true;
-    if (type == NOT_USERNAME)
+    }
+    if (type == NOT_USERNAME) {
+      LogUsingPossibleUsername(client_, /*is_used*/ false, "Local prediction");
       return false;
+    }
   }
 
-  return IsPossibleUsernameValid(*possible_username,
-                                 parsed_submitted_form_->signon_realm,
-                                 base::Time::Now());
+  bool is_possible_username_valid = IsPossibleUsernameValid(
+      *possible_username, parsed_submitted_form_->signon_realm,
+      base::Time::Now());
+  LogUsingPossibleUsername(client_, /*is_used*/ is_possible_username_valid,
+                           "Local heuristics");
+  return is_possible_username_valid;
 }
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index f69c503..e87978f 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -18,6 +18,7 @@
 #include "base/threading/platform_thread.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_field.h"
+#include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/autofill/core/common/form_data_predictions.h"
@@ -51,8 +52,10 @@
 using autofill::NOT_USERNAME;
 using autofill::PasswordForm;
 using autofill::SINGLE_USERNAME;
+using autofill::UNKNOWN_TYPE;
 using autofill::USERNAME;
 using autofill::mojom::PasswordFormFieldPredictionType;
+using base::NumberToString;
 #if defined(SYNC_PASSWORD_REUSE_DETECTION_ENABLED)
 using password_manager::metrics_util::GaiaPasswordHashChange;
 #endif  // SYNC_PASSWORD_REUSE_DETECTION_ENABLED
@@ -159,7 +162,8 @@
 // predictions for corresponding fields. Predictions from |field_info_manager|
 // have priority over server predictions.
 void AddLocallySavedPredictions(FieldInfoManager* field_info_manager,
-                                FormPredictions* predictions) {
+                                FormPredictions* predictions,
+                                BrowserSavePasswordProgressLogger* logger) {
   DCHECK(predictions);
   if (!field_info_manager)
     return;
@@ -176,6 +180,13 @@
       if (field.type != SINGLE_USERNAME && field.type != USERNAME)
         field.type = NOT_USERNAME;
     }
+    if (logger && local_prediction != UNKNOWN_TYPE) {
+      std::string message =
+          "form signature=" + NumberToString(predictions->form_signature) +
+          " , field signature=" + NumberToString(field.signature) + ", type=" +
+          autofill::AutofillType::ServerFieldTypeToString(local_prediction);
+      logger->LogString(Logger::STRING_LOCALLY_SAVED_PREDICTION, message);
+    }
   }
 }
 
@@ -979,7 +990,8 @@
     predictions_[form->form_signature()] =
         ConvertToFormPredictions(driver_id, *form);
     AddLocallySavedPredictions(client_->GetFieldInfoManager(),
-                               &predictions_[form->form_signature()]);
+                               &predictions_[form->form_signature()],
+                               logger.get());
   }
 
   // Create form managers for non-password forms if |predictions_| has evidence
diff --git a/components/password_manager/core/browser/password_manager_util.cc b/components/password_manager/core/browser/password_manager_util.cc
index 9c37ccd..bcb20d5c5 100644
--- a/components/password_manager/core/browser/password_manager_util.cc
+++ b/components/password_manager/core/browser/password_manager_util.cc
@@ -383,31 +383,52 @@
   if (!submitted_form.federation_origin.opaque())
     return nullptr;
 
-  // 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;
-
-    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;
+  // 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;
+    }
   }
 
+  // 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;
+  }
+  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 3928c582..f5dc0af 100644
--- a/components/password_manager/core/browser/password_manager_util_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_util_unittest.cc
@@ -392,6 +392,24 @@
             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/test_password_store.cc b/components/password_manager/core/browser/test_password_store.cc
index 6151ac28..e0f6b80 100644
--- a/components/password_manager/core/browser/test_password_store.cc
+++ b/components/password_manager/core/browser/test_password_store.cc
@@ -131,6 +131,9 @@
 PasswordStoreChangeList TestPasswordStore::AddLoginImpl(
     const autofill::PasswordForm& form,
     AddLoginError* error) {
+  if (error)
+    *error = AddLoginError::kNone;
+
   PasswordStoreChangeList changes;
   auto& passwords_for_signon_realm = stored_passwords_[form.signon_realm];
   auto iter = std::find_if(
@@ -143,23 +146,35 @@
     changes.emplace_back(PasswordStoreChange::REMOVE, *iter);
     changes.emplace_back(PasswordStoreChange::ADD, form);
     *iter = form;
+    iter->in_store = is_account_store_
+                         ? autofill::PasswordForm::Store::kAccountStore
+                         : autofill::PasswordForm::Store::kProfileStore;
     return changes;
   }
 
   changes.emplace_back(PasswordStoreChange::ADD, form);
   passwords_for_signon_realm.push_back(form);
+  passwords_for_signon_realm.back().in_store =
+      is_account_store_ ? autofill::PasswordForm::Store::kAccountStore
+                        : autofill::PasswordForm::Store::kProfileStore;
   return changes;
 }
 
 PasswordStoreChangeList TestPasswordStore::UpdateLoginImpl(
     const autofill::PasswordForm& form,
     UpdateLoginError* error) {
+  if (error)
+    *error = UpdateLoginError::kNone;
+
   PasswordStoreChangeList changes;
   std::vector<autofill::PasswordForm>& forms =
       stored_passwords_[form.signon_realm];
   for (auto it = forms.begin(); it != forms.end(); ++it) {
     if (ArePasswordFormUniqueKeysEqual(form, *it)) {
       *it = form;
+      it->in_store = is_account_store_
+                         ? autofill::PasswordForm::Store::kAccountStore
+                         : autofill::PasswordForm::Store::kProfileStore;
       changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form));
     }
   }
diff --git a/components/password_manager/core/browser/test_password_store.h b/components/password_manager/core/browser/test_password_store.h
index d89ee97d..dd89e8c5 100644
--- a/components/password_manager/core/browser/test_password_store.h
+++ b/components/password_manager/core/browser/test_password_store.h
@@ -17,9 +17,18 @@
 #include "base/sequenced_task_runner.h"
 #include "components/password_manager/core/browser/compromised_credentials_table.h"
 #include "components/password_manager/core/browser/password_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 namespace password_manager {
 
+// A matcher that compares two PasswordForm instances but ignores the |in_store|
+// member.
+MATCHER_P(MatchesFormExceptStore, expected, "") {
+  autofill::PasswordForm arg_copy = arg;
+  arg_copy.in_store = expected.in_store;
+  return arg_copy == expected;
+}
+
 // A very simple PasswordStore implementation that keeps all of the passwords
 // in memory and does all its manipulations on the main thread. Since this
 // is only used for testing, only the parts of the interface that are needed
diff --git a/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter_unittest.cc b/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter_unittest.cc
index 7cc2d12..59a63267 100644
--- a/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter_unittest.cc
+++ b/components/password_manager/core/browser/ui/bulk_leak_check_service_adapter_unittest.cc
@@ -257,6 +257,10 @@
   PasswordForm password =
       MakeSavedPassword(kExampleCom, kUsername1, kPassword1);
   store().AddLogin(password);
+  // When |password| is read back from the store, its |in_store| member will be
+  // set, and SavedPasswordsPresenter::EditPassword() actually depends on that.
+  // So set it here too.
+  password.in_store = PasswordForm::Store::kProfileStore;
   RunUntilIdle();
 
   EXPECT_CALL(factory(), TryCreateBulkLeakCheck).Times(0);
@@ -270,6 +274,10 @@
   PasswordForm password =
       MakeSavedPassword(kExampleCom, kUsername1, kPassword1);
   store().AddLogin(password);
+  // When |password| is read back from the store, its |in_store| member will be
+  // set, and SavedPasswordsPresenter::EditPassword() actually depends on that.
+  // So set it here too.
+  password.in_store = PasswordForm::Store::kProfileStore;
   RunUntilIdle();
 
   std::vector<LeakCheckCredential> expected;
diff --git a/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc b/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
index 7dd6b75..63a1ad04 100644
--- a/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
+++ b/components/password_manager/core/browser/ui/saved_passwords_presenter_unittest.cc
@@ -66,7 +66,8 @@
 
   // Adding a credential should notify observers. Furthermore, the credential
   // should be present of the list that is passed along.
-  EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(form)));
+  EXPECT_CALL(observer, OnSavedPasswordsChanged(
+                            ElementsAre(MatchesFormExceptStore(form))));
   store().AddLogin(form);
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
@@ -122,6 +123,11 @@
   RunUntilIdle();
   EXPECT_FALSE(store().IsEmpty());
 
+  // When |form| is read back from the store, its |in_store| member will be set,
+  // and SavedPasswordsPresenter::EditPassword() actually depends on that. So
+  // set it here too.
+  form.in_store = PasswordForm::Store::kProfileStore;
+
   const base::string16 new_password = base::ASCIIToUTF16("new_password");
   PasswordForm updated = form;
   updated.password_value = new_password;
diff --git a/components/payments/content/installable_payment_app_crawler.cc b/components/payments/content/installable_payment_app_crawler.cc
index 25925fc..c548b99 100644
--- a/components/payments/content/installable_payment_app_crawler.cc
+++ b/components/payments/content/installable_payment_app_crawler.cc
@@ -33,6 +33,7 @@
 namespace payments {
 
 // TODO(crbug.com/782270): Use cache to accelerate crawling procedure.
+// TODO(crbug.com/782270): Add integration tests for this class.
 InstallablePaymentAppCrawler::InstallablePaymentAppCrawler(
     const url::Origin& merchant_origin,
     content::RenderFrameHost* initiator_render_frame_host,
@@ -43,13 +44,7 @@
     : WebContentsObserver(web_contents),
       log_(web_contents),
       merchant_origin_(merchant_origin),
-      initiator_frame_routing_id_(
-          initiator_render_frame_host &&
-                  initiator_render_frame_host->GetProcess()
-              ? content::GlobalFrameRoutingId(
-                    initiator_render_frame_host->GetProcess()->GetID(),
-                    initiator_render_frame_host->GetRoutingID())
-              : content::GlobalFrameRoutingId()),
+      initiator_render_frame_host_(initiator_render_frame_host),
       downloader_(downloader),
       parser_(parser),
       number_of_payment_method_manifest_to_download_(0),
@@ -412,21 +407,13 @@
 
   number_of_web_app_icons_to_download_and_decode_++;
 
-  // If the initiator frame doesn't exists any more, e.g. the frame has
-  // navigated away, don't download the icon.
-  // TODO(crbug.com/1058840): Move this sanity check to ManifestIconDownloader
-  // after DownloadImage refactor is done.
-  content::RenderFrameHost* render_frame_host =
-      content::RenderFrameHost::FromID(initiator_frame_routing_id_);
-  if (!render_frame_host || !render_frame_host->IsCurrent() ||
-      content::WebContents::FromRenderFrameHost(render_frame_host) !=
-          web_contents()) {
-    // Shortcircuit to the done callback with an empty bitmap.
-    OnPaymentWebAppIconDownloadAndDecoded(method_manifest_url,
-                                          web_app_manifest_url, SkBitmap());
-    return;
+  content::GlobalFrameRoutingId frame_routing_id;
+  if (initiator_render_frame_host_ &&
+      initiator_render_frame_host_->GetProcess()) {
+    frame_routing_id = content::GlobalFrameRoutingId(
+        initiator_render_frame_host_->GetProcess()->GetID(),
+        initiator_render_frame_host_->GetRoutingID());
   }
-
   bool can_download_icon = content::ManifestIconDownloader::Download(
       web_contents(), downloader_->FindTestServerURL(best_icon_url),
       IconSizeCalculator::IdealIconHeight(native_view),
@@ -436,7 +423,7 @@
           weak_ptr_factory_.GetWeakPtr(), method_manifest_url,
           web_app_manifest_url),
       false, /* square_only */
-      initiator_frame_routing_id_);
+      frame_routing_id);
   DCHECK(can_download_icon);
 }
 
diff --git a/components/payments/content/installable_payment_app_crawler.h b/components/payments/content/installable_payment_app_crawler.h
index 7b49e2d..fdad1391 100644
--- a/components/payments/content/installable_payment_app_crawler.h
+++ b/components/payments/content/installable_payment_app_crawler.h
@@ -19,7 +19,6 @@
 #include "components/payments/content/utility/payment_manifest_parser.h"
 #include "components/payments/content/web_app_manifest.h"
 #include "components/payments/core/payment_manifest_downloader.h"
-#include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
 #include "url/origin.h"
@@ -112,7 +111,7 @@
 
   DeveloperConsoleLogger log_;
   const url::Origin merchant_origin_;
-  const content::GlobalFrameRoutingId initiator_frame_routing_id_;
+  content::RenderFrameHost* initiator_render_frame_host_;
   PaymentManifestDownloader* downloader_;
   PaymentManifestParser* parser_;
   FinishedCrawlingCallback callback_;
diff --git a/components/safe_browsing/core/db/v4_get_hash_protocol_manager.cc b/components/safe_browsing/core/db/v4_get_hash_protocol_manager.cc
index 268285f..cbcb0a6 100644
--- a/components/safe_browsing/core/db/v4_get_hash_protocol_manager.cc
+++ b/components/safe_browsing/core/db/v4_get_hash_protocol_manager.cc
@@ -16,7 +16,6 @@
 #include "base/timer/timer.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/traced_value.h"
-#include "build/build_config.h"
 #include "components/safe_browsing/core/common/thread_utils.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_response_headers.h"
@@ -301,12 +300,6 @@
     return;
   }
 
-  // TODO(crbug.com/1028755): Enable full hash checks on iOS.
-#if defined(OS_IOS)
-  std::move(callback).Run(cached_full_hash_infos);
-  return;
-#endif
-
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("safe_browsing_v4_get_hash", R"(
         semantics {
@@ -779,12 +772,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(CurrentlyOnThread(ThreadID::IO));
 
-  // Ensure that full hash requests are not being sent on iOS.
-  // TODO(crbug.com/1028755): Enable full hash checks on iOS.
-#if defined(OS_IOS)
-  CHECK(false);
-#endif
-
   int response_code = 0;
   if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers)
     response_code = url_loader->ResponseInfo()->headers->response_code();
diff --git a/components/safe_browsing/ios/BUILD.gn b/components/safe_browsing/ios/BUILD.gn
index 4622ac5..c273050 100644
--- a/components/safe_browsing/ios/BUILD.gn
+++ b/components/safe_browsing/ios/BUILD.gn
@@ -12,7 +12,7 @@
     "//components/safe_browsing/core/browser",
     "//components/safe_browsing/core/common",
     "//components/safe_browsing/core/common:unit_tests",
-    "//components/safe_browsing/core/db",
+    "//components/safe_browsing/core/db:unit_tests_local_db",
     "//components/safe_browsing/core/realtime:unit_tests",
   ]
 }
diff --git a/components/startup_metric_utils/browser/startup_metric_utils.cc b/components/startup_metric_utils/browser/startup_metric_utils.cc
index 429bedf..89485e8 100644
--- a/components/startup_metric_utils/browser/startup_metric_utils.cc
+++ b/components/startup_metric_utils/browser/startup_metric_utils.cc
@@ -42,9 +42,9 @@
 
 base::TimeTicks g_process_creation_ticks;
 
-base::TimeTicks g_browser_main_entry_point_ticks;
+base::TimeTicks g_application_start_ticks;
 
-base::TimeTicks g_browser_exe_main_entry_point_ticks;
+base::TimeTicks g_chrome_main_entry_ticks;
 
 base::TimeTicks g_message_loop_start_ticks;
 
@@ -346,13 +346,14 @@
   return trace_ticks_base - delta_since_base;
 }
 
-void AddStartupEventsForTelemetry()
-{
-  DCHECK(!g_browser_main_entry_point_ticks.is_null());
+void AddStartupEventsForTelemetry() {
+  // Record the event only if RecordChromeMainEntryTime() was called, which is
+  // not the case for some tests.
+  if (g_chrome_main_entry_ticks.is_null())
+    return;
 
-  TRACE_EVENT_INSTANT_WITH_TIMESTAMP0("startup",
-                                      "Startup.BrowserMainEntryPoint", 0,
-                                      g_browser_main_entry_point_ticks);
+  TRACE_EVENT_INSTANT_WITH_TIMESTAMP0(
+      "startup", "Startup.BrowserMainEntryPoint", 0, g_chrome_main_entry_ticks);
 }
 
 bool ShouldLogStartupHistogram() {
@@ -380,16 +381,16 @@
   DCHECK(!g_process_creation_ticks.is_null());
 }
 
-void RecordMainEntryPointTime(base::TimeTicks ticks) {
-  DCHECK(g_browser_main_entry_point_ticks.is_null());
-  g_browser_main_entry_point_ticks = ticks;
-  DCHECK(!g_browser_main_entry_point_ticks.is_null());
+void RecordApplicationStartTime(base::TimeTicks ticks) {
+  DCHECK(g_application_start_ticks.is_null());
+  g_application_start_ticks = ticks;
+  DCHECK(!g_application_start_ticks.is_null());
 }
 
-void RecordExeMainEntryPointTicks(base::TimeTicks ticks) {
-  DCHECK(g_browser_exe_main_entry_point_ticks.is_null());
-  g_browser_exe_main_entry_point_ticks = ticks;
-  DCHECK(!g_browser_exe_main_entry_point_ticks.is_null());
+void RecordChromeMainEntryTime(base::TimeTicks ticks) {
+  DCHECK(g_chrome_main_entry_ticks.is_null());
+  g_chrome_main_entry_ticks = ticks;
+  DCHECK(!g_chrome_main_entry_ticks.is_null());
 }
 
 void RecordMessageLoopStartTicks(base::TimeTicks ticks) {
@@ -434,24 +435,19 @@
   // Record timings between process creation, the main() in the executable being
   // reached and the main() in the shared library being reached.
   if (!g_process_creation_ticks.is_null() &&
-      !g_browser_exe_main_entry_point_ticks.is_null()) {
-    // Process create to chrome.exe:main().
+      !g_application_start_ticks.is_null()) {
+    // Process create to application start.
     UmaHistogramWithTraceAndTemperature(
         &base::UmaHistogramLongTimes,
-        "Startup.LoadTime.ProcessCreateToExeMain2", g_process_creation_ticks,
-        g_browser_exe_main_entry_point_ticks);
+        "Startup.LoadTime.ProcessCreateToApplicationStart",
+        g_process_creation_ticks, g_application_start_ticks);
 
-    // chrome.exe:main() to chrome.dll:main().
+    // Application start to ChromeMain().
+    DCHECK(!g_chrome_main_entry_ticks.is_null());
     UmaHistogramWithTraceAndTemperature(
-        &base::UmaHistogramLongTimes, "Startup.LoadTime.ExeMainToDllMain2",
-        g_browser_exe_main_entry_point_ticks, g_browser_main_entry_point_ticks);
-
-    // Process create to chrome.dll:main(). Reported as a histogram only as
-    // the other two events above are sufficient for tracing purposes.
-    UmaHistogramWithTemperature(
         &base::UmaHistogramLongTimes,
-        "Startup.LoadTime.ProcessCreateToDllMain2",
-        g_browser_main_entry_point_ticks - g_process_creation_ticks);
+        "Startup.LoadTime.ApplicationStartToChromeMain",
+        g_application_start_ticks, g_chrome_main_entry_ticks);
   }
 }
 
@@ -485,6 +481,10 @@
   UmaHistogramAndTraceWithTemperatureAndMaxPressure(
       &base::UmaHistogramLongTimes100,
       "Startup.FirstWebContents.NonEmptyPaint2", g_process_creation_ticks, now);
+  UmaHistogramAndTraceWithTemperatureAndMaxPressure(
+      &base::UmaHistogramLongTimes100,
+      "Startup.FirstWebContents.NonEmptyPaint3", g_application_start_ticks,
+      now);
   UmaHistogramWithTemperature(
       &base::UmaHistogramLongTimes100,
       "Startup.BrowserMessageLoopStart.To.NonEmptyPaint2",
@@ -553,7 +553,7 @@
 }
 
 base::TimeTicks MainEntryPointTicks() {
-  return g_browser_main_entry_point_ticks;
+  return g_chrome_main_entry_ticks;
 }
 
 void RecordWebFooterDidFirstVisuallyNonEmptyPaint(base::TimeTicks ticks) {
diff --git a/components/startup_metric_utils/browser/startup_metric_utils.h b/components/startup_metric_utils/browser/startup_metric_utils.h
index e6d4a08..3168bb4 100644
--- a/components/startup_metric_utils/browser/startup_metric_utils.h
+++ b/components/startup_metric_utils/browser/startup_metric_utils.h
@@ -36,14 +36,14 @@
 void RecordStartupProcessCreationTime(base::Time time);
 
 // Call this with a time recorded as early as possible in the startup process.
-// On Android, the entry point time is the time at which the Java code starts.
-// In Mojo, the entry point time is the time at which the shell starts.
-void RecordMainEntryPointTime(base::TimeTicks ticks);
+// On Android, the application start is the time at which the Java code starts.
+// On Windows, the application start is sampled from chrome.exe:main, before
+// chrome.dll is loaded.
+void RecordApplicationStartTime(base::TimeTicks ticks);
 
-// Call this with the time when the executable is loaded and main() is entered.
-// Can be different from |RecordMainEntryPointTime| when the startup process is
-// contained in a separate dll, such as with chrome.exe / chrome.dll on Windows.
-void RecordExeMainEntryPointTicks(base::TimeTicks ticks);
+// Call this with the time when the executable is loaded and the ChromeMain()
+// function is invoked.
+void RecordChromeMainEntryTime(base::TimeTicks ticks);
 
 // Call this with the time recorded just before the message loop is started.
 // |is_first_run| - is the current launch part of a first run.
diff --git a/components/viz/service/display/process_renderer_perftest_results.py b/components/viz/service/display/process_renderer_perftest_results.py
new file mode 100755
index 0000000..588dece3
--- /dev/null
+++ b/components/viz/service/display/process_renderer_perftest_results.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# 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.
+"""Script which process RendererPerfTest output.
+
+Run viz_perftests.exe --gtest_filter=RendererPerfTest* > stdout.txt.
+Then run this script as
+  python process_renderer_perftest_results.py --test-stdout="stdout.txt"
+
+The perf data will be collected and stored in "output.csv".
+"""
+
+import argparse
+import csv
+import logging
+import sys
+
+
+def SaveResultsAsCSV(csv_data, csv_filename):
+  assert len(csv_data) > 0
+  with open(csv_filename, 'wb') as csv_file:
+    labels = sorted(csv_data[0].keys(), reverse=True)
+    writer = csv.DictWriter(csv_file, fieldnames=labels)
+    writer.writeheader()
+    writer.writerows(csv_data)
+
+
+def FindTestEntry(csv_data, test_name):
+  for entry in csv_data:
+    if entry['TestName'] == test_name:
+      return entry
+  return None
+
+
+def ProcessOutput(lines):
+  csv_data = []
+  for line in lines:
+    line = line.strip()
+    if line.startswith('[ RUN      ]'):
+      test_name = line.split('.')[1]
+      entry = FindTestEntry(csv_data, test_name)
+      if entry is None:
+        entry = {'TestName': test_name}
+        csv_data.append(entry)
+    elif line.startswith('Using '):
+      renderer = line.split()[1][:-8]
+    elif line.startswith('*RESULT '):
+      fps = line.split('=')[1].strip().split()[0]
+      assert renderer == 'GL' or renderer == 'Skia'
+      assert entry is not None
+      entry['FPS_' + renderer] = fps
+    elif line.startswith('Histogram: '):
+      draw_to_swap_us_mean = line.split('=')[1].strip()
+      assert renderer == 'GL' or renderer == 'Skia'
+      assert entry is not None
+      entry['DrawToSwap_' + renderer] = draw_to_swap_us_mean
+    elif line.startswith('[       OK ]'):
+      end_test_name = line.split('.')[1].split()[0]
+      assert test_name == end_test_name
+      test_name = None
+      entry = None
+      renderer = None
+  return csv_data
+
+
+def main():
+  rest_args = sys.argv[1:]
+  parser = argparse.ArgumentParser(
+    description='Gather RendererPerfTest results.',
+    formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+  parser.add_argument(
+    '--test-stdout',
+    metavar='FILE',
+    help='Test stdout filename. Input of this script.')
+  parser.add_argument(
+    '--csv-file',
+    metavar='FILE',
+    default='output.csv',
+    help='CSV filename. Output of this script. '
+    'Default is output.csv.')
+
+  options = parser.parse_args(rest_args)
+  input_filename = options.test_stdout
+  if input_filename is None:
+    logging.error('Specify test stdout filename with --test-stdout.')
+    return 0
+
+  with open(input_filename, 'r') as input_file:
+    lines = input_file.readlines()
+
+  csv_data = ProcessOutput(lines)
+  SaveResultsAsCSV(csv_data, options.csv_file)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 93348ce..442d7d34 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -3601,7 +3601,7 @@
 
 // Trilinear filtering is only supported in the gl renderer.
 // TODO(https://crbug.com/1044841): Flaky, especially on Linux/TSAN and Fuchsia.
-TYPED_TEST(GPURendererPixelTest, DISABLED_TrilinearFiltering) {
+TYPED_TEST(GPURendererPixelTest, TrilinearFiltering) {
   gfx::Rect viewport_rect(this->device_viewport_size_);
 
   int root_pass_id = 1;
diff --git a/content/browser/accessibility/accessibility_win_browsertest.cc b/content/browser/accessibility/accessibility_win_browsertest.cc
index 4b92312..12baf4e 100644
--- a/content/browser/accessibility/accessibility_win_browsertest.cc
+++ b/content/browser/accessibility/accessibility_win_browsertest.cc
@@ -50,6 +50,7 @@
 #include "third_party/isimpledom/ISimpleDOMNode.h"
 #include "ui/accessibility/accessibility_switches.h"
 #include "ui/accessibility/ax_event_generator.h"
+#include "ui/accessibility/platform/ax_fragment_root_win.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
 
@@ -4267,6 +4268,44 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest,
+                       GetFocusFromRootReachesWebContent) {
+  LoadInitialAccessibilityTreeFromHtml(
+      R"HTML(<!DOCTYPE html>
+      <html>
+        <button>Focus target</button>
+        <script>
+          document.querySelector('button').focus();
+        </script>
+      </html>)HTML");
+
+  // Obtain the fragment root from the top-level HWND.
+  HWND hwnd = shell()->window()->GetHost()->GetAcceleratedWidget();
+  ASSERT_NE(gfx::kNullAcceleratedWidget, hwnd);
+  ui::AXFragmentRootWin* fragment_root =
+      ui::AXFragmentRootWin::GetForAcceleratedWidget(hwnd);
+  ASSERT_NE(nullptr, fragment_root);
+  Microsoft::WRL::ComPtr<IRawElementProviderFragmentRoot> uia_fragment_root;
+  ASSERT_HRESULT_SUCCEEDED(
+      fragment_root->GetNativeViewAccessible()->QueryInterface(
+          IID_PPV_ARGS(&uia_fragment_root)));
+
+  // Verify that calling GetFocus on the fragment root reaches web content.
+  Microsoft::WRL::ComPtr<IRawElementProviderFragment> focused_fragment;
+  ASSERT_HRESULT_SUCCEEDED(uia_fragment_root->GetFocus(&focused_fragment));
+
+  Microsoft::WRL::ComPtr<IRawElementProviderSimple> focused_element;
+  ASSERT_HRESULT_SUCCEEDED(focused_fragment.As(&focused_element));
+
+  base::win::ScopedVariant name_property;
+  ASSERT_HRESULT_SUCCEEDED(focused_element->GetPropertyValue(
+      UIA_NamePropertyId, name_property.Receive()));
+  ASSERT_EQ(name_property.type(), VT_BSTR);
+  BSTR name_bstr = name_property.ptr()->bstrVal;
+  base::string16 actual_name(name_bstr, ::SysStringLen(name_bstr));
+  EXPECT_EQ(L"Focus target", actual_name);
+}
+
+IN_PROC_BROWSER_TEST_F(AccessibilityWinUIABrowserTest,
                        LegacyWindowIsNotControlElement) {
   LoadInitialAccessibilityTreeFromHtml(
       R"HTML(<!DOCTYPE html>
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index 688bbeb..f346780 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -765,14 +765,13 @@
   EXPECT_TRUE(NavigateToURL(popup, url_b));
   EXPECT_EQ(1u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount());
 
-  // 5) Navigate to B again. In theory, the current document should be able to
-  // enter the BackForwardCache. It can't, because the RenderFrameHost still
-  // "remembers" it had access to the popup. See
-  // RenderFrameHostImpl::scheduler_tracked_features().
+  // 5) Navigate to B again. As the scripting relationship with the popup is
+  // now severed, the current page (|rfh_a_new|) can enter back-forward cache.
   RenderFrameDeletedObserver delete_observer_rfh_a_new(rfh_a_new);
   EXPECT_TRUE(ExecJs(rfh_a_new, JsReplace("location = $1;", url_b.spec())));
   EXPECT_TRUE(WaitForLoadStop(web_contents()));
-  delete_observer_rfh_a_new.WaitUntilDeleted();
+  EXPECT_FALSE(delete_observer_rfh_a_new.deleted());
+  EXPECT_TRUE(rfh_a_new->is_in_back_forward_cache());
 
   // 6) Go back to A. The current document can finally enter the
   // BackForwardCache, because it is alone in its BrowsingInstance and has never
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 828078d..22e7abc 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -358,7 +358,7 @@
     // No other overrides, so just returns ours as is.
     *factory_override = network::mojom::URLLoaderFactoryOverride::New(
         std::move(devtools_override.overriding_factory),
-        std::move(devtools_override.overridden_factory_receiver));
+        std::move(devtools_override.overridden_factory_receiver), false);
   }
   // ... else things are already taken care of, as handler_override was pointing
   // to factory override and we've done all magic in-place.
@@ -410,7 +410,7 @@
   if (!*factory_override) {
     *factory_override = network::mojom::URLLoaderFactoryOverride::New(
         std::move(devtools_override.overriding_factory),
-        std::move(devtools_override.overridden_factory_receiver));
+        std::move(devtools_override.overridden_factory_receiver), false);
   }
   return true;
 }
diff --git a/content/browser/devtools/protocol/emulation_handler.cc b/content/browser/devtools/protocol/emulation_handler.cc
index 8a0f582..f7b9bdf 100644
--- a/content/browser/devtools/protocol/emulation_handler.cc
+++ b/content/browser/devtools/protocol/emulation_handler.cc
@@ -19,7 +19,6 @@
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/device/public/mojom/geolocation_context.mojom.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom.h"
 #include "ui/events/gesture_detection/gesture_provider_config_helper.h"
 
 namespace content {
@@ -27,17 +26,17 @@
 
 namespace {
 
-blink::mojom::ScreenOrientationType ScreenOrientationFromString(
+blink::WebScreenOrientationType WebScreenOrientationTypeFromString(
     const std::string& type) {
   if (type == Emulation::ScreenOrientation::TypeEnum::PortraitPrimary)
-    return blink::mojom::ScreenOrientationType::kPortraitPrimary;
+    return blink::kWebScreenOrientationPortraitPrimary;
   if (type == Emulation::ScreenOrientation::TypeEnum::PortraitSecondary)
-    return blink::mojom::ScreenOrientationType::kPortraitSecondary;
+    return blink::kWebScreenOrientationPortraitSecondary;
   if (type == Emulation::ScreenOrientation::TypeEnum::LandscapePrimary)
-    return blink::mojom::ScreenOrientationType::kLandscapePrimary;
+    return blink::kWebScreenOrientationLandscapePrimary;
   if (type == Emulation::ScreenOrientation::TypeEnum::LandscapeSecondary)
-    return blink::mojom::ScreenOrientationType::kLandscapeSecondary;
-  return blink::mojom::ScreenOrientationType::kUndefined;
+    return blink::kWebScreenOrientationLandscapeSecondary;
+  return blink::kWebScreenOrientationUndefined;
 }
 
 ui::GestureProviderConfigType TouchEmulationConfigurationToType(
@@ -60,6 +59,7 @@
 EmulationHandler::EmulationHandler()
     : DevToolsDomainHandler(Emulation::Metainfo::domainName),
       touch_emulation_enabled_(false),
+      device_emulation_enabled_(false),
       host_(nullptr) {
 }
 
@@ -80,7 +80,7 @@
   host_ = frame_host;
   if (touch_emulation_enabled_)
     UpdateTouchEventEmulationState();
-  if (device_emulation_params_)
+  if (device_emulation_enabled_)
     UpdateDeviceEmulationState();
 }
 
@@ -94,8 +94,8 @@
     UpdateTouchEventEmulationState();
   }
   user_agent_ = std::string();
-  if (device_emulation_params_) {
-    device_emulation_params_.reset();
+  if (device_emulation_enabled_) {
+    device_emulation_enabled_ = false;
     UpdateDeviceEmulationState();
   }
   return Response::OK();
@@ -205,13 +205,14 @@
                                    base::NumberToString(max_scale));
   }
 
-  blink::mojom::ScreenOrientationType orientation_type =
-      blink::mojom::ScreenOrientationType::kUndefined;
+  blink::WebScreenOrientationType orientationType =
+      blink::kWebScreenOrientationUndefined;
   int orientationAngle = 0;
   if (screen_orientation.isJust()) {
     Emulation::ScreenOrientation* orientation = screen_orientation.fromJust();
-    orientation_type = ScreenOrientationFromString(orientation->GetType());
-    if (orientation_type == blink::mojom::ScreenOrientationType::kUndefined)
+    orientationType = WebScreenOrientationTypeFromString(
+        orientation->GetType());
+    if (orientationType == blink::kWebScreenOrientationUndefined)
       return Response::InvalidParams("Invalid screen orientation type value");
     orientationAngle = orientation->GetAngle();
     if (orientationAngle < 0 || orientationAngle >= max_orientation_angle) {
@@ -222,23 +223,23 @@
   }
 
   blink::WebDeviceEmulationParams params;
-  params.screen_position = mobile ? blink::mojom::ScreenPosition::kMobile
-                                  : blink::mojom::ScreenPosition::kDesktop;
+  params.screen_position = mobile ? blink::WebDeviceEmulationParams::kMobile
+                                  : blink::WebDeviceEmulationParams::kDesktop;
   params.screen_size =
-      gfx::Size(screen_width.fromMaybe(0), screen_height.fromMaybe(0));
+      blink::WebSize(screen_width.fromMaybe(0), screen_height.fromMaybe(0));
   if (position_x.isJust() && position_y.isJust()) {
     params.view_position =
         gfx::Point(position_x.fromMaybe(0), position_y.fromMaybe(0));
   }
   params.device_scale_factor = device_scale_factor;
-  params.view_size = gfx::Size(width, height);
+  params.view_size = blink::WebSize(width, height);
   params.scale = scale.fromMaybe(1);
-  params.screen_orientation_type = orientation_type;
+  params.screen_orientation_type = orientationType;
   params.screen_orientation_angle = orientationAngle;
 
   if (viewport.isJust()) {
-    params.viewport_offset =
-        gfx::PointF(viewport.fromJust()->GetX(), viewport.fromJust()->GetY());
+    params.viewport_offset.SetPoint(viewport.fromJust()->GetX(),
+                                    viewport.fromJust()->GetY());
 
     ScreenInfo screen_info;
     host_->GetRenderWidgetHost()->GetScreenInfo(&screen_info);
@@ -264,7 +265,7 @@
     }
   }
 
-  if (device_emulation_params_ && *device_emulation_params_ == params) {
+  if (device_emulation_enabled_ && params == device_emulation_params_) {
     // Renderer should answer after size was changed, so that the response is
     // only sent to the client once updates were applied.
     if (size_changed)
@@ -272,7 +273,8 @@
     return Response::OK();
   }
 
-  device_emulation_params_ = std::move(params);
+  device_emulation_enabled_ = true;
+  device_emulation_params_ = params;
   UpdateDeviceEmulationState();
 
   // Renderer should answer after emulation params were updated, so that the
@@ -284,12 +286,13 @@
 }
 
 Response EmulationHandler::ClearDeviceMetricsOverride() {
-  if (!device_emulation_params_)
+  if (!device_emulation_enabled_)
     return Response::OK();
   if (!host_)
     return Response::Error("Can't find the associated web contents");
   GetWebContents()->ClearDeviceEmulationSize();
-  device_emulation_params_.reset();
+  device_emulation_enabled_ = false;
+  device_emulation_params_ = blink::WebDeviceEmulationParams();
   UpdateDeviceEmulationState();
   // Renderer should answer after emulation was disabled, so that the response
   // is only sent to the client once updates were applied.
@@ -326,15 +329,20 @@
   return Response::FallThrough();
 }
 
-const base::Optional<blink::WebDeviceEmulationParams>&
-EmulationHandler::GetDeviceEmulationParams() const {
+blink::WebDeviceEmulationParams EmulationHandler::GetDeviceEmulationParams() {
   return device_emulation_params_;
 }
 
 void EmulationHandler::SetDeviceEmulationParams(
-    const base::Optional<blink::WebDeviceEmulationParams>& params) {
-  if (params == device_emulation_params_)
-    return;
+    const blink::WebDeviceEmulationParams& params) {
+  bool enabled = params != blink::WebDeviceEmulationParams();
+  bool enable_changed = enabled != device_emulation_enabled_;
+  bool params_changed = params != device_emulation_params_;
+  if (!device_emulation_enabled_ && !enable_changed)
+    return;  // Still disabled.
+  if (!enable_changed && !params_changed)
+    return;  // Nothing changed.
+  device_emulation_enabled_ = enabled;
   device_emulation_params_ = params;
   UpdateDeviceEmulationState();
 }
@@ -373,11 +381,21 @@
   if (!host_->frame_tree_node()->IsMainFrame())
     return;
 
-  // TODO(caseq,eseckler): We should wait for an ack to these messages from the
-  // renderer. The renderer should send the ack once the emulation params were
-  // applied. That way, we can avoid having to handle
-  // Set/ClearDeviceMetricsOverride in the renderer.
-  host_->SetDeviceEmulation(device_emulation_params_);
+  // TODO(eseckler): Once we change this to mojo, we should wait for an ack to
+  // these messages from the renderer. The renderer should send the ack once the
+  // emulation params were applied. That way, we can avoid having to handle
+  // Set/ClearDeviceMetricsOverride in the renderer. With the old IPC system,
+  // this is tricky since we'd have to track the DevTools message id with the
+  // WidgetMsg and acknowledgment, as well as plump the acknowledgment back to
+  // the EmulationHandler somehow. Mojo callbacks should make this much simpler.
+  if (device_emulation_enabled_) {
+    host_->GetRenderWidgetHost()->Send(new WidgetMsg_EnableDeviceEmulation(
+        host_->GetRenderWidgetHost()->GetRoutingID(),
+        device_emulation_params_));
+  } else {
+    host_->GetRenderWidgetHost()->Send(new WidgetMsg_DisableDeviceEmulation(
+        host_->GetRenderWidgetHost()->GetRoutingID()));
+  }
 }
 
 void EmulationHandler::ApplyOverrides(net::HttpRequestHeaders* headers) {
diff --git a/content/browser/devtools/protocol/emulation_handler.h b/content/browser/devtools/protocol/emulation_handler.h
index 828bc9b..5890b67 100644
--- a/content/browser/devtools/protocol/emulation_handler.h
+++ b/content/browser/devtools/protocol/emulation_handler.h
@@ -8,7 +8,7 @@
 #include "base/macros.h"
 #include "content/browser/devtools/protocol/devtools_domain_handler.h"
 #include "content/browser/devtools/protocol/emulation.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 
 namespace net {
 class HttpRequestHeaders;
@@ -68,12 +68,10 @@
 
   Response SetVisibleSize(int width, int height) override;
 
-  const base::Optional<blink::WebDeviceEmulationParams>&
-  GetDeviceEmulationParams() const;
-  void SetDeviceEmulationParams(
-      const base::Optional<blink::WebDeviceEmulationParams>& params);
+  blink::WebDeviceEmulationParams GetDeviceEmulationParams();
+  void SetDeviceEmulationParams(const blink::WebDeviceEmulationParams& params);
 
-  bool device_emulation_enabled() const { return !!device_emulation_params_; }
+  bool device_emulation_enabled() { return device_emulation_enabled_; }
 
   void ApplyOverrides(net::HttpRequestHeaders* headers);
 
@@ -85,7 +83,8 @@
   bool touch_emulation_enabled_;
   std::string touch_emulation_configuration_;
 
-  base::Optional<blink::WebDeviceEmulationParams> device_emulation_params_;
+  bool device_emulation_enabled_;
+  blink::WebDeviceEmulationParams device_emulation_params_;
   std::string user_agent_;
   std::string accept_language_;
 
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 2a76ec9..1516029 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -678,7 +678,8 @@
         base::BindOnce(&PageHandler::ScreenshotCaptured,
                        weak_factory_.GetWeakPtr(),
                        base::Passed(std::move(callback)), screenshot_format,
-                       screenshot_quality, gfx::Size(), gfx::Size(), nullptr),
+                       screenshot_quality, gfx::Size(), gfx::Size(),
+                       blink::WebDeviceEmulationParams()),
         false);
     return;
   }
@@ -686,13 +687,9 @@
   // Welcome to the neural net of capturing screenshot while emulating device
   // metrics!
   bool emulation_enabled = emulation_handler_->device_emulation_enabled();
-  std::unique_ptr<blink::WebDeviceEmulationParams> original_params;
-  blink::WebDeviceEmulationParams modified_params;
-  if (emulation_enabled) {
-    original_params = std::make_unique<blink::WebDeviceEmulationParams>(
-        *emulation_handler_->GetDeviceEmulationParams());
-    modified_params = *original_params;
-  }
+  blink::WebDeviceEmulationParams original_params =
+      emulation_handler_->GetDeviceEmulationParams();
+  blink::WebDeviceEmulationParams modified_params = original_params;
 
   // Capture original view size if we know we are going to destroy it. We use
   // it in ScreenshotCaptured to restore.
@@ -706,17 +703,16 @@
   ScreenInfo screen_info;
   widget_host->GetScreenInfo(&screen_info);
   if (emulation_enabled) {
-    DCHECK(original_params);
     // When emulating, emulate again and scale to make resulting image match
-    // physical DP resolution. If view_size is not overridden, use actual view
+    // physical DP resolution. If view_size is not overriden, use actual view
     // size.
     float original_scale =
-        original_params->scale > 0 ? original_params->scale : 1;
-    if (!modified_params.view_size.width()) {
+        original_params.scale > 0 ? original_params.scale : 1;
+    if (!modified_params.view_size.width) {
       emulated_view_size.set_width(
           ceil(original_view_size.width() / original_scale));
     }
-    if (!modified_params.view_size.height()) {
+    if (!modified_params.view_size.height) {
       emulated_view_size.set_height(
           ceil(original_view_size.height() / original_scale));
     }
@@ -728,19 +724,22 @@
     // When clip is specified, we scale viewport via clip, otherwise we use
     // scale.
     modified_params.scale = clip.isJust() ? 1 : dpfactor;
-    modified_params.view_size = emulated_view_size;
+    modified_params.view_size.width = emulated_view_size.width();
+    modified_params.view_size.height = emulated_view_size.height();
   } else if (clip.isJust()) {
     // When not emulating, still need to emulate the page size.
-    modified_params.view_size = original_view_size;
-    modified_params.screen_size = gfx::Size();
+    modified_params.view_size.width = original_view_size.width();
+    modified_params.view_size.height = original_view_size.height();
+    modified_params.screen_size.width = 0;
+    modified_params.screen_size.height = 0;
     modified_params.device_scale_factor = 0;
     modified_params.scale = 1;
   }
 
   // Set up viewport in renderer.
   if (clip.isJust()) {
-    modified_params.viewport_offset =
-        gfx::PointF(clip.fromJust()->GetX(), clip.fromJust()->GetY());
+    modified_params.viewport_offset.SetPoint(clip.fromJust()->GetX(),
+                                             clip.fromJust()->GetY());
     modified_params.viewport_scale = clip.fromJust()->GetScale() * dpfactor;
     if (IsUseZoomForDSFEnabled()) {
       modified_params.viewport_offset.Scale(screen_info.device_scale_factor);
@@ -768,7 +767,7 @@
     } else {
       requested_image_size = emulated_view_size;
     }
-    double scale = emulation_enabled ? original_params->device_scale_factor
+    double scale = emulation_enabled ? original_params.device_scale_factor
                                      : screen_info.device_scale_factor;
     if (clip.isJust())
       scale *= clip.fromJust()->GetScale();
@@ -780,7 +779,7 @@
                      weak_factory_.GetWeakPtr(),
                      base::Passed(std::move(callback)), screenshot_format,
                      screenshot_quality, original_view_size,
-                     requested_image_size, std::move(original_params)),
+                     requested_image_size, original_params),
       true);
 }
 
@@ -1107,16 +1106,12 @@
     int quality,
     const gfx::Size& original_view_size,
     const gfx::Size& requested_image_size,
-    std::unique_ptr<blink::WebDeviceEmulationParams> original_emulation_params,
+    const blink::WebDeviceEmulationParams& original_emulation_params,
     const gfx::Image& image) {
   if (original_view_size.width()) {
     RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
     widget_host->GetView()->SetSize(original_view_size);
-    if (original_emulation_params) {
-      emulation_handler_->SetDeviceEmulationParams(*original_emulation_params);
-    } else {
-      emulation_handler_->SetDeviceEmulationParams(base::nullopt);
-    }
+    emulation_handler_->SetDeviceEmulationParams(original_emulation_params);
   }
 
   if (image.IsEmpty()) {
diff --git a/content/browser/devtools/protocol/page_handler.h b/content/browser/devtools/protocol/page_handler.h
index 107117f..b84d23a5 100644
--- a/content/browser/devtools/protocol/page_handler.h
+++ b/content/browser/devtools/protocol/page_handler.h
@@ -183,14 +183,14 @@
       std::unique_ptr<Page::ScreencastFrameMetadata> metadata,
       const protocol::Binary& data);
 
-  void ScreenshotCaptured(std::unique_ptr<CaptureScreenshotCallback> callback,
-                          const std::string& format,
-                          int quality,
-                          const gfx::Size& original_view_size,
-                          const gfx::Size& requested_image_size,
-                          std::unique_ptr<blink::WebDeviceEmulationParams>
-                              original_emulation_params,
-                          const gfx::Image& image);
+  void ScreenshotCaptured(
+      std::unique_ptr<CaptureScreenshotCallback> callback,
+      const std::string& format,
+      int quality,
+      const gfx::Size& original_view_size,
+      const gfx::Size& requested_image_size,
+      const blink::WebDeviceEmulationParams& original_params,
+      const gfx::Image& image);
 
   void GotManifest(std::unique_ptr<GetAppManifestCallback> callback,
                    const GURL& manifest_url,
diff --git a/content/browser/frame_host/back_forward_cache_impl.cc b/content/browser/frame_host/back_forward_cache_impl.cc
index 2de9a62..b7c636b1 100644
--- a/content/browser/frame_host/back_forward_cache_impl.cc
+++ b/content/browser/frame_host/back_forward_cache_impl.cc
@@ -118,8 +118,6 @@
       FeatureToBit(
           WebSchedulerTrackedFeature::kOutstandingIndexedDBTransaction) |
       FeatureToBit(
-          WebSchedulerTrackedFeature::kHasScriptableFramesInMultipleTabs) |
-      FeatureToBit(
           WebSchedulerTrackedFeature::kRequestedNotificationsPermission) |
       FeatureToBit(WebSchedulerTrackedFeature::kRequestedMIDIPermission) |
       FeatureToBit(
diff --git a/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc b/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
index ffcefd9..bc9061e96 100644
--- a/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
+++ b/content/browser/frame_host/back_forward_cache_metrics_browsertest.cc
@@ -37,10 +37,6 @@
 constexpr uint64_t kPageShowFeature = static_cast<uint64_t>(
     blink::scheduler::WebSchedulerTrackedFeature::kPageShowEventListener);
 
-constexpr uint64_t kHasScriptableFramesInMultipleTabsFeature =
-    static_cast<uint64_t>(blink::scheduler::WebSchedulerTrackedFeature::
-                              kHasScriptableFramesInMultipleTabs);
-
 constexpr uint64_t kRequestedGeolocationPermissionFeature =
     static_cast<uint64_t>(blink::scheduler::WebSchedulerTrackedFeature::
                               kRequestedGeolocationPermission);
@@ -648,118 +644,6 @@
                 blink::scheduler::WebSchedulerTrackedFeature::kSharedWorker));
 }
 
-IN_PROC_BROWSER_TEST_F(BackForwardCacheMetricsBrowserTest,
-                       WindowOpen_SameOrigin) {
-  ukm::TestAutoSetUkmRecorder recorder;
-
-  const GURL url1(embedded_test_server()->GetURL("/title1.html"));
-  const GURL url2(embedded_test_server()->GetURL("/title2.html"));
-
-  EXPECT_TRUE(NavigateToURL(shell(), url1));
-
-  ShellAddedObserver observer;
-  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1, '_blank');", url2)));
-  Shell* shell2 = observer.GetShell();
-  EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
-
-  EXPECT_TRUE(NavigateToURL(shell(), url2));
-
-  {
-    // We emit metrics when we navigate back to the previously visited page,
-    // so navigate back to trigger the metrics.
-    TestNavigationObserver navigation_observer(shell()->web_contents());
-    shell()->GoBackOrForward(-1);
-    navigation_observer.WaitForNavigationFinished();
-  }
-
-  ASSERT_EQ(navigation_ids_.size(), static_cast<size_t>(3));
-  // ukm::SourceId id1 = ToSourceId(navigation_ids_[0]);
-  // ukm::SourceId id2 = ToSourceId(navigation_ids_[1]);
-  ukm::SourceId id3 = ToSourceId(navigation_ids_[2]);
-
-  EXPECT_THAT(GetFeatureUsageMetrics(&recorder),
-              testing::ElementsAre(FeatureUsage{
-                  id3, 1 << kHasScriptableFramesInMultipleTabsFeature, 0, 0}));
-}
-
-IN_PROC_BROWSER_TEST_F(BackForwardCacheMetricsBrowserTest,
-                       WindowOpen_CrossOrigin) {
-  ukm::TestAutoSetUkmRecorder recorder;
-
-  const GURL url1(embedded_test_server()->GetURL("/title1.html"));
-  const GURL url2(embedded_test_server()->GetURL("/title2.html"));
-  const GURL url3(
-      embedded_test_server()->GetURL("/cross-site/bar.com/title3.html"));
-
-  EXPECT_TRUE(NavigateToURL(shell(), url1));
-
-  ShellAddedObserver observer;
-  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1, '_blank');", url3)));
-
-  Shell* shell2 = observer.GetShell();
-  EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
-
-  EXPECT_TRUE(NavigateToURL(shell(), url2));
-
-  {
-    // We emit metrics when we navigate back to the previously visited page,
-    // so navigate back to trigger the metrics.
-    TestNavigationObserver navigation_observer(shell()->web_contents());
-    shell()->GoBackOrForward(-1);
-    navigation_observer.WaitForNavigationFinished();
-  }
-
-  ASSERT_EQ(navigation_ids_.size(), static_cast<size_t>(3));
-  // ukm::SourceId id1 = ToSourceId(navigation_ids_[0]);
-  // ukm::SourceId id2 = ToSourceId(navigation_ids_[1]);
-  ukm::SourceId id3 = ToSourceId(navigation_ids_[2]);
-
-  // TODO(altimin): For now we don't distinguish between same-origin and
-  // cross-origin window.opens. We might want to revisit this in the future.
-  EXPECT_THAT(GetFeatureUsageMetrics(&recorder),
-              testing::ElementsAre(FeatureUsage{
-                  id3, 1 << kHasScriptableFramesInMultipleTabsFeature, 0, 0}));
-}
-
-IN_PROC_BROWSER_TEST_F(BackForwardCacheMetricsBrowserTest,
-                       WindowOpen_SameOrigin_Openee) {
-  ukm::TestAutoSetUkmRecorder recorder;
-
-  const GURL url1(embedded_test_server()->GetURL("/title1.html"));
-  const GURL url2(embedded_test_server()->GetURL("/title2.html"));
-  const GURL url3(embedded_test_server()->GetURL("/title3.html"));
-
-  EXPECT_TRUE(NavigateToURL(shell(), url1));
-
-  ShellAddedObserver observer;
-  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.open($1, '_blank');", url2)));
-  Shell* shell2 = observer.GetShell();
-  EXPECT_TRUE(WaitForLoadStop(shell2->web_contents()));
-
-  Observe(shell2->web_contents());
-
-  // Navigate the second shell and ensure that the openee doesn't get cached
-  // too.
-  EXPECT_TRUE(NavigateToURL(shell2, url3));
-
-  {
-    // We emit metrics when we navigate back to the previously visited page,
-    // so navigate back to trigger the metrics.
-    TestNavigationObserver navigation_observer(shell2->web_contents());
-    shell2->GoBackOrForward(-1);
-    navigation_observer.WaitForNavigationFinished();
-  }
-
-  ASSERT_EQ(navigation_ids_.size(), static_cast<size_t>(3));
-  // ukm::SourceId id1 = ToSourceId(navigation_ids_[0]);
-  // ukm::SourceId id2 = ToSourceId(navigation_ids_[1]);
-  ukm::SourceId id3 = ToSourceId(navigation_ids_[2]);
-
-  EXPECT_THAT(GetFeatureUsageMetrics(&recorder),
-              testing::ElementsAre(FeatureUsage{
-                  id3, 1 << kHasScriptableFramesInMultipleTabsFeature, 0, 0}));
-}
-
 IN_PROC_BROWSER_TEST_F(BackForwardCacheMetricsBrowserTest, Geolocation) {
   const GURL url1(embedded_test_server()->GetURL("/title1.html"));
   EXPECT_TRUE(NavigateToURL(shell(), url1));
diff --git a/content/browser/frame_host/render_document_host_browsertest.cc b/content/browser/frame_host/render_document_host_browsertest.cc
index 26c73d8a..06c1885 100644
--- a/content/browser/frame_host/render_document_host_browsertest.cc
+++ b/content/browser/frame_host/render_document_host_browsertest.cc
@@ -6,12 +6,14 @@
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/frame_host/frame_tree_node.h"
 #include "content/browser/web_contents/web_contents_impl.h"
+#include "content/common/content_navigation_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
+#include "content/test/render_document_feature.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
@@ -23,7 +25,11 @@
 class RenderDocumentHostBrowserTest : public ContentBrowserTest {
  protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    feature_list_.InitWithFeatures({features::kRenderDocument}, {});
+    // RenderDocumentHost only works when RenderDocument is enabled at at least
+    // the sub-frame level.
+    InitAndEnableRenderDocumentFeature(
+        &feature_list_,
+        GetRenderDocumentLevelName(RenderDocumentLevel::kSubframe));
   }
 
   void SetUpOnMainThread() override {
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 33e4d999..b17e8fa 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -206,7 +206,6 @@
 #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
 #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
 #include "third_party/blink/public/mojom/frame/media_player_action.mojom.h"
@@ -557,8 +556,8 @@
 
 }  // namespace
 
-bool IsRenderDocumentEnabledForCrashedFrame() {
-  return base::FeatureList::IsEnabled(features::kRenderDocumentForCrashedFrame);
+bool CreateNewHostForCrashedFrame() {
+  return GetRenderDocumentLevel() >= RenderDocumentLevel::kCrashedFrame;
 }
 
 class RenderFrameHostImpl::DroppedInterfaceRequestLogger
@@ -1697,7 +1696,7 @@
   SetLastCommittedUrl(GURL());
   web_bundle_handle_.reset();
 
-  must_be_replaced_ = IsRenderDocumentEnabledForCrashedFrame();
+  must_be_replaced_ = CreateNewHostForCrashedFrame();
   has_committed_any_navigation_ = false;
 
 #if defined(OS_ANDROID)
@@ -2680,7 +2679,7 @@
   } else {
     // RenderDocument: After a local<->local swap, this function is called with
     // a null |proxy|.
-    CHECK(IsRenderDocumentEnabled());
+    CHECK(CreateNewHostForSameSiteSubframe());
 
     // The unload handlers already ran for this document during the
     // local<->local swap. Hence, there is no need to send
@@ -3122,12 +3121,6 @@
   render_view_host_->OnDidContentsPreferredSizeChange(pref_size);
 }
 
-void RenderFrameHostImpl::SetDeviceEmulation(
-    const base::Optional<blink::WebDeviceEmulationParams>& params) {
-  DCHECK(frame_tree_node()->IsMainFrame());
-  device_emulator_remote_->SetDeviceEmulation(params);
-}
-
 void RenderFrameHostImpl::UpdateFaviconURL(
     std::vector<blink::mojom::FaviconURLPtr> favicon_urls) {
   delegate_->UpdateFaviconURL(this, std::move(favicon_urls));
@@ -6108,16 +6101,8 @@
 void RenderFrameHostImpl::BindDevToolsAgent(
     mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host,
     mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> receiver) {
-  mojo::PendingAssociatedReceiver<mojom::DeviceEmulator>
-      pending_emulator_receiver;
-  if (frame_tree_node()->IsMainFrame()) {
-    device_emulator_remote_.reset();
-    pending_emulator_receiver =
-        device_emulator_remote_.BindNewEndpointAndPassReceiver();
-  }
-  GetNavigationControl()->BindDevToolsAgent(
-      std::move(host), std::move(receiver),
-      std::move(pending_emulator_receiver));
+  GetNavigationControl()->BindDevToolsAgent(std::move(host),
+                                            std::move(receiver));
 }
 
 bool RenderFrameHostImpl::IsSameSiteInstance(
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 781a00f..c542d61 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -213,7 +213,7 @@
 
 // True if feature-flags indicate that we should replace crashed RFHs with new
 // instances rather than reusing them. See http://crbug.com/981339.
-CONTENT_EXPORT bool IsRenderDocumentEnabledForCrashedFrame();
+CONTENT_EXPORT bool CreateNewHostForCrashedFrame();
 
 // To be called when a RenderFrameHostImpl receives an event.
 // Provides the host, the event fired, and which node id the event was for.
@@ -1440,9 +1440,6 @@
   // - Ignore any OnUnloadACK sent by the renderer process.
   void DoNotDeleteForTesting();
 
-  void SetDeviceEmulation(
-      const base::Optional<blink::WebDeviceEmulationParams>& params);
-
  protected:
   friend class RenderFrameHostFactory;
 
@@ -2765,8 +2762,6 @@
   // stuck in pending deletion.
   bool do_not_delete_for_testing_ = false;
 
-  mojo::AssociatedRemote<mojom::DeviceEmulator> device_emulator_remote_;
-
   // NOTE: This must be the last member.
   base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_{this};
 
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 98be35f..50533c33 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -829,7 +829,7 @@
 
   // Force using a different RenderFrameHost when RenderDocument is enabled.
   // TODO(arthursonzogni, fergal): Add support for the main frame.
-  if (IsRenderDocumentEnabled() && !frame_tree_node_->IsMainFrame() &&
+  if (CreateNewHostForSameSiteSubframe() && !frame_tree_node_->IsMainFrame() &&
       !request->IsSameDocument() &&
       render_frame_host_->has_committed_any_navigation()) {
     use_current_rfh = false;
@@ -2121,16 +2121,18 @@
   CHECK(new_instance);
   // This DCHECK is going to be fully removed as part of RenderDocument [1].
   //
-  // With RenderDocument: every cross-document navigation creates a new
-  // RenderFrameHost. The navigation is potentially same-SiteInstance.
+  // With RenderDocument for sub frames or main frames: cross-document
+  // navigation creates a new RenderFrameHost. The navigation is potentially
+  // same-SiteInstance.
   //
-  // With RenderDocumentForCrashedFrame: navigations from a crashed
+  // With RenderDocument for crashed frames: navigations from a crashed
   // RenderFrameHost creates a new RenderFrameHost. The navigation is
   // potentially same-SiteInstance.
   //
   // [1] http://crbug.com/936696
   DCHECK(old_instance != new_instance ||
-         render_frame_host_->must_be_replaced() || IsRenderDocumentEnabled());
+         render_frame_host_->must_be_replaced() ||
+         CreateNewHostForSameSiteSubframe());
 
   // The process for the new SiteInstance may (if we're sharing a process with
   // another host that already initialized it) or may not (we have our own
@@ -2161,16 +2163,18 @@
   CHECK(instance);
   // This DCHECK is going to be fully removed as part of RenderDocument [1].
   //
-  // With RenderDocument: every cross-document navigation creates a new
-  // RenderFrameHost. The navigation is potentially same-SiteInstance.
+  // With RenderDocument for sub frames or main frames: cross-document
+  // navigation creates a new RenderFrameHost. The navigation is potentially
+  // same-SiteInstance.
   //
-  // With RenderDocumentForCrashedFrame: navigations from a crashed
+  // With RenderDocument for crashed frames: navigations from a crashed
   // RenderFrameHost creates a new RenderFrameHost. The navigation is
   // potentially same-SiteInstance.
   //
   // [1] http://crbug.com/936696
   DCHECK(render_frame_host_->GetSiteInstance() != instance ||
-         render_frame_host_->must_be_replaced() || IsRenderDocumentEnabled());
+         render_frame_host_->must_be_replaced() ||
+         CreateNewHostForSameSiteSubframe());
 
   std::unique_ptr<RenderFrameHostImpl> new_render_frame_host =
       CreateRenderFrameHost(CreateFrameCase::kCreateSpeculative, instance,
@@ -2505,7 +2509,7 @@
     CHECK_NE(previous_routing_id, MSG_ROUTING_NONE);
     if (!existing_proxy->is_render_frame_proxy_live())
       existing_proxy->InitRenderFrameProxy();
-  } else if (IsRenderDocumentEnabled()) {
+  } else if (CreateNewHostForSameSiteSubframe()) {
     previous_routing_id = current_frame_host()->GetRoutingID();
   }
 
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 9297b0c..73e70f2 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -69,6 +69,7 @@
 #include "content/shell/browser/shell.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/did_commit_navigation_interceptor.h"
+#include "content/test/render_document_feature.h"
 #include "content/test/test_content_browser_client.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/controllable_http_response.h"
@@ -211,15 +212,13 @@
 
 }  // anonymous namespace
 
-class RenderFrameHostManagerTest : public ContentBrowserTest,
-                                   public ::testing::WithParamInterface<bool> {
+class RenderFrameHostManagerTest
+    : public ContentBrowserTest,
+      public ::testing::WithParamInterface<std::string> {
  public:
   RenderFrameHostManagerTest() : foo_com_("foo.com") {
     replace_host_.SetHostStr(foo_com_);
-    if (GetParam()) {
-      feature_list_.InitAndEnableFeature(
-          features::kRenderDocumentForCrashedFrame);
-    }
+    InitAndEnableRenderDocumentFeature(&feature_list_, GetParam());
   }
 
   void SetUpOnMainThread() override {
@@ -6906,8 +6905,8 @@
   ASSERT_EQ(0u, b3->child_count());
 
   EXPECT_FALSE(a1->must_be_replaced());
-  EXPECT_EQ(b2->must_be_replaced(), IsRenderDocumentEnabledForCrashedFrame());
-  EXPECT_EQ(b3->must_be_replaced(), IsRenderDocumentEnabledForCrashedFrame());
+  EXPECT_EQ(b2->must_be_replaced(), CreateNewHostForCrashedFrame());
+  EXPECT_EQ(b3->must_be_replaced(), CreateNewHostForCrashedFrame());
   EXPECT_FALSE(c5->must_be_replaced());
 
   EXPECT_EQ(2u, proxy_count(a1));
@@ -6928,7 +6927,7 @@
   // 3. Reload B2, B6 is created.
   NavigateFrameToURL(b2->frame_tree_node(), b2_url);
 
-  if (IsRenderDocumentEnabledForCrashedFrame()) {
+  if (CreateNewHostForCrashedFrame()) {
     // B2 has been replaced
     EXPECT_NE(b2_routing_id,
               a1->child_at(0)->current_frame_host()->routing_id());
@@ -6940,7 +6939,7 @@
   // B3 hasn't been replaced.
   EXPECT_EQ(b3, a1->child_at(1)->current_frame_host());
   RenderFrameHostImpl* b6 = a1->child_at(0)->current_frame_host();
-  EXPECT_EQ(b3->must_be_replaced(), IsRenderDocumentEnabledForCrashedFrame());
+  EXPECT_EQ(b3->must_be_replaced(), CreateNewHostForCrashedFrame());
   EXPECT_FALSE(b6->must_be_replaced());
 
   EXPECT_EQ(a_site_instance, a1->GetSiteInstance());
@@ -6963,24 +6962,27 @@
   EXPECT_TRUE(is_proxy_live(c5, b_site_instance));
 }
 
-// These tests are parametrized to toggle kRenderDocumentForCrashedFrame on/off.
-INSTANTIATE_TEST_SUITE_P(All, RenderFrameHostManagerTest, ::testing::Bool());
+INSTANTIATE_TEST_SUITE_P(All,
+                         RenderFrameHostManagerTest,
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(
     All,
     ProactivelySwapBrowsingInstancesCrossSiteSwapProcessTest,
-    ::testing::Bool());
+    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(
     All,
     ProactivelySwapBrowsingInstancesCrossSiteReuseProcessTest,
-    ::testing::Bool());
+    testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
                          RenderFrameHostManagerUnloadBrowserTest,
-                         ::testing::Bool());
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
                          RenderFrameHostManagerSpoofingTest,
-                         ::testing::Bool());
-INSTANTIATE_TEST_SUITE_P(All, RFHMProcessPerTabTest, ::testing::Bool());
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
+INSTANTIATE_TEST_SUITE_P(All,
+                         RFHMProcessPerTabTest,
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
                          RenderFrameHostManagerDefaultProcessTest,
-                         ::testing::Bool());
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_manager_unittest.cc b/content/browser/frame_host/render_frame_host_manager_unittest.cc
index b57e96b..85113ef 100644
--- a/content/browser/frame_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_unittest.cc
@@ -22,7 +22,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/test/with_feature_override.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "content/browser/child_process_security_policy_impl.h"
@@ -33,6 +32,7 @@
 #include "content/browser/frame_host/render_frame_proxy_host.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/webui/web_ui_controller_factory_registry.h"
+#include "content/common/content_navigation_policy.h"
 #include "content/common/frame_messages.h"
 #include "content/common/input_messages.h"
 #include "content/common/view_messages.h"
@@ -56,6 +56,7 @@
 #include "content/public/test/test_utils.h"
 #include "content/test/mock_widget_input_handler.h"
 #include "content/test/navigation_simulator_impl.h"
+#include "content/test/render_document_feature.h"
 #include "content/test/test_content_browser_client.h"
 #include "content/test/test_content_client.h"
 #include "content/test/test_render_frame_host.h"
@@ -266,11 +267,48 @@
 
 }  // namespace
 
-class RenderFrameHostManagerTest : public base::test::WithFeatureOverride,
-                                   public RenderViewHostImplTestHarness {
+// Test that the "level" feature param has the expected effect.
+class RenderDocumentFeatureTest : public testing::Test {
+ protected:
+  void SetLevel(const RenderDocumentLevel level) {
+    InitAndEnableRenderDocumentFeature(&feature_list_,
+                                       GetRenderDocumentLevelName(level));
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(RenderDocumentFeatureTest, FeatureDisabled) {
+  EXPECT_FALSE(CreateNewHostForCrashedFrame());
+  EXPECT_FALSE(CreateNewHostForSameSiteSubframe());
+}
+
+TEST_F(RenderDocumentFeatureTest, LevelDisabled) {
+  SetLevel(RenderDocumentLevel::kDisabled);
+  EXPECT_FALSE(CreateNewHostForCrashedFrame());
+  EXPECT_FALSE(CreateNewHostForSameSiteSubframe());
+}
+
+TEST_F(RenderDocumentFeatureTest, LevelCrashed) {
+  SetLevel(RenderDocumentLevel::kCrashedFrame);
+  EXPECT_TRUE(CreateNewHostForCrashedFrame());
+  EXPECT_FALSE(CreateNewHostForSameSiteSubframe());
+}
+
+TEST_F(RenderDocumentFeatureTest, LevelSub) {
+  SetLevel(RenderDocumentLevel::kSubframe);
+  EXPECT_TRUE(CreateNewHostForCrashedFrame());
+  EXPECT_TRUE(CreateNewHostForSameSiteSubframe());
+}
+
+class RenderFrameHostManagerTest
+    : public RenderViewHostImplTestHarness,
+      public ::testing::WithParamInterface<std::string> {
  public:
-  RenderFrameHostManagerTest()
-      : WithFeatureOverride(features::kRenderDocumentForCrashedFrame) {}
+  RenderFrameHostManagerTest() {
+    InitAndEnableRenderDocumentFeature(&feature_list_, GetParam());
+  }
 
   void SetUp() override {
     RenderViewHostImplTestHarness::SetUp();
@@ -1945,7 +1983,7 @@
   EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
   EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());
   EXPECT_EQ((bool)contents1->GetMainFrame()->GetView(),
-            IsRenderDocumentEnabledForCrashedFrame());
+            CreateNewHostForCrashedFrame());
   EXPECT_FALSE(contents2->GetMainFrame()->GetView());
 
   // |contents1| creates an out of process iframe.
@@ -3575,12 +3613,14 @@
   ExpectAdSubframeSignalForFrameProxy(proxy_to_main_frame, true);
 }
 
-INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(RenderFrameHostManagerTest);
+INSTANTIATE_TEST_SUITE_P(All,
+                         RenderFrameHostManagerTest,
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
                          RenderFrameHostManagerTestWithSiteIsolation,
-                         ::testing::Bool());
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 INSTANTIATE_TEST_SUITE_P(All,
                          RenderFrameHostManagerAdTaggingSignalTest,
-                         ::testing::Bool());
+                         testing::ValuesIn(RenderDocumentFeatureLevelValues()));
 
 }  // namespace content
diff --git a/content/browser/renderer_host/input/scroll_latency_browsertest.cc b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
index 525c63fc..e7cb819f 100644
--- a/content/browser/renderer_host/input/scroll_latency_browsertest.cc
+++ b/content/browser/renderer_host/input/scroll_latency_browsertest.cc
@@ -44,7 +44,7 @@
     "<title>Scroll latency histograms browsertests.</title>"
     "<style>"
     "body {"
-    "  height:3000px;"
+    "  height:9000px;"
     "}"
     "</style>"
     "</head>"
@@ -265,6 +265,51 @@
       0, "Event.Latency.ScrollBegin.Touch.TimeToScrollUpdateSwapBegin4"));
 }
 
+using ScrollThroughputBrowserTest = ScrollLatencyBrowserTest;
+
+// The test does a fling during the test, and it times out in slower builds
+// (e.g. when sanitizers are turned on).
+#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+#define MAYBE_ScrollThroughputMetrics DISABLED_ScrollThroughputMetrics
+#else
+#define MAYBE_ScrollThroughputMetrics ScrollThroughputMetrics
+#endif
+IN_PROC_BROWSER_TEST_F(ScrollThroughputBrowserTest,
+                       MAYBE_ScrollThroughputMetrics) {
+  LoadURL();
+  auto scroll_update_watcher = std::make_unique<InputMsgWatcher>(
+      GetWidgetHost(), blink::WebInputEvent::kGestureScrollEnd);
+
+  SyntheticSmoothScrollGestureParams params;
+  params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
+  params.anchor = gfx::PointF(10, 10);
+  params.distances.push_back(gfx::Vector2d(0, -6000));
+  params.fling_velocity_x = 0;
+  params.fling_velocity_y = -2000;
+  params.prevent_fling = false;
+
+  run_loop_ = std::make_unique<base::RunLoop>();
+
+  auto gesture = std::make_unique<SyntheticSmoothScrollGesture>(params);
+  GetWidgetHost()->QueueSyntheticGesture(
+      std::move(gesture),
+      base::BindOnce(&ScrollLatencyBrowserTest::OnSyntheticGestureCompleted,
+                     base::Unretained(this)));
+  run_loop_->Run();
+
+  while (!GetSampleCountForHistogram(
+      "Graphics.Smoothness.PercentDroppedFrames.CompositorThread."
+      "TouchScroll")) {
+    GiveItSomeTime();
+    FetchHistogramsFromChildProcesses();
+  }
+  EXPECT_TRUE(VerifyRecordedSamplesForHistogram(
+      1,
+      "Graphics.Smoothness.PercentDroppedFrames.ScrollingThread.TouchScroll"));
+  EXPECT_TRUE(VerifyRecordedSamplesForHistogram(
+      1, "Event.Latency.ScrollBegin.Touch.BrowserNotifiedToBeforeGpuSwap2"));
+}
+
 class ScrollLatencyScrollbarBrowserTest : public ScrollLatencyBrowserTest {
  public:
   ScrollLatencyScrollbarBrowserTest() = default;
diff --git a/content/browser/site_per_process_unload_browsertest.cc b/content/browser/site_per_process_unload_browsertest.cc
index c0c35df..409b23e 100644
--- a/content/browser/site_per_process_unload_browsertest.cc
+++ b/content/browser/site_per_process_unload_browsertest.cc
@@ -1330,7 +1330,7 @@
       WebContents::FromRenderFrameHost(web_contents()->GetMainFrame()));
 
   // All the documents must be properly deleted:
-  if (IsRenderDocumentEnabled())
+  if (CreateNewHostForSameSiteSubframe())
     delete_B2.WaitUntilDeleted();
   delete_B3.WaitUntilDeleted();
   delete_C4.WaitUntilDeleted();
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index a440a8f..f22f6c8 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -443,7 +443,6 @@
     "ax_content_tree_data.mojom",
     "ax_content_tree_update.mojom",
     "child_process.mojom",
-    "device_emulator.mojom",
     "document_scoped_interface_bundle.mojom",
     "download/mhtml_file_writer.mojom",
     "field_trial_recorder.mojom",
diff --git a/content/common/common_param_traits_macros.h b/content/common/common_param_traits_macros.h
index f647c66..0701e83 100644
--- a/content/common/common_param_traits_macros.h
+++ b/content/common/common_param_traits_macros.h
@@ -12,13 +12,22 @@
 #include "content/common/frame_messages.h"
 #include "content/common/visual_properties.h"
 #include "ipc/ipc_message_macros.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 
 #undef IPC_MESSAGE_EXPORT
 #define IPC_MESSAGE_EXPORT CONTENT_EXPORT
 
+// Traits for VisualProperties.
+IPC_ENUM_TRAITS_MAX_VALUE(blink::WebDeviceEmulationParams::ScreenPosition,
+                          blink::WebDeviceEmulationParams::kScreenPositionLast)
+
 IPC_ENUM_TRAITS_MAX_VALUE(content::ScreenOrientationValues,
                           content::SCREEN_ORIENTATION_VALUES_LAST)
 
+IPC_ENUM_TRAITS_MIN_MAX_VALUE(blink::WebScreenOrientationType,
+                              blink::kWebScreenOrientationUndefined,
+                              blink::WebScreenOrientationTypeLast)
+
 IPC_ENUM_TRAITS_MAX_VALUE(blink::mojom::DisplayMode,
                           blink::mojom::DisplayMode::kMaxValue)
 
diff --git a/content/common/content_navigation_policy.cc b/content/common/content_navigation_policy.cc
index f89c0d2..da00bec 100644
--- a/content/common/content_navigation_policy.cc
+++ b/content/common/content_navigation_policy.cc
@@ -70,9 +70,34 @@
   return GetProactivelySwapBrowsingInstanceLevel() >=
          ProactivelySwapBrowsingInstanceLevel::kCrossSiteReuseProcess;
 }
+const char kRenderDocumentLevelParameterName[] = "level";
 
-bool IsRenderDocumentEnabled() {
-  return base::FeatureList::IsEnabled(features::kRenderDocument);
+constexpr base::FeatureParam<RenderDocumentLevel>::Option
+    render_document_levels[] = {
+        {RenderDocumentLevel::kDisabled, "disabled"},
+        {RenderDocumentLevel::kCrashedFrame, "crashed-frame"},
+        {RenderDocumentLevel::kSubframe, "subframe"}};
+const base::FeatureParam<RenderDocumentLevel> render_document_level{
+    &features::kRenderDocument, kRenderDocumentLevelParameterName,
+    RenderDocumentLevel::kDisabled, &render_document_levels};
+
+RenderDocumentLevel GetRenderDocumentLevel() {
+  if (base::FeatureList::IsEnabled(features::kRenderDocument))
+    return render_document_level.Get();
+  return RenderDocumentLevel::kDisabled;
+}
+
+std::string GetRenderDocumentLevelName(RenderDocumentLevel level) {
+  for (size_t i = 0; i < render_document_level.option_count; ++i) {
+    if (level == render_document_level.options[i].value)
+      return render_document_level.options[i].name;
+  }
+  NOTREACHED();
+  return "";
+}
+
+bool CreateNewHostForSameSiteSubframe() {
+  return GetRenderDocumentLevel() >= RenderDocumentLevel::kSubframe;
 }
 
 }  // namespace content
diff --git a/content/common/content_navigation_policy.h b/content/common/content_navigation_policy.h
index 7107208b..3d9abb7 100644
--- a/content/common/content_navigation_policy.h
+++ b/content/common/content_navigation_policy.h
@@ -7,6 +7,8 @@
 
 #include "content/common/content_export.h"
 
+#include <string>
+
 namespace content {
 
 CONTENT_EXPORT bool IsBackForwardCacheEnabled();
@@ -26,11 +28,25 @@
   // navigations with process reuse.
 };
 CONTENT_EXPORT bool IsProactivelySwapBrowsingInstanceEnabled();
+
 CONTENT_EXPORT bool IsProactivelySwapBrowsingInstanceWithProcessReuseEnabled();
 CONTENT_EXPORT extern const char
     kProactivelySwapBrowsingInstanceLevelParameterName[];
 
-CONTENT_EXPORT bool IsRenderDocumentEnabled();
+// Levels of RenderDocument support. These are additive in that features enabled
+// at lower levels remain enabled at all higher levels.
+enum class RenderDocumentLevel {
+  kDisabled = 0,
+  // Do not reused RenderFrameHosts when recovering from crashes.
+  kCrashedFrame = 1,
+  // Also do not reuse RenderFrameHosts when navigating subframes.
+  kSubframe = 2,
+};
+CONTENT_EXPORT bool CreateNewHostForSameSiteSubframe();
+CONTENT_EXPORT RenderDocumentLevel GetRenderDocumentLevel();
+CONTENT_EXPORT std::string GetRenderDocumentLevelName(
+    RenderDocumentLevel level);
+CONTENT_EXPORT extern const char kRenderDocumentLevelParameterName[];
 
 }  // namespace content
 
diff --git a/content/common/device_emulator.mojom b/content/common/device_emulator.mojom
deleted file mode 100644
index cbbfb72..0000000
--- a/content/common/device_emulator.mojom
+++ /dev/null
@@ -1,17 +0,0 @@
-// 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.
-
-module content.mojom;
-
-import "third_party/blink/public/mojom/devtools/device_emulation_params.mojom";
-
-// Interface exposed by the renderer to allow configuration of device emulator.
-// Used internally by DevTools, but needs to be a separate interface now, as it
-// needs to be implemented in content and be associated to
-// FrameNavigationControl.
-interface DeviceEmulator {
-  // Conigure device emulation parameter. When |params| is null, the emulation
-  // is disabled and all parameters are reverted to original values.
-  SetDeviceEmulation(blink.mojom.DeviceEmulationParams? params);
-};
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index c4ff6c3..edf716bc 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -5,7 +5,6 @@
 module content.mojom;
 
 import "content/common/ax_content_tree_update.mojom";
-import "content/common/device_emulator.mojom";
 import "content/common/document_scoped_interface_bundle.mojom";
 import "content/common/frame_messages.mojom";
 import "content/common/native_types.mojom";
@@ -173,13 +172,9 @@
   // Returned DevToolsAgent must be associated with navigation control,
   // due to various ordering dependencies between DevTools protocol and
   // navigation.
-  // The same applies to DeviceEmulator interface, which is currently
-  // a separate interface for convenience of implementation and is only
-  // passed for the root frame.
   BindDevToolsAgent(
       pending_associated_remote<blink.mojom.DevToolsAgentHost> agent_host,
-      pending_associated_receiver<blink.mojom.DevToolsAgent> agent,
-      pending_associated_receiver<DeviceEmulator>? emulator);
+      pending_associated_receiver<blink.mojom.DevToolsAgent> agent);
 
   // Request for the renderer to execute JavaScript in the frame's context.
   //
diff --git a/content/common/widget_messages.h b/content/common/widget_messages.h
index 3a3137d6d..dfc2894 100644
--- a/content/common/widget_messages.h
+++ b/content/common/widget_messages.h
@@ -46,6 +46,19 @@
   IPC_STRUCT_TRAITS_MEMBER(height)
 IPC_STRUCT_TRAITS_END()
 
+IPC_STRUCT_TRAITS_BEGIN(blink::WebDeviceEmulationParams)
+  IPC_STRUCT_TRAITS_MEMBER(screen_position)
+  IPC_STRUCT_TRAITS_MEMBER(screen_size)
+  IPC_STRUCT_TRAITS_MEMBER(view_position)
+  IPC_STRUCT_TRAITS_MEMBER(device_scale_factor)
+  IPC_STRUCT_TRAITS_MEMBER(view_size)
+  IPC_STRUCT_TRAITS_MEMBER(scale)
+  IPC_STRUCT_TRAITS_MEMBER(viewport_offset)
+  IPC_STRUCT_TRAITS_MEMBER(viewport_scale)
+  IPC_STRUCT_TRAITS_MEMBER(screen_orientation_angle)
+  IPC_STRUCT_TRAITS_MEMBER(screen_orientation_type)
+IPC_STRUCT_TRAITS_END()
+
 IPC_ENUM_TRAITS_MAX_VALUE(base::i18n::TextDirection,
                           base::i18n::TEXT_DIRECTION_MAX)
 
@@ -93,6 +106,13 @@
 // Expects a Close_ACK message when finished.
 IPC_MESSAGE_ROUTED0(WidgetMsg_Close)
 
+// Enables device emulation. See WebDeviceEmulationParams for description.
+IPC_MESSAGE_ROUTED1(WidgetMsg_EnableDeviceEmulation,
+                    blink::WebDeviceEmulationParams /* params */)
+
+// Disables device emulation, enabled previously by EnableDeviceEmulation.
+IPC_MESSAGE_ROUTED0(WidgetMsg_DisableDeviceEmulation)
+
 // Sent to inform the widget that it was hidden.  This allows it to reduce its
 // resource utilization.
 IPC_MESSAGE_ROUTED0(WidgetMsg_WasHidden)
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 5022acc..e16445a 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -478,10 +478,6 @@
 // Design doc: https://bit.ly/renderdocument
 // Main bug tracker: https://crbug.com/936696
 
-// Enable using the RenderDocument when recovering from crashes.
-const base::Feature kRenderDocumentForCrashedFrame{
-    "RenderDocumentForCrashedFrame", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enable using the RenderDocument.
 const base::Feature kRenderDocument{"RenderDocument",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index fb8cf5d..1f03f8b 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -104,7 +104,6 @@
 CONTENT_EXPORT extern const base::Feature
     kRelaxIsolatedWorldCorsInFileUrlLoaderFactory;
 CONTENT_EXPORT extern const base::Feature kReloadHiddenTabsWithCrashedSubframes;
-CONTENT_EXPORT extern const base::Feature kRenderDocumentForCrashedFrame;
 CONTENT_EXPORT extern const base::Feature kRenderDocument;
 CONTENT_EXPORT extern const base::Feature kRequestUnbufferedDispatch;
 CONTENT_EXPORT extern const base::Feature kResamplingInputEvents;
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index e512760..88c9b0d 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -736,7 +736,18 @@
     LOG(ERROR) << "WebContents was destroyed during waiting for load stop.";
     return false;
   }
-  return IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL);
+  bool is_page_normal =
+      IsLastCommittedEntryOfPageType(web_contents, PAGE_TYPE_NORMAL);
+  if (!is_page_normal) {
+    NavigationEntry* last_entry =
+        web_contents->GetController().GetLastCommittedEntry();
+    if (last_entry)
+      LOG(ERROR) << "Http status code = " << last_entry->GetHttpStatusCode()
+                 << ", page type = " << last_entry->GetPageType();
+    else
+      LOG(ERROR) << "No committed entry.";
+  }
+  return is_page_normal;
 }
 
 void PrepContentsForBeforeUnloadTest(WebContents* web_contents,
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 4928dc7..e964089 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3677,12 +3677,8 @@
 
 void RenderFrameImpl::BindDevToolsAgent(
     mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host,
-    mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
-    mojo::PendingAssociatedReceiver<mojom::DeviceEmulator> device_emulator) {
-  frame_->BindDevToolsAgent(host.PassHandle(), agent.PassHandle());
-  DCHECK_EQ(!!device_emulator, IsMainFrame());
-  if (device_emulator)
-    render_widget_->BindDeviceEmulator(std::move(device_emulator));
+    mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> receiver) {
+  frame_->BindDevToolsAgent(host.PassHandle(), receiver.PassHandle());
 }
 
 // blink::WebLocalFrameClient implementation
@@ -4116,7 +4112,7 @@
       CHECK_EQ(routing_id_, proxy->provisional_frame_routing_id());
       proxy->set_provisional_frame_routing_id(MSG_ROUTING_NONE);
     } else
-      CHECK(IsRenderDocumentEnabled());
+      CHECK(CreateNewHostForSameSiteSubframe());
   }
 
   delete this;
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 7080a4d..ac3e90f 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -32,7 +32,6 @@
 #include "base/values.h"
 #include "build/build_config.h"
 #include "content/common/buildflags.h"
-#include "content/common/device_emulator.mojom.h"
 #include "content/common/download/mhtml_file_writer.mojom.h"
 #include "content/common/frame.mojom.h"
 #include "content/common/frame_delete_intention.h"
@@ -597,8 +596,7 @@
           subresource_loader_factories) override;
   void BindDevToolsAgent(
       mojo::PendingAssociatedRemote<blink::mojom::DevToolsAgentHost> host,
-      mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent,
-      mojo::PendingAssociatedReceiver<mojom::DeviceEmulator> device_emulator)
+      mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> receiver)
       override;
 
   void JavaScriptExecuteRequest(
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index 2e827ba..85f08187ff 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -69,7 +69,6 @@
 #include "services/network/public/cpp/resource_request_body.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
 #include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
 #include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
@@ -84,6 +83,7 @@
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/web/web_autofill_client.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_document_loader.h"
 #include "third_party/blink/public/web/web_frame_content_dumper.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
@@ -278,12 +278,20 @@
     return GetRenderAccessibilityManager()->GetAccessibilityMode();
   }
 
-  void SetDeviceEmulation(
-      RenderViewImpl* view,
-      const base::Optional<blink::WebDeviceEmulationParams>& params) {
-    mojom::DeviceEmulator* emulator =
+  void ReceiveDisableDeviceEmulation(RenderViewImpl* view) {
+    // Emulates receiving an IPC message.
+    RenderWidget* widget =
         view->GetMainRenderFrame()->GetLocalRootRenderWidget();
-    emulator->SetDeviceEmulation(params);
+    widget->OnDisableDeviceEmulation();
+  }
+
+  void ReceiveEnableDeviceEmulation(
+      RenderViewImpl* view,
+      const blink::WebDeviceEmulationParams& params) {
+    // Emulates receiving an IPC message.
+    RenderWidget* widget =
+        view->GetMainRenderFrame()->GetLocalRootRenderWidget();
+    widget->OnEnableDeviceEmulation(params);
   }
 
   void ReceiveSetTextDirection(RenderWidget* widget,
@@ -522,12 +530,13 @@
     static base::string16 get_dpr =
         base::ASCIIToUTF16("Number(window.devicePixelRatio * 10)");
 
-    blink::WebDeviceEmulationParams params;
-    params.view_size = gfx::Size(width, height);
-    params.device_scale_factor = dpr;
-    SetDeviceEmulation(view(), params);
     int emulated_width, emulated_height;
     int emulated_dpr;
+    blink::WebDeviceEmulationParams params;
+    params.view_size.width = width;
+    params.view_size.height = height;
+    params.device_scale_factor = dpr;
+    ReceiveEnableDeviceEmulation(view(), params);
     EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_width, &emulated_width));
     EXPECT_EQ(width, emulated_width);
     EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(get_height,
@@ -694,11 +703,14 @@
   gfx::Rect emulated_widget_rect(150, 160, 980, 1200);
   // In mobile emulation the WindowScreenRect and ScreenRect are both set to
   // match the WidgetScreenRect, which we set here.
-  emulation_params.screen_position = blink::mojom::ScreenPosition::kMobile;
+  emulation_params.screen_position = blink::WebDeviceEmulationParams::kMobile;
   emulation_params.view_size = emulated_widget_rect.size();
   emulation_params.view_position = emulated_widget_rect.origin();
-  mojom::DeviceEmulator* emulator = main_widget();
-  emulator->SetDeviceEmulation(emulation_params);
+  {
+    WidgetMsg_EnableDeviceEmulation msg(main_widget()->routing_id(),
+                                        emulation_params);
+    main_widget()->OnMessageReceived(msg);
+  }
 
   {
     // Make a popup again. It should inherit device emulation params.
@@ -1102,9 +1114,10 @@
             main_widget()->GetOriginalScreenInfo().device_scale_factor);
   EXPECT_EQ(device_scale, child_proxy->screen_info().device_scale_factor);
 
-  SetDeviceEmulation(view(), base::nullopt);
+  ReceiveDisableDeviceEmulation(view());
 
-  SetDeviceEmulation(view(), blink::WebDeviceEmulationParams());
+  blink::WebDeviceEmulationParams params;
+  ReceiveEnableDeviceEmulation(view(), params);
   // Don't disable here to test that emulation is being shutdown properly.
 }
 
@@ -2763,9 +2776,10 @@
     TestEmulatedSizeDprDsf(1005, 1102, 3.f, 1.f);
   }
 
-  SetDeviceEmulation(view(), base::nullopt);
+  ReceiveDisableDeviceEmulation(view());
 
-  SetDeviceEmulation(view(), blink::WebDeviceEmulationParams());
+  blink::WebDeviceEmulationParams params;
+  ReceiveEnableDeviceEmulation(view(), params);
   // Don't disable here to test that emulation is being shutdown properly.
 }
 
@@ -2791,9 +2805,10 @@
     TestEmulatedSizeDprDsf(1005, 1102, 3.f, compositor_dsf);
   }
 
-  SetDeviceEmulation(view(), base::nullopt);
+  ReceiveDisableDeviceEmulation(view());
 
-  SetDeviceEmulation(view(), blink::WebDeviceEmulationParams());
+  blink::WebDeviceEmulationParams params;
+  ReceiveEnableDeviceEmulation(view(), params);
   // Don't disable here to test that emulation is being shutdown properly.
 }
 
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 0678ea4b..f0cb483 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1102,8 +1102,12 @@
 }
 
 void RenderViewImpl::SetScreenMetricsEmulationParametersForWidget(
-    const base::Optional<blink::WebDeviceEmulationParams>& params) {
-  GetWebView()->SetDeviceEmulation(params);
+    bool enabled,
+    const blink::WebDeviceEmulationParams& params) {
+  if (enabled)
+    GetWebView()->EnableDeviceEmulation(params);
+  else
+    GetWebView()->DisableDeviceEmulation();
 }
 
 // IPC message handlers -----------------------------------------
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 40816db..701aac4 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -382,7 +382,8 @@
       const gfx::Size& visible_viewport_size,
       cc::BrowserControlsParams browser_controls_params) override;
   void SetScreenMetricsEmulationParametersForWidget(
-      const base::Optional<blink::WebDeviceEmulationParams>& params) override;
+      bool enabled,
+      const blink::WebDeviceEmulationParams& params) override;
 
   // Old WebLocalFrameClient implementations
   // ----------------------------------------
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index a24fc4a..640945d 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -92,6 +92,7 @@
 #include "third_party/blink/public/platform/web_size.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/web/web_autofill_client.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
 #include "third_party/blink/public/web/web_input_method_controller.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -548,6 +549,19 @@
 }
 
 bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
+  // The EnableDeviceEmulation message is sent to a provisional RenderWidget
+  // before the navigation completes. Some investigation into why is done in
+  // https://chromium-review.googlesource.com/c/chromium/src/+/1853675/5#message-e6edc3fd708d7d267ee981ffe43cae090b37a906
+  // but it's unclear what would need to be done to delay this until after
+  // navigation.
+  bool handled = false;
+  IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
+    IPC_MESSAGE_HANDLER(WidgetMsg_EnableDeviceEmulation,
+                        OnEnableDeviceEmulation)
+  IPC_END_MESSAGE_MAP()
+  if (handled)
+    return true;
+
   // We shouldn't receive IPC messages on provisional frames. It's possible the
   // message was destined for a RenderWidget that was destroyed and then
   // recreated since it keeps the same routing id. Just drop it here if that
@@ -560,8 +574,9 @@
     return text_input_client_observer_->OnMessageReceived(message);
 #endif
 
-  bool handled = false;
   IPC_BEGIN_MESSAGE_MAP(RenderWidget, message)
+    IPC_MESSAGE_HANDLER(WidgetMsg_DisableDeviceEmulation,
+                        OnDisableDeviceEmulation)
     IPC_MESSAGE_HANDLER(WidgetMsg_ShowContextMenu, OnShowContextMenu)
     IPC_MESSAGE_HANDLER(WidgetMsg_Close, OnClose)
     IPC_MESSAGE_HANDLER(WidgetMsg_UpdateVisualProperties,
@@ -906,6 +921,34 @@
   AfterUpdateVisualProperties();
 }
 
+void RenderWidget::OnEnableDeviceEmulation(
+    const blink::WebDeviceEmulationParams& params) {
+  // Device emulation can only be applied to the local main frame render widget.
+  // TODO(https://crbug.com/1006052): We should move emulation into the browser
+  // and send consistent ScreenInfo and ScreenRects to all RenderWidgets based
+  // on emulation.
+  if (!delegate_)
+    return;
+
+  if (!device_emulator_) {
+    device_emulator_ = std::make_unique<RenderWidgetScreenMetricsEmulator>(
+        this, screen_info_, size_, visible_viewport_size_, widget_screen_rect_,
+        window_screen_rect_);
+  }
+  device_emulator_->ChangeEmulationParams(params);
+}
+
+void RenderWidget::OnDisableDeviceEmulation() {
+  // Device emulation can only be applied to the local main frame render widget.
+  // TODO(https://crbug.com/1006052): We should move emulation into the browser
+  // and send consistent ScreenInfo and ScreenRects to all RenderWidgets based
+  // on emulation.
+  if (!delegate_ || !device_emulator_)
+    return;
+  device_emulator_->DisableAndApply();
+  device_emulator_.reset();
+}
+
 float RenderWidget::GetEmulatorScale() const {
   if (device_emulator_)
     return device_emulator_->scale();
@@ -1017,11 +1060,6 @@
   layer_tree_host_->SetNeedsCommitWithForcedRedraw();
 }
 
-void RenderWidget::BindDeviceEmulator(
-    mojo::PendingAssociatedReceiver<mojom::DeviceEmulator> pending_receiver) {
-  device_emulator_receiver_.Bind(std::move(pending_receiver));
-}
-
 void RenderWidget::DidPresentForceDrawFrame(
     int snapshot_id,
     const gfx::PresentationFeedback& feedback) {
@@ -1524,29 +1562,6 @@
   return layer_tree_host_->device_viewport_rect();
 }
 
-void RenderWidget::SetDeviceEmulation(
-    const base::Optional<::blink::WebDeviceEmulationParams>& params) {
-  // Device emulation can only be applied to the local main frame render widget.
-  // TODO(https://crbug.com/1006052): We should move emulation into the browser
-  // and send consistent ScreenInfo and ScreenRects to all RenderWidgets based
-  // on emulation.
-  DCHECK(delegate_);
-
-  if (params) {
-    if (!device_emulator_) {
-      device_emulator_ = std::make_unique<RenderWidgetScreenMetricsEmulator>(
-          this, screen_info_, size_, visible_viewport_size_,
-          widget_screen_rect_, window_screen_rect_);
-    }
-    device_emulator_->ChangeEmulationParams(*params);
-  } else {
-    if (!device_emulator_)
-      return;
-    device_emulator_->DisableAndApply();
-    device_emulator_.reset();
-  }
-}
-
 void RenderWidget::SetScreenInfoAndSize(
     const ScreenInfo& screen_info,
     const gfx::Size& widget_size,
@@ -1583,10 +1598,11 @@
 }
 
 void RenderWidget::SetScreenMetricsEmulationParameters(
-    const base::Optional<blink::WebDeviceEmulationParams>& params) {
-  // Device emulation can only be applied to the local main frame render widget.
+    bool enabled,
+    const blink::WebDeviceEmulationParams& params) {
+  // This is only supported in RenderView, which has an delegate().
   DCHECK(delegate());
-  delegate()->SetScreenMetricsEmulationParametersForWidget(params);
+  delegate()->SetScreenMetricsEmulationParametersForWidget(enabled, params);
 }
 
 void RenderWidget::SetScreenRects(const gfx::Rect& widget_screen_rect,
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 0568b41..fd76b067 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -33,7 +33,6 @@
 #include "content/common/buildflags.h"
 #include "content/common/content_export.h"
 #include "content/common/content_to_visible_time_reporter.h"
-#include "content/common/device_emulator.mojom.h"
 #include "content/common/drag_event_source_info.h"
 #include "content/common/edit_command.h"
 #include "content/common/widget.mojom.h"
@@ -49,7 +48,6 @@
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_sender.h"
-#include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ppapi/buildflags/buildflags.h"
@@ -148,7 +146,6 @@
       public IPC::Sender,
       public blink::WebPagePopupClient,  // Is-a WebWidgetClient also.
       public mojom::Widget,
-      public mojom::DeviceEmulator,
       public LayerTreeViewDelegate,
       public RenderWidgetInputHandlerDelegate,
       public RenderWidgetScreenMetricsEmulatorDelegate,
@@ -357,7 +354,8 @@
 
   // RenderWidgetScreenMetricsEmulatorDelegate
   void SetScreenMetricsEmulationParameters(
-      const base::Optional<blink::WebDeviceEmulationParams>& params) override;
+      bool enabled,
+      const blink::WebDeviceEmulationParams& params) override;
   void SetScreenInfoAndSize(const ScreenInfo& screen_info,
                             const gfx::Size& widget_size,
                             const gfx::Size& visible_viewport_size) override;
@@ -612,9 +610,6 @@
       base::OnceCallback<void(const gfx::PresentationFeedback&)>;
   virtual void RequestPresentation(PresentationTimeCallback callback);
 
-  void BindDeviceEmulator(
-      mojo::PendingAssociatedReceiver<mojom::DeviceEmulator> pending_receiver);
-
   base::WeakPtr<RenderWidget> AsWeakPtr();
 
  protected:
@@ -673,10 +668,6 @@
   // is always in physical pixels.
   gfx::Rect CompositorViewportRect() const;
 
-  // DeviceEmulator mojom overrides.
-  void SetDeviceEmulation(
-      const base::Optional<::blink::WebDeviceEmulationParams>& params) override;
-
   // RenderWidget IPC message handlers.
   void OnHandleInputEvent(
       const blink::WebInputEvent* event,
@@ -686,6 +677,8 @@
   void OnClose();
   void OnUpdateVisualProperties(const VisualProperties& properties);
   void OnCreatingNewAck();
+  void OnEnableDeviceEmulation(const blink::WebDeviceEmulationParams& params);
+  void OnDisableDeviceEmulation();
   void OnWasHidden();
   void OnWasShown(
       base::TimeTicks show_request_timestamp,
@@ -1064,8 +1057,6 @@
   scoped_refptr<MainThreadEventQueue> input_event_queue_;
 
   mojo::Receiver<mojom::Widget> widget_receiver_;
-  mojo::AssociatedReceiver<mojom::DeviceEmulator> device_emulator_receiver_{
-      this};
 
   gfx::Rect compositor_visible_rect_;
 
diff --git a/content/renderer/render_widget_delegate.h b/content/renderer/render_widget_delegate.h
index 2a7db42d..bd7d362 100644
--- a/content/renderer/render_widget_delegate.h
+++ b/content/renderer/render_widget_delegate.h
@@ -76,7 +76,8 @@
   // Called when RenderWidget services RenderWidgetScreenMetricsEmulatorDelegate
   // SetScreenMetricsEmulationParameters().
   virtual void SetScreenMetricsEmulationParametersForWidget(
-      const base::Optional<blink::WebDeviceEmulationParams>& params) = 0;
+      bool enabled,
+      const blink::WebDeviceEmulationParams& params) = 0;
 };
 
 }  // namespace content
diff --git a/content/renderer/render_widget_screen_metrics_emulator.cc b/content/renderer/render_widget_screen_metrics_emulator.cc
index f72894f..c1dbc65 100644
--- a/content/renderer/render_widget_screen_metrics_emulator.cc
+++ b/content/renderer/render_widget_screen_metrics_emulator.cc
@@ -27,7 +27,7 @@
     default;
 
 void RenderWidgetScreenMetricsEmulator::DisableAndApply() {
-  delegate_->SetScreenMetricsEmulationParameters(base::nullopt);
+  delegate_->SetScreenMetricsEmulationParameters(false, emulation_params_);
   delegate_->SetScreenRects(original_view_screen_rect_,
                             original_window_screen_rect_);
   delegate_->SetScreenInfoAndSize(original_screen_info_, original_widget_size_,
@@ -59,14 +59,14 @@
 
   // If either the width or height are specified by the emulator, then we use
   // that size, and assume that they have the scale pre-applied to them.
-  if (emulation_params_.view_size.width()) {
-    widget_size.set_width(emulation_params_.view_size.width());
+  if (emulation_params_.view_size.width) {
+    widget_size.set_width(emulation_params_.view_size.width);
   } else {
     widget_size.set_width(
         gfx::ToRoundedInt(widget_size.width() / emulation_params_.scale));
   }
-  if (emulation_params_.view_size.height()) {
-    widget_size.set_height(emulation_params_.view_size.height());
+  if (emulation_params_.view_size.height) {
+    widget_size.set_height(emulation_params_.view_size.height);
   } else {
     widget_size.set_height(
         gfx::ToRoundedInt(widget_size.height() / emulation_params_.scale));
@@ -113,21 +113,21 @@
   uint16_t orientation_angle = original_screen_info().orientation_angle;
 
   switch (emulation_params_.screen_orientation_type) {
-    case blink::mojom::ScreenOrientationType::kUndefined:
+    case blink::kWebScreenOrientationUndefined:
       break;  // Leave as the real value.
-    case blink::mojom::ScreenOrientationType::kPortraitPrimary:
+    case blink::kWebScreenOrientationPortraitPrimary:
       orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_PRIMARY;
       orientation_angle = emulation_params_.screen_orientation_angle;
       break;
-    case blink::mojom::ScreenOrientationType::kPortraitSecondary:
+    case blink::kWebScreenOrientationPortraitSecondary:
       orientation_type = SCREEN_ORIENTATION_VALUES_PORTRAIT_SECONDARY;
       orientation_angle = emulation_params_.screen_orientation_angle;
       break;
-    case blink::mojom::ScreenOrientationType::kLandscapePrimary:
+    case blink::kWebScreenOrientationLandscapePrimary:
       orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_PRIMARY;
       orientation_angle = emulation_params_.screen_orientation_angle;
       break;
-    case blink::mojom::ScreenOrientationType::kLandscapeSecondary:
+    case blink::kWebScreenOrientationLandscapeSecondary:
       orientation_type = SCREEN_ORIENTATION_VALUES_LANDSCAPE_SECONDARY;
       orientation_angle = emulation_params_.screen_orientation_angle;
       break;
@@ -136,10 +136,11 @@
   // Pass three emulation parameters to the blink side:
   // - we keep the real device scale factor in compositor to produce sharp image
   //   even when emulating different scale factor;
-  auto modified_emulation_params = emulation_params_;
+  blink::WebDeviceEmulationParams modified_emulation_params = emulation_params_;
   modified_emulation_params.device_scale_factor =
       original_screen_info().device_scale_factor;
-  delegate_->SetScreenMetricsEmulationParameters(modified_emulation_params);
+  delegate_->SetScreenMetricsEmulationParameters(true,
+                                                 modified_emulation_params);
 
   delegate_->SetScreenRects(gfx::Rect(widget_pos, widget_size),
                             gfx::Rect(window_pos, window_size));
diff --git a/content/renderer/render_widget_screen_metrics_emulator.h b/content/renderer/render_widget_screen_metrics_emulator.h
index bae4f8e..469a46d 100644
--- a/content/renderer/render_widget_screen_metrics_emulator.h
+++ b/content/renderer/render_widget_screen_metrics_emulator.h
@@ -9,7 +9,7 @@
 
 #include "content/common/content_export.h"
 #include "content/public/common/screen_info.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -68,7 +68,7 @@
  private:
   bool emulating_desktop() const {
     return emulation_params_.screen_position ==
-           blink::mojom::ScreenPosition::kDesktop;
+           blink::WebDeviceEmulationParams::kDesktop;
   }
 
   // Applies emulated values to the RenderWidget.
@@ -76,7 +76,7 @@
 
   RenderWidgetScreenMetricsEmulatorDelegate* const delegate_;
 
-  // Parameters as passed by RenderWidget::SetScreenMetricsEmulation.
+  // Parameters as passed by RenderWidget::EnableScreenMetricsEmulation.
   blink::WebDeviceEmulationParams emulation_params_;
 
   // Original values to restore back after emulation ends.
diff --git a/content/renderer/render_widget_screen_metrics_emulator_delegate.h b/content/renderer/render_widget_screen_metrics_emulator_delegate.h
index 12c714d..2ad2536 100644
--- a/content/renderer/render_widget_screen_metrics_emulator_delegate.h
+++ b/content/renderer/render_widget_screen_metrics_emulator_delegate.h
@@ -19,7 +19,8 @@
  public:
   // Passes device emulation parameters to the delegate.
   virtual void SetScreenMetricsEmulationParameters(
-      const base::Optional<blink::WebDeviceEmulationParams>& params) = 0;
+      bool enabled,
+      const blink::WebDeviceEmulationParams& params) = 0;
 
   // Passes an updated ScreenInfo and sizes to the delegate.
   virtual void SetScreenInfoAndSize(const ScreenInfo& screen_info,
diff --git a/content/renderer/render_widget_unittest.cc b/content/renderer/render_widget_unittest.cc
index 177e8f6..f03bffc 100644
--- a/content/renderer/render_widget_unittest.cc
+++ b/content/renderer/render_widget_unittest.cc
@@ -40,6 +40,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_coalesced_input_event.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_external_widget.h"
 #include "third_party/blink/public/web/web_external_widget_client.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
@@ -550,7 +551,8 @@
                                 const gfx::Size& visible_viewport_size,
                                 cc::BrowserControlsParams) override {}
   void SetScreenMetricsEmulationParametersForWidget(
-      const base::Optional<blink::WebDeviceEmulationParams>& params) override {}
+      bool enabled,
+      const blink::WebDeviceEmulationParams& params) override {}
 };
 
 // Tests that the value of VisualProperties::is_pinch_gesture_active is
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index d895f2d2..548ce84 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -295,6 +295,8 @@
     "portal/portal_created_observer.h",
     "portal/portal_interceptor_for_testing.cc",
     "portal/portal_interceptor_for_testing.h",
+    "render_document_feature.cc",
+    "render_document_feature.h",
     "storage_partition_test_utils.cc",
     "storage_partition_test_utils.h",
     "stub_layer_tree_view_delegate.cc",
diff --git a/content/test/data/accessibility/aria/annotation-roles-expected-uia-win.txt b/content/test/data/accessibility/aria/annotation-roles-expected-uia-win.txt
index a1889b7..9ecfe799 100644
--- a/content/test/data/accessibility/aria/annotation-roles-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/annotation-roles-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++++Text LocalizedControlType='highlight'
 ++++++Text Name='highlighted'
 ++++Text Name='.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
index bba71c2..0a9b5cb 100644
--- a/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-button-expected-uia-win.txt
@@ -13,4 +13,3 @@
 ++Button Name='Complex pop up button ' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++++Text Name='Complex pop up button '
 ++++Edit
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-code-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-code-expected-uia-win.txt
index b676cf7..b246de5d 100644
--- a/content/test/data/accessibility/aria/aria-code-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-code-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Text LocalizedControlType='text' Name=' '
 ++Text LocalizedControlType='code' Name='include me'
 ++++Text LocalizedControlType='text' Name='element (with name)'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win.txt
index 0765bdf..b265954b 100644
--- a/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='content information' LocalizedLandmarkType='content information'
 ++++Text Name='This is ARIA role contentinfo.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win7.txt b/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win7.txt
index d9d968e6..00a31d3 100644
--- a/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win7.txt
+++ b/content/test/data/accessibility/aria/aria-contentinfo-expected-uia-win7.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='content information'
 ++++Text Name='This is ARIA role contentinfo.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-emphasis-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-emphasis-expected-uia-win.txt
index 13e36bdb..9d472d9e 100644
--- a/content/test/data/accessibility/aria/aria-emphasis-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-emphasis-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Text LocalizedControlType='text' Name=' '
 ++Text LocalizedControlType='emphasis' Name='include me'
 ++++Text LocalizedControlType='text' Name='element (with name)'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-generic-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-generic-expected-uia-win.txt
index 7b2c656..faf2453 100644
--- a/content/test/data/accessibility/aria/aria-generic-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-generic-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group
 ++++Text Name='content'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-gridcell-focused-only-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-gridcell-focused-only-expected-uia-win.txt
index 1b14abe..c069b1c 100644
--- a/content/test/data/accessibility/aria/aria-gridcell-focused-only-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-gridcell-focused-only-expected-uia-win.txt
@@ -11,4 +11,3 @@
 ++++++DataItem Name='Blink' GridItem.Column=1 GridItem.ColumnSpan=1 GridItem.Row=1 GridItem.RowSpan=1
 ++++++++Text Name='Blink' IsControlElement=false
 ++Text Name='Done'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
index e3c48954..ad28b7ec 100644
--- a/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-described-by-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++Group Name='span-B' DescribedBy='span-2'
 ++Group Name='span-C' DescribedBy='span-3'
 ++Group Name='span-D' DescribedBy='span-4'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-uia-win.txt
index 1423b9bb..e958bf8 100644
--- a/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-hidden-labelled-by-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Group Name='span-2' LabeledBy='span-2'
 ++Group Name='span-3' LabeledBy='span-4'
 ++Group Name='span-4' LabeledBy='span-4'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-strong-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-strong-expected-uia-win.txt
index d429537..090fa66 100644
--- a/content/test/data/accessibility/aria/aria-strong-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-strong-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Text LocalizedControlType='text' Name=' '
 ++Text LocalizedControlType='strong' Name='include me'
 ++++Text LocalizedControlType='text' Name='element (with name)'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-tab-nested-in-lists-expected-blink.txt b/content/test/data/accessibility/aria/aria-tab-nested-in-lists-expected-blink.txt
index a441ed9..fd69e57 100644
--- a/content/test/data/accessibility/aria/aria-tab-nested-in-lists-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-tab-nested-in-lists-expected-blink.txt
@@ -9,4 +9,3 @@
 ++++++++tab name='tab3' setSize=5 posInSet=4 selected=false
 ++++++listItem hierarchicalLevel=1
 ++++++++tab name='tab4' setSize=5 posInSet=5 selected=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-tablist-expected-win.txt b/content/test/data/accessibility/aria/aria-tablist-expected-win.txt
index 2b3925f..69bb54e 100644
--- a/content/test/data/accessibility/aria/aria-tablist-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-tablist-expected-win.txt
@@ -9,4 +9,3 @@
 ++++++++ROLE_SYSTEM_PAGETABLIST IA2_STATE_HORIZONTAL xml-roles:tablist level:3
 ++++++++++ROLE_SYSTEM_PAGETAB name='Tab 1, level 3' xml-roles:tab
 ++++ROLE_SYSTEM_PAGETAB name='Tab 3, level 1' xml-roles:tab
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-time-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-time-expected-uia-win.txt
index 2e4ed51..def6784 100644
--- a/content/test/data/accessibility/aria/aria-time-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-time-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++Text LocalizedControlType='text' Name=' '
 ++Text LocalizedControlType='time' Name='include me'
 ++++Text LocalizedControlType='text' Name='element (with name)'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win.txt
index 93c4b8d..4c13f7d 100644
--- a/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Group PositionInSet=0 SizeOfSet=0
 ++++TreeItem Name='card content' PositionInSet=2 SizeOfSet=2 ExpandCollapse.ExpandCollapseState='LeafNode'
 ++++++Text Name='card content' IsControlElement=false PositionInSet=0 SizeOfSet=0
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt
index 02ecc25..b3137d3 100644
--- a/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt
+++ b/content/test/data/accessibility/aria/aria-tree-discontinuous-expected-uia-win7.txt
@@ -5,4 +5,3 @@
 ++++Group
 ++++TreeItem Name='card content' ExpandCollapse.ExpandCollapseState='LeafNode'
 ++++++Text Name='card content' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
index b5f9193..554aad7 100644
--- a/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/hidden-described-by-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group Name='span-A3' DescribedBy='span-A4'
 ++Group Name='span-B' DescribedBy='span-A2'
 ++Group Name='span-C'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/hidden-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-expected-uia-win.txt
index 923230f8..7b3188f 100644
--- a/content/test/data/accessibility/aria/hidden-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/hidden-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group Name='b'
 ++Group Name='c'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt b/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt
index 52d2557c..2456b36 100644
--- a/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/hidden-labelled-by-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group Name='span-A4' LabeledBy='span-A4'
 ++Group Name='span-A2' LabeledBy='span-A2'
 ++Group Name='span-C'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/css/transform-expected-uia-win.txt b/content/test/data/accessibility/css/transform-expected-uia-win.txt
index 572f77a..1b1d409 100644
--- a/content/test/data/accessibility/css/transform-expected-uia-win.txt
+++ b/content/test/data/accessibility/css/transform-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group BoundingRectangle=(0, 50, 48, 18) IsControlElement=false
 ++++Group BoundingRectangle=(0, 50, 48, 18) IsControlElement=false
 ++++++Text BoundingRectangle=(0, 50, 48, 17) Name='content'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/css/visibility-expected-uia-win.txt b/content/test/data/accessibility/css/visibility-expected-uia-win.txt
index 68486b2..c70accf2 100644
--- a/content/test/data/accessibility/css/visibility-expected-uia-win.txt
+++ b/content/test/data/accessibility/css/visibility-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Text Name='visible link'
 ++Text Name='visible link'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/aria-combo-box-collapse-expected-uia-win7.txt b/content/test/data/accessibility/event/aria-combo-box-collapse-expected-uia-win7.txt
new file mode 100644
index 0000000..b665a9a
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-combo-box-collapse-expected-uia-win7.txt
@@ -0,0 +1,6 @@
+AriaProperties changed on role=combobox
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=option, name=Orange
+ExpandCollapseExpandCollapseState changed on role=combobox
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/aria-combo-box-expand-expected-uia-win7.txt b/content/test/data/accessibility/event/aria-combo-box-expand-expected-uia-win7.txt
new file mode 100644
index 0000000..5d77f3e
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-combo-box-expand-expected-uia-win7.txt
@@ -0,0 +1,8 @@
+AriaProperties changed on role=combobox
+AriaProperties changed on role=option, name=Apple
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=option, name=Apple
+AutomationFocusChanged on role=option, name=Apple
+ExpandCollapseExpandCollapseState changed on role=combobox
+SelectionItem_ElementSelected on role=option, name=Apple
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/aria-combo-box-next-expected-uia-win7.txt b/content/test/data/accessibility/event/aria-combo-box-next-expected-uia-win7.txt
new file mode 100644
index 0000000..649cf09
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-combo-box-next-expected-uia-win7.txt
@@ -0,0 +1,14 @@
+AriaProperties changed on role=option, name=Apple
+AriaProperties changed on role=option, name=Orange
+AutomationFocusChanged on role=option, name=Apple
+AutomationFocusChanged on role=option, name=Orange
+AutomationFocusChanged on role=option, name=Orange
+SelectionItem_ElementSelected on role=option, name=Orange
+=== Start Continuation ===
+AriaProperties changed on role=option, name=Banana
+AriaProperties changed on role=option, name=Orange
+AutomationFocusChanged on role=option, name=Banana
+AutomationFocusChanged on role=option, name=Banana
+AutomationFocusChanged on role=option, name=Orange
+SelectionItem_ElementSelected on role=option, name=Banana
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/listbox-next-expected-uia-win7.txt b/content/test/data/accessibility/event/listbox-next-expected-uia-win7.txt
new file mode 100644
index 0000000..ab7a250
--- /dev/null
+++ b/content/test/data/accessibility/event/listbox-next-expected-uia-win7.txt
@@ -0,0 +1,7 @@
+AriaProperties changed on role=option, name=Apple
+AriaProperties changed on role=option, name=Orange
+AutomationFocusChanged on role=option, name=Apple
+AutomationFocusChanged on role=option, name=Orange
+AutomationFocusChanged on role=option, name=Orange
+SelectionItem_ElementSelected on role=option, name=Orange
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt b/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt
index 4dc69e86..9fcb8ce 100644
--- a/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt
@@ -1,2 +1,5 @@
+AXFocusedUIElementChanged on AXPopUpButton AXValue="Apple"
+=== Start Continuation ===
 AXSelectedChildrenChanged on AXMenu
 AXValueChanged on AXPopUpButton
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-expected-uia-win.txt b/content/test/data/accessibility/event/menulist-collapse-expected-uia-win.txt
index 4cd300f..90ceb35 100644
--- a/content/test/data/accessibility/event/menulist-collapse-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-expected-uia-win.txt
@@ -1,3 +1,7 @@
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=combobox
+=== Start Continuation ===
 AriaProperties changed on role=listitem, name=Apple
 SelectionItem_ElementRemovedFromSelection on role=listitem, name=Apple
 ValueValue changed on role=combobox
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-expected-uia-win7.txt b/content/test/data/accessibility/event/menulist-collapse-expected-uia-win7.txt
new file mode 100644
index 0000000..22a595f
--- /dev/null
+++ b/content/test/data/accessibility/event/menulist-collapse-expected-uia-win7.txt
@@ -0,0 +1,8 @@
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=combobox
+=== Start Continuation ===
+AriaProperties changed on role=listitem, name=Apple
+AutomationFocusChanged on role=combobox
+SelectionItem_ElementRemovedFromSelection on role=listitem, name=Apple
+ValueValue changed on role=combobox
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-expected-win.txt b/content/test/data/accessibility/event/menulist-collapse-expected-win.txt
index 286e043..6903d28 100644
--- a/content/test/data/accessibility/event/menulist-collapse-expected-win.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-expected-win.txt
@@ -1,4 +1,7 @@
+EVENT_OBJECT_FOCUS on <select> role=ROLE_SYSTEM_COMBOBOX value="Apple" FOCUSED,COLLAPSED,FOCUSABLE,HASPOPUP SetSize=3
+=== Start Continuation ===
 EVENT_OBJECT_SELECTIONREMOVE on <option> role=ROLE_SYSTEM_LISTITEM name="Apple" INVISIBLE,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
 EVENT_OBJECT_SELECTIONWITHIN on role=ROLE_SYSTEM_LIST INVISIBLE SetSize=3
 EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Apple" INVISIBLE,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
-EVENT_OBJECT_VALUECHANGE on <select> role=ROLE_SYSTEM_COMBOBOX FOCUSED,COLLAPSED,FOCUSABLE,HASPOPUP SetSize=3
\ No newline at end of file
+EVENT_OBJECT_VALUECHANGE on <select> role=ROLE_SYSTEM_COMBOBOX FOCUSED,COLLAPSED,FOCUSABLE,HASPOPUP SetSize=3
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-auralinux.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-auralinux.txt
index 09f8aed..a7ef68a 100644
--- a/content/test/data/accessibility/event/menulist-collapse-next-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-auralinux.txt
@@ -1,2 +1,8 @@
+FOCUS-EVENT role=ROLE_COMBO_BOX name='(null)' ENABLED,EXPANDABLE,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+FOCUS-EVENT role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
+STATE-CHANGE:FOCUSED:FALSE role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
+STATE-CHANGE:FOCUSED:TRUE role=ROLE_COMBO_BOX name='(null)' ENABLED,EXPANDABLE,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+=== Start Continuation ===
 SELECTION-CHANGED role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
 STATE-CHANGE:SELECTED:TRUE role=ROLE_MENU_ITEM name='Orange' ENABLED,FOCUSABLE,SELECTABLE,SELECTED,SENSITIVE,SHOWING,VISIBLE
+<-- End-of-file -->
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt
index ac839fd..9f95856 100644
--- a/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt
@@ -1,2 +1,5 @@
+AXFocusedUIElementChanged on AXPopUpButton AXValue="Apple"
+=== Start Continuation ===
 AXSelectedChildrenChanged on AXMenu
 AXValueChanged on AXPopUpButton AXValue="Orange"
+<-- End-of-file -->
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win.txt
index 33216a32..b12bee8 100644
--- a/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win.txt
@@ -1,4 +1,8 @@
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=combobox
+=== Start Continuation ===
 AriaProperties changed on role=listitem, name=Apple
 AriaProperties changed on role=listitem, name=Orange
 SelectionItem_ElementSelected on role=listitem, name=Orange
 ValueValue changed on role=combobox
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win7.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win7.txt
new file mode 100644
index 0000000..b7aa066b
--- /dev/null
+++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-uia-win7.txt
@@ -0,0 +1,9 @@
+AutomationFocusChanged on role=combobox
+AutomationFocusChanged on role=combobox
+=== Start Continuation ===
+AriaProperties changed on role=listitem, name=Apple
+AriaProperties changed on role=listitem, name=Orange
+AutomationFocusChanged on role=combobox
+SelectionItem_ElementSelected on role=listitem, name=Orange
+ValueValue changed on role=combobox
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-win.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-win.txt
index 64feb950..d7e2c78 100644
--- a/content/test/data/accessibility/event/menulist-collapse-next-expected-win.txt
+++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-win.txt
@@ -1,5 +1,8 @@
+EVENT_OBJECT_FOCUS on <select> role=ROLE_SYSTEM_COMBOBOX value="Apple" FOCUSED,COLLAPSED,FOCUSABLE,HASPOPUP SetSize=3
+=== Start Continuation ===
 EVENT_OBJECT_SELECTION on <option> role=ROLE_SYSTEM_LISTITEM name="Orange" SELECTED,FOCUSABLE,SELECTABLE PosInSet=2 SetSize=3
 EVENT_OBJECT_SELECTIONWITHIN on role=ROLE_SYSTEM_LIST INVISIBLE SetSize=3
 EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Apple" INVISIBLE,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
 EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Orange" SELECTED,FOCUSABLE,SELECTABLE PosInSet=2 SetSize=3
 EVENT_OBJECT_VALUECHANGE on <select> role=ROLE_SYSTEM_COMBOBOX value="Orange" FOCUSED,COLLAPSED,FOCUSABLE,HASPOPUP SetSize=3
+<-- End-of-file -->
diff --git a/content/test/data/accessibility/event/menulist-collapse-next.html b/content/test/data/accessibility/event/menulist-collapse-next.html
index d1b01f5..75d3377 100644
--- a/content/test/data/accessibility/event/menulist-collapse-next.html
+++ b/content/test/data/accessibility/event/menulist-collapse-next.html
@@ -10,9 +10,19 @@
   <option>Banana</option>
 </select>
 <script>
-  document.querySelector('select').focus();
+  const go_passes = [
+    () => {
+      document.querySelector('select').focus();
+    },
+    () => {
+      document.querySelector('select').selectedIndex = 1;
+    }
+  ];
+
+  let current_pass = 0;
   function go() {
-    document.querySelector('select').selectedIndex = 1;
+    go_passes[current_pass++].call();
+    return current_pass < go_passes.length;
   }
 </script>
 </body>
diff --git a/content/test/data/accessibility/event/menulist-collapse.html b/content/test/data/accessibility/event/menulist-collapse.html
index ac1dacb..48a41e45 100644
--- a/content/test/data/accessibility/event/menulist-collapse.html
+++ b/content/test/data/accessibility/event/menulist-collapse.html
@@ -10,10 +10,20 @@
   <option>Banana</option>
 </select>
 <script>
-  document.querySelector('select').focus();
+  const go_passes = [
+    () => {
+      document.querySelector('select').focus();
+    },
+    () => {
+      // Setting |selectedIndex| to -1 unselects any selected item.
+      document.querySelector('select').selectedIndex = -1;
+    }
+  ];
+
+  let current_pass = 0;
   function go() {
-    // Setting |selectedIndex| to -1 unselects any selected item.
-    document.querySelector('select').selectedIndex = -1;
+    go_passes[current_pass++].call();
+    return current_pass < go_passes.length;
   }
 </script>
 </body>
diff --git a/content/test/data/accessibility/html/a-expected-uia-win.txt b/content/test/data/accessibility/html/a-expected-uia-win.txt
index 8bc3713..fa7c7d51 100644
--- a/content/test/data/accessibility/html/a-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Hyperlink Name='normal link'
 ++++++Text Name='normal link' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/a-name-calc-expected-uia-win.txt b/content/test/data/accessibility/html/a-name-calc-expected-uia-win.txt
index 9d86ffb..2151687 100644
--- a/content/test/data/accessibility/html/a-name-calc-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-name-calc-expected-uia-win.txt
@@ -13,4 +13,3 @@
 ++Hyperlink Name='Title4'
 ++Hyperlink Name='Label5'
 ++Hyperlink Name='LabelledBy6'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/a-name-calc-expected-uia-win7.txt b/content/test/data/accessibility/html/a-name-calc-expected-uia-win7.txt
index 3329f62..73728b2 100644
--- a/content/test/data/accessibility/html/a-name-calc-expected-uia-win7.txt
+++ b/content/test/data/accessibility/html/a-name-calc-expected-uia-win7.txt
@@ -13,4 +13,3 @@
 ++Hyperlink Name='Title4'
 ++Hyperlink Name='Label5'
 ++Hyperlink Name='LabelledBy6'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/a-name-expected-uia-win.txt b/content/test/data/accessibility/html/a-name-expected-uia-win.txt
index d527391e..a0d54cc 100644
--- a/content/test/data/accessibility/html/a-name-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-name-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='named anchor'
 ++Hyperlink Name='both a named anchor and a link'
 ++++Text Name='both a named anchor and a link' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/a-onclick-expected-uia-win.txt b/content/test/data/accessibility/html/a-onclick-expected-uia-win.txt
index 1699f6a..d0ff9c9 100644
--- a/content/test/data/accessibility/html/a-onclick-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-onclick-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='link with no href but onclick' IsControlElement=false
 ++Hyperlink Name='link with no href and click handler added via script'
 ++++Text Name='link with no href and click handler added via script' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/a-with-img-expected-uia-win.txt b/content/test/data/accessibility/html/a-with-img-expected-uia-win.txt
index 8036d5d..c36522bb 100644
--- a/content/test/data/accessibility/html/a-with-img-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/a-with-img-expected-uia-win.txt
@@ -17,4 +17,3 @@
 ++++Hyperlink Name='Link with image at the end'
 ++++++Text Name='Link with image at the ' IsControlElement=false
 ++++++Image Name='end'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/abbr-expected-uia-win.txt b/content/test/data/accessibility/html/abbr-expected-uia-win.txt
index 6b673dac..489fed7 100644
--- a/content/test/data/accessibility/html/abbr-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/abbr-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++Text Name='World Health Organization'
 ++++++Text Name='WHO'
 ++++Text Name=' was founded in 1948.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/action-verbs-expected-uia-win.txt b/content/test/data/accessibility/html/action-verbs-expected-uia-win.txt
index 84529d4c..a227e47 100644
--- a/content/test/data/accessibility/html/action-verbs-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/action-verbs-expected-uia-win.txt
@@ -34,4 +34,3 @@
 ++Button Name='ARIA button with negative tab index'
 ++Group
 ++++Button Name='ARIA button that is an active descendant'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/actions-expected-uia-win.txt b/content/test/data/accessibility/html/actions-expected-uia-win.txt
index 02dad7ee..8f988d0 100644
--- a/content/test/data/accessibility/html/actions-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/actions-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Slider RangeValue.IsReadOnly=false RangeValue.LargeChange=10.00 RangeValue.SmallChange=1.00 RangeValue.Maximum=100.00 RangeValue.Minimum=1.00 RangeValue.Value=50.00 Value.Value='50'
 ++++Edit Name='Test textfield'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/address-expected-uia-win.txt b/content/test/data/accessibility/html/address-expected-uia-win.txt
index 280eadc..a79add76 100644
--- a/content/test/data/accessibility/html/address-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/address-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Text Name='Please contact John Citizen for more information.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/area-expected-uia-win.txt b/content/test/data/accessibility/html/area-expected-uia-win.txt
index f8dee90..435a60028 100644
--- a/content/test/data/accessibility/html/area-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/area-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Document Name='pipe'
 ++++++Hyperlink Name='pipe1'
 ++++++Text Name='pipe2'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/article-expected-uia-win.txt b/content/test/data/accessibility/html/article-expected-uia-win.txt
index 9117567a..86b5882 100644
--- a/content/test/data/accessibility/html/article-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/article-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='article'
 ++++Text Name='This is an article element.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/aside-expected-uia-win.txt b/content/test/data/accessibility/html/aside-expected-uia-win.txt
index ab252b8f..45ccd12 100644
--- a/content/test/data/accessibility/html/aside-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/aside-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++++++Text Name='Aside tag' IsControlElement=false
 ++++Group IsControlElement=false
 ++++++Text Name='The aside content should be related to the surrounding content.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/b-expected-uia-win.txt b/content/test/data/accessibility/html/b-expected-uia-win.txt
index d9305a3..1bb666f 100644
--- a/content/test/data/accessibility/html/b-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/b-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='Some '
 ++++Text Name='bold'
 ++++Text Name=' text'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/bdo-expected-uia-win.txt b/content/test/data/accessibility/html/bdo-expected-uia-win.txt
index fb47567..50e795d7 100644
--- a/content/test/data/accessibility/html/bdo-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/bdo-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++Text Name=' '
 ++++Text Name='Some RTL text '
 ++++Text Name='with some LTR text embedded'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/blockquote-expected-uia-win.txt b/content/test/data/accessibility/html/blockquote-expected-uia-win.txt
index ab30414..46cf324 100644
--- a/content/test/data/accessibility/html/blockquote-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/blockquote-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Text Name='First blockquote has a child element.'
 ++Group
 ++++Text Name='Second blockquote has no child.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/body-expected-uia-win.txt b/content/test/data/accessibility/html/body-expected-uia-win.txt
index 38cda4f0..d51a884 100644
--- a/content/test/data/accessibility/html/body-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/body-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Text Name='This test is for body tag'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/br-expected-uia-win.txt b/content/test/data/accessibility/html/br-expected-uia-win.txt
index 0c3aeffe..3bb54ec 100644
--- a/content/test/data/accessibility/html/br-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/br-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++++Separator Name='<newline>'
 ++++Text Name='Text line 3'
 ++Separator Name='<newline>'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/br-expected-win.txt b/content/test/data/accessibility/html/br-expected-win.txt
index 52ede30..31dc103 100644
--- a/content/test/data/accessibility/html/br-expected-win.txt
+++ b/content/test/data/accessibility/html/br-expected-win.txt
@@ -6,4 +6,3 @@
 ++++ROLE_SYSTEM_WHITESPACE name='<newline>'
 ++++ROLE_SYSTEM_STATICTEXT name='Text line 3'
 ++ROLE_SYSTEM_WHITESPACE name='<newline>'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/button-expected-uia-win.txt b/content/test/data/accessibility/html/button-expected-uia-win.txt
index 81c50aa..302c3e0 100644
--- a/content/test/data/accessibility/html/button-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/button-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Button Name='Click me!'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/button-name-calc-expected-uia-win.txt b/content/test/data/accessibility/html/button-name-calc-expected-uia-win.txt
index 3b260f2..1b1cbaf3 100644
--- a/content/test/data/accessibility/html/button-name-calc-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/button-name-calc-expected-uia-win.txt
@@ -18,4 +18,3 @@
 ++++Group
 ++++++Group IsControlElement=false
 ++++++++Text Name='grandchild' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/button-name-calc-expected-uia-win7.txt b/content/test/data/accessibility/html/button-name-calc-expected-uia-win7.txt
index 5b04033..479732d 100644
--- a/content/test/data/accessibility/html/button-name-calc-expected-uia-win7.txt
+++ b/content/test/data/accessibility/html/button-name-calc-expected-uia-win7.txt
@@ -18,4 +18,3 @@
 ++++Group
 ++++++Group IsControlElement=false
 ++++++++Text Name='grandchild' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/button-with-listbox-popup-expected-uia-win.txt b/content/test/data/accessibility/html/button-with-listbox-popup-expected-uia-win.txt
index ff31457..84e300ea 100644
--- a/content/test/data/accessibility/html/button-with-listbox-popup-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/button-with-listbox-popup-expected-uia-win.txt
@@ -8,4 +8,3 @@
 ++++++ListItem Name='Baz' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='Choose one:'
 ++++++ListItem Name='Bar' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='Choose one:'
 ++++++ListItem Name='Foo' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='Choose one:'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/canvas-expected-uia-win.txt b/content/test/data/accessibility/html/canvas-expected-uia-win.txt
index c1c594a..b98eb69 100644
--- a/content/test/data/accessibility/html/canvas-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/canvas-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Image
 ++++++Hyperlink Name='Interactive fallback'
 ++++++++Text Name='Interactive fallback' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt b/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
index 6857a09..9588e631 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
@@ -19,4 +19,3 @@
 ++++++Text IsControlElement=false
 ++++++++Text Name='Visibility hidden paragraph in fallback content' IsControlElement=false
 ++++++Text Name='<newline>  '
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/caption-expected-uia-win.txt b/content/test/data/accessibility/html/caption-expected-uia-win.txt
index ba5fe04..55a6af78 100644
--- a/content/test/data/accessibility/html/caption-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/caption-expected-uia-win.txt
@@ -17,4 +17,3 @@
 ++++++++Text Name='Safari' IsControlElement=false
 ++++++DataItem Name='WebKit' GridItem.Column=1 GridItem.ColumnSpan=1 GridItem.Row=2 GridItem.RowSpan=1 GridItem.ContainingGrid='Browser and Engine'
 ++++++++Text Name='WebKit' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/checkbox-name-calc-expected-uia-win.txt b/content/test/data/accessibility/html/checkbox-name-calc-expected-uia-win.txt
index 01cccc0..edda8fb 100644
--- a/content/test/data/accessibility/html/checkbox-name-calc-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/checkbox-name-calc-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++CheckBox Name='LabelledBy3' Toggle.ToggleState='Off'
 ++CheckBox Name='LabelledBy4' Toggle.ToggleState='Off'
 ++CheckBox Toggle.ToggleState='Off'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/cite-expected-uia-win.txt b/content/test/data/accessibility/html/cite-expected-uia-win.txt
index 168a646..0a4ea99 100644
--- a/content/test/data/accessibility/html/cite-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/cite-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++Group IsControlElement=false
 ++++Text Name='The pipe'
 ++++Text Name=' clicked by SomeOne.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/combobox-optgroup-expected-uia-win.txt b/content/test/data/accessibility/html/combobox-optgroup-expected-uia-win.txt
index 63a71be..48a9d6a 100644
--- a/content/test/data/accessibility/html/combobox-optgroup-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/combobox-optgroup-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++++++++ListItem Name='Saab Label' SelectionItem.IsSelected=false
 ++++++++ListItem Name='Mercedes Label' SelectionItem.IsSelected=true
 ++++++++ListItem Name='Audi Label' SelectionItem.IsSelected=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/contenteditable-descendants-expected-uia-win.txt b/content/test/data/accessibility/html/contenteditable-descendants-expected-uia-win.txt
index 108f5bc..ed82d56 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/contenteditable-descendants-expected-uia-win.txt
@@ -21,4 +21,3 @@
 ++++Text Name='Non-editable paragraph.'
 ++Group
 ++++Text Name='Should keep the role but change the state.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-uia-win.txt b/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-uia-win.txt
index 25e26fc..e75c2d3 100644
--- a/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/contenteditable-descendants-with-selection-expected-uia-win.txt
@@ -17,4 +17,3 @@
 ++++++ListItem
 ++++++++Text Name='1. '
 ++++++++Text Name='Editable list item.' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-uia-win.txt b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-uia-win.txt
index b53b59de7..760c6bdd 100644
--- a/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/contenteditable-with-embedded-contenteditables-expected-uia-win.txt
@@ -8,4 +8,3 @@
 ++++++Text Name='But this one is.'
 ++++Group
 ++++++Text Name='So is this one.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/contenteditable-with-no-descendants-expected-uia-win.txt b/content/test/data/accessibility/html/contenteditable-with-no-descendants-expected-uia-win.txt
index 7180b0d5..03278132 100644
--- a/content/test/data/accessibility/html/contenteditable-with-no-descendants-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/contenteditable-with-no-descendants-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++Group Name='title'
 ++Group IsControlElement=false
 ++++Text Name='description'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/dd-expected-uia-win.txt b/content/test/data/accessibility/html/dd-expected-uia-win.txt
index 959d844..2549407 100644
--- a/content/test/data/accessibility/html/dd-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dd-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Text Name='Coffee'
 ++++Text IsControlElement=false
 ++++++Text Name='Black hot drink'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/del-expected-uia-win.txt b/content/test/data/accessibility/html/del-expected-uia-win.txt
index 1e8c418..50bd303 100644
--- a/content/test/data/accessibility/html/del-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/del-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='I am '
 ++++Group IsControlElement=false
 ++++++Text Name='vegetarian'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/details-expected-uia-win.txt b/content/test/data/accessibility/html/details-expected-uia-win.txt
index 372b8f7..8fa186c 100644
--- a/content/test/data/accessibility/html/details-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/details-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++++Text Name='details tag open'
 ++++Group IsControlElement=false
 ++++++Text Name='The details tag with open specifies that the details should be visible (open) to the user.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/dfn-expected-uia-win.txt b/content/test/data/accessibility/html/dfn-expected-uia-win.txt
index c75ba543..4099a4e2 100644
--- a/content/test/data/accessibility/html/dfn-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dfn-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Text Name='Web Browser'
 ++++Text Name=' A computer program with a graphical user interface for displaying HTML files, used to navigate the World Wide Web.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/dialog-expected-uia-win.txt b/content/test/data/accessibility/html/dialog-expected-uia-win.txt
index 37ad3409..68f7175 100644
--- a/content/test/data/accessibility/html/dialog-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dialog-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Pane IsControlElement=false Window.IsModal=false
 ++++Text Name='Text in dialog'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/div-expected-uia-win.txt b/content/test/data/accessibility/html/div-expected-uia-win.txt
index d963f2d5..ccf3cc90 100644
--- a/content/test/data/accessibility/html/div-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/div-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='Unfocusable div'
 ++Group Name='Focusable div'
 ++++Text Name='Focusable div'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/dl-expected-uia-win.txt b/content/test/data/accessibility/html/dl-expected-uia-win.txt
index 0b9cc322..843aff6 100644
--- a/content/test/data/accessibility/html/dl-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dl-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++++++Text Name='Description'
 ++Group
 ++++Text Name='Definition'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/dt-expected-uia-win.txt b/content/test/data/accessibility/html/dt-expected-uia-win.txt
index 959d844..2549407 100644
--- a/content/test/data/accessibility/html/dt-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/dt-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Text Name='Coffee'
 ++++Text IsControlElement=false
 ++++++Text Name='Black hot drink'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/element-class-id-src-attr-expected-uia-win.txt b/content/test/data/accessibility/html/element-class-id-src-attr-expected-uia-win.txt
index c88ec897..bfea8dd 100644
--- a/content/test/data/accessibility/html/element-class-id-src-attr-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/element-class-id-src-attr-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Text Name='Image' ClassName='headerClass'
 ++++Text Name='Image' IsControlElement=false
 ++Image Name='ImageAlt' ClassName='imageClass'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/em-expected-uia-win.txt b/content/test/data/accessibility/html/em-expected-uia-win.txt
index 5ea0eae1..bc76855 100644
--- a/content/test/data/accessibility/html/em-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/em-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='One word is '
 ++++Text Name='emphasized'
 ++++Text Name='.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/embed-expected-uia-win.txt b/content/test/data/accessibility/html/embed-expected-uia-win.txt
index 56da86f..136cb730 100644
--- a/content/test/data/accessibility/html/embed-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/embed-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Pane
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/fieldset-expected-uia-win.txt b/content/test/data/accessibility/html/fieldset-expected-uia-win.txt
index 83b9965..5400322 100644
--- a/content/test/data/accessibility/html/fieldset-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/fieldset-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Group Name='Browser Engines:'
 ++++++Text IsControlElement=false
 ++++++++Text Name='Browser Engines:' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/figcaption-expected-uia-win.txt b/content/test/data/accessibility/html/figcaption-expected-uia-win.txt
index 8409d1af..b404448 100644
--- a/content/test/data/accessibility/html/figcaption-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/figcaption-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Image Name='This is a green box.'
 ++++Text IsControlElement=false
 ++++++Text Name='Fig.1 - A green Box'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/figure-expected-uia-win.txt b/content/test/data/accessibility/html/figure-expected-uia-win.txt
index 80d5f36..6b39590 100644
--- a/content/test/data/accessibility/html/figure-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/figure-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='figure'
 ++++Image Name='Sunspots'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/footer-expected-uia-win.txt b/content/test/data/accessibility/html/footer-expected-uia-win.txt
index 5d5fff4..d8cc1b4 100644
--- a/content/test/data/accessibility/html/footer-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/footer-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='footer' LocalizedLandmarkType='content information'
 ++++Text Name='Footer element'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/footer-expected-uia-win7.txt b/content/test/data/accessibility/html/footer-expected-uia-win7.txt
index a0433b5..0aedab9 100644
--- a/content/test/data/accessibility/html/footer-expected-uia-win7.txt
+++ b/content/test/data/accessibility/html/footer-expected-uia-win7.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='footer'
 ++++Text Name='Footer element'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/footer-inside-other-section-expected-uia-win.txt b/content/test/data/accessibility/html/footer-inside-other-section-expected-uia-win.txt
index 1ef9e077..d3d4913 100644
--- a/content/test/data/accessibility/html/footer-inside-other-section-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/footer-inside-other-section-expected-uia-win.txt
@@ -11,4 +11,3 @@
 ++++Group LocalizedControlType='footer'
 ++++++Group IsControlElement=false
 ++++++++Text Name='footer inside main.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/form-expected-uia-win.txt b/content/test/data/accessibility/html/form-expected-uia-win.txt
index 7801cb85..32f3f57 100644
--- a/content/test/data/accessibility/html/form-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/form-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Button Name='Submit'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/form-validation-message-expected-uia-win.txt b/content/test/data/accessibility/html/form-validation-message-expected-uia-win.txt
index fb5d2d21..4e33eb6 100644
--- a/content/test/data/accessibility/html/form-validation-message-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/form-validation-message-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Text Name='Pet name:'
 ++++Edit Name='Pet name:' IsRequiredForForm=true
 ++Text Name='Please enter pet name'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/frameset-expected-uia-win.txt b/content/test/data/accessibility/html/frameset-expected-uia-win.txt
index 32ef044..47ec438b 100644
--- a/content/test/data/accessibility/html/frameset-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/frameset-expected-uia-win.txt
@@ -16,4 +16,3 @@
 ++++++++Text IsControlElement=false
 ++++++++++Text Name='mark tag'
 ++++++++Text Name='.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/header-expected-uia-win.txt b/content/test/data/accessibility/html/header-expected-uia-win.txt
index 4ac24c2..83c9be43 100644
--- a/content/test/data/accessibility/html/header-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/header-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group LocalizedControlType='header'
 ++++Text Name='Chromium Browser'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/header-inside-other-section-expected-uia-win.txt b/content/test/data/accessibility/html/header-inside-other-section-expected-uia-win.txt
index 9194254..658d233 100644
--- a/content/test/data/accessibility/html/header-inside-other-section-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/header-inside-other-section-expected-uia-win.txt
@@ -11,4 +11,3 @@
 ++++Group LocalizedControlType='header'
 ++++++Group IsControlElement=false
 ++++++++Text Name='Header inside main.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/heading-expected-uia-win.txt b/content/test/data/accessibility/html/heading-expected-uia-win.txt
index c1da12e0..5e37774 100644
--- a/content/test/data/accessibility/html/heading-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/heading-expected-uia-win.txt
@@ -11,4 +11,3 @@
 ++++Text Name='Heading 5' IsControlElement=false
 ++Text Name='Heading 6'
 ++++Text Name='Heading 6' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/hr-expected-uia-win.txt b/content/test/data/accessibility/html/hr-expected-uia-win.txt
index 6ecff90d..59a0b829 100644
--- a/content/test/data/accessibility/html/hr-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/hr-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++Separator
 ++Group IsControlElement=false
 ++++Text Name='After.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/i-expected-uia-win.txt b/content/test/data/accessibility/html/i-expected-uia-win.txt
index d67794e..d75d733 100644
--- a/content/test/data/accessibility/html/i-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/i-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='This is to check '
 ++++Text Name='italic property'
 ++++Text Name=' using i tag.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/iframe-coordinates-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-coordinates-expected-uia-win.txt
index fbdec8ed..2d19938 100644
--- a/content/test/data/accessibility/html/iframe-coordinates-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/iframe-coordinates-expected-uia-win.txt
@@ -13,4 +13,3 @@
 ++++++Document
 ++++++++Group IsControlElement=false
 ++++++++++Button Name='Scrolled Button'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/iframe-cross-process-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-cross-process-expected-uia-win.txt
index 66d83c5..f24f037 100644
--- a/content/test/data/accessibility/html/iframe-cross-process-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/iframe-cross-process-expected-uia-win.txt
@@ -8,4 +8,3 @@
 ++++++++++Text Name='Text in iframe'
 ++Group IsControlElement=false
 ++++Text Name='After frame'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/iframe-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-expected-uia-win.txt
index a2b9659..b322bcbf 100644
--- a/content/test/data/accessibility/html/iframe-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/iframe-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Document Name='Empty iframe'
 ++++++Document
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/iframe-presentational-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-presentational-expected-uia-win.txt
index 6183ef9..4869b37 100644
--- a/content/test/data/accessibility/html/iframe-presentational-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/iframe-presentational-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Group
 ++++++Group
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/iframe-transform-expected-uia-win.txt b/content/test/data/accessibility/html/iframe-transform-expected-uia-win.txt
index e3fd32c..4c69ae2 100644
--- a/content/test/data/accessibility/html/iframe-transform-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/iframe-transform-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Document
 ++++Document
 ++++++Image
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ignored-selection-between-text-expected-uia-win.txt b/content/test/data/accessibility/html/ignored-selection-between-text-expected-uia-win.txt
index a07b593..bc02e8c 100644
--- a/content/test/data/accessibility/html/ignored-selection-between-text-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ignored-selection-between-text-expected-uia-win.txt
@@ -6,4 +6,3 @@
 ++Group IsControlElement=false
 ++++Text Name='after selection'
 ++Text Name='Done'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ignored-selection-expected-uia-win.txt b/content/test/data/accessibility/html/ignored-selection-expected-uia-win.txt
index 82c12758..c424caa8 100644
--- a/content/test/data/accessibility/html/ignored-selection-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ignored-selection-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++Group IsControlElement=false
 ++++Text Name='after selection'
 ++Text Name='Done'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-mac.txt b/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-mac.txt
index a7977fa0..0f075244 100644
--- a/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-mac.txt
+++ b/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-mac.txt
@@ -1,3 +1,2 @@
 AXWebArea
 ++AXStaticText AXValue='Done'
-<-- End-of-file -->
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-uia-win.txt b/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-uia-win.txt
index a44b16b4..98ca2d2 100644
--- a/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ignored-selection-no-unignored-expected-uia-win.txt
@@ -1,3 +1,2 @@
 Document
 ++Text Name='Done'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/img-empty-alt-expected-uia-win.txt b/content/test/data/accessibility/html/img-empty-alt-expected-uia-win.txt
index e52f496..1cc2677 100644
--- a/content/test/data/accessibility/html/img-empty-alt-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/img-empty-alt-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Image IsControlElement=false
 ++++Image
 ++++Image Name='full'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/img-expected-uia-win.txt b/content/test/data/accessibility/html/img-expected-uia-win.txt
index 09b1da33..e14113e 100644
--- a/content/test/data/accessibility/html/img-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/img-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++Text Name=' '
 ++++Text Name=' '
 ++++Image Name='  '
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/img-link-empty-alt-expected-uia-win.txt b/content/test/data/accessibility/html/img-link-empty-alt-expected-uia-win.txt
index b3a85da..c23e5f0 100644
--- a/content/test/data/accessibility/html/img-link-empty-alt-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/img-link-empty-alt-expected-uia-win.txt
@@ -9,4 +9,3 @@
 ++++Hyperlink Name='read'
 ++++++Image
 ++++++Text Name='read' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt b/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
index 6a82693..23a18c4 100644
--- a/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/in-page-links-expected-uia-win.txt
@@ -33,4 +33,3 @@
 ++++++Text Name='Span with content'
 ++Group IsControlElement=false
 ++++Text Name='Paragraph with content'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-button-expected-uia-win.txt b/content/test/data/accessibility/html/input-button-expected-uia-win.txt
index fe57d81..8309aa9a 100644
--- a/content/test/data/accessibility/html/input-button-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-button-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Button Name='Button'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-button-in-menu-expected-uia-win.txt b/content/test/data/accessibility/html/input-button-in-menu-expected-uia-win.txt
index 6833aafd..398507a 100644
--- a/content/test/data/accessibility/html/input-button-in-menu-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-button-in-menu-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++MenuItem Name='Button in menu element'
 ++Menu Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
 ++++MenuItem Name='Button in element with menu role'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-checkbox-expected-uia-win.txt b/content/test/data/accessibility/html/input-checkbox-expected-uia-win.txt
index 6cdfded..b191ea0 100644
--- a/content/test/data/accessibility/html/input-checkbox-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-checkbox-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++CheckBox Name='Checkbox2' Toggle.ToggleState='On'
 ++++CheckBox Name='Checkbox3' Toggle.ToggleState='On'
 ++++CheckBox Name='Checkbox4' Toggle.ToggleState='Indeterminate'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-checkbox-in-menu-expected-uia-win.txt b/content/test/data/accessibility/html/input-checkbox-in-menu-expected-uia-win.txt
index 0ebde5e..9247649 100644
--- a/content/test/data/accessibility/html/input-checkbox-in-menu-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-checkbox-in-menu-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++Menu Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
 ++++CheckBox Name='Checkbox3' Toggle.ToggleState='On'
 ++++CheckBox Name='Checkbox4' Toggle.ToggleState='Indeterminate'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-color-expected-uia-win.txt b/content/test/data/accessibility/html/input-color-expected-uia-win.txt
index e300dff9..334a8db 100644
--- a/content/test/data/accessibility/html/input-color-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-color-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Button LocalizedControlType='color picker' Value.Value='100% red 60% green 0% blue'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-date-expected-uia-win.txt b/content/test/data/accessibility/html/input-date-expected-uia-win.txt
index 024e44e3..3f6a56a 100644
--- a/content/test/data/accessibility/html/input-date-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-date-expected-uia-win.txt
@@ -24,4 +24,3 @@
 ++++++++++Spinner Name='Year When' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=275760.00 RangeValue.Minimum=1.00 RangeValue.Value=2008.00 Value.Value='2008'
 ++++++++++++Text Name='2008'
 ++++++Button Name='Show date picker' ExpandCollapse.ExpandCollapseState='Collapsed'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-date-with-popup-open-multiple-for-win-expected-uia-win.txt b/content/test/data/accessibility/html/input-date-with-popup-open-multiple-for-win-expected-uia-win.txt
index 56c74508e..6b65984 100644
--- a/content/test/data/accessibility/html/input-date-with-popup-open-multiple-for-win-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-date-with-popup-open-multiple-for-win-expected-uia-win.txt
@@ -68,4 +68,3 @@
 ++++++++++++++++++Group IsControlElement=false
 ++++++++++++++++++++Group IsControlElement=false
 ++++++++++++++++++Button Name='Today'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-datetime-expected-uia-win.txt b/content/test/data/accessibility/html/input-datetime-expected-uia-win.txt
index 9abfb60..3051169 100644
--- a/content/test/data/accessibility/html/input-datetime-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-datetime-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Edit Value.Value='1/1/2015 1:00AM'
 ++++Edit Name='Launch' Value.Value='1/1/2015 1:00AM'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-datetime-local-expected-uia-win.txt b/content/test/data/accessibility/html/input-datetime-local-expected-uia-win.txt
index 73555b0c..b3510762 100644
--- a/content/test/data/accessibility/html/input-datetime-local-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-datetime-local-expected-uia-win.txt
@@ -21,4 +21,3 @@
 ++++++++++Spinner Name='AM/PM' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=2.00 RangeValue.Minimum=1.00 RangeValue.Value=0.00 Value.Value='0'
 ++++++++++++Text Name='--'
 ++++++Button Name='Show local date and time picker' ExpandCollapse.ExpandCollapseState='Collapsed'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-email-expected-uia-win.txt b/content/test/data/accessibility/html/input-email-expected-uia-win.txt
index a75ec01..0f305cee 100644
--- a/content/test/data/accessibility/html/input-email-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-email-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document LocalizedControlType='document'
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Edit LocalizedControlType='email' Value.Value='someone@example.com'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-file-expected-uia-win.txt b/content/test/data/accessibility/html/input-file-expected-uia-win.txt
index 1e0b340..4791dbd1 100644
--- a/content/test/data/accessibility/html/input-file-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-file-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Button Name='Choose File'
 ++++++Button Name='Choose File'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-image-expected-uia-win.txt b/content/test/data/accessibility/html/input-image-expected-uia-win.txt
index 7801cb85..32f3f57 100644
--- a/content/test/data/accessibility/html/input-image-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-image-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Button Name='Submit'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-list-expected-uia-win.txt b/content/test/data/accessibility/html/input-list-expected-uia-win.txt
index f671924..45ddce3a 100644
--- a/content/test/data/accessibility/html/input-list-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-list-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text
 ++++++Text Name='Choose a pokemon '
 ++++++ComboBox Name='Choose a pokemon' ExpandCollapse.ExpandCollapseState='LeafNode'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-number-expected-uia-win.txt b/content/test/data/accessibility/html/input-number-expected-uia-win.txt
index 45cdd54c..697b418 100644
--- a/content/test/data/accessibility/html/input-number-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-number-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Spinner RangeValue.IsReadOnly=false RangeValue.LargeChange=10.00 RangeValue.SmallChange=1.00 RangeValue.Maximum=0.00 RangeValue.Minimum=0.00 RangeValue.Value=1.00 Value.Value='1'
 ++++Spinner RangeValue.IsReadOnly=false RangeValue.LargeChange=10.00 RangeValue.SmallChange=1.00 RangeValue.Maximum=10.00 RangeValue.Minimum=5.00 RangeValue.Value=6.00 Value.Value='6'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-password-expected-uia-win.txt b/content/test/data/accessibility/html/input-password-expected-uia-win.txt
index ce0730e5..7123ac8 100644
--- a/content/test/data/accessibility/html/input-password-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-password-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit IsPassword=true Value.Value='••••••'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt
index cc92e69d..8eede08 100644
--- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt
@@ -10,4 +10,3 @@
 ++++++Text Name='label exposed for checkbox '
 ++++++CheckBox Name='label exposed for checkbox' Toggle.ToggleState='Off'
 ++++++Text Name=' '
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-radio-expected-uia-win.txt b/content/test/data/accessibility/html/input-radio-expected-uia-win.txt
index f14091e..7283713 100644
--- a/content/test/data/accessibility/html/input-radio-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-radio-expected-uia-win.txt
@@ -11,4 +11,3 @@
 ++Group IsControlElement=false
 ++++RadioButton Name='Radio5' SelectionItem.IsSelected=false
 ++++RadioButton Name='Radio6' SelectionItem.IsSelected=true
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-radio-in-menu-expected-uia-win.txt b/content/test/data/accessibility/html/input-radio-in-menu-expected-uia-win.txt
index b08d816..f0301d6 100644
--- a/content/test/data/accessibility/html/input-radio-in-menu-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-radio-in-menu-expected-uia-win.txt
@@ -9,4 +9,3 @@
 ++++RadioButton Name='Radio3' SelectionItem.IsSelected=false
 ++++RadioButton SelectionItem.IsSelected=false
 ++++RadioButton SelectionItem.IsSelected=true
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-range-expected-uia-win.txt b/content/test/data/accessibility/html/input-range-expected-uia-win.txt
index 2ee0808e..7db37e3 100644
--- a/content/test/data/accessibility/html/input-range-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-range-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Slider RangeValue.IsReadOnly=false RangeValue.LargeChange=10.00 RangeValue.SmallChange=1.00 RangeValue.Maximum=10.00 RangeValue.Minimum=1.00 RangeValue.Value=5.00 Value.Value='5'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-reset-expected-uia-win.txt b/content/test/data/accessibility/html/input-reset-expected-uia-win.txt
index 90883b4..02273a0 100644
--- a/content/test/data/accessibility/html/input-reset-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-reset-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Edit
 ++++Button Name='Reset'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-search-expected-uia-win.txt b/content/test/data/accessibility/html/input-search-expected-uia-win.txt
index 4bd58fa3..d545640 100644
--- a/content/test/data/accessibility/html/input-search-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-search-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document LocalizedControlType='document'
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Edit LocalizedControlType='search box' Value.Value='Search terms'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-submit-expected-uia-win.txt b/content/test/data/accessibility/html/input-submit-expected-uia-win.txt
index 55a1b26b..2196c3c 100644
--- a/content/test/data/accessibility/html/input-submit-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-submit-expected-uia-win.txt
@@ -12,4 +12,3 @@
 ++++++Image
 ++++++Text Name='Second image button in a form not a valid default button' IsControlElement=false
 ++Button Name='Submit outside of form not a valid default button'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-suggestions-source-element-expected-uia-win.txt b/content/test/data/accessibility/html/input-suggestions-source-element-expected-uia-win.txt
index 9ca5a80a..cd8c68f 100644
--- a/content/test/data/accessibility/html/input-suggestions-source-element-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-suggestions-source-element-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++ComboBox ExpandCollapse.ExpandCollapseState='LeafNode'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-tel-expected-uia-win.txt b/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
index f3b5007..57a06b1 100644
--- a/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-tel-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document LocalizedControlType='document'
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Edit LocalizedControlType='telephone' Value.Value='123-456-7890'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-text-expected-uia-win.txt b/content/test/data/accessibility/html/input-text-expected-uia-win.txt
index 52e66a28..7fb8ed5d 100644
--- a/content/test/data/accessibility/html/input-text-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-text-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Name='Name'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-text-name-calc-expected-uia-win.txt b/content/test/data/accessibility/html/input-text-name-calc-expected-uia-win.txt
index 3be26c3..41d8fbd5 100644
--- a/content/test/data/accessibility/html/input-text-name-calc-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-text-name-calc-expected-uia-win.txt
@@ -9,4 +9,3 @@
 ++Edit Name='Placeholder5' HelpText='Placeholder5'
 ++Edit Name='LabelledBy6' HelpText='Title6'
 ++Edit Name='AriaLabel7' HelpText='Placeholder7'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-text-read-only-expected-uia-win.txt b/content/test/data/accessibility/html/input-text-read-only-expected-uia-win.txt
index 52e66a28..7fb8ed5d 100644
--- a/content/test/data/accessibility/html/input-text-read-only-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-text-read-only-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Name='Name'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-text-value-expected-uia-win.txt b/content/test/data/accessibility/html/input-text-value-expected-uia-win.txt
index 7dc1deb..f9de9a3 100644
--- a/content/test/data/accessibility/html/input-text-value-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-text-value-expected-uia-win.txt
@@ -16,4 +16,3 @@
 ++++Edit Name='l5'
 ++++Edit Name='l6' Value.Value='Value'
 ++++Edit Name='Name' Value.Value='value'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-text-with-selection-expected-uia-win.txt b/content/test/data/accessibility/html/input-text-with-selection-expected-uia-win.txt
index 418bb10..ba8a9bb 100644
--- a/content/test/data/accessibility/html/input-text-with-selection-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-text-with-selection-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Value.Value='Selection'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-time-expected-uia-win.txt b/content/test/data/accessibility/html/input-time-expected-uia-win.txt
index 3755c37..53b5cb1 100644
--- a/content/test/data/accessibility/html/input-time-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-time-expected-uia-win.txt
@@ -24,4 +24,3 @@
 ++++++++++Spinner Name='AM/PM Breakfast' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=2.00 RangeValue.Minimum=1.00 RangeValue.Value=1.00 Value.Value='AM'
 ++++++++++++Text Name='AM'
 ++++++Button Name='Show time picker' ExpandCollapse.ExpandCollapseState='Collapsed'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-types-expected-uia-win.txt b/content/test/data/accessibility/html/input-types-expected-uia-win.txt
index 2f83314..b602363 100644
--- a/content/test/data/accessibility/html/input-types-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-types-expected-uia-win.txt
@@ -50,4 +50,3 @@
 ++++Text
 ++++++Text Name='Url: '
 ++++++Edit Name='Url:'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-url-expected-uia-win.txt b/content/test/data/accessibility/html/input-url-expected-uia-win.txt
index 00454b26..25228dd1 100644
--- a/content/test/data/accessibility/html/input-url-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-url-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document LocalizedControlType='document'
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++Edit LocalizedControlType='url' Value.Value='example.com'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/input-week-expected-uia-win.txt b/content/test/data/accessibility/html/input-week-expected-uia-win.txt
index 7a93e8d..3d8c20f 100644
--- a/content/test/data/accessibility/html/input-week-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/input-week-expected-uia-win.txt
@@ -10,4 +10,3 @@
 ++++++++++Spinner Name='Year' RangeValue.IsReadOnly=false RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=275760.00 RangeValue.Minimum=1.00 RangeValue.Value=0.00 Value.Value='0'
 ++++++++++++Text Name='----'
 ++++++Button Name='Show week picker' ExpandCollapse.ExpandCollapseState='Collapsed'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ins-expected-uia-win.txt b/content/test/data/accessibility/html/ins-expected-uia-win.txt
index 930cfb4..8cb0cbe0 100644
--- a/content/test/data/accessibility/html/ins-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ins-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++Group IsControlElement=false
 ++++++Text Name='Chrome'
 ++++Text Name='!'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/label-expected-uia-win.txt b/content/test/data/accessibility/html/label-expected-uia-win.txt
index 1c39d278..ef51a7f 100644
--- a/content/test/data/accessibility/html/label-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/label-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Text
 ++++++Text Name='Label'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/landmark-expected-uia-win.txt b/content/test/data/accessibility/html/landmark-expected-uia-win.txt
index 8d58982..f58663d 100644
--- a/content/test/data/accessibility/html/landmark-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/landmark-expected-uia-win.txt
@@ -147,4 +147,3 @@
 ++Group
 ++++Group
 ++++++Text Name='This should NOT have footer role.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/legend-expected-uia-win.txt b/content/test/data/accessibility/html/legend-expected-uia-win.txt
index cceb92b6..1e5da53 100644
--- a/content/test/data/accessibility/html/legend-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/legend-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++++Edit
 ++++++Text Name=' Rendering Engine: ' IsControlElement=false
 ++++++Edit
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/li-expected-uia-win.txt b/content/test/data/accessibility/html/li-expected-uia-win.txt
index 4c07ffe2..9679367 100644
--- a/content/test/data/accessibility/html/li-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/li-expected-uia-win.txt
@@ -12,4 +12,3 @@
 ++++++Group
 ++++++++Text Name='• ' IsControlElement=false
 ++++++Text Name='Item 3' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/link-inside-heading-expected-uia-win.txt b/content/test/data/accessibility/html/link-inside-heading-expected-uia-win.txt
index 1ae6278..316b3a7 100644
--- a/content/test/data/accessibility/html/link-inside-heading-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/link-inside-heading-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Text Name='Link In Heading'
 ++++Hyperlink Name='Link In Heading'
 ++++++Text Name='Link In Heading' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-expected-uia-win.txt b/content/test/data/accessibility/html/list-expected-uia-win.txt
index f4d9449..06d3906 100644
--- a/content/test/data/accessibility/html/list-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/list-expected-uia-win.txt
@@ -21,4 +21,3 @@
 ++++Text Name=' '
 ++++ListItem
 ++++++Text Name='toe' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win.txt b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win.txt
index ce62b08..d203d83 100644
--- a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win.txt
@@ -13,4 +13,3 @@
 ++++++Text Name='Level 1, item 7 of set size 2' PositionInSet=0 SizeOfSet=0 Level=0
 ++++ListItem PositionInSet=8 SizeOfSet=3 Level=1
 ++++++Text Name='Level 1, item 8 of set size 3' PositionInSet=0 SizeOfSet=0 Level=0
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win7.txt b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win7.txt
index 5a91cb4..4f6ba84 100644
--- a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win7.txt
+++ b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-expected-uia-win7.txt
@@ -13,4 +13,3 @@
 ++++++Text Name='Level 1, item 7 of set size 2'
 ++++ListItem
 ++++++Text Name='Level 1, item 8 of set size 3'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win.txt b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win.txt
index 64ff21399..8ded380 100644
--- a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win.txt
@@ -30,4 +30,3 @@
 ++++List PositionInSet=0 SizeOfSet=5 Level=0
 ++++++ListItem PositionInSet=5 SizeOfSet=5 Level=2
 ++++++++Text Name='Level Unspecified, aria-setsize attribute does not exist, item 5 of set size 5' PositionInSet=0 SizeOfSet=0 Level=0
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win7.txt b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win7.txt
index 018941b..7422339 100644
--- a/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win7.txt
+++ b/content/test/data/accessibility/html/list-item-aria-setsize-unknown-flattened-expected-uia-win7.txt
@@ -30,4 +30,3 @@
 ++++List
 ++++++ListItem
 ++++++++Text Name='Level Unspecified, aria-setsize attribute does not exist, item 5 of set size 5'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/list-markers-expected-uia-win.txt b/content/test/data/accessibility/html/list-markers-expected-uia-win.txt
index 1f5bdd0..25ed2d1f 100644
--- a/content/test/data/accessibility/html/list-markers-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/list-markers-expected-uia-win.txt
@@ -18,4 +18,3 @@
 ++++++Text Name='Some ' IsControlElement=false
 ++++++Text Name='more' IsControlElement=false
 ++++++Text Name=' text.' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/main-expected-uia-win.txt b/content/test/data/accessibility/html/main-expected-uia-win.txt
index 4c58089..62a1457a 100644
--- a/content/test/data/accessibility/html/main-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/main-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='This is main element.'
 ++Group
 ++++Text Name='This is an ARIA role main.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/mark-expected-uia-win.txt b/content/test/data/accessibility/html/mark-expected-uia-win.txt
index 93247766..c9a7bc5 100644
--- a/content/test/data/accessibility/html/mark-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/mark-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++Text LocalizedControlType='highlight' IsControlElement=false
 ++++++Text Name='mark tag'
 ++++Text Name='.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/math-expected-uia-win.txt b/content/test/data/accessibility/html/math-expected-uia-win.txt
index ce60f2c..e58ec51 100644
--- a/content/test/data/accessibility/html/math-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/math-expected-uia-win.txt
@@ -8,4 +8,3 @@
 ++++Group IsControlElement=false
 ++++++Text Name='b'
 ++++++Text Name='2'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/meter-expected-uia-win.txt b/content/test/data/accessibility/html/meter-expected-uia-win.txt
index d3f72020..4608cfba 100644
--- a/content/test/data/accessibility/html/meter-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/meter-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document LocalizedControlType='document'
 ++Group LocalizedControlType='group' IsControlElement=false
 ++++ProgressBar LocalizedControlType='meter' RangeValue.IsReadOnly=true RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=10.00 RangeValue.Minimum=1.00 RangeValue.Value=2.00 Value.Value='2'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/modal-dialog-closed-expected-uia-win.txt b/content/test/data/accessibility/html/modal-dialog-closed-expected-uia-win.txt
index 5945bae..1ae2e9fe 100644
--- a/content/test/data/accessibility/html/modal-dialog-closed-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/modal-dialog-closed-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++++List Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
 ++++++++ListItem Name='This should be in the tree.' SelectionItem.IsSelected=true
 ++Button Value.Value='0% red 0% green 0% blue'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/navigation-expected-uia-win.txt b/content/test/data/accessibility/html/navigation-expected-uia-win.txt
index a73a099a..950a8df8 100644
--- a/content/test/data/accessibility/html/navigation-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/navigation-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group
 ++++Hyperlink Name='Don't click on me'
 ++++++Text Name='Don't click on me' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/node-changed-crash-in-editable-text-expected-uia-win.txt b/content/test/data/accessibility/html/node-changed-crash-in-editable-text-expected-uia-win.txt
index 7ca663b..98ccfe4 100644
--- a/content/test/data/accessibility/html/node-changed-crash-in-editable-text-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/node-changed-crash-in-editable-text-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group Name='Done'
 ++++Group
 ++++Group
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/object-expected-uia-win.txt b/content/test/data/accessibility/html/object-expected-uia-win.txt
index 8a379cf..b4a1703 100644
--- a/content/test/data/accessibility/html/object-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/object-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Document
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/offscreen-iframe-expected-uia-win.txt b/content/test/data/accessibility/html/offscreen-iframe-expected-uia-win.txt
index f989df8..6ec11cc 100644
--- a/content/test/data/accessibility/html/offscreen-iframe-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/offscreen-iframe-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Document
 ++++++++Group Name='iframe_onscreen'
 ++++++++Group Name='iframe_offscreen'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/offscreen-select-expected-uia-win.txt b/content/test/data/accessibility/html/offscreen-select-expected-uia-win.txt
index 78ec07d..271dd48 100644
--- a/content/test/data/accessibility/html/offscreen-select-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/offscreen-select-expected-uia-win.txt
@@ -9,4 +9,3 @@
 ++++++ListItem Name='Offscreen 1' SelectionItem.IsSelected=true
 ++++++ListItem Name='Offscreen 2' SelectionItem.IsSelected=false
 ++++++ListItem Name='Offscreen 3' SelectionItem.IsSelected=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ol-expected-uia-win.txt b/content/test/data/accessibility/html/ol-expected-uia-win.txt
index 2c3aa2dd..1dc05bc 100644
--- a/content/test/data/accessibility/html/ol-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ol-expected-uia-win.txt
@@ -25,4 +25,3 @@
 ++++++Group
 ++++++++Text Name='12. ' IsControlElement=false
 ++++++Text Name='Windows' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/optgroup-expected-uia-win.txt b/content/test/data/accessibility/html/optgroup-expected-uia-win.txt
index 2cb7b26d..019326a 100644
--- a/content/test/data/accessibility/html/optgroup-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/optgroup-expected-uia-win.txt
@@ -13,4 +13,3 @@
 ++++++ListItem Name='Two' IsEnabled=false
 ++++++ListItem Name='Three' IsEnabled=false
 ++++++ListItem Name='Four' IsEnabled=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/output-expected-uia-win.txt b/content/test/data/accessibility/html/output-expected-uia-win.txt
index 8c4c31d8..1c87df1 100644
--- a/content/test/data/accessibility/html/output-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/output-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Spinner RangeValue.IsReadOnly=false RangeValue.LargeChange=10.00 RangeValue.SmallChange=1.00 RangeValue.Maximum=0.00 RangeValue.Minimum=0.00 RangeValue.Value=0.00
 ++++Text Name=' ='
 ++++StatusBar LocalizedControlType='output' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/p-expected-uia-win.txt b/content/test/data/accessibility/html/p-expected-uia-win.txt
index f0bda32..2e7ccfc 100644
--- a/content/test/data/accessibility/html/p-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/p-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++Group IsControlElement=false
 ++++Text Name='Paragraph'
 ++Text Name='After'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/portal-expected-uia-win.txt b/content/test/data/accessibility/html/portal-expected-uia-win.txt
index a764ebe..c33b658f 100644
--- a/content/test/data/accessibility/html/portal-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/portal-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Document Name='Text in iframe'
 ++Group IsControlElement=false
 ++++Text Name='After portal'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/portal-name-from-text-expected-auralinux.txt b/content/test/data/accessibility/html/portal-name-from-text-expected-auralinux.txt
index 2e9aeed5..f9115ef 100644
--- a/content/test/data/accessibility/html/portal-name-from-text-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/portal-name-from-text-expected-auralinux.txt
@@ -2,4 +2,3 @@
 ++[section]
 ++++[push button] name='Text in iframe'
 ++++++[document web] name='Text in iframe'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/portal-name-from-text-expected-blink.txt b/content/test/data/accessibility/html/portal-name-from-text-expected-blink.txt
index 020b059..405b4c4 100644
--- a/content/test/data/accessibility/html/portal-name-from-text-expected-blink.txt
+++ b/content/test/data/accessibility/html/portal-name-from-text-expected-blink.txt
@@ -2,4 +2,3 @@
 ++genericContainer
 ++++portal
 ++++++rootWebArea name='Text in iframe'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/pre-expected-uia-win.txt b/content/test/data/accessibility/html/pre-expected-uia-win.txt
index b2a6f5f..4fabf81 100644
--- a/content/test/data/accessibility/html/pre-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/pre-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++Text Name='This test is to check   pre<newline>formatting.'
 ++Group IsControlElement=false
 ++++Text Name='This test is to check pre formatting.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/progress-expected-uia-win.txt b/content/test/data/accessibility/html/progress-expected-uia-win.txt
index 3c2af83..dd08f429 100644
--- a/content/test/data/accessibility/html/progress-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/progress-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++ProgressBar RangeValue.IsReadOnly=true RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=100.00 RangeValue.Minimum=0.00 RangeValue.Value=22.00 Value.Value='22'
 ++++ProgressBar RangeValue.IsReadOnly=true RangeValue.LargeChange=0.00 RangeValue.SmallChange=0.00 RangeValue.Maximum=1.00 RangeValue.Minimum=0.00 RangeValue.Value=0.00
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/q-expected-uia-win.txt b/content/test/data/accessibility/html/q-expected-uia-win.txt
index 7fb9316..f829a962 100644
--- a/content/test/data/accessibility/html/q-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/q-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Text Name='Chromium Blink'
 ++++Text Name='"'
 ++++Text Name=' based browser.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ruby-expected-uia-win.txt b/content/test/data/accessibility/html/ruby-expected-uia-win.txt
index 54296ed..18fc8a4 100644
--- a/content/test/data/accessibility/html/ruby-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ruby-expected-uia-win.txt
@@ -4,4 +4,3 @@
 ++++++Text IsControlElement=false
 ++++++++Text Name='ruby text'
 ++++++Text Name='ruby base'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/scrollable-expected-uia-win.txt b/content/test/data/accessibility/html/scrollable-expected-uia-win.txt
index 0aa0f99b..ffebe6e 100644
--- a/content/test/data/accessibility/html/scrollable-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/scrollable-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='not scrollable'
 ++Group Name='x' Scroll.HorizontalScrollPercent=0.00 Scroll.HorizontalViewSize=50.00 Scroll.HorizontallyScrollable=true Scroll.VerticalScrollPercent=-1.00 Scroll.VerticalViewSize=100.00 Scroll.VerticallyScrollable=false
 ++Group Name='y' Scroll.HorizontalScrollPercent=-1.00 Scroll.HorizontalViewSize=100.00 Scroll.HorizontallyScrollable=false Scroll.VerticalScrollPercent=0.00 Scroll.VerticalViewSize=50.00 Scroll.VerticallyScrollable=true
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/scrollable-textarea-expected-uia-win.txt b/content/test/data/accessibility/html/scrollable-textarea-expected-uia-win.txt
index b2dc4fd..0d030ad8 100644
--- a/content/test/data/accessibility/html/scrollable-textarea-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/scrollable-textarea-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group IsControlElement=false
 ++++Edit Value.Value='little'
 ++++Edit Value.Value='lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text+ lots+of+text'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/section-expected-uia-win.txt b/content/test/data/accessibility/html/section-expected-uia-win.txt
index a15a2b7..91224b28 100644
--- a/content/test/data/accessibility/html/section-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/section-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='This is a section element.'
 ++Group LocalizedControlType='section' Name='section'
 ++++Text Name='This is a named section element.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/select-expected-uia-win.txt b/content/test/data/accessibility/html/select-expected-uia-win.txt
index c605a56..50c8276 100644
--- a/content/test/data/accessibility/html/select-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/select-expected-uia-win.txt
@@ -23,4 +23,3 @@
 ++++++ListItem Name='Option 1' SelectionItem.IsSelected=false
 ++++++ListItem Name='Option 2' SelectionItem.IsSelected=false
 ++++++ListItem Name='Option 3' SelectionItem.IsSelected=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/selection-container-expected-uia-win.txt b/content/test/data/accessibility/html/selection-container-expected-uia-win.txt
index f8a2d6db..3416c2d 100644
--- a/content/test/data/accessibility/html/selection-container-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/selection-container-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++++ListItem Name='Two' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='selection_list'
 ++++++ListItem Name='Three' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='selection_list'
 ++++++ListItem Name='Four' SelectionItem.IsSelected=false SelectionItem.SelectionContainer='selection_list'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/span-expected-uia-win.txt b/content/test/data/accessibility/html/span-expected-uia-win.txt
index 2815441..2e727e9 100644
--- a/content/test/data/accessibility/html/span-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/span-expected-uia-win.txt
@@ -68,4 +68,3 @@
 ++++Text Name='K8. Keep'
 ++++Text Name=' '
 ++++Text Name='space'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/sub-expected-uia-win.txt b/content/test/data/accessibility/html/sub-expected-uia-win.txt
index 13d3317..ca9e205 100644
--- a/content/test/data/accessibility/html/sub-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/sub-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='This text contains '
 ++++Text Name='subscript'
 ++++Text Name=' text.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/summary-expected-uia-win.txt b/content/test/data/accessibility/html/summary-expected-uia-win.txt
index c8b1315c..5737814 100644
--- a/content/test/data/accessibility/html/summary-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/summary-expected-uia-win.txt
@@ -2,4 +2,3 @@
 ++Group
 ++++Button Name='details tag' ExpandCollapse.ExpandCollapseState='Collapsed'
 ++++++Text Name='details tag'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/sup-expected-uia-win.txt b/content/test/data/accessibility/html/sup-expected-uia-win.txt
index b11f90f..53eba64f 100644
--- a/content/test/data/accessibility/html/sup-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/sup-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='This text contains'
 ++++Text Name='superscript'
 ++++Text Name='text.'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/svg-expected-uia-win.txt b/content/test/data/accessibility/html/svg-expected-uia-win.txt
index 08454be..ab8d00b 100644
--- a/content/test/data/accessibility/html/svg-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/svg-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Image Name='svg' HelpText='SVG Title Tag'
 ++++++Group IsControlElement=false
 ++++++++Text Name='Test'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/table-focusable-sections-expected-uia-win.txt b/content/test/data/accessibility/html/table-focusable-sections-expected-uia-win.txt
index ca8790ef..7710b401 100644
--- a/content/test/data/accessibility/html/table-focusable-sections-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/table-focusable-sections-expected-uia-win.txt
@@ -23,4 +23,3 @@
 ++++++++++Text Name='12' IsControlElement=false
 ++++++++DataItem Name='3' GridItem.Column=1 GridItem.ColumnSpan=1 GridItem.Row=3 GridItem.RowSpan=1
 ++++++++++Text Name='3' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/table-layout-expected-uia-win.txt b/content/test/data/accessibility/html/table-layout-expected-uia-win.txt
index 0a8c4b5..808887c 100644
--- a/content/test/data/accessibility/html/table-layout-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/table-layout-expected-uia-win.txt
@@ -21,4 +21,3 @@
 ++++++++Text Name='8' IsControlElement=false
 ++++++DataItem Name='9' GridItem.Column=2 GridItem.ColumnSpan=1 GridItem.Row=2 GridItem.RowSpan=1
 ++++++++Text Name='9' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/table-presentation-expected-uia-win.txt b/content/test/data/accessibility/html/table-presentation-expected-uia-win.txt
index cbb3a621..f939a808 100644
--- a/content/test/data/accessibility/html/table-presentation-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/table-presentation-expected-uia-win.txt
@@ -7,4 +7,3 @@
 ++++Text Name='4'
 ++Group IsControlElement=false
 ++++Text Name='5'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/table-th-colheader-expected-uia-win.txt b/content/test/data/accessibility/html/table-th-colheader-expected-uia-win.txt
index 53e879b..111b354 100644
--- a/content/test/data/accessibility/html/table-th-colheader-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/table-th-colheader-expected-uia-win.txt
@@ -10,4 +10,3 @@
 ++++++++Text Name='Jill' IsControlElement=false
 ++++++DataItem Name='Smith' GridItem.Column=1 GridItem.ColumnSpan=1 GridItem.Row=1 GridItem.RowSpan=1
 ++++++++Text Name='Smith' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/table-th-rowheader-expected-uia-win.txt b/content/test/data/accessibility/html/table-th-rowheader-expected-uia-win.txt
index 63e08936..e6d6bee 100644
--- a/content/test/data/accessibility/html/table-th-rowheader-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/table-th-rowheader-expected-uia-win.txt
@@ -10,4 +10,3 @@
 ++++++++Text Name='Lastname' IsControlElement=false
 ++++++DataItem Name='Smith' GridItem.Column=1 GridItem.ColumnSpan=1 GridItem.Row=1 GridItem.RowSpan=1
 ++++++++Text Name='Smith' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/textarea-expected-uia-win.txt b/content/test/data/accessibility/html/textarea-expected-uia-win.txt
index 8375af8..412d53e 100644
--- a/content/test/data/accessibility/html/textarea-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/textarea-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Value.Value='The <newline>textarea tag  defines a multi-line text input control.<newline>'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/textarea-read-only-expected-uia-win.txt b/content/test/data/accessibility/html/textarea-read-only-expected-uia-win.txt
index a8b62c0..a728828 100644
--- a/content/test/data/accessibility/html/textarea-read-only-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/textarea-read-only-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Value.Value='The textarea tag defines a multi-line text input control.<newline>'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/textarea-with-selection-expected-uia-win.txt b/content/test/data/accessibility/html/textarea-with-selection-expected-uia-win.txt
index a8b62c0..a728828 100644
--- a/content/test/data/accessibility/html/textarea-with-selection-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/textarea-with-selection-expected-uia-win.txt
@@ -1,4 +1,3 @@
 Document
 ++Group IsControlElement=false
 ++++Edit Value.Value='The textarea tag defines a multi-line text input control.<newline>'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/time-expected-uia-win.txt b/content/test/data/accessibility/html/time-expected-uia-win.txt
index f00991d6..905bae09 100644
--- a/content/test/data/accessibility/html/time-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/time-expected-uia-win.txt
@@ -5,4 +5,3 @@
 ++++Text Name=' '
 ++++Text LocalizedControlType='time'
 ++++++Text Name='Valentines day'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/title-expected-uia-win.txt b/content/test/data/accessibility/html/title-expected-uia-win.txt
index 8b05cda4..f2760f9 100644
--- a/content/test/data/accessibility/html/title-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/title-expected-uia-win.txt
@@ -1,2 +1 @@
 Document Name='Title of the document'
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/ul-expected-uia-win.txt b/content/test/data/accessibility/html/ul-expected-uia-win.txt
index 4c07ffe2..9679367 100644
--- a/content/test/data/accessibility/html/ul-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/ul-expected-uia-win.txt
@@ -12,4 +12,3 @@
 ++++++Group
 ++++++++Text Name='• ' IsControlElement=false
 ++++++Text Name='Item 3' IsControlElement=false
-<-- End-of-file -->
diff --git a/content/test/data/accessibility/html/wbr-expected-uia-win.txt b/content/test/data/accessibility/html/wbr-expected-uia-win.txt
index cec2de9..4ab988a 100644
--- a/content/test/data/accessibility/html/wbr-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/wbr-expected-uia-win.txt
@@ -3,4 +3,3 @@
 ++++Text Name='Supercali'
 ++++Text Name='fragilistic'
 ++++Text Name='expialidocious'
-<-- End-of-file -->
diff --git a/content/test/render_document_feature.cc b/content/test/render_document_feature.cc
new file mode 100644
index 0000000..39183b45
--- /dev/null
+++ b/content/test/render_document_feature.cc
@@ -0,0 +1,30 @@
+// 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 "content/test/render_document_feature.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "content/common/content_navigation_policy.h"
+#include "content/public/common/content_features.h"
+
+namespace content {
+
+void InitAndEnableRenderDocumentFeature(
+    base::test::ScopedFeatureList* feature_list,
+    std::string level) {
+  std::map<std::string, std::string> parameters;
+  parameters[kRenderDocumentLevelParameterName] = level;
+  feature_list->InitAndEnableFeatureWithParameters(features::kRenderDocument,
+                                                   parameters);
+}
+
+std::vector<std::string> RenderDocumentFeatureLevelValues() {
+  return {
+      GetRenderDocumentLevelName(RenderDocumentLevel::kDisabled),
+      GetRenderDocumentLevelName(RenderDocumentLevel::kCrashedFrame),
+      // TODO(https://crbug.com/936696): Add kSubframe when tests are passing.
+  };
+}
+
+}  // namespace content
diff --git a/content/test/render_document_feature.h b/content/test/render_document_feature.h
new file mode 100644
index 0000000..7606001
--- /dev/null
+++ b/content/test/render_document_feature.h
@@ -0,0 +1,30 @@
+// 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 CONTENT_TEST_RENDER_DOCUMENT_FEATURE_H_
+#define CONTENT_TEST_RENDER_DOCUMENT_FEATURE_H_
+
+#include <string>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace test {
+class ScopedFeatureList;
+}  // namespace test
+}  // namespace base
+
+namespace content {
+
+void InitAndEnableRenderDocumentFeature(
+    base::test::ScopedFeatureList* feature_list,
+    std::string level);
+
+// The list of values to test for the "level" parameter.
+std::vector<std::string> RenderDocumentFeatureLevelValues();
+
+}  // namespace content
+
+#endif  // CONTENT_TEST_RENDER_DOCUMENT_FEATURE_H_
diff --git a/docs/android_debugging_instructions.md b/docs/android_debugging_instructions.md
index 3596bf2d..85476a6 100644
--- a/docs/android_debugging_instructions.md
+++ b/docs/android_debugging_instructions.md
@@ -222,19 +222,13 @@
 **Googlers Only**: For official build mapping files, see
 [go/chromejavadeobfuscation](https://goto.google.com/chromejavadeobfuscation).
 
-Once you have a .mapping file, build the `java_deobfuscate` tool:
-
-```shell
-ninja -C out/Default java_deobfuscate
-```
-
-Then run it via:
+Once you have a .mapping file:
 
 ```shell
 # For a file:
-out/Default/bin/java_deobfuscate PROGUARD_MAPPING_FILE.mapping < FILE
+build/android/stacktrace/java_deobfuscate.py PROGUARD_MAPPING_FILE.mapping < FILE
 # For logcat:
-adb logcat | out/Default/bin/java_deobfuscate PROGUARD_MAPPING_FILE.mapping
+adb logcat | build/android/stacktrace/java_deobfuscate.py PROGUARD_MAPPING_FILE.mapping
 ```
 
 ## Get WebKit code to output to the adb log
diff --git a/docs/testing/android_test_instructions.md b/docs/testing/android_test_instructions.md
index b728075..ec2786c0 100644
--- a/docs/testing/android_test_instructions.md
+++ b/docs/testing/android_test_instructions.md
@@ -265,7 +265,7 @@
 If running with `is_debug=false`, Java stacks from logcat need to be fixed up:
 
 ```shell
-out/Release/bin/java_deobfuscate out/Release/apks/ChromePublicTest.apk.mapping < stacktrace.txt
+build/android/stacktrace/java_deobfuscate.py out/Release/apks/ChromePublicTest.apk.mapping < stacktrace.txt
 ```
 
 Any stacks produced by test runner output will already be deobfuscated.
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index addc904..d2aefe5 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1522,6 +1522,7 @@
   PASSWORDSPRIVATE_STOPPASSWORDCHECK = 1459,
   PASSWORDSPRIVATE_GETPASSWORDCHECKSTATUS = 1460,
   TERMINALPRIVATE_OPENVMSHELLPROCESS = 1461,
+  PASSWORDSPRIVATE_OPTINFORACCOUNTSTORAGE = 1462,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/gpu/command_buffer/service/shared_image_backing_ozone.cc b/gpu/command_buffer/service/shared_image_backing_ozone.cc
index 25474b4..5354bf4 100644
--- a/gpu/command_buffer/service/shared_image_backing_ozone.cc
+++ b/gpu/command_buffer/service/shared_image_backing_ozone.cc
@@ -24,6 +24,7 @@
 #include "gpu/command_buffer/service/shared_image_manager.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "gpu/command_buffer/service/shared_image_representation_gl_ozone.h"
+#include "gpu/command_buffer/service/shared_image_representation_skia_gl.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/buffer_types.h"
@@ -144,6 +145,23 @@
     SharedImageManager* manager,
     MemoryTypeTracker* tracker,
     scoped_refptr<SharedContextState> context_state) {
+  if (context_state->GrContextIsGL()) {
+    auto gl_representation = ProduceGLTexture(manager, tracker);
+    if (!gl_representation) {
+      LOG(ERROR) << "SharedImageBackingOzone::ProduceSkia failed to create GL "
+                    "representation";
+      return nullptr;
+    }
+    auto skia_representation = SharedImageRepresentationSkiaGL::Create(
+        std::move(gl_representation), std::move(context_state), manager, this,
+        tracker);
+    if (!skia_representation) {
+      LOG(ERROR) << "SharedImageBackingOzone::ProduceSkia failed to create "
+                    "Skia representation";
+      return nullptr;
+    }
+    return skia_representation;
+  }
   NOTIMPLEMENTED_LOG_ONCE();
   return nullptr;
 }
diff --git a/gpu/vulkan/vulkan_fence_helper.cc b/gpu/vulkan/vulkan_fence_helper.cc
index 224e474..502436a8 100644
--- a/gpu/vulkan/vulkan_fence_helper.cc
+++ b/gpu/vulkan/vulkan_fence_helper.cc
@@ -241,8 +241,10 @@
   // recover from this.
   CHECK(result == VK_SUCCESS || result == VK_ERROR_DEVICE_LOST);
   bool device_lost = result == VK_ERROR_DEVICE_LOST;
-  if (!device_lost)
-    current_generation_ = next_generation_ - 1;
+
+  // We're going to destroy all fences below, so we should consider them as
+  // passed.
+  current_generation_ = next_generation_ - 1;
 
   // Run all cleanup tasks. Create a temporary vector of tasks to run to avoid
   // reentrancy issues.
diff --git a/gpu/vulkan/vulkan_instance.cc b/gpu/vulkan/vulkan_instance.cc
index f647f27c1..b1eddae 100644
--- a/gpu/vulkan/vulkan_instance.cc
+++ b/gpu/vulkan/vulkan_instance.cc
@@ -237,15 +237,22 @@
   }
 #endif
 
-  CollectInfo();
+  if (!CollectInfo())
+    return false;
   return true;
 }
 
-void VulkanInstance::CollectInfo() {
+bool VulkanInstance::CollectInfo() {
   uint32_t count = 0;
   VkResult result = vkEnumeratePhysicalDevices(vk_instance_, &count, nullptr);
   if (result != VK_SUCCESS) {
     DLOG(ERROR) << "vkEnumeratePhysicalDevices failed: " << result;
+    return false;
+  }
+
+  if (!count) {
+    DLOG(ERROR) << "vkEnumeratePhysicalDevices returns zero device.";
+    return false;
   }
 
   std::vector<VkPhysicalDevice> physical_devices(count);
@@ -253,7 +260,7 @@
       vkEnumeratePhysicalDevices(vk_instance_, &count, physical_devices.data());
   if (VK_SUCCESS != result) {
     DLOG(ERROR) << "vkEnumeratePhysicalDevices() failed: " << result;
-    return;
+    return false;
   }
 
   vulkan_info_.physical_devices.reserve(count);
@@ -307,6 +314,7 @@
                                                info.queue_families.data());
     }
   }
+  return true;
 }
 
 void VulkanInstance::Destroy() {
diff --git a/gpu/vulkan/vulkan_instance.h b/gpu/vulkan/vulkan_instance.h
index bd0092e..ad4497d3b 100644
--- a/gpu/vulkan/vulkan_instance.h
+++ b/gpu/vulkan/vulkan_instance.h
@@ -35,7 +35,7 @@
   VkInstance vk_instance() { return vk_instance_; }
 
  private:
-  void CollectInfo();
+  bool CollectInfo();
   void Destroy();
 
   VulkanInfo vulkan_info_;
diff --git a/infra/config/consoles/main-m80.star b/infra/config/consoles/main-m80.star
index 8956955..f4c4395 100644
--- a/infra/config/consoles/main-m80.star
+++ b/infra/config/consoles/main-m80.star
@@ -6,7 +6,7 @@
     # TODO(gbeaty) Define the main consoles inside the respective versioned
     # directories once their contents are stablilized
     refs = ['refs/branch-heads/3987'],
-    title = 'Chromium Stable Console',
+    title = 'Chromium M80 Console',
     entries = [
         luci.console_view_entry(
             builder = 'ci-m80/Linux Builder',
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 1887a91..2b975170 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -11147,7 +11147,7 @@
 >
 consoles: <
   id: "main-m80"
-  name: "Chromium Stable Console"
+  name: "Chromium M80 Console"
   repo_url: "https://chromium.googlesource.com/chromium/src"
   refs: "regexp:refs/branch-heads/3987"
   manifest_name: "REVISION"
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn
index b356644..ce08bf87 100644
--- a/ios/chrome/browser/flags/BUILD.gn
+++ b/ios/chrome/browser/flags/BUILD.gn
@@ -26,6 +26,7 @@
     "//components/omnibox/common",
     "//components/password_manager/core/common",
     "//components/payments/core",
+    "//components/safe_browsing/core:features",
     "//components/search_provider_logos",
     "//components/security_state/core",
     "//components/send_tab_to_self",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 1b3094a..0c33cc33 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -39,6 +39,7 @@
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/payments/core/features.h"
+#include "components/safe_browsing/core/features.h"
 #include "components/security_state/core/features.h"
 #include "components/send_tab_to_self/features.h"
 #include "components/signin/core/browser/account_reconcilor.h"
@@ -577,6 +578,9 @@
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(
          web::features::kIOSLookalikeUrlNavigationSuggestionsUI)},
+    {"safe-browsing-available", flag_descriptions::kSafeBrowsingAvailableName,
+     flag_descriptions::kSafeBrowsingAvailableDescription, flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(safe_browsing::kSafeBrowsingAvailableOnIOS)},
 };
 
 // Add all switches from experimental flags to |command_line|.
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index 21c88acf..3d38215 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -327,6 +327,11 @@
     "When enabled, the first time the renderer crashes, the page is reloaded "
     "instead of showing the SadTab";
 
+const char kSafeBrowsingAvailableName[] = "Make Safe Browsing available";
+const char kSafeBrowsingAvailableDescription[] =
+    "When enabled, navigation URLs are compared to Safe Browsing blocklists, "
+    "subject to an opt-out preference.";
+
 const char kSaveCardInfobarMessagesUIName[] = "Save Card Infobar Messages UI";
 const char kSaveCardInfobarMessagesUIDescription[] =
     "When enabled, Save Card Infobar uses the new Messages UI.";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 16b0b8d5..054474d4 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -279,6 +279,10 @@
 extern const char kReloadSadTabName[];
 extern const char kReloadSadTabDescription[];
 
+// Title and description for the flag that makes Safe Browsing available.
+extern const char kSafeBrowsingAvailableName[];
+extern const char kSafeBrowsingAvailableDescription[];
+
 // Title and description for the flag that enables Messages UI on
 // SaveCard Infobars.
 extern const char kSaveCardInfobarMessagesUIName[];
diff --git a/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_coordinator.mm
index 3fdfd2c..2671b53b 100644
--- a/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/advanced_settings_signin/advanced_settings_signin_coordinator.mm
@@ -62,13 +62,12 @@
   // Init and start Google settings coordinator.
   GoogleServicesSettingsMode mode =
       GoogleServicesSettingsModeAdvancedSigninSettings;
-  self.googleServicesSettingsCoordinator = [[GoogleServicesSettingsCoordinator
-      alloc]
-      initWithBaseViewController:self.advancedSettingsSigninNavigationController
-                         browser:self.browser
-                            mode:mode];
-  self.googleServicesSettingsCoordinator.baseNavigationController =
-      self.advancedSettingsSigninNavigationController;
+  self.googleServicesSettingsCoordinator =
+      [[GoogleServicesSettingsCoordinator alloc]
+          initWithBaseNavigationController:
+              self.advancedSettingsSigninNavigationController
+                                   browser:self.browser
+                                      mode:mode];
   [self.googleServicesSettingsCoordinator start];
 
   // Create the mediator.
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.h b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.h
index 65af37d..971ce39 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.h
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.h
@@ -52,11 +52,12 @@
                                                     promoAction;
 
 // Returns a coordinator for first run sign-in workflow.
-// |viewController| presents the sign-in. Will be responsible for dismissing
-// itself upon sign-in completion.
-+ (instancetype)firstRunCoordinatorWithBaseViewController:
-                    (UINavigationController*)viewController
-                                                  browser:(Browser*)browser;
+// |navigationController| presents the sign-in. Will be responsible for
+// dismissing itself upon sign-in completion.
++ (instancetype)firstRunCoordinatorWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                                        browser:
+                                                            (Browser*)browser;
 
 // Returns a coordinator for upgrade sign-in workflow.
 // |viewController| presents the sign-in.
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
index 99432bf..2259bbbe 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
@@ -37,18 +37,18 @@
                           logger:logger];
 }
 
-+ (instancetype)firstRunCoordinatorWithBaseViewController:
-                    (UINavigationController*)viewController
-                                                  browser:(Browser*)browser {
++ (instancetype)firstRunCoordinatorWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                                        browser:
+                                                            (Browser*)browser {
   UserSigninLogger* logger = [[FirstRunSigninLogger alloc]
       initWithAccessPoint:AccessPoint::ACCESS_POINT_START_PAGE
               promoAction:PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO];
   return [[UserSigninCoordinator alloc]
-      initWithBaseViewController:viewController
-                         browser:browser
-                        identity:nil
-                    signinIntent:UserSigninIntentFirstRun
-                          logger:logger];
+      initWithBaseNavigationController:navigationController
+                               browser:browser
+                          signinIntent:UserSigninIntentFirstRun
+                                logger:logger];
 }
 
 + (instancetype)
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.h b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.h
index a564ebc..177ad5b 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.h
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.h
@@ -36,6 +36,13 @@
                                     logger:(UserSigninLogger*)logger
     NS_DESIGNATED_INITIALIZER;
 
+// Convenience initializer using UINavigationController.
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                    signinIntent:(UserSigninIntent)signinIntent
+                                          logger:(UserSigninLogger*)logger;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_AUTHENTICATION_SIGNIN_USER_SIGNIN_USER_SIGNIN_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
index b14eff1..f0b0bc0 100644
--- a/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/user_signin/user_signin_coordinator.mm
@@ -59,8 +59,25 @@
 
 @implementation UserSigninCoordinator
 
+@synthesize baseNavigationController = _baseNavigationController;
+
 #pragma mark - Public
 
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                    signinIntent:(UserSigninIntent)signinIntent
+                                          logger:(UserSigninLogger*)logger {
+  if (self = [self initWithBaseViewController:navigationController
+                                      browser:browser
+                                     identity:nil
+                                 signinIntent:signinIntent
+                                       logger:logger]) {
+    _baseNavigationController = navigationController;
+  }
+  return self;
+}
+
 - (instancetype)initWithBaseViewController:(UIViewController*)viewController
                                    browser:(Browser*)browser
                                   identity:(ChromeIdentity*)identity
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 3924d45..173ff136 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -2278,7 +2278,7 @@
       kToolsMenuGuide,
       kTabSwitcherGuide,
       kTranslateInfobarOptionsGuide,
-      kSearchButtonGuide,
+      kNewTabButtonGuide,
       kSecondaryToolbarGuide,
       kVoiceSearchButtonGuide,
     ];
diff --git a/ios/chrome/browser/ui/bubble/bubble_presenter.mm b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
index 197d3f51..f44be16a 100644
--- a/ios/chrome/browser/ui/bubble/bubble_presenter.mm
+++ b/ios/chrome/browser/ui/bubble/bubble_presenter.mm
@@ -184,7 +184,7 @@
       IsSplitToolbarMode() ? BubbleArrowDirectionDown : BubbleArrowDirectionUp;
   NSString* text =
       l10n_util::GetNSString(IDS_IOS_LONG_PRESS_TOOLBAR_IPH_PROMOTION_TEXT);
-  CGPoint searchButtonAnchor =
+  CGPoint tabGridButtonAnchor =
       IsRegularXRegularSizeClass() &&
               !base::FeatureList::IsEnabled(kChangeTabSwitcherPosition)
           ? [self anchorPointToGuide:kTabStripTabSwitcherGuide
@@ -203,7 +203,7 @@
         voiceOverAnnouncement:
             l10n_util::GetNSString(
                 IDS_IOS_LONG_PRESS_TOOLBAR_IPH_PROMOTION_VOICE_OVER)
-                  anchorPoint:searchButtonAnchor];
+                  anchorPoint:tabGridButtonAnchor];
   if (!presenter)
     return;
 
@@ -246,8 +246,8 @@
   BubbleArrowDirection arrowDirection = BubbleArrowDirectionDown;
   NSString* text = l10n_util::GetNSStringWithFixup(
       IDS_IOS_BOTTOM_TOOLBAR_IPH_PROMOTION_TEXT);
-  CGPoint searchButtonAnchor =
-      [self anchorPointToGuide:kSearchButtonGuide direction:arrowDirection];
+  CGPoint newTabButtonAnchor = [self anchorPointToGuide:kNewTabButtonGuide
+                                              direction:arrowDirection];
 
   // If the feature engagement tracker does not consider it valid to display
   // the tip, then end early to prevent the potential reassignment of the
@@ -260,7 +260,7 @@
         voiceOverAnnouncement:
             l10n_util::GetNSString(
                 IDS_IOS_BOTTOM_TOOLBAR_IPH_PROMOTION_VOICE_OVER)
-                  anchorPoint:searchButtonAnchor];
+                  anchorPoint:newTabButtonAnchor];
   if (!presenter)
     return;
 
diff --git a/ios/chrome/browser/ui/commands/popup_menu_commands.h b/ios/chrome/browser/ui/commands/popup_menu_commands.h
index c67b40b5..0c49aa9 100644
--- a/ios/chrome/browser/ui/commands/popup_menu_commands.h
+++ b/ios/chrome/browser/ui/commands/popup_menu_commands.h
@@ -26,8 +26,8 @@
 - (void)showTabGridButtonPopup;
 // Shows the popup for the tab grid button in the tab strip.
 - (void)showTabStripTabGridButtonPopup;
-// Shows the popup for the search button.
-- (void)showSearchButtonPopup;
+// Shows the popup for the new tab button.
+- (void)showNewTabButtonPopup;
 // Dismisses the currently presented popup.
 - (void)dismissPopupMenuAnimated:(BOOL)animated;
 
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.h b/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.h
index 3d02332..138057e 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.h
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.h
@@ -21,11 +21,6 @@
 // Shared instance of this singleton.
 + (instancetype)sharedInstance;
 
-// Whether the @"focusOmniboxFromSearchButton" selector has been called on the
-// location bar coordinator. This is the method swizzled by the methods below.
-@property(nonatomic, assign, readonly)
-    BOOL locationBarCoordinatorSearchButtonMethodCalled;
-
 // Resets the stored additionalSuggestions helper with |URL|.
 - (void)resetAdditionalSuggestionsHelperWithURL:(const GURL&)URL;
 // Returns the stored additionalSuggestionsHelper.
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.mm
index 147d9f37..c0bc2e3 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_provider_test_singleton.mm
@@ -8,7 +8,6 @@
 
 #include "components/ntp_snippets/content_suggestion.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_test_utils.h"
-#include "ios/testing/scoped_block_swizzler.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -19,8 +18,6 @@
   ntp_snippets::MockContentSuggestionsProvider* _provider;
   std::unique_ptr<ntp_snippets::AdditionalSuggestionsHelper>
       _additionalSuggestionsHelper;
-  std::unique_ptr<ScopedBlockSwizzler> _swizzler;
-  __block BOOL _tapped;
 }
 
 + (instancetype)sharedInstance {
@@ -56,8 +53,4 @@
   service->RegisterProvider(std::move(provider));
 }
 
-- (BOOL)locationBarCoordinatorSearchButtonMethodCalled {
-  return _tapped;
-}
-
 @end
diff --git a/ios/chrome/browser/ui/coordinators/chrome_coordinator.h b/ios/chrome/browser/ui/coordinators/chrome_coordinator.h
index 9510daa..7e7f928b 100644
--- a/ios/chrome/browser/ui/coordinators/chrome_coordinator.h
+++ b/ios/chrome/browser/ui/coordinators/chrome_coordinator.h
@@ -47,7 +47,8 @@
 // Parent coordinator can set this to allow the child coordinator to push their
 // view controller to the navigationController instead of presenting it if
 // needed. This is usually the same object as |baseViewController|.
-@property(weak, nonatomic) UINavigationController* baseNavigationController;
+@property(weak, nonatomic, readonly)
+    UINavigationController* baseNavigationController;
 
 // The coordinator's BrowserState.
 @property(assign, nonatomic, readonly) ChromeBrowserState* browserState;
diff --git a/ios/chrome/browser/ui/location_bar/location_bar_coordinator.mm b/ios/chrome/browser/ui/location_bar/location_bar_coordinator.mm
index 55a4b26..5f85d10 100644
--- a/ios/chrome/browser/ui/location_bar/location_bar_coordinator.mm
+++ b/ios/chrome/browser/ui/location_bar/location_bar_coordinator.mm
@@ -306,20 +306,6 @@
 
 #pragma mark - OmniboxFocuser
 
-- (void)focusOmniboxFromSearchButton {
-  // TODO(crbug.com/931284): Temporary workaround for intermediate broken state
-  // in the NTP.  Remove this once crbug.com/899827 is fixed.
-  if (self.webState) {
-    NewTabPageTabHelper* NTPHelper =
-        NewTabPageTabHelper::FromWebState(self.webState);
-    if (NTPHelper && NTPHelper->IsActive() && NTPHelper->IgnoreLoadRequests()) {
-      return;
-    }
-  }
-  [self.omniboxCoordinator setNextFocusSourceAsSearchButton];
-  [self focusOmnibox];
-}
-
 - (void)focusOmniboxFromFakebox {
   [self.omniboxCoordinator focusOmnibox];
 }
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.h b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.h
index ad5c3a5d..22cbe54 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.h
@@ -54,8 +54,6 @@
 - (void)updateOmniboxState;
 // Use this method to make the omnibox first responder.
 - (void)focusOmnibox;
-// Marks the next omnibox focus event source as the search button.
-- (void)setNextFocusSourceAsSearchButton;
 // Use this method to resign |textField| as the first responder.
 - (void)endEditing;
 // Creates a child popup coordinator. The popup coordinator is linked to the
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
index 144f24e..18bf751 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_coordinator.mm
@@ -121,11 +121,6 @@
   _editView->UpdateAppearance();
 }
 
-- (void)setNextFocusSourceAsSearchButton {
-  OmniboxEditModel* model = _editView->model();
-  model->set_focus_source(OmniboxFocusSource::SEARCH_BUTTON);
-}
-
 - (BOOL)isOmniboxFirstResponder {
   return [self.textField isFirstResponder];
 }
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index 08f1269..b1c48dd 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -117,6 +117,8 @@
     "//ios/chrome/browser/ui/popup_menu/cells",
     "//ios/chrome/browser/ui/popup_menu/public:popup_menu_ui",
     "//ios/chrome/browser/ui/toolbar/test",
+    "//ios/chrome/browser/web",
+    "//ios/chrome/browser/web:feature_flags",
     "//ios/chrome/browser/web:test_support",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/browser/web_state_list",
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index 544cfb2a..e4b43161 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -123,10 +123,10 @@
             fromNamedGuide:kTabSwitcherGuide];
 }
 
-- (void)showSearchButtonPopup {
+- (void)showNewTabButtonPopup {
   base::RecordAction(base::UserMetricsAction("MobileToolbarShowNewTabMenu"));
-  [self presentPopupOfType:PopupMenuTypeSearch
-            fromNamedGuide:kSearchButtonGuide];
+  [self presentPopupOfType:PopupMenuTypeNewTab
+            fromNamedGuide:kNewTabButtonGuide];
 }
 
 - (void)showTabStripTabGridButtonPopup {
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
index fcef641c..a6c14b1 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator.mm
@@ -432,7 +432,7 @@
       case PopupMenuTypeTabStripTabGrid:
         [self createTabGridMenuItems];
         break;
-      case PopupMenuTypeSearch:
+      case PopupMenuTypeNewTab:
         [self createSearchMenuItems];
         break;
     }
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator_unittest.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator_unittest.mm
index 01b1eb3d..9ed9352 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_mediator_unittest.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h"
 
+#include "base/test/scoped_feature_list.h"
 #include "base/time/default_clock.h"
 #include "components/feature_engagement/test/mock_tracker.h"
 #include "components/language/ios/browser/ios_language_detection_tab_helper.h"
@@ -23,6 +24,8 @@
 #import "ios/chrome/browser/ui/toolbar/test/toolbar_test_web_state.h"
 #include "ios/chrome/browser/web/chrome_web_client.h"
 #import "ios/chrome/browser/web/chrome_web_test.h"
+#include "ios/chrome/browser/web/features.h"
+#import "ios/chrome/browser/web/font_size_tab_helper.h"
 #include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -363,3 +366,25 @@
   queue->CancelAllRequests();
   EXPECT_TRUE(HasItem(consumer, kToolsMenuReadLater, /*enabled=*/YES));
 }
+
+// Tests that the "Text Zoom..." button is disabled on non-HTML pages.
+TEST_F(PopupMenuMediatorTest, TextTextZoomDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(web::kWebPageTextAccessibility);
+
+  CreateMediator(PopupMenuTypeToolsMenu, /*is_incognito=*/NO,
+                 /*trigger_incognito_hint=*/NO);
+  mediator_.webStateList = web_state_list_.get();
+
+  FakePopupMenuConsumer* consumer = [[FakePopupMenuConsumer alloc] init];
+  mediator_.popupMenu = consumer;
+  FontSizeTabHelper::CreateForWebState(web_state_list_->GetWebStateAt(0));
+  SetUpActiveWebState();
+  EXPECT_TRUE(HasItem(consumer, kToolsMenuTextZoom, /*enabled=*/YES));
+
+  web_state_->SetContentIsHTML(false);
+  // Fake a navigationFinished to force the popup menu items to update.
+  web::FakeNavigationContext context;
+  web_state_->OnNavigationFinished(&context);
+  EXPECT_TRUE(HasItem(consumer, kToolsMenuTextZoom, /*enabled=*/NO));
+}
diff --git a/ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_updating.h b/ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_updating.h
index 391e3b2..f3a33594 100644
--- a/ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_updating.h
+++ b/ios/chrome/browser/ui/popup_menu/public/popup_menu_ui_updating.h
@@ -13,7 +13,7 @@
   PopupMenuTypeNavigationBackward,
   PopupMenuTypeNavigationForward,
   PopupMenuTypeTabGrid,
-  PopupMenuTypeSearch,
+  PopupMenuTypeNewTab,
   PopupMenuTypeTabStripTabGrid,
 };
 
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h
index 325253b8..1da4102 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h
@@ -43,9 +43,11 @@
 // |viewController|: navigation controller.
 // |browser|: browser.
 // |mode|: mode to display the Google services settings.
-- (instancetype)initWithBaseViewController:(UIViewController*)viewController
-                                   browser:(Browser*)browser
-                                      mode:(GoogleServicesSettingsMode)mode
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                            mode:
+                                                (GoogleServicesSettingsMode)mode
     NS_DESIGNATED_INITIALIZER;
 
 @end
diff --git a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
index 849a240..d0f21da 100644
--- a/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.mm
@@ -64,10 +64,15 @@
 
 @implementation GoogleServicesSettingsCoordinator
 
-- (instancetype)initWithBaseViewController:(UIViewController*)viewController
-                                   browser:(Browser*)browser
-                                      mode:(GoogleServicesSettingsMode)mode {
-  if ([super initWithBaseViewController:viewController browser:browser]) {
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+                                            mode:(GoogleServicesSettingsMode)
+                                                     mode {
+  if ([super initWithBaseViewController:navigationController browser:browser]) {
+    _baseNavigationController = navigationController;
     _mode = mode;
   }
   return self;
@@ -241,10 +246,8 @@
 - (void)openManageSyncSettings {
   DCHECK(!self.manageSyncSettingsCoordinator);
   self.manageSyncSettingsCoordinator = [[ManageSyncSettingsCoordinator alloc]
-      initWithBaseViewController:self.viewController
-                         browser:self.browser];
-  self.manageSyncSettingsCoordinator.baseNavigationController =
-      self.baseNavigationController;
+      initWithBaseNavigationController:self.baseNavigationController
+                               browser:self.browser];
   self.manageSyncSettingsCoordinator.delegate = self;
   [self.manageSyncSettingsCoordinator start];
 }
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h
index f92e756..5145e34 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h
@@ -23,6 +23,19 @@
 // relies on GoogleServicesSettingsCoordinator to commit the sync changes.
 @interface ManageSyncSettingsCoordinator : ChromeCoordinator
 
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+    NS_UNAVAILABLE;
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                              browserState:(ChromeBrowserState*)browserState
+    NS_UNAVAILABLE;
+- (instancetype)initWithBaseViewController:(UIViewController*)viewController
+                                   browser:(Browser*)browser NS_UNAVAILABLE;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser
+    NS_DESIGNATED_INITIALIZER;
+
 // Delegate.
 @property(nonatomic, weak) id<ManageSyncSettingsCoordinatorDelegate> delegate;
 
diff --git a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
index 49a1167..af9be5123 100644
--- a/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.mm
@@ -61,6 +61,18 @@
 
 @implementation ManageSyncSettingsCoordinator
 
+@synthesize baseNavigationController = _baseNavigationController;
+
+- (instancetype)initWithBaseNavigationController:
+                    (UINavigationController*)navigationController
+                                         browser:(Browser*)browser {
+  if (self = [super initWithBaseViewController:navigationController
+                                       browser:browser]) {
+    _baseNavigationController = navigationController;
+  }
+  return self;
+}
+
 - (void)start {
   DCHECK(self.baseNavigationController);
   self.mediator = [[ManageSyncSettingsMediator alloc]
diff --git a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
index 0edc17e..fbdf106c2 100644
--- a/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_navigation_controller.mm
@@ -366,10 +366,9 @@
   }
   self.googleServicesSettingsCoordinator =
       [[GoogleServicesSettingsCoordinator alloc]
-          initWithBaseViewController:self
-                             browser:self.browser
-                                mode:GoogleServicesSettingsModeSettings];
-  self.googleServicesSettingsCoordinator.baseNavigationController = self;
+          initWithBaseNavigationController:self
+                                   browser:self.browser
+                                      mode:GoogleServicesSettingsModeSettings];
   self.googleServicesSettingsCoordinator.delegate = self;
   [self.googleServicesSettingsCoordinator start];
 }
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 0b005fc..4b8ce94 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -922,11 +922,9 @@
   DCHECK(!_googleServicesSettingsCoordinator);
   _googleServicesSettingsCoordinator =
       [[GoogleServicesSettingsCoordinator alloc]
-          initWithBaseViewController:self.navigationController
-                             browser:_browser
-                                mode:GoogleServicesSettingsModeSettings];
-  _googleServicesSettingsCoordinator.baseNavigationController =
-      self.navigationController;
+          initWithBaseNavigationController:self.navigationController
+                                   browser:_browser
+                                      mode:GoogleServicesSettingsModeSettings];
   _googleServicesSettingsCoordinator.delegate = self;
   [_googleServicesSettingsCoordinator start];
 }
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
index 95e5c1f..b6bd11b 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_egtest.mm
@@ -101,8 +101,8 @@
 }
 
 // Returns a matcher for the search button.
-id<GREYMatcher> SearchButton() {
-  return grey_accessibilityID(kToolbarSearchButtonIdentifier);
+id<GREYMatcher> NewTabButton() {
+  return grey_accessibilityID(kToolbarNewTabButtonIdentifier);
 }
 
 // Returns a matcher for the tab grid button.
@@ -233,7 +233,7 @@
     // Those buttons are hidden by the keyboard.
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityNone);
-    CheckVisibilityInToolbar(SearchButton(), ButtonVisibilityNone);
+    CheckVisibilityInToolbar(NewTabButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ToolsMenuButton(), ButtonVisibilityNone);
   } else {
@@ -245,7 +245,7 @@
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilitySecondary);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilitySecondary);
-    CheckVisibilityInToolbar(SearchButton(), ButtonVisibilitySecondary);
+    CheckVisibilityInToolbar(NewTabButton(), ButtonVisibilitySecondary);
     CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilitySecondary);
     CheckVisibilityInToolbar(ToolsMenuButton(), ButtonVisibilitySecondary);
   }
@@ -263,7 +263,7 @@
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityNone);
-    CheckVisibilityInToolbar(SearchButton(), ButtonVisibilityNone);
+    CheckVisibilityInToolbar(NewTabButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(ToolsMenuButton(), ButtonVisibilityNone);
   } else {
@@ -275,7 +275,7 @@
 
     CheckVisibilityInToolbar(BackButton(), ButtonVisibilityPrimary);
     CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityPrimary);
-    CheckVisibilityInToolbar(SearchButton(), ButtonVisibilityNone);
+    CheckVisibilityInToolbar(NewTabButton(), ButtonVisibilityNone);
     CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilityPrimary);
     CheckVisibilityInToolbar(ToolsMenuButton(), ButtonVisibilityPrimary);
   }
@@ -296,7 +296,7 @@
 
   CheckVisibilityInToolbar(BackButton(), ButtonVisibilityPrimary);
   CheckVisibilityInToolbar(ForwardButton(), ButtonVisibilityPrimary);
-  CheckVisibilityInToolbar(SearchButton(), ButtonVisibilityNone);
+  CheckVisibilityInToolbar(NewTabButton(), ButtonVisibilityNone);
   CheckVisibilityInToolbar(TabGridButton(), ButtonVisibilityNone);
   CheckVisibilityInToolbar(ToolsMenuButton(), ButtonVisibilityPrimary);
 
@@ -586,16 +586,16 @@
       assertWithMatcher:grey_not(grey_enabled())];
 }
 
-// Tests that tapping the omnibox button focuses the omnibox.
-- (void)testOmniboxButton {
+// Tests that tapping the NewTab button opens a new tab.
+- (void)testNewTabButton {
   if (![ChromeEarlGrey isSplitToolbarMode]) {
-    EARL_GREY_TEST_SKIPPED(@"No omnibox button to tap.");
+    EARL_GREY_TEST_SKIPPED(@"No button to tap.");
   }
 
   [ChromeEarlGrey waitForMainTabCount:1];
 
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
-                                          kToolbarSearchButtonIdentifier)]
+                                          kToolbarNewTabButtonIdentifier)]
       performAction:grey_tap()];
 
   [ChromeEarlGrey waitForMainTabCount:2];
@@ -652,7 +652,7 @@
   [ChromeEarlGrey openNewTab];
 
   // Check that the bottom toolbar is visible.
-  [[EarlGrey selectElementWithMatcher:SearchButton()]
+  [[EarlGrey selectElementWithMatcher:NewTabButton()]
       assertWithMatcher:grey_sufficientlyVisible()];
 }
 
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
index c0d1e08..aa845a6 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view.h
@@ -37,8 +37,8 @@
 @property(nonatomic, strong, readonly) ToolbarButton* bookmarkButton;
 // Button to display the tools menu.
 @property(nonatomic, strong, readonly) ToolbarToolsMenuButton* toolsMenuButton;
-// Button to display the tools menu.
-@property(nonatomic, strong, readonly) ToolbarButton* searchButton;
+// Button to create a new tab.
+@property(nonatomic, strong, readonly) ToolbarButton* openNewTabButton;
 // Separator between the toolbar and the content.
 @property(nonatomic, strong, readonly) UIView* separator;
 
diff --git a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
index bbb8a02..0269d7a2 100644
--- a/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
+++ b/ios/chrome/browser/ui/toolbar/adaptive_toolbar_view_controller.mm
@@ -92,14 +92,14 @@
   // Adds the layout guide to the buttons.
   self.view.toolsMenuButton.guideName = kToolsMenuGuide;
   self.view.tabGridButton.guideName = kTabSwitcherGuide;
-  self.view.searchButton.guideName = kSearchButtonGuide;
+  self.view.openNewTabButton.guideName = kNewTabButtonGuide;
   self.view.forwardButton.guideName = kForwardButtonGuide;
   self.view.backButton.guideName = kBackButtonGuide;
 
   // Add navigation popup menu triggers.
   [self addLongPressGestureToView:self.view.backButton];
   [self addLongPressGestureToView:self.view.forwardButton];
-  [self addLongPressGestureToView:self.view.searchButton];
+  [self addLongPressGestureToView:self.view.openNewTabButton];
   [self addLongPressGestureToView:self.view.tabGridButton];
   [self addLongPressGestureToView:self.view.toolsMenuButton];
 
@@ -247,8 +247,8 @@
     case PopupMenuTypeNavigationBackward:
       selectedButton = self.view.backButton;
       break;
-    case PopupMenuTypeSearch:
-      selectedButton = self.view.searchButton;
+    case PopupMenuTypeNewTab:
+      selectedButton = self.view.openNewTabButton;
       break;
     case PopupMenuTypeTabGrid:
       selectedButton = self.view.tabGridButton;
@@ -271,7 +271,7 @@
 - (void)updateUIForMenuDismissed {
   self.view.backButton.spotlighted = NO;
   self.view.forwardButton.spotlighted = NO;
-  self.view.searchButton.spotlighted = NO;
+  self.view.openNewTabButton.spotlighted = NO;
   self.view.tabGridButton.spotlighted = NO;
   self.view.toolsMenuButton.spotlighted = NO;
 
@@ -343,7 +343,7 @@
 - (void)addStandardActionsForAllButtons {
   for (ToolbarButton* button in self.view.allButtons) {
     if (button != self.view.toolsMenuButton &&
-        button != self.view.searchButton) {
+        button != self.view.openNewTabButton) {
       [button addTarget:self.dispatcher
                     action:@selector(cancelOmniboxEdit)
           forControlEvents:UIControlEventTouchUpInside];
@@ -375,7 +375,7 @@
     base::RecordAction(base::UserMetricsAction("MobileToolbarShowStackView"));
   } else if (sender == self.view.shareButton) {
     base::RecordAction(base::UserMetricsAction("MobileToolbarShareMenu"));
-  } else if (sender == self.view.searchButton) {
+  } else if (sender == self.view.openNewTabButton) {
     base::RecordAction(base::UserMetricsAction("MobileToolbarNewTabShortcut"));
   } else {
     NOTREACHED();
@@ -399,8 +399,8 @@
       [self.dispatcher showNavigationHistoryBackPopupMenu];
     } else if (gesture.view == self.view.forwardButton) {
       [self.dispatcher showNavigationHistoryForwardPopupMenu];
-    } else if (gesture.view == self.view.searchButton) {
-      [self.dispatcher showSearchButtonPopup];
+    } else if (gesture.view == self.view.openNewTabButton) {
+      [self.dispatcher showNewTabButtonPopup];
     } else if (gesture.view == self.view.tabGridButton) {
       [self.dispatcher showTabGridButtonPopup];
     } else if (gesture.view == self.view.toolsMenuButton) {
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
index 1958371d..39e21cc 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.h
@@ -51,8 +51,8 @@
 - (ToolbarButton*)stopButton;
 // Bookmark ToolbarButton.
 - (ToolbarButton*)bookmarkButton;
-// ToolbarButton to focus the omnibox.
-- (ToolbarButton*)searchButton;
+// ToolbarButton to create a new tab.
+- (ToolbarButton*)openNewTabButton;
 // Button to cancel the edit of the location bar.
 - (UIButton*)cancelButton;
 
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
index f9327fb..6a92702 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
@@ -168,28 +168,27 @@
   return bookmarkButton;
 }
 
-// TODO(crbug.com/974751): Rename this as the button is no longer for search.
-- (ToolbarButton*)searchButton {
+- (ToolbarButton*)openNewTabButton {
   UIImage* buttonImage = [UIImage imageNamed:@"toolbar_new_tab_page"];
-  ToolbarSearchButton* searchButton =
+  ToolbarSearchButton* newTabButton =
       [ToolbarSearchButton toolbarButtonWithImage:buttonImage];
 
-  [searchButton addTarget:self.actionHandler
+  [newTabButton addTarget:self.actionHandler
                    action:@selector(searchAction:)
          forControlEvents:UIControlEventTouchUpInside];
   BOOL isIncognito = self.style == INCOGNITO;
 
-  [self configureButton:searchButton width:kAdaptiveToolbarButtonWidth];
+  [self configureButton:newTabButton width:kAdaptiveToolbarButtonWidth];
 
-  searchButton.accessibilityLabel =
+  newTabButton.accessibilityLabel =
       l10n_util::GetNSString(isIncognito ? IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB
                                          : IDS_IOS_TOOLS_MENU_NEW_TAB);
 
-  searchButton.accessibilityIdentifier = kToolbarSearchButtonIdentifier;
+  newTabButton.accessibilityIdentifier = kToolbarNewTabButtonIdentifier;
 
-  searchButton.visibilityMask =
+  newTabButton.visibilityMask =
       self.visibilityConfiguration.searchButtonVisibility;
-  return searchButton;
+  return newTabButton;
 }
 
 - (UIButton*)cancelButton {
diff --git a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
index 3f9c6426..5dee3c6 100644
--- a/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/primary_toolbar_view.mm
@@ -432,7 +432,8 @@
 
 #pragma mark - AdaptiveToolbarView
 
-- (ToolbarButton*)searchButton {
+- (ToolbarButton*)openNewTabButton {
   return nil;
 }
+
 @end
diff --git a/ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h b/ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h
index 631eeb1..de4f4826 100644
--- a/ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h
+++ b/ios/chrome/browser/ui/toolbar/public/omnibox_focuser.h
@@ -12,8 +12,6 @@
 // Give focus to the omnibox, if it is visible. No-op if it is not visible.  If
 // current page is an NTP, first focus the NTP fakebox.
 - (void)focusOmnibox;
-// Set next focus source as SEARCH_BUTTON and then call -focusOmnibox.
-- (void)focusOmniboxFromSearchButton;
 // Focus the omnibox but skip the NTP check.
 - (void)focusOmniboxFromFakebox;
 // Cancel omnibox edit (from shield tap or cancel button tap).
diff --git a/ios/chrome/browser/ui/toolbar/public/toolbar_constants.h b/ios/chrome/browser/ui/toolbar/public/toolbar_constants.h
index c62f059b..ca70302d 100644
--- a/ios/chrome/browser/ui/toolbar/public/toolbar_constants.h
+++ b/ios/chrome/browser/ui/toolbar/public/toolbar_constants.h
@@ -88,8 +88,8 @@
 extern NSString* const kToolbarStackButtonIdentifier;
 // Accessibility identifier of the share button.
 extern NSString* const kToolbarShareButtonIdentifier;
-// Accessibility identifier of the omnibox button.
-extern NSString* const kToolbarSearchButtonIdentifier;
+// Accessibility identifier of the NewTab button.
+extern NSString* const kToolbarNewTabButtonIdentifier;
 // Accessibility identifier of the cancel omnibox edit button.
 extern NSString* const kToolbarCancelOmniboxEditButtonIdentifier;
 
diff --git a/ios/chrome/browser/ui/toolbar/public/toolbar_constants.mm b/ios/chrome/browser/ui/toolbar/public/toolbar_constants.mm
index 0bb3bcd..d83187a 100644
--- a/ios/chrome/browser/ui/toolbar/public/toolbar_constants.mm
+++ b/ios/chrome/browser/ui/toolbar/public/toolbar_constants.mm
@@ -60,8 +60,8 @@
     @"kToolbarStackButtonIdentifier";
 NSString* const kToolbarShareButtonIdentifier =
     @"kToolbarShareButtonIdentifier";
-NSString* const kToolbarSearchButtonIdentifier =
-    @"kToolbarSearchButtonIdentifier";
+NSString* const kToolbarNewTabButtonIdentifier =
+    @"kToolbarNewTabButtonIdentifier";
 NSString* const kToolbarCancelOmniboxEditButtonIdentifier =
     @"kToolbarCancelOmniboxEditButtonIdentifier";
 
diff --git a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
index 6e25db01..47736553 100644
--- a/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
+++ b/ios/chrome/browser/ui/toolbar/secondary_toolbar_view.mm
@@ -47,8 +47,8 @@
 @property(nonatomic, strong, readwrite) ToolbarToolsMenuButton* toolsMenuButton;
 // Button to display the tab grid, redefined as readwrite.
 @property(nonatomic, strong, readwrite) ToolbarTabGridButton* tabGridButton;
-// Button to focus the omnibox, redefined as readwrite.
-@property(nonatomic, strong, readwrite) ToolbarButton* searchButton;
+// Button to create a new tab, redefined as readwrite.
+@property(nonatomic, strong, readwrite) ToolbarButton* openNewTabButton;
 
 @end
 
@@ -60,7 +60,7 @@
 @synthesize backButton = _backButton;
 @synthesize forwardButton = _forwardButton;
 @synthesize toolsMenuButton = _toolsMenuButton;
-@synthesize searchButton = _searchButton;
+@synthesize openNewTabButton = _openNewTabButton;
 @synthesize tabGridButton = _tabGridButton;
 
 #pragma mark - Public
@@ -111,7 +111,7 @@
 
   self.backButton = [self.buttonFactory backButton];
   self.forwardButton = [self.buttonFactory forwardButton];
-  self.searchButton = [self.buttonFactory searchButton];
+  self.openNewTabButton = [self.buttonFactory openNewTabButton];
   self.tabGridButton = [self.buttonFactory tabGridButton];
   self.toolsMenuButton = [self.buttonFactory toolsMenuButton];
 
@@ -122,8 +122,8 @@
       CGAffineTransformMakeTranslation(textDirection * kToolsMenuOffset, 0);
 
   self.allButtons = @[
-    self.backButton, self.forwardButton, self.searchButton, self.tabGridButton,
-    self.toolsMenuButton
+    self.backButton, self.forwardButton, self.openNewTabButton,
+    self.tabGridButton, self.toolsMenuButton
   ];
 
   self.separator = [[UIView alloc] init];
diff --git a/ios/chrome/browser/ui/util/layout_guide_names.h b/ios/chrome/browser/ui/util/layout_guide_names.h
index d3422d5..69c8527 100644
--- a/ios/chrome/browser/ui/util/layout_guide_names.h
+++ b/ios/chrome/browser/ui/util/layout_guide_names.h
@@ -39,8 +39,8 @@
 extern GuideName* const kBackButtonGuide;
 // A guide that is constrained to match the frame of the forward button's image.
 extern GuideName* const kForwardButtonGuide;
-// A guide that is constrained to match the frame of the Search button.
-extern GuideName* const kSearchButtonGuide;
+// A guide that is constrained to match the frame of the NewTab button.
+extern GuideName* const kNewTabButtonGuide;
 // A guide that is constrained to match the frame of the TabSwitcher button's
 // image.
 extern GuideName* const kTabSwitcherGuide;
diff --git a/ios/chrome/browser/ui/util/layout_guide_names.mm b/ios/chrome/browser/ui/util/layout_guide_names.mm
index 0a7bb75..a7a2b70e 100644
--- a/ios/chrome/browser/ui/util/layout_guide_names.mm
+++ b/ios/chrome/browser/ui/util/layout_guide_names.mm
@@ -19,7 +19,7 @@
 GuideName* const kOmniboxTextFieldGuide = @"kOmniboxTextFieldGuide";
 GuideName* const kBackButtonGuide = @"kBackButtonGuide";
 GuideName* const kForwardButtonGuide = @"kForwardButtonGuide";
-GuideName* const kSearchButtonGuide = @"kSearchButtonGuide";
+GuideName* const kNewTabButtonGuide = @"kNewTabButtonGuide";
 GuideName* const kTabSwitcherGuide = @"kTabSwitcherGuide";
 GuideName* const kTabStripTabSwitcherGuide = @"kTabStripTabSwitcherGuide";
 GuideName* const kToolsMenuGuide = @"kToolsMenuGuide";
diff --git a/ios/third_party/material_components_ios/BUILD.gn b/ios/third_party/material_components_ios/BUILD.gn
index a94b3d0..1c247fb 100644
--- a/ios/third_party/material_components_ios/BUILD.gn
+++ b/ios/third_party/material_components_ios/BUILD.gn
@@ -126,6 +126,7 @@
   "src/components/Chips/src/MDCChipCollectionViewCell.h",
   "src/components/Chips/src/MDCChipCollectionViewFlowLayout.h",
   "src/components/Chips/src/MDCChipField.h",
+  "src/components/Chips/src/MDCChipFieldDelegate.h",
   "src/components/Chips/src/MDCChipView.h",
   "src/components/Chips/src/MaterialChips.h",
   "src/components/Chips/src/Theming/MDCChipView+MaterialTheming.h",
@@ -254,7 +255,6 @@
   "src/components/ProgressView/src/MaterialProgressView.h",
   "src/components/ProgressView/src/Theming/MDCProgressView+MaterialTheming.h",
   "src/components/ProgressView/src/Theming/MaterialProgressView+Theming.h",
-  "src/components/ProgressView/src/private/MDCProgressViewMotionSpec.h",
   "src/components/Ripple/src/MDCRippleTouchController.h",
   "src/components/Ripple/src/MDCRippleView.h",
   "src/components/Ripple/src/MDCStatefulRippleView.h",
@@ -577,7 +577,6 @@
   "src/components/Palettes/src/private",
   "src/components/ProgressView/src",
   "src/components/ProgressView/src/Theming",
-  "src/components/ProgressView/src/private",
   "src/components/Ripple/src",
   "src/components/Ripple/src/private",
   "src/components/ShadowElevations/src",
@@ -835,6 +834,7 @@
   "src/components/Chips/src/MDCChipCollectionViewFlowLayout.m",
   "src/components/Chips/src/MDCChipField.h",
   "src/components/Chips/src/MDCChipField.m",
+  "src/components/Chips/src/MDCChipFieldDelegate.h",
   "src/components/Chips/src/MDCChipView.h",
   "src/components/Chips/src/MDCChipView.m",
   "src/components/Chips/src/MaterialChips.h",
@@ -1034,8 +1034,6 @@
   "src/components/ProgressView/src/Theming/MDCProgressView+MaterialTheming.h",
   "src/components/ProgressView/src/Theming/MDCProgressView+MaterialTheming.m",
   "src/components/ProgressView/src/Theming/MaterialProgressView+Theming.h",
-  "src/components/ProgressView/src/private/MDCProgressViewMotionSpec.h",
-  "src/components/ProgressView/src/private/MDCProgressViewMotionSpec.m",
   "src/components/Ripple/src/MDCRippleTouchController.h",
   "src/components/Ripple/src/MDCRippleTouchController.m",
   "src/components/Ripple/src/MDCRippleView.h",
diff --git a/ios/web/web_state/web_state_observer_inttest.mm b/ios/web/web_state/web_state_observer_inttest.mm
index 2378404..bc9ed3a 100644
--- a/ios/web/web_state/web_state_observer_inttest.mm
+++ b/ios/web/web_state/web_state_observer_inttest.mm
@@ -17,7 +17,6 @@
 #import "base/test/ios/wait_util.h"
 #include "ios/testing/embedded_test_server_handlers.h"
 #include "ios/web/common/features.h"
-#include "ios/web/navigation/web_kit_constants.h"
 #include "ios/web/navigation/wk_navigation_util.h"
 #import "ios/web/public/navigation/navigation_context.h"
 #import "ios/web/public/navigation/navigation_item.h"
@@ -1070,7 +1069,7 @@
     EXPECT_CALL(observer_, DidFinishNavigation(web_state(), _))
         .WillOnce(VerifyErrorFinishedContext(
             web_state(), GURL(webkit_rewritten_url_spec), &context, &nav_id,
-            /*committed=*/false, kWebKitErrorCannotShowUrl));
+            /*committed=*/false, net::ERR_FAILED));
 
     EXPECT_CALL(observer_,
                 PageLoaded(web_state(), PageLoadCompletionStatus::FAILURE));
diff --git a/ios/web_view/internal/autofill/cwv_autofill_data_manager_unittest.mm b/ios/web_view/internal/autofill/cwv_autofill_data_manager_unittest.mm
index b793b46..571b1eb 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_data_manager_unittest.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_data_manager_unittest.mm
@@ -247,7 +247,8 @@
   password_store_->AddLogin(test_password);
   NSArray<CWVPassword*>* fetched_passwords = FetchPasswords();
   EXPECT_EQ(1ul, fetched_passwords.count);
-  EXPECT_EQ(test_password, *[fetched_passwords[0] internalPasswordForm]);
+  EXPECT_THAT(test_password, password_manager::MatchesFormExceptStore(
+                                 *[fetched_passwords[0] internalPasswordForm]));
 }
 
 // Tests CWVAutofillDataManager properly deletes passwords.
diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
index b7dbd7a31..98c78e5 100644
--- a/mojo/public/tools/bindings/chromium_bindings_configuration.gni
+++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni
@@ -30,7 +30,6 @@
   "//mojo/public/cpp/bindings/tests/chromium_typemaps.gni",
   "//sandbox/mac/mojom/typemaps.gni",
   "//services/media_session/public/cpp/typemaps.gni",
-  "//services/network/public/cpp/typemaps.gni",
   "//services/proxy_resolver/public/cpp/typemaps.gni",
   "//services/resource_coordinator/public/cpp/typemaps.gni",
   "//services/service_manager/public/cpp/typemaps.gni",
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index c25dded..e11992b 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -93,6 +93,7 @@
     DeleteCallback delete_callback,
     const ResourceRequest& resource_request,
     bool ignore_isolated_world_origin,
+    bool skip_cors_enabled_scheme_check,
     mojo::PendingRemote<mojom::URLLoaderClient> client,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
     mojom::URLLoaderFactory* network_loader_factory,
@@ -111,7 +112,8 @@
       traffic_annotation_(traffic_annotation),
       origin_access_list_(origin_access_list),
       factory_bound_origin_access_list_(factory_bound_origin_access_list),
-      preflight_controller_(preflight_controller) {
+      preflight_controller_(preflight_controller),
+      skip_cors_enabled_scheme_check_(skip_cors_enabled_scheme_check) {
   if (ignore_isolated_world_origin)
     request_.isolated_world_origin = base::nullopt;
 
@@ -422,7 +424,7 @@
 }
 
 void CorsURLLoader::StartRequest() {
-  if (fetch_cors_flag_ &&
+  if (fetch_cors_flag_ && !skip_cors_enabled_scheme_check_ &&
       !base::Contains(url::GetCorsEnabledSchemes(), request_.url.scheme())) {
     HandleComplete(URLLoaderCompletionStatus(
         CorsErrorStatus(mojom::CorsError::kCorsDisabledScheme)));
diff --git a/services/network/cors/cors_url_loader.h b/services/network/cors/cors_url_loader.h
index 3f47a81..daa86393 100644
--- a/services/network/cors/cors_url_loader.h
+++ b/services/network/cors/cors_url_loader.h
@@ -46,6 +46,7 @@
       DeleteCallback delete_callback,
       const ResourceRequest& resource_request,
       bool ignore_isolated_world_origin,
+      bool skip_cors_enabled_scheme_check,
       mojo::PendingRemote<mojom::URLLoaderClient> client,
       const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
       mojom::URLLoaderFactory* network_loader_factory,
@@ -181,6 +182,9 @@
   const OriginAccessList* const factory_bound_origin_access_list_;
   PreflightController* preflight_controller_;
 
+  // Flag to specify if the CORS-enabled scheme check should be applied.
+  const bool skip_cors_enabled_scheme_check_;
+
   // Used to run asynchronous class instance bound callbacks safely.
   base::WeakPtrFactory<CorsURLLoader> weak_factory_{this};
 
diff --git a/services/network/cors/cors_url_loader_factory.cc b/services/network/cors/cors_url_loader_factory.cc
index 631ddc9b..a453272 100644
--- a/services/network/cors/cors_url_loader_factory.cc
+++ b/services/network/cors/cors_url_loader_factory.cc
@@ -81,16 +81,23 @@
                   std::unique_ptr<URLLoaderFactory> network_loader_factory)
       : network_loader_factory_(std::move(network_loader_factory),
                                 std::move(params->overridden_factory_receiver)),
-        overriding_factory_(std::move(params->overriding_factory)) {}
+        overriding_factory_(std::move(params->overriding_factory)),
+        skip_cors_enabled_scheme_check_(
+            params->skip_cors_enabled_scheme_check) {}
 
   FactoryOverride(const FactoryOverride&) = delete;
   FactoryOverride& operator=(const FactoryOverride&) = delete;
 
   mojom::URLLoaderFactory* get() { return overriding_factory_.get(); }
 
+  bool ShouldSkipCorsEnabledSchemeCheck() {
+    return skip_cors_enabled_scheme_check_;
+  }
+
  private:
   ExposedNetworkLoaderFactory network_loader_factory_;
   mojo::Remote<mojom::URLLoaderFactory> overriding_factory_;
+  bool skip_cors_enabled_scheme_check_;
 };
 
 bool CorsURLLoaderFactory::allow_external_preflights_for_testing_ = false;
@@ -182,9 +189,11 @@
         std::move(receiver), process_id_, routing_id, request_id, options,
         base::BindOnce(&CorsURLLoaderFactory::DestroyURLLoader,
                        base::Unretained(this)),
-        resource_request, ignore_isolated_world_origin_, std::move(client),
-        traffic_annotation, inner_url_loader_factory, origin_access_list_,
-        factory_bound_origin_access_list_.get(),
+        resource_request, ignore_isolated_world_origin_,
+        factory_override_ &&
+            factory_override_->ShouldSkipCorsEnabledSchemeCheck(),
+        std::move(client), traffic_annotation, inner_url_loader_factory,
+        origin_access_list_, factory_bound_origin_access_list_.get(),
         context_->cors_preflight_controller());
     auto* raw_loader = loader.get();
     OnLoaderCreated(std::move(loader));
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index a365dbfa..d1865a2 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -328,7 +328,8 @@
   void ResetFactory(base::Optional<url::Origin> initiator,
                     uint32_t process_id,
                     bool is_trusted,
-                    bool ignore_isolated_world_origin) {
+                    bool ignore_isolated_world_origin,
+                    bool skip_cors_enabled_scheme_check) {
     test_url_loader_factory_ = std::make_unique<TestURLLoaderFactory>();
     test_url_loader_factory_receiver_ =
         std::make_unique<mojo::Receiver<mojom::URLLoaderFactory>>(
@@ -337,12 +338,15 @@
     auto factory_params = network::mojom::URLLoaderFactoryParams::New();
     if (initiator) {
       factory_params->request_initiator_site_lock = *initiator;
-      factory_params->factory_bound_access_patterns =
-          network::mojom::CorsOriginAccessPatterns::New();
-      factory_params->factory_bound_access_patterns->source_origin = *initiator;
-      for (const auto& item : factory_bound_allow_patterns_) {
-        factory_params->factory_bound_access_patterns->allow_patterns.push_back(
-            item.Clone());
+      if (!initiator->opaque()) {
+        factory_params->factory_bound_access_patterns =
+            network::mojom::CorsOriginAccessPatterns::New();
+        factory_params->factory_bound_access_patterns->source_origin =
+            *initiator;
+        for (const auto& item : factory_bound_allow_patterns_) {
+          factory_params->factory_bound_access_patterns->allow_patterns
+              .push_back(item.Clone());
+        }
       }
     }
     factory_params->is_trusted = is_trusted;
@@ -352,6 +356,8 @@
     factory_params->factory_override = mojom::URLLoaderFactoryOverride::New();
     factory_params->factory_override->overriding_factory =
         test_url_loader_factory_receiver_->BindNewPipeAndPassRemote();
+    factory_params->factory_override->skip_cors_enabled_scheme_check =
+        skip_cors_enabled_scheme_check;
     auto resource_scheduler_client =
         base::MakeRefCounted<ResourceSchedulerClient>(
             process_id, ++last_issued_route_id, &resource_scheduler_,
@@ -368,7 +374,8 @@
                     uint32_t process_id) {
     auto params = network::mojom::URLLoaderFactoryParams::New();
     ResetFactory(initiator, process_id, params->is_trusted,
-                 params->ignore_isolated_world_origin);
+                 params->ignore_isolated_world_origin,
+                 false /* skip_cors_enabled_scheme_check */);
   }
 
   NetworkContext* network_context() { return network_context_.get(); }
@@ -692,6 +699,57 @@
             client().completion_status().cors_error_status->cors_error);
 }
 
+TEST_F(CorsURLLoaderTest, CorsEnabledSameCustomSchemeRequest) {
+  // Custom scheme should not be permitted by default.
+  const GURL origin("my-scheme://foo/index.html");
+  const GURL url("my-scheme://bar/baz.png");
+  CreateLoaderAndStart(origin, url, mojom::RequestMode::kCors);
+  RunUntilComplete();
+
+  EXPECT_FALSE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_FALSE(client().has_received_response());
+  EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code);
+  ASSERT_TRUE(client().completion_status().cors_error_status);
+  EXPECT_EQ(mojom::CorsError::kCorsDisabledScheme,
+            client().completion_status().cors_error_status->cors_error);
+
+  // Scheme check can be skipped via the factory params.
+  auto params = network::mojom::URLLoaderFactoryParams::New();
+  ResetFactory(url::Origin::Create(origin), mojom::kBrowserProcessId,
+               params->is_trusted, params->ignore_isolated_world_origin,
+               true /* skip_cors_enabled_scheme_check */);
+
+  // "Access-Control-Allow-Origin: *" accepts the custom scheme.
+  CreateLoaderAndStart(origin, url, mojom::RequestMode::kCors);
+  RunUntilCreateLoaderAndStartCalled();
+
+  NotifyLoaderClientOnReceiveResponse({"Access-Control-Allow-Origin: *"});
+  NotifyLoaderClientOnComplete(net::OK);
+
+  RunUntilComplete();
+
+  EXPECT_TRUE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_TRUE(client().has_received_response());
+  EXPECT_EQ(net::OK, client().completion_status().error_code);
+
+  // "Access-Control-Allow-Origin: null" accepts the custom scheme as a custom
+  // scheme is an opaque origin.
+  CreateLoaderAndStart(origin, url, mojom::RequestMode::kCors);
+  RunUntilCreateLoaderAndStartCalled();
+
+  NotifyLoaderClientOnReceiveResponse({"Access-Control-Allow-Origin: null"});
+  NotifyLoaderClientOnComplete(net::OK);
+
+  RunUntilComplete();
+
+  EXPECT_TRUE(IsNetworkLoaderStarted());
+  EXPECT_FALSE(client().has_received_redirect());
+  EXPECT_TRUE(client().has_received_response());
+  EXPECT_EQ(net::OK, client().completion_status().error_code);
+}
+
 TEST_F(CorsURLLoaderTest, StripUsernameAndPassword) {
   const GURL origin("http://example.com");
   const GURL url("http://foo:bar@other.com/foo.png");
@@ -1280,7 +1338,8 @@
   const GURL url("http://other.com/foo.png");
 
   ResetFactory(main_world_origin, kRendererProcessId, false /* trusted */,
-               false /* ignore_isolated_world_origin */);
+               false /* ignore_isolated_world_origin */,
+               false /* skip_cors_enabled_scheme_check */);
 
   AddAllowListEntryForOrigin(isolated_world_origin, url.scheme(), url.host(),
                              mojom::CorsDomainMatchMode::kDisallowSubdomains);
@@ -1323,7 +1382,8 @@
   const GURL new_url("http://other.com/bar.png");
 
   ResetFactory(main_world_origin, kRendererProcessId, false /* trusted */,
-               false /* ignore_isolated_world_origin */);
+               false /* ignore_isolated_world_origin */,
+               false /* skip_cors_enabled_scheme_check */);
 
   AddAllowListEntryForOrigin(isolated_world_origin, url.scheme(), url.host(),
                              mojom::CorsDomainMatchMode::kDisallowSubdomains);
@@ -1376,7 +1436,8 @@
   const GURL url("http://other.com/foo.png");
 
   ResetFactory(main_world_origin, kRendererProcessId, false /* trusted */,
-               true /* ignore_isolated_world_origin */);
+               true /* ignore_isolated_world_origin */,
+               false /* skip_cors_enabled_scheme_check */);
 
   AddAllowListEntryForOrigin(isolated_world_origin, url.scheme(), url.host(),
                              mojom::CorsDomainMatchMode::kDisallowSubdomains);
@@ -2013,7 +2074,8 @@
   for (bool is_trusted : {false, true}) {
     bool ignore_isolated_world_origin = true;  // This is the default.
     ResetFactory(base::nullopt, kRendererProcessId, is_trusted,
-                 ignore_isolated_world_origin);
+                 ignore_isolated_world_origin,
+                 false /* skip_cors_enabled_scheme_check */);
 
     BadMessageTestHelper bad_message_helper;
 
@@ -2060,7 +2122,8 @@
 // NetworkIsolationKey, CorsURLLoaderFactory does not reject the request.
 TEST_F(CorsURLLoaderTest, RestrictedPrefetchSucceedsWithNIK) {
   ResetFactory(base::nullopt, kRendererProcessId, true /* is_trusted */,
-               true /* ignore_isolated_world_origin */);
+               true /* ignore_isolated_world_origin */,
+               false /* skip_cors_enabled_scheme_check */);
 
   BadMessageTestHelper bad_message_helper;
 
@@ -2100,7 +2163,8 @@
 // make use of their TrustedParams' |network_isolation_key|.
 TEST_F(CorsURLLoaderTest, RestrictedPrefetchFailsWithoutNIK) {
   ResetFactory(base::nullopt, kRendererProcessId, true /* is_trusted */,
-               true /* ignore_isolated_world_origin */);
+               true /* ignore_isolated_world_origin */,
+               false /* skip_cors_enabled_scheme_check */);
 
   BadMessageTestHelper bad_message_helper;
 
diff --git a/services/network/public/cpp/OWNERS b/services/network/public/cpp/OWNERS
index 12614fd..999eb16 100644
--- a/services/network/public/cpp/OWNERS
+++ b/services/network/public/cpp/OWNERS
@@ -4,8 +4,6 @@
 per-file *_param_traits*.*=file://ipc/SECURITY_OWNERS
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
 per-file manifest.cc=set noparent
 per-file manifest.cc=file://ipc/SECURITY_OWNERS
 per-file manifest.h=set noparent
diff --git a/services/network/public/cpp/address_family.typemap b/services/network/public/cpp/address_family.typemap
deleted file mode 100644
index 3f37ac73..0000000
--- a/services/network/public/cpp/address_family.typemap
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/address_family.mojom"
-public_headers = [ "//net/base/address_family.h" ]
-traits_headers =
-    [ "/services/network/public/cpp/address_family_mojom_traits.h" ]
-sources = [
-  "//services/network/public/cpp/address_family_mojom_traits.cc",
-]
-type_mappings = [ "network.mojom.AddressFamily=::net::AddressFamily" ]
-public_deps = [
-  "//net",
-]
diff --git a/services/network/public/cpp/address_family_mojom_traits.h b/services/network/public/cpp/address_family_mojom_traits.h
index 645e7a4f..568ca5b7 100644
--- a/services/network/public/cpp/address_family_mojom_traits.h
+++ b/services/network/public/cpp/address_family_mojom_traits.h
@@ -6,6 +6,7 @@
 #define SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_
 
 #include "mojo/public/cpp/bindings/enum_traits.h"
+#include "net/base/address_family.h"
 #include "services/network/public/mojom/address_family.mojom.h"
 
 namespace mojo {
diff --git a/services/network/public/cpp/address_list.typemap b/services/network/public/cpp/address_list.typemap
deleted file mode 100644
index 78c7f3a..0000000
--- a/services/network/public/cpp/address_list.typemap
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/address_list.mojom"
-public_headers = [ "//net/base/address_list.h" ]
-traits_headers = [ "//services/network/public/cpp/address_list_mojom_traits.h" ]
-type_mappings = [ "network.mojom.AddressList=::net::AddressList" ]
-public_deps = [ "//net" ]
diff --git a/services/network/public/cpp/cookie_manager.typemap b/services/network/public/cpp/cookie_manager.typemap
deleted file mode 100644
index 2e43fdf..0000000
--- a/services/network/public/cpp/cookie_manager.typemap
+++ /dev/null
@@ -1,31 +0,0 @@
-mojom = "//services/network/public/mojom/cookie_manager.mojom"
-public_headers = [
-  "//ipc/ipc_message_utils.h",
-  "//net/cookies/cookie_options.h",
-  "//net/cookies/canonical_cookie.h",
-  "//net/cookies/cookie_change_dispatcher.h",
-  "//net/cookies/cookie_constants.h",
-]
-traits_headers =
-    [ "//services/network/public/cpp/cookie_manager_mojom_traits.h" ]
-sources = [
-  "//services/network/public/cpp/cookie_manager_mojom_traits.cc",
-]
-public_deps = [
-  "//net",
-]
-type_mappings = [
-  "network.mojom.CookiePriority=::net::CookiePriority",
-  "network.mojom.CookieSameSite=::net::CookieSameSite",
-  "network.mojom.CookieSameSiteContext=::net::CookieOptions::SameSiteCookieContext",
-  "network.mojom.CookieAccessSemantics=::net::CookieAccessSemantics",
-  "network.mojom.CookieOptions=::net::CookieOptions",
-  "network.mojom.CanonicalCookie=::net::CanonicalCookie",
-  "network.mojom.CookieInclusionStatusWarningReason=::net::CanonicalCookie::CookieInclusionStatus::WarningReason",
-  "network.mojom.CookieInclusionStatus=::net::CanonicalCookie::CookieInclusionStatus[move_only]",
-  "network.mojom.CookieWithStatus=::net::CookieWithStatus",
-  "network.mojom.CookieAndLineWithStatus=::net::CookieAndLineWithStatus",
-  "network.mojom.CookieChangeCause=::net::CookieChangeCause",
-  "network.mojom.CookieChangeInfo=::net::CookieChangeInfo",
-  "network.mojom.CookieSourceScheme=::net::CookieSourceScheme",
-]
diff --git a/services/network/public/cpp/cors_error_status.typemap b/services/network/public/cpp/cors_error_status.typemap
deleted file mode 100644
index 8e07b29..0000000
--- a/services/network/public/cpp/cors_error_status.typemap
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/url_loader.mojom"
-public_headers = [ "//services/network/public/cpp/cors/cors_error_status.h" ]
-traits_headers = [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
-deps = [
-  "//net",
-]
-public_deps = [
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [ "network.mojom.CorsErrorStatus=::network::CorsErrorStatus" ]
diff --git a/services/network/public/cpp/cross_origin_embedder_policy.typemap b/services/network/public/cpp/cross_origin_embedder_policy.typemap
deleted file mode 100644
index 986f520e..0000000
--- a/services/network/public/cpp/cross_origin_embedder_policy.typemap
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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.
-
-mojom = "//services/network/public/mojom/cross_origin_embedder_policy.mojom"
-public_headers =
-    [ "//services/network/public/cpp/cross_origin_embedder_policy.h" ]
-traits_headers = [
-  "//services/network/public/cpp/cross_origin_embedder_policy_mojom_traits.h",
-]
-sources = []
-type_mappings = [
-  "network.mojom.CrossOriginEmbedderPolicy=network::CrossOriginEmbedderPolicy",
-]
diff --git a/services/network/public/cpp/default_credentials.typemap b/services/network/public/cpp/default_credentials.typemap
deleted file mode 100644
index 09530f0..0000000
--- a/services/network/public/cpp/default_credentials.typemap
+++ /dev/null
@@ -1,17 +0,0 @@
-mojom = "//services/network/public/mojom/default_credentials.mojom"
-public_headers = [
-  "//ipc/ipc_message_utils.h",
-  "//net/http/http_auth_preferences.h",
-]
-traits_headers = [
-  "//services/network/public/cpp/default_credentials_mojom_traits.h",
-]
-sources = [
-  "//services/network/public/cpp/default_credentials_mojom_traits.cc",
-]
-public_deps = [
-  "//net",
-]
-type_mappings = [
-  "network.mojom.DefaultCredentials=net::HttpAuthPreferences::DefaultCredentials",
-]
diff --git a/services/network/public/cpp/digitally_signed.typemap b/services/network/public/cpp/digitally_signed.typemap
deleted file mode 100644
index a2c3781b..0000000
--- a/services/network/public/cpp/digitally_signed.typemap
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/digitally_signed.mojom"
-public_headers = [ "//net/cert/signed_certificate_timestamp.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/digitally_signed_mojom_traits.h" ]
-sources = [
-  "//services/network/public/cpp/digitally_signed_mojom_traits.cc",
-]
-type_mappings = [
-  "network.mojom.HashAlgorithm=::net::ct::DigitallySigned::HashAlgorithm",
-  "network.mojom.SignatureAlgorithm=::net::ct::DigitallySigned::SignatureAlgorithm",
-  "network.mojom.DigitallySigned=::net::ct::DigitallySigned",
-]
-public_deps = [
-  "//net",
-]
diff --git a/services/network/public/cpp/host_resolver.typemap b/services/network/public/cpp/host_resolver.typemap
deleted file mode 100644
index e081e71..0000000
--- a/services/network/public/cpp/host_resolver.typemap
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/host_resolver.mojom"
-public_headers = [
-  "//net/dns/dns_config.h",
-  "//net/dns/dns_config_overrides.h",
-  "//net/dns/host_resolver.h",
-  "//net/dns/host_resolver_source.h",
-  "//net/dns/public/resolve_error_info.h",
-  "//net/dns/public/dns_query_type.h",
-]
-traits_headers =
-    [ "//services/network/public/cpp/host_resolver_mojom_traits.h" ]
-sources = [
-  "//services/network/public/cpp/host_resolver_mojom_traits.cc",
-]
-public_deps = [
-  "//net",
-]
-type_mappings = [
-  "network.mojom.DnsConfigOverrides=::net::DnsConfigOverrides",
-  "network.mojom.DnsQueryType=::net::DnsQueryType",
-  "network.mojom.ResolveErrorInfo=::net::ResolveErrorInfo",
-  "network.mojom.ResolveHostParameters.Source=::net::HostResolverSource",
-  "network.mojom.MdnsListenClient.UpdateType=::net::HostResolver::MdnsListener::Delegate::UpdateType",
-  "network.mojom.SecureDnsMode=::net::DnsConfig::SecureDnsMode",
-]
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.cc b/services/network/public/cpp/host_resolver_mojom_traits.cc
index 1dfb0baaa..307dd56 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.cc
+++ b/services/network/public/cpp/host_resolver_mojom_traits.cc
@@ -7,6 +7,7 @@
 #include "mojo/public/cpp/base/time_mojom_traits.h"
 #include "services/network/public/cpp/ip_address_mojom_traits.h"
 #include "services/network/public/cpp/ip_endpoint_mojom_traits.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 
 namespace mojo {
 
diff --git a/services/network/public/cpp/host_resolver_mojom_traits.h b/services/network/public/cpp/host_resolver_mojom_traits.h
index 084ca52..cae8e0e 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits.h
+++ b/services/network/public/cpp/host_resolver_mojom_traits.h
@@ -13,6 +13,7 @@
 #include "base/time/time.h"
 #include "mojo/public/cpp/bindings/array_traits.h"
 #include "mojo/public/cpp/bindings/enum_traits.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/address_family.h"
 #include "net/base/ip_address.h"
@@ -22,7 +23,8 @@
 #include "net/dns/dns_hosts.h"
 #include "net/dns/host_resolver.h"
 #include "net/dns/public/dns_query_type.h"
-#include "services/network/public/mojom/host_resolver.mojom.h"
+#include "services/network/public/mojom/host_resolver.mojom-forward.h"
+#include "services/network/public/mojom/host_resolver.mojom-shared.h"
 
 namespace mojo {
 
@@ -47,9 +49,9 @@
   static base::Optional<std::vector<network::mojom::DnsHostPtr>> hosts(
       const net::DnsConfigOverrides& overrides);
 
-  static network::mojom::DnsConfigOverrides::Tristate
-  append_to_multi_label_name(const net::DnsConfigOverrides& overrides);
-  static network::mojom::DnsConfigOverrides::Tristate randomize_ports(
+  static network::mojom::DnsConfigOverrides_Tristate append_to_multi_label_name(
+      const net::DnsConfigOverrides& overrides);
+  static network::mojom::DnsConfigOverrides_Tristate randomize_ports(
       const net::DnsConfigOverrides& overrides);
 
   static int ndots(const net::DnsConfigOverrides& overrides) {
@@ -65,9 +67,9 @@
     return overrides.attempts.value_or(-1);
   }
 
-  static network::mojom::DnsConfigOverrides::Tristate rotate(
+  static network::mojom::DnsConfigOverrides_Tristate rotate(
       const net::DnsConfigOverrides& overrides);
-  static network::mojom::DnsConfigOverrides::Tristate use_local_ipv6(
+  static network::mojom::DnsConfigOverrides_Tristate use_local_ipv6(
       const net::DnsConfigOverrides& overrides);
 
   static base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
@@ -76,7 +78,7 @@
   static network::mojom::OptionalSecureDnsMode secure_dns_mode(
       const net::DnsConfigOverrides& overrides);
 
-  static network::mojom::DnsConfigOverrides::Tristate
+  static network::mojom::DnsConfigOverrides_Tristate
   allow_dns_over_https_upgrade(const net::DnsConfigOverrides& overrides);
 
   static const base::Optional<std::vector<std::string>>&
@@ -96,21 +98,21 @@
 };
 
 template <>
-struct EnumTraits<network::mojom::ResolveHostParameters::Source,
+struct EnumTraits<network::mojom::ResolveHostParameters_Source,
                   net::HostResolverSource> {
-  static network::mojom::ResolveHostParameters::Source ToMojom(
+  static network::mojom::ResolveHostParameters_Source ToMojom(
       net::HostResolverSource input);
-  static bool FromMojom(network::mojom::ResolveHostParameters::Source input,
+  static bool FromMojom(network::mojom::ResolveHostParameters_Source input,
                         net::HostResolverSource* output);
 };
 
 template <>
-struct EnumTraits<network::mojom::MdnsListenClient::UpdateType,
+struct EnumTraits<network::mojom::MdnsListenClient_UpdateType,
                   net::HostResolver::MdnsListener::Delegate::UpdateType> {
-  static network::mojom::MdnsListenClient::UpdateType ToMojom(
+  static network::mojom::MdnsListenClient_UpdateType ToMojom(
       net::HostResolver::MdnsListener::Delegate::UpdateType input);
   static bool FromMojom(
-      network::mojom::MdnsListenClient::UpdateType input,
+      network::mojom::MdnsListenClient_UpdateType input,
       net::HostResolver::MdnsListener::Delegate::UpdateType* output);
 };
 
diff --git a/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc b/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc
index dbd9cab..ee172ed 100644
--- a/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc
@@ -9,6 +9,7 @@
 #include "net/base/net_errors.h"
 #include "services/network/public/cpp/ip_address_mojom_traits.h"
 #include "services/network/public/cpp/ip_endpoint_mojom_traits.h"
+#include "services/network/public/mojom/host_resolver.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace network {
diff --git a/services/network/public/cpp/http_request_headers.typemap b/services/network/public/cpp/http_request_headers.typemap
deleted file mode 100644
index ad4a917..0000000
--- a/services/network/public/cpp/http_request_headers.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/http_request_headers.mojom"
-public_headers = [ "//net/http/http_request_headers.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/http_request_headers_mojom_traits.h" ]
-public_deps = [
-  "//net",
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [ "network.mojom.HttpRequestHeaders=::net::HttpRequestHeaders" ]
diff --git a/services/network/public/cpp/ip_address.typemap b/services/network/public/cpp/ip_address.typemap
deleted file mode 100644
index 4adb33590..0000000
--- a/services/network/public/cpp/ip_address.typemap
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/ip_address.mojom"
-public_headers = [ "//net/base/ip_address.h" ]
-traits_headers = [ "//services/network/public/cpp/ip_address_mojom_traits.h" ]
-type_mappings = [ "network.mojom.IPAddress=::net::IPAddress" ]
-public_deps = [ "//net" ]
diff --git a/services/network/public/cpp/ip_endpoint.typemap b/services/network/public/cpp/ip_endpoint.typemap
deleted file mode 100644
index 204e6b7..0000000
--- a/services/network/public/cpp/ip_endpoint.typemap
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/ip_endpoint.mojom"
-public_headers = [ "//net/base/ip_endpoint.h" ]
-traits_headers = [ "//services/network/public/cpp/ip_endpoint_mojom_traits.h" ]
-type_mappings = [ "network.mojom.IPEndPoint=::net::IPEndPoint" ]
-public_deps = [ "//net" ]
diff --git a/services/network/public/cpp/load_timing_info.typemap b/services/network/public/cpp/load_timing_info.typemap
deleted file mode 100644
index c2636ee..0000000
--- a/services/network/public/cpp/load_timing_info.typemap
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/load_timing_info.mojom"
-public_headers = [ "//net/base/load_timing_info.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/load_timing_info_mojom_traits.h" ]
-sources = [
-  "//services/network/public/cpp/load_timing_info_mojom_traits.cc",
-]
-type_mappings = [
-  "network.mojom.LoadTimingInfo=net::LoadTimingInfo",
-  "network.mojom.LoadTimingInfoConnectTiming=net::LoadTimingInfo::ConnectTiming",
-]
diff --git a/services/network/public/cpp/load_timing_info_mojom_traits.h b/services/network/public/cpp/load_timing_info_mojom_traits.h
index 87c0f39..c118264 100644
--- a/services/network/public/cpp/load_timing_info_mojom_traits.h
+++ b/services/network/public/cpp/load_timing_info_mojom_traits.h
@@ -7,7 +7,7 @@
 
 #include "mojo/public/cpp/bindings/struct_traits.h"
 #include "net/base/load_timing_info.h"
-#include "services/network/public/mojom/load_timing_info.mojom.h"
+#include "services/network/public/mojom/load_timing_info.mojom-shared.h"
 
 namespace mojo {
 
diff --git a/services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap b/services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap
deleted file mode 100644
index d2d8772..0000000
--- a/services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"
-public_headers = [ "//net/traffic_annotation/network_traffic_annotation.h" ]
-traits_headers = [ "//services/network/public/cpp/" +
-                   "mutable_network_traffic_annotation_tag_mojom_traits.h" ]
-deps = [
-  "//net/traffic_annotation",
-]
-type_mappings = [ "network.mojom.MutableNetworkTrafficAnnotationTag=" +
-                  "::net::MutableNetworkTrafficAnnotationTag" ]
diff --git a/services/network/public/cpp/mutable_partial_network_traffic_annotation_tag.typemap b/services/network/public/cpp/mutable_partial_network_traffic_annotation_tag.typemap
deleted file mode 100644
index c38c890..0000000
--- a/services/network/public/cpp/mutable_partial_network_traffic_annotation_tag.typemap
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/mutable_partial_network_traffic_annotation_tag.mojom"
-public_headers = [ "//net/traffic_annotation/network_traffic_annotation.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/" +
-      "mutable_partial_network_traffic_annotation_tag_mojom_traits.h" ]
-deps = [
-  "//net/traffic_annotation",
-]
-type_mappings = [ "network.mojom.MutablePartialNetworkTrafficAnnotationTag=" +
-                  "::net::MutablePartialNetworkTrafficAnnotationTag" ]
diff --git a/services/network/public/cpp/net_log.typemap b/services/network/public/cpp/net_log.typemap
deleted file mode 100644
index 6449f57..0000000
--- a/services/network/public/cpp/net_log.typemap
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/net_log.mojom"
-public_headers = [
-  "//net/log/net_log_capture_mode.h",
-  "//net/log/net_log_event_type.h",
-]
-traits_headers = [ "/services/network/public/cpp/net_log_mojom_traits.h" ]
-sources = [ "//services/network/public/cpp/net_log_mojom_traits.cc" ]
-type_mappings = [
-  "network.mojom.NetLogCaptureMode=::net::NetLogCaptureMode",
-  "network.mojom.NetLogEventPhase=::net::NetLogEventPhase",
-]
-public_deps = [ "//net" ]
diff --git a/services/network/public/cpp/net_log_mojom_traits.h b/services/network/public/cpp/net_log_mojom_traits.h
index f9e13f2..6ef895a3 100644
--- a/services/network/public/cpp/net_log_mojom_traits.h
+++ b/services/network/public/cpp/net_log_mojom_traits.h
@@ -6,7 +6,9 @@
 #define SERVICES_NETWORK_PUBLIC_CPP_NET_LOG_MOJOM_TRAITS_H_
 
 #include "mojo/public/cpp/bindings/enum_traits.h"
-#include "services/network/public/mojom/net_log.mojom.h"
+#include "net/log/net_log_capture_mode.h"
+#include "net/log/net_log_event_type.h"
+#include "services/network/public/mojom/net_log.mojom-shared.h"
 
 namespace mojo {
 
diff --git a/services/network/public/cpp/network_interface.typemap b/services/network/public/cpp/network_interface.typemap
deleted file mode 100644
index 6433cfb..0000000
--- a/services/network/public/cpp/network_interface.typemap
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/network_interface.mojom"
-public_headers = [ "//net/base/network_interfaces.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/network_interface_mojom_traits.h" ]
-public_deps = [ "//net" ]
-type_mappings = [ "network.mojom.NetworkInterface=::net::NetworkInterface" ]
diff --git a/services/network/public/cpp/network_isolation_key.typemap b/services/network/public/cpp/network_isolation_key.typemap
deleted file mode 100644
index a83a435..0000000
--- a/services/network/public/cpp/network_isolation_key.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.↵
-
-mojom = "//services/network/public/mojom/network_isolation_key.mojom"
-public_headers = [ "//net/base/network_isolation_key.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/network_isolation_key_mojom_traits.h" ]
-public_deps = [
-  "//net",
-]
-type_mappings =
-    [ "network.mojom.NetworkIsolationKey=::net::NetworkIsolationKey" ]
diff --git a/services/network/public/cpp/network_param.typemap b/services/network/public/cpp/network_param.typemap
deleted file mode 100644
index ca29d0a..0000000
--- a/services/network/public/cpp/network_param.typemap
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/network_param.mojom"
-public_headers = [
-  "//net/base/auth.h",
-  "//net/base/host_port_pair.h",
-  "//net/cert/cert_verify_result.h",
-  "//net/cert/ct_verify_result.h",
-  "//net/cert/x509_certificate.h",
-  "//net/http/http_request_headers.h",
-  "//net/http/http_response_headers.h",
-  "//net/http/http_version.h",
-  "//net/ssl/ssl_cert_request_info.h",
-  "//net/ssl/ssl_info.h",
-]
-traits_headers = [
-  "//services/network/public/cpp/net_ipc_param_traits.h",
-  "//services/network/public/cpp/network_param_mojom_traits.h",
-]
-
-sources = [
-  "//services/network/public/cpp/network_param_mojom_traits.cc",
-]
-
-deps = [
-  "//ipc",
-]
-
-public_deps = [
-  "//net",
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [
-  "network.mojom.AuthChallengeInfo=::net::AuthChallengeInfo",
-  "network.mojom.AuthCredentials=::net::AuthCredentials",
-  "network.mojom.CertVerifyResult=::net::CertVerifyResult",
-  "network.mojom.CTVerifyResult=::net::ct::CTVerifyResult",
-  "network.mojom.HostPortPair=::net::HostPortPair",
-  "network.mojom.HttpResponseHeaders=::scoped_refptr<::net::HttpResponseHeaders>[nullable_is_same_type]",
-  "network.mojom.HttpVersion=::net::HttpVersion",
-  "network.mojom.SSLCertRequestInfo=::scoped_refptr<::net::SSLCertRequestInfo>[nullable_is_same_type]",
-  "network.mojom.SSLInfo=::net::SSLInfo",
-  "network.mojom.X509Certificate=::scoped_refptr<::net::X509Certificate>[nullable_is_same_type]",
-]
diff --git a/services/network/public/cpp/network_types.typemap b/services/network/public/cpp/network_types.typemap
deleted file mode 100644
index 4a03345..0000000
--- a/services/network/public/cpp/network_types.typemap
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/network_types.mojom"
-public_deps = [
-  "//net",
-]
-public_headers = [
-  "//net/cert/ct_policy_status.h",
-  "//net/http/http_response_info.h",
-  "//net/nqe/effective_connection_type.h",
-]
-traits_headers = [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
-deps = [
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [
-  "network.mojom.ConnectionInfo=::net::HttpResponseInfo::ConnectionInfo",
-  "network.mojom.CTPolicyCompliance=::net::ct::CTPolicyCompliance",
-  "network.mojom.EffectiveConnectionType=::net::EffectiveConnectionType",
-]
diff --git a/services/network/public/cpp/origin_policy.typemap b/services/network/public/cpp/origin_policy.typemap
deleted file mode 100644
index 39e545e..0000000
--- a/services/network/public/cpp/origin_policy.typemap
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/origin_policy_manager.mojom"
-public_headers = [ "//services/network/public/cpp/origin_policy.h" ]
-traits_headers = [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
-deps = [
-  "//url/mojom:url_mojom_gurl",
-]
-type_mappings = [ "network.mojom.OriginPolicy=::network::OriginPolicy" ]
diff --git a/services/network/public/cpp/p2p.typemap b/services/network/public/cpp/p2p.typemap
deleted file mode 100644
index 600b50c..0000000
--- a/services/network/public/cpp/p2p.typemap
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/p2p.mojom"
-
-public_headers = [ "//services/network/public/cpp/p2p_socket_type.h" ]
-
-traits_headers = [ "//services/network/public/cpp/p2p_param_traits.h" ]
-
-public_deps = [
-  "//services/network/public/mojom:mutable_network_traffic_annotation_interface",
-  "//third_party/webrtc_overrides:webrtc_component",
-]
-
-type_mappings = [
-  "network.mojom.P2PPacketInfo=::network::P2PPacketInfo",
-  "network.mojom.P2PSendPacketMetrics=::network::P2PSendPacketMetrics",
-  "network.mojom.P2PPortRange=::network::P2PPortRange",
-  "network.mojom.P2PHostAndIPEndPoint=::network::P2PHostAndIPEndPoint",
-  "network.mojom.P2PSocketOption=::network::P2PSocketOption",
-  "network.mojom.P2PSocketType=::network::P2PSocketType",
-]
diff --git a/services/network/public/cpp/p2p_param_traits.h b/services/network/public/cpp/p2p_param_traits.h
index 9572939..88a5120 100644
--- a/services/network/public/cpp/p2p_param_traits.h
+++ b/services/network/public/cpp/p2p_param_traits.h
@@ -15,7 +15,6 @@
 #include "net/base/ip_address.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_interfaces.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/p2p_socket_type.h"
 #include "third_party/webrtc/rtc_base/async_packet_socket.h"
 
diff --git a/services/network/public/cpp/proxy_config.typemap b/services/network/public/cpp/proxy_config.typemap
deleted file mode 100644
index 9c9b1c5..0000000
--- a/services/network/public/cpp/proxy_config.typemap
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/proxy_config.mojom"
-public_headers = [
-  "//net/proxy_resolution/proxy_bypass_rules.h",
-  "//net/proxy_resolution/proxy_config.h",
-  "//net/proxy_resolution/proxy_list.h",
-]
-traits_headers = [ "//services/network/public/cpp/proxy_config_mojom_traits.h" ]
-deps = [
-  "//net:net",
-  "//services/network/public/mojom:mojom_shared",
-]
-type_mappings = [
-  "network.mojom.ProxyBypassRules=::net::ProxyBypassRules",
-  "network.mojom.ProxyList=::net::ProxyList",
-  "network.mojom.ProxyRulesType=::net::ProxyConfig::ProxyRules::Type",
-  "network.mojom.ProxyRules=::net::ProxyConfig::ProxyRules",
-  "network.mojom.ProxyConfig=::net::ProxyConfig",
-  "network.mojom.ProxyConfigWithAnnotation=::net::ProxyConfigWithAnnotation",
-]
diff --git a/services/network/public/cpp/proxy_config_mojom_traits.h b/services/network/public/cpp/proxy_config_mojom_traits.h
index 6d60b0d..b429344 100644
--- a/services/network/public/cpp/proxy_config_mojom_traits.h
+++ b/services/network/public/cpp/proxy_config_mojom_traits.h
@@ -10,6 +10,7 @@
 #include "mojo/public/cpp/base/big_string_mojom_traits.h"
 #include "net/proxy_resolution/proxy_bypass_rules.h"
 #include "net/proxy_resolution/proxy_config.h"
+#include "net/proxy_resolution/proxy_config_with_annotation.h"
 #include "net/proxy_resolution/proxy_list.h"
 #include "services/network/public/mojom/proxy_config.mojom-shared.h"
 #include "url/mojom/url_gurl_mojom_traits.h"
diff --git a/services/network/public/cpp/proxy_config_with_annotation.typemap b/services/network/public/cpp/proxy_config_with_annotation.typemap
deleted file mode 100644
index 5c4e29a5..0000000
--- a/services/network/public/cpp/proxy_config_with_annotation.typemap
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/proxy_config_with_annotation.mojom"
-public_headers = [ "//net/proxy_resolution/proxy_config_with_annotation.h" ]
-traits_headers = [
-  "//services/network/public/cpp/proxy_config_with_annotation_mojom_traits.h",
-]
-deps = [
-  "//net:net",
-  "//services/network/public/mojom:mojom_shared",
-]
-type_mappings = [
-  "network.mojom.ProxyConfigWithAnnotation=::net::ProxyConfigWithAnnotation",
-]
diff --git a/services/network/public/cpp/site_for_cookies.typemap b/services/network/public/cpp/site_for_cookies.typemap
deleted file mode 100644
index 7b1a7c8..0000000
--- a/services/network/public/cpp/site_for_cookies.typemap
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright 2019 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.↵
-
-mojom = "//services/network/public/mojom/site_for_cookies.mojom"
-public_headers = [ "//net/cookies/site_for_cookies.h" ]
-traits_headers =
-    [ "//services/network/public/cpp/site_for_cookies_mojom_traits.h" ]
-public_deps = [
-  "//net",
-]
-type_mappings = [ "network.mojom.SiteForCookies=::net::SiteForCookies" ]
diff --git a/services/network/public/cpp/typemaps.gni b/services/network/public/cpp/typemaps.gni
deleted file mode 100644
index 2922835..0000000
--- a/services/network/public/cpp/typemaps.gni
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//services/network/public/cpp/features.gni")
-
-typemaps = [
-  "//services/network/public/cpp/address_family.typemap",
-  "//services/network/public/cpp/address_list.typemap",
-  "//services/network/public/cpp/cookie_manager.typemap",
-  "//services/network/public/cpp/cross_origin_embedder_policy.typemap",
-  "//services/network/public/cpp/default_credentials.typemap",
-  "//services/network/public/cpp/cors_error_status.typemap",
-  "//services/network/public/cpp/digitally_signed.typemap",
-  "//services/network/public/cpp/http_request_headers.typemap",
-  "//services/network/public/cpp/host_resolver.typemap",
-  "//services/network/public/cpp/ip_address.typemap",
-  "//services/network/public/cpp/ip_endpoint.typemap",
-  "//services/network/public/cpp/load_timing_info.typemap",
-  "//services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap",
-  "//services/network/public/cpp/mutable_partial_network_traffic_annotation_tag.typemap",
-  "//services/network/public/cpp/net_log.typemap",
-  "//services/network/public/cpp/network_interface.typemap",
-  "//services/network/public/cpp/network_isolation_key.typemap",
-  "//services/network/public/cpp/network_param.typemap",
-  "//services/network/public/cpp/network_types.typemap",
-  "//services/network/public/cpp/origin_policy.typemap",
-  "//services/network/public/cpp/p2p.typemap",
-  "//services/network/public/cpp/proxy_config.typemap",
-  "//services/network/public/cpp/proxy_config_with_annotation.typemap",
-  "//services/network/public/cpp/site_for_cookies.typemap",
-  "//services/network/public/cpp/url_loader_completion_status.typemap",
-  "//services/network/public/cpp/url_request.typemap",
-  "//services/network/public/cpp/url_request_redirect_info.typemap",
-]
diff --git a/services/network/public/cpp/url_loader_completion_status.typemap b/services/network/public/cpp/url_loader_completion_status.typemap
deleted file mode 100644
index c4b7e5d3..0000000
--- a/services/network/public/cpp/url_loader_completion_status.typemap
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/url_loader.mojom"
-public_headers =
-    [ "//services/network/public/cpp/url_loader_completion_status.h" ]
-traits_headers = [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
-deps = [
-  "//net",
-]
-public_deps = [
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [ "network.mojom.URLLoaderCompletionStatus=::network::URLLoaderCompletionStatus" ]
diff --git a/services/network/public/cpp/url_request.typemap b/services/network/public/cpp/url_request.typemap
deleted file mode 100644
index cec7cad..0000000
--- a/services/network/public/cpp/url_request.typemap
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/url_loader.mojom"
-public_headers = [
-  "//base/memory/scoped_refptr.h",
-  "//services/network/public/cpp/data_element.h",
-  "//services/network/public/cpp/resource_request.h",
-  "//services/network/public/cpp/resource_request_body.h",
-]
-traits_headers = [
-  "//services/network/public/cpp/network_ipc_param_traits.h",
-  "//services/network/public/cpp/url_request_mojom_traits.h",
-  "//services/network/public/cpp/network_isolation_key_mojom_traits.h",
-  "//url/mojom/origin_mojom_traits.h",
-  "//url/mojom/url_gurl_mojom_traits.h",
-]
-public_deps = [
-  "//base",
-  "//services/network/public/cpp:cpp_base",
-]
-type_mappings = [
-  "network.mojom.TrustedUrlRequestParams=::network::ResourceRequest::TrustedParams",
-  "network.mojom.URLRequest=::network::ResourceRequest",
-  "network.mojom.URLRequestBody=::scoped_refptr<::network::ResourceRequestBody>[nullable_is_same_type,copyable_pass_by_value]",
-  "network.mojom.URLRequestReferrerPolicy=::net::URLRequest::ReferrerPolicy",
-  "network.mojom.RequestPriority=::net::RequestPriority",
-  "network.mojom.DataElement=::network::DataElement[move_only]",
-]
diff --git a/services/network/public/cpp/url_request_mojom_traits.h b/services/network/public/cpp/url_request_mojom_traits.h
index a8de298..b16e58c 100644
--- a/services/network/public/cpp/url_request_mojom_traits.h
+++ b/services/network/public/cpp/url_request_mojom_traits.h
@@ -27,6 +27,7 @@
 #include "services/network/public/mojom/data_pipe_getter.mojom.h"
 #include "services/network/public/mojom/trust_tokens.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom-shared.h"
+#include "url/mojom/url_gurl_mojom_traits.h"
 
 namespace mojo {
 
diff --git a/services/network/public/cpp/url_request_redirect_info.typemap b/services/network/public/cpp/url_request_redirect_info.typemap
deleted file mode 100644
index 25add786..0000000
--- a/services/network/public/cpp/url_request_redirect_info.typemap
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2016 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//services/network/public/mojom/url_loader.mojom"
-public_headers = [ "//net/url_request/redirect_info.h" ]
-traits_headers = [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
-public_deps = [
-  "//net:net",
-]
-type_mappings = [ "network.mojom.URLRequestRedirectInfo=::net::RedirectInfo" ]
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 0aba89b..5a24bf3e 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -19,6 +19,67 @@
   ]
 
   public_deps = [ "//url/mojom:url_mojom_gurl" ]
+
+  shared_cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.IPAddress"
+          cpp = "::net::IPAddress"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/ip_address_mojom_traits.h" ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.IPEndPoint"
+          cpp = "::net::IPEndPoint"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/ip_endpoint_mojom_traits.h" ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+  ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.AddressFamily"
+          cpp = "::net::AddressFamily"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/address_family_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/address_family_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.AddressList"
+          cpp = "::net::AddressList"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/address_list_mojom_traits.h" ]
+      traits_public_deps = [ "//net" ]
+    },
+  ]
+  cpp_typemaps += shared_cpp_typemaps
+
+  blink_cpp_typemaps = shared_cpp_typemaps
 }
 
 # As with mojom_ip_address, this is a separate target to avoid a circular
@@ -37,6 +98,21 @@
     export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
     export_header_blink = "third_party/blink/public/platform/web_common.h"
   }
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.NetworkIsolationKey"
+          cpp = "::net::NetworkIsolationKey"
+        },
+      ]
+      traits_headers = [
+        "//services/network/public/cpp/network_isolation_key_mojom_traits.h",
+      ]
+      traits_public_deps = [ "//net" ]
+    },
+  ]
 }
 
 # These interfaces are put in their own target to avoid a circular dependency,
@@ -66,6 +142,33 @@
     "mutable_network_traffic_annotation_tag.mojom",
     "mutable_partial_network_traffic_annotation_tag.mojom",
   ]
+
+  mutable_network_traffic_annotation_tag_typemap = {
+    types = [
+      {
+        mojom = "network.mojom.MutableNetworkTrafficAnnotationTag"
+        cpp = "::net::MutableNetworkTrafficAnnotationTag"
+      },
+    ]
+    traits_headers = [ "//services/network/public/cpp/mutable_network_traffic_annotation_tag_mojom_traits.h" ]
+    traits_public_deps = [ "//net/traffic_annotation" ]
+  }
+
+  cpp_typemaps = [
+    mutable_network_traffic_annotation_tag_typemap,
+    {
+      types = [
+        {
+          mojom = "network.mojom.MutablePartialNetworkTrafficAnnotationTag"
+          cpp = "::net::MutablePartialNetworkTrafficAnnotationTag"
+        },
+      ]
+      traits_headers = [ "//services/network/public/cpp/mutable_partial_network_traffic_annotation_tag_mojom_traits.h" ]
+      traits_public_deps = [ "//net/traffic_annotation" ]
+    },
+  ]
+
+  blink_cpp_typemaps = [ mutable_network_traffic_annotation_tag_typemap ]
 }
 
 # These interfaces are put in their own target to avoid a circular dependency,
@@ -103,6 +206,67 @@
     export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
     export_header_blink = "third_party/blink/public/platform/web_common.h"
   }
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.AuthChallengeInfo"
+          cpp = "::net::AuthChallengeInfo"
+        },
+        {
+          mojom = "network.mojom.AuthCredentials"
+          cpp = "::net::AuthCredentials"
+        },
+        {
+          mojom = "network.mojom.CertVerifyResult"
+          cpp = "::net::CertVerifyResult"
+        },
+        {
+          mojom = "network.mojom.CTVerifyResult"
+          cpp = "::net::ct::CTVerifyResult"
+        },
+        {
+          mojom = "network.mojom.HostPortPair"
+          cpp = "::net::HostPortPair"
+        },
+        {
+          mojom = "network.mojom.HttpResponseHeaders"
+          cpp = "::scoped_refptr<::net::HttpResponseHeaders>"
+          nullable_is_same_type = true
+        },
+        {
+          mojom = "network.mojom.HttpVersion"
+          cpp = "::net::HttpVersion"
+        },
+        {
+          mojom = "network.mojom.SSLCertRequestInfo"
+          cpp = "::scoped_refptr<::net::SSLCertRequestInfo>"
+          nullable_is_same_type = true
+        },
+        {
+          mojom = "network.mojom.SSLInfo"
+          cpp = "::net::SSLInfo"
+        },
+        {
+          mojom = "network.mojom.X509Certificate"
+          cpp = "::scoped_refptr<::net::X509Certificate>"
+          nullable_is_same_type = true
+        },
+      ]
+      traits_headers = [
+        "//services/network/public/cpp/net_ipc_param_traits.h",
+        "//services/network/public/cpp/network_param_mojom_traits.h",
+      ]
+      traits_sources =
+          [ "//services/network/public/cpp/network_param_mojom_traits.cc" ]
+      traits_public_deps = [
+        "//ipc",
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+  ]
 }
 
 mojom("mojom") {
@@ -203,4 +367,380 @@
   if (builtin_cert_verifier_feature_supported) {
     enabled_features += [ "is_builtin_cert_verifier_feature_supported" ]
   }
+
+  # Typemaps which apply to both Blink and non-Blink bindings.
+  shared_cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.CrossOriginEmbedderPolicy"
+          cpp = "::network::CrossOriginEmbedderPolicy"
+        },
+      ]
+      traits_headers = [ "//services/network/public/cpp/cross_origin_embedder_policy_mojom_traits.h" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.TrustedUrlRequestParams"
+          cpp = "::network::ResourceRequest::TrustedParams"
+        },
+        {
+          mojom = "network.mojom.URLRequest"
+          cpp = "::network::ResourceRequest"
+        },
+        {
+          mojom = "network.mojom.URLRequestBody"
+          cpp = "::scoped_refptr<::network::ResourceRequestBody>"
+          nullable_is_same_type = true
+          copyable_pass_by_value = true
+        },
+        {
+          mojom = "network.mojom.URLRequestReferrerPolicy"
+          cpp = "::net::URLRequest::ReferrerPolicy"
+        },
+        {
+          mojom = "network.mojom.RequestPriority"
+          cpp = "::net::RequestPriority"
+        },
+        {
+          mojom = "network.mojom.DataElement"
+          cpp = "::network::DataElement"
+          move_only = true
+        },
+      ]
+      traits_headers = [
+        "//services/network/public/cpp/network_ipc_param_traits.h",
+        "//services/network/public/cpp/url_request_mojom_traits.h",
+        "//services/network/public/cpp/network_isolation_key_mojom_traits.h",
+      ]
+      traits_public_deps = [
+        "//base",
+        "//services/network/public/cpp:cpp_base",
+        "//url/mojom:url_mojom_gurl",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.HttpRequestHeaders"
+          cpp = "::net::HttpRequestHeaders"
+        },
+      ]
+      traits_headers = [
+        "//services/network/public/cpp/http_request_headers_mojom_traits.h",
+      ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.NetworkInterface"
+          cpp = "::net::NetworkInterface"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/network_interface_mojom_traits.h" ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.SiteForCookies"
+          cpp = "::net::SiteForCookies"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/site_for_cookies_mojom_traits.h" ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.P2PPacketInfo"
+          cpp = "::network::P2PPacketInfo"
+        },
+        {
+          mojom = "network.mojom.P2PSendPacketMetrics"
+          cpp = "::network::P2PSendPacketMetrics"
+        },
+        {
+          mojom = "network.mojom.P2PPortRange"
+          cpp = "::network::P2PPortRange"
+        },
+        {
+          mojom = "network.mojom.P2PHostAndIPEndPoint"
+          cpp = "::network::P2PHostAndIPEndPoint"
+        },
+        {
+          mojom = "network.mojom.P2PSocketOption"
+          cpp = "::network::P2PSocketOption"
+        },
+        {
+          mojom = "network.mojom.P2PSocketType"
+          cpp = "::network::P2PSocketType"
+        },
+      ]
+      traits_headers = [ "//services/network/public/cpp/p2p_param_traits.h" ]
+      traits_public_deps = [
+        "//services/network/public/cpp:cpp_base",
+        "//third_party/webrtc_overrides:webrtc_component",
+      ]
+    },
+  ]
+
+  # Typemaps applied only to non-Blink bindings
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "network.mojom.CookiePriority"
+          cpp = "::net::CookiePriority"
+        },
+        {
+          mojom = "network.mojom.CookieSameSite"
+          cpp = "::net::CookieSameSite"
+        },
+        {
+          mojom = "network.mojom.CookieSameSiteContext"
+          cpp = "::net::CookieOptions::SameSiteCookieContext"
+        },
+        {
+          mojom = "network.mojom.CookieAccessSemantics"
+          cpp = "::net::CookieAccessSemantics"
+        },
+        {
+          mojom = "network.mojom.CookieOptions"
+          cpp = "::net::CookieOptions"
+        },
+        {
+          mojom = "network.mojom.CanonicalCookie"
+          cpp = "::net::CanonicalCookie"
+        },
+        {
+          mojom = "network.mojom.CookieInclusionStatusWarningReason"
+          cpp = "::net::CanonicalCookie::CookieInclusionStatus::WarningReason"
+        },
+        {
+          mojom = "network.mojom.CookieInclusionStatus"
+          cpp = "::net::CanonicalCookie::CookieInclusionStatus"
+          move_only = true
+        },
+        {
+          mojom = "network.mojom.CookieWithStatus"
+          cpp = "::net::CookieWithStatus"
+        },
+        {
+          mojom = "network.mojom.CookieAndLineWithStatus"
+          cpp = "::net::CookieAndLineWithStatus"
+        },
+        {
+          mojom = "network.mojom.CookieChangeCause"
+          cpp = "::net::CookieChangeCause"
+        },
+        {
+          mojom = "network.mojom.CookieChangeInfo"
+          cpp = "::net::CookieChangeInfo"
+        },
+        {
+          mojom = "network.mojom.CookieSourceScheme"
+          cpp = "::net::CookieSourceScheme"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/cookie_manager_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/cookie_manager_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.DefaultCredentials"
+          cpp = "::net::HttpAuthPreferences::DefaultCredentials"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/default_credentials_mojom_traits.h" ]
+      traits_sources = [
+        "//services/network/public/cpp/default_credentials_mojom_traits.cc",
+      ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.CTPolicyCompliance"
+          cpp = "::net::ct::CTPolicyCompliance"
+        },
+        {
+          mojom = "network.mojom.ConnectionInfo"
+          cpp = "::net::HttpResponseInfo::ConnectionInfo"
+        },
+        {
+          mojom = "network.mojom.CorsErrorStatus"
+          cpp = "::network::CorsErrorStatus"
+        },
+        {
+          mojom = "network.mojom.EffectiveConnectionType"
+          cpp = "::net::EffectiveConnectionType"
+        },
+        {
+          mojom = "network.mojom.OriginPolicy"
+          cpp = "::network::OriginPolicy"
+        },
+        {
+          mojom = "network.mojom.URLLoaderCompletionStatus"
+          cpp = "::network::URLLoaderCompletionStatus"
+        },
+        {
+          mojom = "network.mojom.URLRequestRedirectInfo"
+          cpp = "::net::RedirectInfo"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/network_ipc_param_traits.h" ]
+      traits_public_deps = [
+        "//net",
+        "//services/network/public/cpp:cpp_base",
+      ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.HashAlgorithm"
+          cpp = "::net::ct::DigitallySigned::HashAlgorithm"
+        },
+        {
+          mojom = "network.mojom.SignatureAlgorithm"
+          cpp = "::net::ct::DigitallySigned::SignatureAlgorithm"
+        },
+        {
+          mojom = "network.mojom.DigitallySigned"
+          cpp = "::net::ct::DigitallySigned"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/digitally_signed_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/digitally_signed_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.DnsConfigOverrides"
+          cpp = "::net::DnsConfigOverrides"
+        },
+        {
+          mojom = "network.mojom.DnsQueryType"
+          cpp = "::net::DnsQueryType"
+        },
+        {
+          mojom = "network.mojom.ResolveErrorInfo"
+          cpp = "::net::ResolveErrorInfo"
+        },
+        {
+          mojom = "network.mojom.ResolveHostParameters.Source"
+          cpp = "::net::HostResolverSource"
+        },
+        {
+          mojom = "network.mojom.MdnsListenClient.UpdateType"
+          cpp = "::net::HostResolver::MdnsListener::Delegate::UpdateType"
+        },
+        {
+          mojom = "network.mojom.SecureDnsMode"
+          cpp = "::net::DnsConfig::SecureDnsMode"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/host_resolver_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/host_resolver_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.LoadTimingInfo"
+          cpp = "::net::LoadTimingInfo"
+        },
+        {
+          mojom = "network.mojom.LoadTimingInfoConnectTiming"
+          cpp = "::net::LoadTimingInfo::ConnectTiming"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/load_timing_info_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/load_timing_info_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.NetLogCaptureMode"
+          cpp = "::net::NetLogCaptureMode"
+        },
+        {
+          mojom = "network.mojom.NetLogEventPhase"
+          cpp = "::net::NetLogEventPhase"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/net_log_mojom_traits.h" ]
+      traits_sources =
+          [ "//services/network/public/cpp/net_log_mojom_traits.cc" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.ProxyBypassRules"
+          cpp = "::net::ProxyBypassRules"
+        },
+        {
+          mojom = "network.mojom.ProxyList"
+          cpp = "::net::ProxyList"
+        },
+        {
+          mojom = "network.mojom.ProxyRulesType"
+          cpp = "::net::ProxyConfig::ProxyRules::Type"
+        },
+        {
+          mojom = "network.mojom.ProxyRules"
+          cpp = "::net::ProxyConfig::ProxyRules"
+        },
+        {
+          mojom = "network.mojom.ProxyConfig"
+          cpp = "::net::ProxyConfig"
+        },
+      ]
+      traits_headers =
+          [ "//services/network/public/cpp/proxy_config_mojom_traits.h" ]
+      traits_public_deps = [ "//net" ]
+    },
+    {
+      types = [
+        {
+          mojom = "network.mojom.ProxyConfigWithAnnotation"
+          cpp = "::net::ProxyConfigWithAnnotation"
+        },
+      ]
+      traits_headers = [ "//services/network/public/cpp/proxy_config_with_annotation_mojom_traits.h" ]
+      traits_public_deps = [ "//net" ]
+    },
+  ]
+  cpp_typemaps += shared_cpp_typemaps
+
+  blink_cpp_typemaps = shared_cpp_typemaps
 }
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index b74677d..88d890a7 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -563,6 +563,10 @@
 
   // A receiver for the replaced "internal" URLLoaderFactory.
   pending_receiver<URLLoaderFactory>? overridden_factory_receiver;
+
+  // Specifies if we should accept unknown schemes for CORS-enabled requests
+  // so that the |overriding_factory| can provide content for custom schemes.
+  bool skip_cors_enabled_scheme_check = false;
 };
 
 struct ClientSecurityState {
diff --git a/testing/scripts/run_android_wpt.py b/testing/scripts/run_android_wpt.py
index efdea2f..8b01a6f 100755
--- a/testing/scripts/run_android_wpt.py
+++ b/testing/scripts/run_android_wpt.py
@@ -58,10 +58,6 @@
 WEBLAYER_SHELL_PKG = 'org.chromium.weblayer.shell'
 WEBLAYER_SUPPORT_PKG = 'org.chromium.weblayer.support'
 
-# This avoids having to update the hosts file on device.
-HOST_RESOLVER_ARGS = ['--host-resolver-rules=MAP nonexistent.*.test ~NOTFOUND,'
-                      ' MAP *.test 127.0.0.1']
-
 # List of supported products.
 PRODUCTS = ['android_weblayer', 'android_webview', 'chrome_android']
 
@@ -117,15 +113,8 @@
       "--no-capture-stdio",
       "--no-manifest-download",
       "--no-fail-on-unexpected",
-      #TODO(aluo): Tune this as tests are stabilized
-      "--timeout-multiplier",
-      "0.25",
     ])
 
-    # Lets weblayer know it's running in test mode.
-    if self.options.product == 'android_weblayer':
-      rest_args.extend(["--binary-arg=--run-web-tests"])
-
     # Default to the apk's package name for chrome_android
     if not self.options.package_name:
       if self.options.product == 'chrome_android':
diff --git a/third_party/blink/common/devtools/OWNERS b/third_party/blink/common/devtools/OWNERS
deleted file mode 100644
index d5fefd8..0000000
--- a/third_party/blink/common/devtools/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-per-file *_mojom_traits*.*=set noparent
-per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/common/devtools/device_emulation_params_mojom_traits.cc b/third_party/blink/common/devtools/device_emulation_params_mojom_traits.cc
deleted file mode 100644
index ce76fc8..0000000
--- a/third_party/blink/common/devtools/device_emulation_params_mojom_traits.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// 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/public/common/devtools/device_emulation_params_mojom_traits.h"
-
-#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h"
-
-namespace mojo {
-
-bool StructTraits<blink::mojom::DeviceEmulationParamsDataView,
-                  blink::WebDeviceEmulationParams>::
-    Read(blink::mojom::DeviceEmulationParamsDataView data,
-         blink::WebDeviceEmulationParams* params) {
-  if (!data.ReadScreenPosition(&params->screen_position))
-    return false;
-  if (!data.ReadScreenSize(&params->screen_size))
-    return false;
-  if (!data.ReadViewPosition(&params->view_position))
-    return false;
-  if (!data.ReadViewSize(&params->view_size))
-    return false;
-  params->device_scale_factor = data.device_scale_factor();
-  params->scale = data.scale();
-  if (!data.ReadViewportOffset(&params->viewport_offset))
-    return false;
-  params->viewport_scale = data.viewport_scale();
-  if (!data.ReadScreenOrientationType(&params->screen_orientation_type))
-    return false;
-  params->screen_orientation_angle = data.screen_orientation_angle();
-  return true;
-}
-
-}  // namespace mojo
diff --git a/third_party/blink/common/scheduler/web_scheduler_tracked_feature.cc b/third_party/blink/common/scheduler/web_scheduler_tracked_feature.cc
index 207dd06f..d0365bd 100644
--- a/third_party/blink/common/scheduler/web_scheduler_tracked_feature.cc
+++ b/third_party/blink/common/scheduler/web_scheduler_tracked_feature.cc
@@ -47,8 +47,6 @@
       return "ServiceWorker-controlled page";
     case WebSchedulerTrackedFeature::kOutstandingIndexedDBTransaction:
       return "outstanding IndexedDB transaction";
-    case WebSchedulerTrackedFeature::kHasScriptableFramesInMultipleTabs:
-      return "has scriptable frames in multiple tabs";
     case WebSchedulerTrackedFeature::kRequestedGeolocationPermission:
       return "requested geolocation permission";
     case WebSchedulerTrackedFeature::kRequestedNotificationsPermission:
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index f4cecb6..2e338cb 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -307,6 +307,7 @@
     "web/web_context_menu_data.h",
     "web/web_crypto_normalize.h",
     "web/web_custom_element.h",
+    "web/web_device_emulation_params.h",
     "web/web_document.h",
     "web/web_document_loader.h",
     "web/web_dom_activity_logger.h",
diff --git a/third_party/blink/public/blink_typemaps.gni b/third_party/blink/public/blink_typemaps.gni
index a96c2e6..397290a7 100644
--- a/third_party/blink/public/blink_typemaps.gni
+++ b/third_party/blink/public/blink_typemaps.gni
@@ -5,8 +5,6 @@
 typemaps = [
   "//gpu/ipc/common/mailbox_holder_for_blink.typemap",
   "//gpu/ipc/common/sync_token.typemap",
-  "//services/network/public/cpp/cross_origin_embedder_policy.typemap",
-  "//services/network/public/cpp/url_request.typemap",
   "//services/viz/public/cpp/compositing/begin_frame_args_for_blink.typemap",
   "//services/viz/public/cpp/compositing/compositor_frame_for_blink.typemap",
   "//services/viz/public/cpp/compositing/frame_sink_id.typemap",
diff --git a/third_party/blink/public/common/BUILD.gn b/third_party/blink/public/common/BUILD.gn
index 0181ec0..c9936f6 100644
--- a/third_party/blink/public/common/BUILD.gn
+++ b/third_party/blink/public/common/BUILD.gn
@@ -60,7 +60,6 @@
     "css/navigation_controls.h",
     "css/preferred_color_scheme.h",
     "device_memory/approximated_device_memory.h",
-    "devtools/web_device_emulation_params.h",
     "dom_storage/session_storage_namespace_id.h",
     "experiments/memory_ablation_experiment.h",
     "feature_policy/document_policy.h",
diff --git a/third_party/blink/public/common/devtools/OWNERS b/third_party/blink/public/common/devtools/OWNERS
deleted file mode 100644
index 7aebc8abb..0000000
--- a/third_party/blink/public/common/devtools/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-per-file *_mojom_traits*.*=set noparent
-per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
-per-file *.typemap=set noparent
-per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/third_party/blink/public/common/devtools/device_emulation_params.typemap b/third_party/blink/public/common/devtools/device_emulation_params.typemap
deleted file mode 100644
index 4397465..0000000
--- a/third_party/blink/public/common/devtools/device_emulation_params.typemap
+++ /dev/null
@@ -1,19 +0,0 @@
-# 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.
-
-mojom =
-    "//third_party/blink/public/mojom/devtools/device_emulation_params.mojom"
-public_headers = [
-  "//third_party/blink/public/common/devtools/web_device_emulation_params.h",
-  "//third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h",
-  "//ui/gfx/geometry/point_f.h",
-  "//ui/gfx/geometry/size.h",
-]
-traits_headers = [ "//third_party/blink/public/common/devtools/device_emulation_params_mojom_traits.h" ]
-sources = [
-  "//third_party/blink/common/devtools/device_emulation_params_mojom_traits.cc",
-]
-public_deps = [ "//base" ]
-type_mappings =
-    [ "blink.mojom.DeviceEmulationParams=::blink::WebDeviceEmulationParams" ]
diff --git a/third_party/blink/public/common/devtools/device_emulation_params_mojom_traits.h b/third_party/blink/public/common/devtools/device_emulation_params_mojom_traits.h
deleted file mode 100644
index 81c8c0a..0000000
--- a/third_party/blink/public/common/devtools/device_emulation_params_mojom_traits.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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_PUBLIC_COMMON_DEVTOOLS_DEVICE_EMULATION_PARAMS_MOJOM_TRAITS_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_DEVTOOLS_DEVICE_EMULATION_PARAMS_MOJOM_TRAITS_H_
-
-#include <utility>
-
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom.h"
-
-namespace mojo {
-
-template <>
-struct BLINK_COMMON_EXPORT
-    StructTraits<blink::mojom::DeviceEmulationParamsDataView,
-                 blink::WebDeviceEmulationParams> {
-  static blink::mojom::ScreenPosition screen_position(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.screen_position;
-  }
-
-  static gfx::Size screen_size(const blink::WebDeviceEmulationParams& params) {
-    return params.screen_size;
-  }
-
-  static base::Optional<gfx::Point> view_position(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.view_position;
-  }
-
-  static gfx::Size view_size(const blink::WebDeviceEmulationParams& params) {
-    return params.view_size;
-  }
-
-  static float device_scale_factor(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.device_scale_factor;
-  }
-
-  static float scale(const blink::WebDeviceEmulationParams& params) {
-    return params.scale;
-  }
-
-  static gfx::PointF viewport_offset(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.viewport_offset;
-  }
-
-  static float viewport_scale(const blink::WebDeviceEmulationParams& params) {
-    return params.viewport_scale;
-  }
-
-  static blink::mojom::ScreenOrientationType screen_orientation_type(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.screen_orientation_type;
-  }
-
-  static int screen_orientation_angle(
-      const blink::WebDeviceEmulationParams& params) {
-    return params.screen_orientation_angle;
-  }
-
-  static bool Read(blink::mojom::DeviceEmulationParamsDataView data,
-                   blink::WebDeviceEmulationParams* params);
-};
-
-}  // namespace mojo
-
-#endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_DEVTOOLS_DEVICE_EMULATION_PARAMS_MOJOM_TRAITS_H_
diff --git a/third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h b/third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h
index ea0603ec..834011e 100644
--- a/third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h
+++ b/third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h
@@ -44,13 +44,6 @@
 
   kOutstandingIndexedDBTransaction = 17,
 
-  // Whether there are other pages which can potentially synchronously script
-  // the current one (e.g. due to window.open being used).
-  // This is a conservative estimation which doesn't take into account the
-  // origin, so it may be true if the related page is cross-origin.
-  // Recorded only for the main frame.
-  kHasScriptableFramesInMultipleTabs = 18,
-
   // Whether the page tried to request a permission regardless of the outcome.
   // TODO(altimin): Track this more accurately depending on the data.
   // See permission.mojom for more details.
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 21c4e81..1e90874 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -41,7 +41,6 @@
     "crash/crash_memory_metrics_reporter.mojom",
     "credentialmanager/credential_manager.mojom",
     "devtools/console_message.mojom",
-    "devtools/device_emulation_params.mojom",
     "devtools/devtools_agent.mojom",
     "devtools/devtools_frontend.mojom",
     "devtools/inspector_issue.mojom",
diff --git a/third_party/blink/public/mojom/devtools/device_emulation_params.mojom b/third_party/blink/public/mojom/devtools/device_emulation_params.mojom
deleted file mode 100644
index 991ad4a..0000000
--- a/third_party/blink/public/mojom/devtools/device_emulation_params.mojom
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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.
-
-module blink.mojom;
-
-import "ui/gfx/geometry/mojom/geometry.mojom";
-
-enum ScreenPosition {
-  kDesktop,
-  kMobile,
-};
-
-enum ScreenOrientationType {
-  kUndefined = 0,
-  kPortraitPrimary,
-  kPortraitSecondary,
-  kLandscapePrimary,
-  kLandscapeSecondary,
-};
-
-struct DeviceEmulationParams {
-  ScreenPosition screen_position;
-
-  // Emulated screen size. Typically full / physical size of the device screen
-  // in DIP. Empty size means using default value: original one for kDesktop
-  // screen position, equal to |view_size| for kMobile.
-  gfx.mojom.Size screen_size;
-
-  // Position of view on the screen. Missing position means using default value:
-  // original one for kDesktop screen position, (0, 0) for kMobile.
-  gfx.mojom.Point? view_position;
-
-  // Emulated view size. A width or height of 0 means no override in that
-  // dimension, but the other can still be applied. When both are 0, then the
-  // |scale| will be applied to the view instead.
-  gfx.mojom.Size view_size;
-
-  // If zero, the original device scale factor is preserved.
-  float device_scale_factor;
-
-  // Scale the contents of the main frame. The view's size will be scaled by
-  // this number when they are not specified in |view_size|.
-  float scale = 1;
-
-  // If present, forced viewport offset for screenshots during emulation.
-  gfx.mojom.PointF? viewport_offset;
-
-  // Viewport scale for screenshots during emulation, 0 for current.
-  float viewport_scale;
-
-  // Optional screen orientation type, with WebScreenOrientationUndefined
-  // value meaning no emulation necessary.
-  ScreenOrientationType screen_orientation_type;
-
-  // Screen orientation angle, used together with screenOrientationType.
-  int32 screen_orientation_angle;
-};
diff --git a/third_party/blink/public/public_typemaps.gni b/third_party/blink/public/public_typemaps.gni
index 5c49197..0a428d8 100644
--- a/third_party/blink/public/public_typemaps.gni
+++ b/third_party/blink/public/public_typemaps.gni
@@ -4,7 +4,6 @@
 
 # These are typemaps which are exposed by Blink to its embedder.
 typemaps = [
-  "//third_party/blink/public/common/devtools/device_emulation_params.typemap",
   "//third_party/blink/public/common/fetch/fetch_api_request_body.typemap",
   "//third_party/blink/public/common/fetch/fetch_api_request_headers.typemap",
   "//third_party/blink/public/common/indexeddb/indexed_db_default.typemap",
diff --git a/third_party/blink/public/common/devtools/web_device_emulation_params.h b/third_party/blink/public/web/web_device_emulation_params.h
similarity index 80%
rename from third_party/blink/public/common/devtools/web_device_emulation_params.h
rename to third_party/blink/public/web/web_device_emulation_params.h
index bff3b08..f5f1088b 100644
--- a/third_party/blink/public/common/devtools/web_device_emulation_params.h
+++ b/third_party/blink/public/web/web_device_emulation_params.h
@@ -2,25 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_DEVTOOLS_WEB_DEVICE_EMULATION_PARAMS_H_
-#define THIRD_PARTY_BLINK_PUBLIC_COMMON_DEVTOOLS_WEB_DEVICE_EMULATION_PARAMS_H_
+#ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_DEVICE_EMULATION_PARAMS_H_
+#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_DEVICE_EMULATION_PARAMS_H_
 
 #include "base/optional.h"
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom-shared.h"
+#include "third_party/blink/public/common/screen_orientation/web_screen_orientation_type.h"
+#include "third_party/blink/public/platform/web_rect.h"
+#include "third_party/blink/public/platform/web_size.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_f.h"
-#include "ui/gfx/geometry/size.h"
 
 namespace blink {
 
 // All sizes are measured in device independent pixels.
 struct WebDeviceEmulationParams {
-  mojom::ScreenPosition screen_position;
+  enum ScreenPosition { kDesktop, kMobile, kScreenPositionLast = kMobile };
+
+  ScreenPosition screen_position;
 
   // Emulated screen size. Typically full / physical size of the device screen
   // in DIP. Empty size means using default value: original one for kDesktop
   // screen position, equal to |view_size| for kMobile.
-  gfx::Size screen_size;
+  WebSize screen_size;
 
   // Position of view on the screen. Missing position means using default value:
   // original one for kDesktop screen position, (0, 0) for kMobile.
@@ -29,7 +32,7 @@
   // Emulated view size. A width or height of 0 means no override in that
   // dimension, but the other can still be applied. When both are 0, then the
   // |scale| will be applied to the view instead.
-  gfx::Size view_size;
+  WebSize view_size;
 
   // If zero, the original device scale factor is preserved.
   float device_scale_factor;
@@ -47,18 +50,18 @@
 
   // Optional screen orientation type, with WebScreenOrientationUndefined
   // value meaning no emulation necessary.
-  mojom::ScreenOrientationType screen_orientation_type;
+  WebScreenOrientationType screen_orientation_type;
 
   // Screen orientation angle, used together with screenOrientationType.
   int screen_orientation_angle;
 
   WebDeviceEmulationParams()
-      : screen_position(mojom::ScreenPosition::kDesktop),
+      : screen_position(kDesktop),
         device_scale_factor(0),
         scale(1),
         viewport_offset(-1, -1),
         viewport_scale(0),
-        screen_orientation_type(mojom::ScreenOrientationType::kUndefined),
+        screen_orientation_type(kWebScreenOrientationUndefined),
         screen_orientation_angle(0) {}
 };
 
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 3e5c9bb0..406c19b 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -354,10 +354,12 @@
 
   // Developer tools -----------------------------------------------------
 
-  // Enables device emulation as specified in params, or disables if params are
-  // null.
-  virtual void SetDeviceEmulation(
-      const base::Optional<WebDeviceEmulationParams>& params) = 0;
+  // Enables device emulation as specified in params.
+  virtual void EnableDeviceEmulation(const WebDeviceEmulationParams&) = 0;
+
+  // Cancel emulation started via |enableDeviceEmulation| call.
+  virtual void DisableDeviceEmulation() = 0;
+
 
   // Context menu --------------------------------------------------------
 
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 9c651b6..11c0aef 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
@@ -97,7 +97,8 @@
             "double": "double",
             "unrestricted double": "double",
         }
-        return TypeInfo(cxx_type[real_type.keyword_typename])
+        return TypeInfo(
+            cxx_type[real_type.keyword_typename], const_ref_fmt="{}")
 
     if real_type.is_string:
         return TypeInfo(
@@ -129,19 +130,11 @@
                 has_null_value=True)
         elif "AllowShared" in real_type.extended_attributes:
             return TypeInfo(
-                "DOM{}".format(real_type.keyword_typename),
-                member_fmt="Member<{}>",
-                ref_fmt="MaybeShared<{}>",
-                const_ref_fmt="const MaybeShared<{}>",
-                value_fmt="MaybeShared<{}>",
+                "MaybeShared<DOM{}>".format(real_type.keyword_typename),
                 has_null_value=True)
         else:
             return TypeInfo(
-                "DOM{}".format(real_type.keyword_typename),
-                member_fmt="Member<{}>",
-                ref_fmt="NotShared<{}>",
-                const_ref_fmt="const NotShared<{}>",
-                value_fmt="NotShared<{}>",
+                "NotShared<DOM{}>".format(real_type.keyword_typename),
                 has_null_value=True)
 
     if real_type.is_symbol:
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py b/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
index 4781d5f..6b5c5731 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/dictionary.py
@@ -114,21 +114,32 @@
 
     member = cg_context.dict_member
     blink_member_name = _blink_member_name(member)
+    name = blink_member_name.get_api
+    blink_type = blink_type_info(member.idl_type)
 
-    func_def = CxxFuncDefNode(
-        name=blink_member_name.get_api,
+    if blink_type.ref_t != blink_type.const_ref_t:
+        func_def = CxxFuncDefNode(
+            name=name, arg_decls=[], return_type=blink_type.ref_t)
+        func_def.set_base_template_vars(cg_context.template_bindings())
+        func_def.body.extend([
+            TextNode(_format("DCHECK({}());", blink_member_name.has_api)),
+            TextNode(_format("return {};", blink_member_name.value_var)),
+        ])
+    else:
+        func_def = None
+
+    const_func_def = CxxFuncDefNode(
+        name=name,
         arg_decls=[],
-        return_type=blink_type_info(member.idl_type).ref_t,
+        return_type=blink_type.const_ref_t,
         const=True)
-    func_def.set_base_template_vars(cg_context.template_bindings())
-    body = func_def.body
-
-    body.extend([
+    const_func_def.set_base_template_vars(cg_context.template_bindings())
+    const_func_def.body.extend([
         TextNode(_format("DCHECK({}());", blink_member_name.has_api)),
         TextNode(_format("return {};", blink_member_name.value_var)),
     ])
 
-    return func_def
+    return ListNode([func_def, const_func_def])
 
 
 def make_dict_member_has_def(cg_context):
@@ -789,5 +800,5 @@
 
 
 def generate_dictionaries(web_idl_database):
-    dictionary = web_idl_database.find("OriginTrialsTestDictionary")
+    dictionary = web_idl_database.find("RTCQuicStreamWriteParameters")
     generate_dictionary(dictionary)
diff --git a/third_party/blink/renderer/core/css/resolver/font_builder.cc b/third_party/blink/renderer/core/css/resolver/font_builder.cc
index 05b4e523a..e196127 100644
--- a/third_party/blink/renderer/core/css/resolver/font_builder.cc
+++ b/third_party/blink/renderer/core/css/resolver/font_builder.cc
@@ -422,8 +422,7 @@
   FontSelector* font_selector = document_->GetStyleEngine().GetFontSelector();
   UpdateAdjustedSize(description, style, font_selector);
 
-  style.SetFontDescription(description);
-  style.GetFont().Update(font_selector);
+  style.SetFontInternal(Font(description, font_selector));
   flags_ = 0;
 }
 
@@ -441,10 +440,9 @@
   UpdateComputedSize(font_description, document_style);
 
   font_description.SetOrientation(document_style.ComputeFontOrientation());
-  document_style.SetFontDescription(font_description);
 
   FontSelector* font_selector = document_->GetStyleEngine().GetFontSelector();
-  document_style.GetFont().Update(font_selector);
+  document_style.SetFontInternal(Font(font_description, font_selector));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 5360018..32f3704b 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -51,7 +51,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/context_menu_data/edit_flags.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
 #include "third_party/blink/public/common/page/launching_process_state.h"
@@ -70,6 +69,7 @@
 #include "third_party/blink/public/platform/web_url_response.h"
 #include "third_party/blink/public/web/web_console_message.h"
 #include "third_party/blink/public/web/web_context_menu_data.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_document_loader.h"
 #include "third_party/blink/public/web/web_form_element.h"
@@ -9911,8 +9911,8 @@
 
 TEST_F(DeviceEmulationTest, DeviceSizeInvalidatedOnResize) {
   WebDeviceEmulationParams params;
-  params.screen_position = mojom::ScreenPosition::kMobile;
-  web_view_helper_.GetWebView()->SetDeviceEmulation(params);
+  params.screen_position = WebDeviceEmulationParams::kMobile;
+  web_view_helper_.GetWebView()->EnableDeviceEmulation(params);
 
   TestResize(WebSize(700, 500), "300x300");
   TestResize(WebSize(710, 500), "400x300");
@@ -9923,7 +9923,7 @@
   TestResize(WebSize(690, 490), "200x200");
   TestResize(WebSize(800, 600), "400x400");
 
-  web_view_helper_.GetWebView()->SetDeviceEmulation(base::nullopt);
+  web_view_helper_.GetWebView()->DisableDeviceEmulation();
 }
 
 TEST_F(DeviceEmulationTest, PointerAndHoverTypes) {
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index ca83daa..43fc2bb 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1476,10 +1476,14 @@
   fullscreen_controller_->DidExitFullscreen();
 }
 
-void WebViewImpl::SetWebFrameWidget(WebFrameWidget* widget) {
+void WebViewImpl::SetMainFrameWidgetBase(WebFrameWidgetBase* widget) {
   web_widget_ = widget;
 }
 
+WebFrameWidgetBase* WebViewImpl::MainFrameWidgetBase() {
+  return web_widget_;
+}
+
 void WebViewImpl::SetSuppressFrameRequestsWorkaroundFor704763Only(
     bool suppress_frame_requests) {
   AsView().page->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only(
@@ -2859,13 +2863,18 @@
   return device_emulation_transform_;
 }
 
-void WebViewImpl::SetDeviceEmulation(
-    const base::Optional<WebDeviceEmulationParams>& params) {
+void WebViewImpl::EnableDeviceEmulation(
+    const WebDeviceEmulationParams& params) {
   TransformationMatrix device_emulation_transform =
-      dev_tools_emulator_->SetDeviceEmulation(params);
+      dev_tools_emulator_->EnableDeviceEmulation(params);
   SetDeviceEmulationTransform(device_emulation_transform);
 }
 
+void WebViewImpl::DisableDeviceEmulation() {
+  dev_tools_emulator_->DisableDeviceEmulation();
+  SetDeviceEmulationTransform(TransformationMatrix());
+}
+
 void WebViewImpl::PerformCustomContextMenuAction(unsigned action) {
   if (AsView().page) {
     AsView().page->GetContextMenuController().CustomContextMenuItemSelected(
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 503f1769..60cfa74 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -94,6 +94,7 @@
 class WebRemoteFrame;
 class WebSettingsImpl;
 class WebViewClient;
+class WebFrameWidgetBase;
 
 struct WebTextAutosizerPageInfo;
 
@@ -194,8 +195,8 @@
   WebHitTestResult HitTestResultForTap(const gfx::Point&,
                                        const WebSize&) override;
   uint64_t CreateUniqueIdentifierForRequest() override;
-  void SetDeviceEmulation(
-      const base::Optional<WebDeviceEmulationParams>&) override;
+  void EnableDeviceEmulation(const WebDeviceEmulationParams&) override;
+  void DisableDeviceEmulation() override;
   void PerformCustomContextMenuAction(unsigned action) override;
   void DidCloseContextMenu() override;
   void CancelPagePopup() override;
@@ -428,7 +429,8 @@
   void DidEnterFullscreen();
   void DidExitFullscreen();
 
-  void SetWebFrameWidget(WebFrameWidget* widget);
+  void SetMainFrameWidgetBase(WebFrameWidgetBase* widget);
+  WebFrameWidgetBase* MainFrameWidgetBase();
 
  private:
   FRIEND_TEST_ALL_PREFIXES(WebFrameTest, DivScrollIntoEditableTest);
@@ -715,7 +717,7 @@
 
   // The WebWidget for the main frame. This is expected to be unset when the
   // WebWidget destroys itself.
-  WebFrameWidget* web_widget_ = nullptr;
+  WeakPersistent<WebFrameWidgetBase> web_widget_;
 
   // We defer commits when transitioning to a new page. ChromeClientImpl calls
   // StopDeferringCommits() to release this when a new page is loaded.
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 0c33a9c..558c9b1 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -48,7 +48,6 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
@@ -63,6 +62,7 @@
 #include "third_party/blink/public/public_buildflags.h"
 #include "third_party/blink/public/web/web_autofill_client.h"
 #include "third_party/blink/public/web/web_console_message.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_element.h"
 #include "third_party/blink/public/web/web_frame.h"
@@ -2358,15 +2358,15 @@
   float page_scale_expected = web_view_impl->PageScaleFactor();
 
   WebDeviceEmulationParams params;
-  params.screen_position = mojom::ScreenPosition::kDesktop;
+  params.screen_position = WebDeviceEmulationParams::kDesktop;
   params.device_scale_factor = 0;
   params.scale = 1;
 
-  web_view_impl->SetDeviceEmulation(params);
+  web_view_impl->EnableDeviceEmulation(params);
 
   web_view_impl->SetPageScaleFactor(2);
 
-  web_view_impl->SetDeviceEmulation(base::nullopt);
+  web_view_impl->DisableDeviceEmulation();
 
   EXPECT_EQ(page_scale_expected, web_view_impl->PageScaleFactor());
 }
@@ -4966,14 +4966,14 @@
 
   WebDeviceEmulationParams emulation_params;
   emulation_params.scale = 2.f;
-  web_view_impl->SetDeviceEmulation(emulation_params);
+  web_view_impl->EnableDeviceEmulation(emulation_params);
   expected_matrix.MakeIdentity().Scale(2.f);
   EXPECT_EQ(expected_matrix, web_view_impl->GetDeviceEmulationTransform());
 
   // Device metrics offset and scale are applied before viewport override.
   emulation_params.viewport_offset = gfx::PointF(5, 10);
   emulation_params.viewport_scale = 1.5f;
-  web_view_impl->SetDeviceEmulation(emulation_params);
+  web_view_impl->EnableDeviceEmulation(emulation_params);
   expected_matrix.MakeIdentity()
       .Scale(1.5f)
       .Translate(-5, -10)
@@ -5005,7 +5005,7 @@
   WebDeviceEmulationParams emulation_params;
   emulation_params.viewport_offset = gfx::PointF(50, 55);
   emulation_params.viewport_scale = 2.f;
-  web_view_impl->SetDeviceEmulation(emulation_params);
+  web_view_impl->EnableDeviceEmulation(emulation_params);
   expected_matrix.MakeIdentity()
       .Scale(2.f)
       .Translate(-50, -55)
@@ -5190,17 +5190,17 @@
   EXPECT_NE(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
 
   WebDeviceEmulationParams params;
-  params.screen_position = mojom::ScreenPosition::kMobile;
+  params.screen_position = WebDeviceEmulationParams::kMobile;
   params.device_scale_factor = 0;
   params.scale = 1;
 
-  web_view->SetDeviceEmulation(params);
+  web_view->EnableDeviceEmulation(params);
 
   // The visual viewport should now proivde the scrollbars instead of the view.
   EXPECT_TRUE(frame_view->VisualViewportSuppliesScrollbars());
   EXPECT_EQ(nullptr, frame_view->LayoutViewport()->VerticalScrollbar());
 
-  web_view->SetDeviceEmulation(base::nullopt);
+  web_view->DisableDeviceEmulation();
 
   // The view should once again provide the scrollbars.
   EXPECT_FALSE(frame_view->VisualViewportSuppliesScrollbars());
diff --git a/third_party/blink/renderer/core/frame/frame_overlay_test.cc b/third_party/blink/renderer/core/frame/frame_overlay_test.cc
index 82cfce5..62132d7 100644
--- a/third_party/blink/renderer/core/frame/frame_overlay_test.cc
+++ b/third_party/blink/renderer/core/frame/frame_overlay_test.cc
@@ -8,7 +8,7 @@
 
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
@@ -116,7 +116,7 @@
 TEST_P(FrameOverlayTest, DeviceEmulationScale) {
   WebDeviceEmulationParams params;
   params.scale = 1.5;
-  GetWebView()->SetDeviceEmulation(params);
+  GetWebView()->EnableDeviceEmulation(params);
   GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
       DocumentUpdateReason::kTest);
 
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index 90bb1ef..c0d594d 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -13,13 +13,13 @@
 #include "cc/trees/transform_node.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/platform/web_coalesced_input_event.h"
 #include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
 #include "third_party/blink/public/web/web_ax_context.h"
 #include "third_party/blink/public/web/web_context_menu_data.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/public/web/web_document.h"
 #include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/public/web/web_script_source.h"
@@ -2593,7 +2593,7 @@
   WebDeviceEmulationParams params;
   params.viewport_offset = gfx::PointF();
   params.viewport_scale = 1.f;
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
 
   UpdateAllLifecyclePhasesExceptPaint();
   EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode());
@@ -2603,7 +2603,7 @@
 
   // Set device mulation with viewport offset should repaint visual viewport.
   params.viewport_offset = gfx::PointF(314, 159);
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
 
   UpdateAllLifecyclePhasesExceptPaint();
   EXPECT_TRUE(GetFrame()->View()->VisualViewportNeedsRepaint());
@@ -2617,7 +2617,7 @@
   // Change device emulation with scale should not repaint visual viewport.
   params.viewport_offset = gfx::PointF();
   params.viewport_scale = 1.5f;
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
 
   UpdateAllLifecyclePhasesExceptPaint();
   EXPECT_FALSE(GetFrame()->View()->VisualViewportNeedsRepaint());
@@ -2629,7 +2629,7 @@
 
   // Set an identity device emulation transform and ensure the transform
   // paint property node is cleared and repaint visual viewport.
-  WebView()->SetDeviceEmulation(WebDeviceEmulationParams());
+  WebView()->EnableDeviceEmulation(WebDeviceEmulationParams());
   UpdateAllLifecyclePhasesExceptPaint();
   EXPECT_TRUE(GetFrame()->View()->VisualViewportNeedsRepaint());
   EXPECT_FALSE(visual_viewport.GetDeviceEmulationTransformNode());
@@ -2685,21 +2685,21 @@
   WebDeviceEmulationParams params;
   params.viewport_offset = gfx::PointF();
   params.viewport_scale = 1.5f;
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
   UpdateAllLifecyclePhases();
   ASSERT_EQ(scrollbar,
             GetFrame()->View()->RootCcLayer()->children().back().get());
   check_scrollbar(scrollbar, 1.5f);
 
   params.viewport_scale = 1.f;
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
   UpdateAllLifecyclePhases();
   ASSERT_EQ(scrollbar,
             GetFrame()->View()->RootCcLayer()->children().back().get());
   check_scrollbar(scrollbar, 1.f);
 
   params.viewport_scale = 0.75f;
-  WebView()->SetDeviceEmulation(params);
+  WebView()->EnableDeviceEmulation(params);
   UpdateAllLifecyclePhases();
   ASSERT_EQ(scrollbar,
             GetFrame()->View()->RootCcLayer()->children().back().get());
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 241e51c..74d7118 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -30,7 +30,7 @@
                          std::move(widget)),
       web_view_(&web_view),
       self_keep_alive_(PERSISTENT_FROM_HERE, this) {
-  web_view_->SetWebFrameWidget(this);
+  web_view_->SetMainFrameWidgetBase(this);
 }
 
 WebViewFrameWidget::~WebViewFrameWidget() = default;
@@ -39,7 +39,7 @@
   GetPage()->WillCloseAnimationHost(nullptr);
   // Closing the WebViewFrameWidget happens in response to the local main frame
   // being detached from the Page/WebViewImpl.
-  web_view_->SetWebFrameWidget(nullptr);
+  web_view_->SetMainFrameWidgetBase(nullptr);
   web_view_ = nullptr;
   WebFrameWidgetBase::Close();
   self_keep_alive_.Clear();
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
index 6835a35..9755fc7 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
@@ -99,112 +99,9 @@
               : nullptr;
 
       if (Is3d()) {
-        CanvasResourceProvider::ResourceUsage usage;
-        if (SharedGpuContext::IsGpuCompositingEnabled()) {
-          if (LowLatencyEnabled()) {
-            usage = CanvasResourceProvider::ResourceUsage::
-                kAcceleratedDirect3DResourceUsage;
-          } else {
-            usage = CanvasResourceProvider::ResourceUsage::
-                kAcceleratedCompositedResourceUsage;
-          }
-        } else {
-          usage = CanvasResourceProvider::ResourceUsage::
-              kSoftwareCompositedResourceUsage;
-        }
-
-        uint8_t presentation_mode =
-            CanvasResourceProvider::kDefaultPresentationMode;
-        if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
-          presentation_mode |=
-              CanvasResourceProvider::kAllowImageChromiumPresentationMode;
-        }
-        if (RenderingContext() && RenderingContext()->UsingSwapChain()) {
-          DCHECK(LowLatencyEnabled());
-          // Allow swap chain presentation only if 3d context is using a swap
-          // chain since we'll be importing it as a passthrough texture.
-          presentation_mode |=
-              CanvasResourceProvider::kAllowSwapChainPresentationMode;
-        }
-
-        base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderUsage",
-                                      usage);
-        ReplaceResourceProvider(CanvasResourceProvider::Create(
-            Size(), usage, SharedGpuContext::ContextProviderWrapper(),
-            0 /* msaa_sample_count */, FilterQuality(), ColorParams(),
-            presentation_mode, std::move(dispatcher),
-            RenderingContext()->IsOriginTopLeft()));
-        if (ResourceProvider() && ResourceProvider()->IsValid()) {
-          base::UmaHistogramBoolean(
-              "Blink.Canvas.ResourceProviderIsAccelerated",
-              ResourceProvider()->IsAccelerated());
-          base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
-                                        ResourceProvider()->GetType());
-        }
+        CreateCanvasResourceProvider3D(hint, dispatcher);
       } else {
-        DCHECK(Is2d());
-        const bool want_acceleration =
-            hint == kPreferAcceleration && ShouldAccelerate2dContext();
-
-        CanvasResourceProvider::ResourceUsage usage;
-        if (want_acceleration) {
-          if (LowLatencyEnabled()) {
-            usage = CanvasResourceProvider::ResourceUsage::
-                kAcceleratedDirect2DResourceUsage;
-          } else {
-            usage = CanvasResourceProvider::ResourceUsage::
-                kAcceleratedCompositedResourceUsage;
-          }
-        } else {
-          usage = CanvasResourceProvider::ResourceUsage::
-              kSoftwareCompositedResourceUsage;
-        }
-
-        uint8_t presentation_mode =
-            CanvasResourceProvider::kDefaultPresentationMode;
-        // Allow GMB image resources if the runtime feature is enabled or if
-        // we want to use it for low latency mode.
-        if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled() ||
-            (base::FeatureList::IsEnabled(
-                 features::kLowLatencyCanvas2dImageChromium) &&
-             LowLatencyEnabled() && want_acceleration)) {
-          presentation_mode |=
-              CanvasResourceProvider::kAllowImageChromiumPresentationMode;
-        }
-        // Allow swap chains only if the runtime feature is enabled and we're
-        // in low latency mode too.
-        if (base::FeatureList::IsEnabled(
-                features::kLowLatencyCanvas2dSwapChain) &&
-            LowLatencyEnabled() && want_acceleration) {
-          presentation_mode |=
-              CanvasResourceProvider::kAllowSwapChainPresentationMode;
-        }
-
-        // It is important to not use the context's IsOriginTopLeft() here
-        // because that denotes the current state and could change after the
-        // new resource provider is created e.g. due to switching between
-        // unaccelerated and accelerated modes during tab switching.
-        const bool is_origin_top_left =
-            !want_acceleration || LowLatencyEnabled();
-
-        base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderUsage",
-                                      usage);
-        ReplaceResourceProvider(CanvasResourceProvider::Create(
-            Size(), usage, SharedGpuContext::ContextProviderWrapper(),
-            GetMSAASampleCountFor2dContext(), FilterQuality(), ColorParams(),
-            presentation_mode, std::move(dispatcher), is_origin_top_left));
-
-        if (ResourceProvider()) {
-          if (ResourceProvider()->IsValid()) {
-            base::UmaHistogramBoolean(
-                "Blink.Canvas.ResourceProviderIsAccelerated",
-                ResourceProvider()->IsAccelerated());
-            base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
-                                          ResourceProvider()->GetType());
-          }
-          ResourceProvider()->SetFilterQuality(FilterQuality());
-          ResourceProvider()->SetResourceRecyclingEnabled(true);
-        }
+        CreateCanvasResourceProvider2D(hint, dispatcher);
       }
     }
     if (!ResourceProvider())
@@ -213,6 +110,111 @@
   return ResourceProvider();
 }
 
+void CanvasRenderingContextHost::CreateCanvasResourceProvider3D(
+    AccelerationHint hint,
+    base::WeakPtr<CanvasResourceDispatcher> dispatcher) {
+  DCHECK(Is3d());
+
+  uint8_t presentation_mode = CanvasResourceProvider::kDefaultPresentationMode;
+  if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
+    presentation_mode |=
+        CanvasResourceProvider::kAllowImageChromiumPresentationMode;
+  }
+
+  CanvasResourceProvider::ResourceUsage usage;
+  if (SharedGpuContext::IsGpuCompositingEnabled()) {
+    if (LowLatencyEnabled() && RenderingContext() &&
+        RenderingContext()->UsingSwapChain()) {
+      // Allow swap chain presentation only if 3d context is using a swap
+      // chain since we'll be importing it as a passthrough texture.
+      usage = CanvasResourceProvider::ResourceUsage::
+          kAcceleratedDirect3DResourceUsage;
+      presentation_mode |=
+          CanvasResourceProvider::kAllowSwapChainPresentationMode;
+    } else {
+      usage = CanvasResourceProvider::ResourceUsage::
+          kAcceleratedCompositedResourceUsage;
+    }
+  } else {
+    usage =
+        CanvasResourceProvider::ResourceUsage::kSoftwareCompositedResourceUsage;
+  }
+
+  base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderUsage", usage);
+  ReplaceResourceProvider(CanvasResourceProvider::Create(
+      Size(), usage, SharedGpuContext::ContextProviderWrapper(),
+      0 /* msaa_sample_count */, FilterQuality(), ColorParams(),
+      presentation_mode, std::move(dispatcher),
+      RenderingContext()->IsOriginTopLeft()));
+  if (ResourceProvider() && ResourceProvider()->IsValid()) {
+    base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
+                              ResourceProvider()->IsAccelerated());
+    base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
+                                  ResourceProvider()->GetType());
+  }
+}
+
+void CanvasRenderingContextHost::CreateCanvasResourceProvider2D(
+    AccelerationHint hint,
+    base::WeakPtr<CanvasResourceDispatcher> dispatcher) {
+  DCHECK(Is2d());
+  const bool want_acceleration =
+      hint == kPreferAcceleration && ShouldAccelerate2dContext();
+
+  uint8_t presentation_mode = CanvasResourceProvider::kDefaultPresentationMode;
+  // Allow GMB image resources if the runtime feature is enabled or if
+  // we want to use it for low latency mode.
+  if (RuntimeEnabledFeatures::Canvas2dImageChromiumEnabled() ||
+      (base::FeatureList::IsEnabled(
+           features::kLowLatencyCanvas2dImageChromium) &&
+       LowLatencyEnabled() && want_acceleration)) {
+    presentation_mode |=
+        CanvasResourceProvider::kAllowImageChromiumPresentationMode;
+  }
+
+  CanvasResourceProvider::ResourceUsage usage;
+  if (want_acceleration) {
+    if (LowLatencyEnabled() &&
+        base::FeatureList::IsEnabled(features::kLowLatencyCanvas2dSwapChain)) {
+      // Allow swap chains only if the runtime feature is enabled and we're
+      // in low latency mode too.
+      usage = CanvasResourceProvider::ResourceUsage::
+          kAcceleratedDirect2DResourceUsage;
+      presentation_mode |=
+          CanvasResourceProvider::kAllowSwapChainPresentationMode;
+    } else {
+      usage = CanvasResourceProvider::ResourceUsage::
+          kAcceleratedCompositedResourceUsage;
+    }
+  } else {
+    usage =
+        CanvasResourceProvider::ResourceUsage::kSoftwareCompositedResourceUsage;
+  }
+
+  // It is important to not use the context's IsOriginTopLeft() here
+  // because that denotes the current state and could change after the
+  // new resource provider is created e.g. due to switching between
+  // unaccelerated and accelerated modes during tab switching.
+  const bool is_origin_top_left = !want_acceleration || LowLatencyEnabled();
+
+  base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderUsage", usage);
+  ReplaceResourceProvider(CanvasResourceProvider::Create(
+      Size(), usage, SharedGpuContext::ContextProviderWrapper(),
+      GetMSAASampleCountFor2dContext(), FilterQuality(), ColorParams(),
+      presentation_mode, std::move(dispatcher), is_origin_top_left));
+
+  if (ResourceProvider()) {
+    if (ResourceProvider()->IsValid()) {
+      base::UmaHistogramBoolean("Blink.Canvas.ResourceProviderIsAccelerated",
+                                ResourceProvider()->IsAccelerated());
+      base::UmaHistogramEnumeration("Blink.Canvas.ResourceProviderType",
+                                    ResourceProvider()->GetType());
+    }
+    ResourceProvider()->SetFilterQuality(FilterQuality());
+    ResourceProvider()->SetResourceRecyclingEnabled(true);
+  }
+}
+
 CanvasColorParams CanvasRenderingContextHost::ColorParams() const {
   if (RenderingContext())
     return RenderingContext()->ColorParams();
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
index be22ce7b..e0503886 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
@@ -110,6 +110,13 @@
 
   scoped_refptr<StaticBitmapImage> CreateTransparentImage(const IntSize&) const;
 
+  void CreateCanvasResourceProvider2D(
+      AccelerationHint hint,
+      base::WeakPtr<CanvasResourceDispatcher> dispatcher);
+  void CreateCanvasResourceProvider3D(
+      AccelerationHint hint,
+      base::WeakPtr<CanvasResourceDispatcher> dispatcher);
+
   bool did_fail_to_create_resource_provider_ = false;
   bool did_record_canvas_size_to_uma_ = false;
   HostType host_type_ = kNone;
diff --git a/third_party/blink/renderer/core/html/parser/css_preload_scanner.cc b/third_party/blink/renderer/core/html/parser/css_preload_scanner.cc
index 1dd6703..279776c 100644
--- a/third_party/blink/renderer/core/html/parser/css_preload_scanner.cc
+++ b/third_party/blink/renderer/core/html/parser/css_preload_scanner.cc
@@ -236,7 +236,7 @@
 }
 
 void CSSPreloadScanner::EmitRule(const SegmentedString& source) {
-  if (DeprecatedEqualIgnoringCase(rule_, "import")) {
+  if (EqualIgnoringASCIICase(rule_, "import")) {
     String url = ParseCSSStringOrURL(rule_value_.ToString());
     TextPosition position =
         TextPosition(source.CurrentLine(), source.CurrentColumn());
@@ -250,7 +250,7 @@
       requests_->push_back(std::move(request));
     }
     state_ = kInitial;
-  } else if (DeprecatedEqualIgnoringCase(rule_, "charset"))
+  } else if (EqualIgnoringASCIICase(rule_, "charset"))
     state_ = kInitial;
   else
     state_ = kDoneParsingImportRules;
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc b/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
index 6ce0ad9..b36be7c8 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
+++ b/third_party/blink/renderer/core/inspector/dev_tools_emulator.cc
@@ -6,7 +6,6 @@
 
 #include <algorithm>
 
-#include "third_party/blink/public/mojom/devtools/device_emulation_params.mojom-blink.h"
 #include "third_party/blink/public/web/web_settings.h"
 #include "third_party/blink/renderer/core/events/web_input_event_conversion.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
@@ -63,6 +62,7 @@
 
 DevToolsEmulator::DevToolsEmulator(WebViewImpl* web_view)
     : web_view_(web_view),
+      device_metrics_enabled_(false),
       emulate_mobile_enabled_(false),
       is_overlay_scrollbars_enabled_(false),
       is_orientation_event_enabled_(false),
@@ -110,14 +110,16 @@
 
 void DevToolsEmulator::SetTextAutosizingEnabled(bool enabled) {
   embedder_text_autosizing_enabled_ = enabled;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
     web_view_->GetPage()->GetSettings().SetTextAutosizingEnabled(enabled);
 }
 
 void DevToolsEmulator::SetDeviceScaleAdjustment(float device_scale_adjustment) {
   embedder_device_scale_adjustment_ = device_scale_adjustment;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled) {
     web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
         device_scale_adjustment);
@@ -129,7 +131,8 @@
     return;
 
   embedder_prefer_compositing_to_lcd_text_enabled_ = enabled;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled) {
     web_view_->GetPage()->GetSettings().SetPreferCompositingToLCDTextEnabled(
         enabled);
@@ -138,14 +141,16 @@
 
 void DevToolsEmulator::SetViewportStyle(WebViewportStyle style) {
   embedder_viewport_style_ = style;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
     web_view_->GetPage()->GetSettings().SetViewportStyle(style);
 }
 
 void DevToolsEmulator::SetPluginsEnabled(bool enabled) {
   embedder_plugins_enabled_ = enabled;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled)
     web_view_->GetPage()->GetSettings().SetPluginsEnabled(enabled);
 }
@@ -178,7 +183,8 @@
 
 void DevToolsEmulator::SetMainFrameResizesAreOrientationChanges(bool value) {
   embedder_main_frame_resizes_are_orientation_changes_ = value;
-  bool emulate_mobile_enabled = emulation_params_ && emulate_mobile_enabled_;
+  bool emulate_mobile_enabled =
+      device_metrics_enabled_ && emulate_mobile_enabled_;
   if (!emulate_mobile_enabled) {
     web_view_->GetPage()
         ->GetSettings()
@@ -210,40 +216,35 @@
     web_view_->GetPage()->GetSettings().SetPrimaryHoverType(hover_type);
 }
 
-TransformationMatrix DevToolsEmulator::SetDeviceEmulation(
-    const base::Optional<blink::WebDeviceEmulationParams>& params) {
-  if (!params) {
-    DisableDeviceEmulation();
-    return TransformationMatrix();
-  }
-  if (emulation_params_ && emulation_params_->view_size == params->view_size &&
-      emulation_params_->screen_position == params->screen_position &&
-      emulation_params_->device_scale_factor == params->device_scale_factor &&
-      emulation_params_->scale == params->scale &&
-      emulation_params_->viewport_offset == params->viewport_offset &&
-      emulation_params_->viewport_scale == params->viewport_scale) {
+TransformationMatrix DevToolsEmulator::EnableDeviceEmulation(
+    const WebDeviceEmulationParams& params) {
+  if (device_metrics_enabled_ &&
+      emulation_params_.view_size == params.view_size &&
+      emulation_params_.screen_position == params.screen_position &&
+      emulation_params_.device_scale_factor == params.device_scale_factor &&
+      emulation_params_.scale == params.scale &&
+      emulation_params_.viewport_offset == params.viewport_offset &&
+      emulation_params_.viewport_scale == params.viewport_scale) {
     return ComputeRootLayerTransform();
   }
-  if (!emulation_params_ ||
-      emulation_params_->device_scale_factor != params->device_scale_factor) {
+  if (emulation_params_.device_scale_factor != params.device_scale_factor ||
+      !device_metrics_enabled_)
     GetMemoryCache()->EvictResources();
-  }
 
   emulation_params_ = params;
+  device_metrics_enabled_ = true;
 
   web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
-      calculateDeviceScaleAdjustment(params->view_size.width(),
-                                     params->view_size.height(),
-                                     params->device_scale_factor));
+      calculateDeviceScaleAdjustment(params.view_size.width,
+                                     params.view_size.height,
+                                     params.device_scale_factor));
 
-  if (params->screen_position == mojom::blink::ScreenPosition::kMobile) {
+  if (params.screen_position == WebDeviceEmulationParams::kMobile)
     EnableMobileEmulation();
-  } else {
+  else
     DisableMobileEmulation();
-  }
 
-  web_view_->SetCompositorDeviceScaleFactorOverride(
-      params->device_scale_factor);
+  web_view_->SetCompositorDeviceScaleFactorOverride(params.device_scale_factor);
 
   // TODO(wjmaclean): Tell all local frames in the WebView's frame tree, not
   // just a local main frame.
@@ -253,18 +254,18 @@
       document->MediaQueryAffectingValueChanged();
   }
 
-  if (params->viewport_offset.x() >= 0)
-    return ForceViewport(params->viewport_offset, params->viewport_scale);
-
-  return ResetViewport();
+  if (params.viewport_offset.x() >= 0)
+    return ForceViewport(params.viewport_offset, params.viewport_scale);
+  else
+    return ResetViewport();
 }
 
 void DevToolsEmulator::DisableDeviceEmulation() {
-  if (!emulation_params_)
+  if (!device_metrics_enabled_)
     return;
 
   GetMemoryCache()->EvictResources();
-  emulation_params_ = base::nullopt;
+  device_metrics_enabled_ = false;
   web_view_->GetPage()->GetSettings().SetDeviceScaleAdjustment(
       embedder_device_scale_adjustment_);
   DisableMobileEmulation();
@@ -415,8 +416,8 @@
   // Apply device emulation transform first, so that it is affected by the
   // viewport override.
   ApplyViewportOverride(&transform);
-  if (emulation_params_)
-    transform.Scale(emulation_params_->scale);
+  if (device_metrics_enabled_)
+    transform.Scale(emulation_params_.scale);
   return transform;
 }
 
@@ -436,7 +437,7 @@
 }
 
 float DevToolsEmulator::InputEventsScaleForEmulation() {
-  return emulation_params_ ? emulation_params_->scale : 1.0;
+  return device_metrics_enabled_ ? emulation_params_.scale : 1.0;
 }
 
 void DevToolsEmulator::SetTouchEventEmulationEnabled(bool enabled,
diff --git a/third_party/blink/renderer/core/inspector/dev_tools_emulator.h b/third_party/blink/renderer/core/inspector/dev_tools_emulator.h
index 464155a..67aeaa498 100644
--- a/third_party/blink/renderer/core/inspector/dev_tools_emulator.h
+++ b/third_party/blink/renderer/core/inspector/dev_tools_emulator.h
@@ -7,9 +7,9 @@
 
 #include <memory>
 #include "base/optional.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/platform/pointer_properties.h"
 #include "third_party/blink/public/platform/web_viewport_style.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
@@ -47,11 +47,11 @@
   void SetPrimaryHoverType(HoverType);
   void SetMainFrameResizesAreOrientationChanges(bool);
 
-  // Sets the parameters for emulation (enables emulation if it hasn't been
-  // previously enabled and disables it if params is null).
-  // Returns the emulation transform to be used as a result.
-  TransformationMatrix SetDeviceEmulation(
-      const base::Optional<blink::WebDeviceEmulationParams>& params);
+  // Enables and/or sets the parameters for emulation. Returns the emulation
+  // transform to be used as a result.
+  TransformationMatrix EnableDeviceEmulation(const WebDeviceEmulationParams&);
+  // Disables emulation.
+  void DisableDeviceEmulation();
 
   bool ResizeIsDeviceSizeChange();
   void SetTouchEventEmulationEnabled(bool, int max_touch_points);
@@ -87,7 +87,6 @@
  private:
   void EnableMobileEmulation();
   void DisableMobileEmulation();
-  void DisableDeviceEmulation();
 
   // Enables viewport override and returns the emulation transform to be used.
   // The |position| is in CSS pixels, and |scale| is relative to a page scale of
@@ -105,8 +104,9 @@
 
   WebViewImpl* web_view_;
 
+  bool device_metrics_enabled_;
   bool emulate_mobile_enabled_;
-  base::Optional<blink::WebDeviceEmulationParams> emulation_params_;
+  WebDeviceEmulationParams emulation_params_;
 
   struct ViewportOverride {
     FloatPoint position;
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index a6f08dde..d4a8211 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -3361,10 +3361,10 @@
     if (IsBox())
       return false;
     // Non-LayoutBox objects (such as LayoutInline) don't necessarily create NG
-    // LayoutObjects, even if they are laid out by an NG container. Allow their
-    // fragments to be traversed, assuming that we're contained by an NG
-    // container.
-    DCHECK(RootInlineFormattingContext());
+    // LayoutObjects. If they are laid out by an NG container, though, we may be
+    // allowed to traverse their fragments. Otherwise, bail now.
+    if (!IsInLayoutNGInlineFormattingContext())
+      return false;
   }
   // Bail if we have an NGPaintFragment. NGPaintFragment will be removed, and we
   // will not attempt to add support for them here.
diff --git a/third_party/blink/renderer/core/layout/scrollbars_test.cc b/third_party/blink/renderer/core/layout/scrollbars_test.cc
index 70e54c14..a8efe8b0 100644
--- a/third_party/blink/renderer/core/layout/scrollbars_test.cc
+++ b/third_party/blink/renderer/core/layout/scrollbars_test.cc
@@ -1360,8 +1360,8 @@
 
   // Turn on mobile emulator.
   WebDeviceEmulationParams params;
-  params.screen_position = mojom::ScreenPosition::kMobile;
-  WebView().SetDeviceEmulation(params);
+  params.screen_position = WebDeviceEmulationParams::kMobile;
+  WebView().EnableDeviceEmulation(params);
 
   // For root Scrollbar, mobile emulator will change them to page VisualViewport
   // scrollbar layer.
@@ -1371,7 +1371,7 @@
   EXPECT_TRUE(div_scrollable->VerticalScrollbar()->IsCustomScrollbar());
 
   // Turn off mobile emulator.
-  WebView().SetDeviceEmulation(base::nullopt);
+  WebView().DisableDeviceEmulation();
 
   EXPECT_TRUE(root_scrollable->VerticalScrollbar());
   EXPECT_TRUE(root_scrollable->VerticalScrollbar()->IsCustomScrollbar());
@@ -1611,8 +1611,8 @@
 
   // Turn on mobile emulator.
   WebDeviceEmulationParams params;
-  params.screen_position = mojom::ScreenPosition::kMobile;
-  WebView().SetDeviceEmulation(params);
+  params.screen_position = WebDeviceEmulationParams::kMobile;
+  WebView().EnableDeviceEmulation(params);
 
   // For root Scrollbar, mobile emulator will change them to page VisualViewport
   // scrollbar layer.
@@ -1621,8 +1621,9 @@
   // Ensure div scrollbar also change to mobile overlay theme.
   EXPECT_TRUE(div_scrollable->VerticalScrollbar()->IsOverlayScrollbar());
   EXPECT_TRUE(div_scrollable->VerticalScrollbar()->IsSolidColor());
+
   // Turn off mobile emulator.
-  WebView().SetDeviceEmulation(base::nullopt);
+  WebView().DisableDeviceEmulation();
 
   EXPECT_TRUE(root_scrollable->VerticalScrollbar());
   EXPECT_FALSE(root_scrollable->VerticalScrollbar()->IsCustomScrollbar());
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index e4af0e7..a238c82 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -569,7 +569,18 @@
       }
     }
 
-    new_image_content = ImageResourceContent::Fetch(params, document.Fetcher());
+    if (lazy_image_load_state_ == LazyImageLoadState::kDeferred &&
+        was_fully_deferred_ && !ShouldLoadImmediately(url)) {
+      // TODO(rajendrant): Remove this temporary workaround of creating a 1x1
+      // placeholder to fix an intersection observer issue not firing with
+      // certain styles (https://crbug.com/992765). Instead
+      // NoImageResourceToLoad() should be skipped when the image is deferred.
+      // https://crbug.com/999209
+      new_image_content = ImageResourceContent::CreateLazyImagePlaceholder();
+    } else {
+      new_image_content =
+          ImageResourceContent::Fetch(params, document.Fetcher());
+    }
 
     // If this load is starting while navigating away, treat it as an auditing
     // keepalive request, and don't report its results back to the element.
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index 455ede2..ff279d31 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -161,11 +161,6 @@
     page->prev_related_page_ = opener;
     page->next_related_page_ = next;
     next->prev_related_page_ = page;
-
-    // No need to update |prev| here as if |next| != |prev|, |prev| was already
-    // marked as having related pages.
-    next->UpdateHasRelatedPages();
-    page->UpdateHasRelatedPages();
   }
 
   OrdinaryPages().insert(page);
@@ -315,9 +310,6 @@
   main_frame_ = main_frame;
 
   page_scheduler_->SetIsMainFrameLocal(main_frame->IsLocalFrame());
-  // |has_related_pages_| is only reported when the main frame is local, so make
-  // sure it's updated after the main frame changes.
-  UpdateHasRelatedPages();
 }
 
 LocalFrame* Page::DeprecatedLocalMainFrame() const {
@@ -865,8 +857,6 @@
                                         mojom::blink::ScrollType::kProgrammatic,
                                         mojom::blink::ScrollBehavior::kInstant,
                                         ScrollableArea::ScrollCallback());
-    // Update |has_related_pages_| as features are reset after navigation.
-    UpdateHasRelatedPages();
   }
   GetLinkHighlight().ResetForPageNavigation();
 }
@@ -949,10 +939,6 @@
     prev->next_related_page_ = next;
     this->prev_related_page_ = nullptr;
     this->next_related_page_ = nullptr;
-    if (prev != this)
-      prev->UpdateHasRelatedPages();
-    if (next != this)
-      next->UpdateHasRelatedPages();
   }
 
   if (scrolling_coordinator_)
@@ -1049,22 +1035,6 @@
   return inside_portal_;
 }
 
-void Page::UpdateHasRelatedPages() {
-  bool has_related_pages = next_related_page_ != this;
-  if (!has_related_pages) {
-    has_related_pages_.reset();
-  } else {
-    LocalFrame* local_main_frame = DynamicTo<LocalFrame>(main_frame_.Get());
-    // We want to record this only for the pages which have local main frame,
-    // which is fine as we are aggregating results across all processes.
-    if (!local_main_frame || !local_main_frame->IsAttached())
-      return;
-    has_related_pages_ = local_main_frame->GetFrameScheduler()->RegisterFeature(
-        SchedulingPolicy::Feature::kHasScriptableFramesInMultipleTabs,
-        {SchedulingPolicy::RecordMetricsForBackForwardCache()});
-  }
-}
-
 void Page::SetMediaFeatureOverride(const AtomicString& media_feature,
                                    const String& value) {
   if (!media_feature_overrides_) {
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index b16f7f5f1..1074204d 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -367,11 +367,8 @@
 
   void SetPageScheduler(std::unique_ptr<PageScheduler>);
 
-  void UpdateHasRelatedPages();
-
   void InvalidateColorScheme();
   void InvalidatePaint();
-
   // Typically, the main frame and Page should both be owned by the embedder,
   // which must call Page::willBeDestroyed() prior to destroying Page. This
   // call detaches the main frame and clears this pointer, thus ensuring that
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index b14fbbf..aad1e1eb 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -74,8 +74,8 @@
  public:
   ScrollingTest() : base_url_("http://www.test.com/") {
     helper_.Initialize(nullptr, nullptr, nullptr, &ConfigureSettings);
-    GetWebView()->MainFrameWidget()->Resize(IntSize(320, 240));
-    GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
+    GetWebView()->MainFrameWidgetBase()->Resize(IntSize(320, 240));
+    GetWebView()->MainFrameWidgetBase()->UpdateAllLifecyclePhases(
         DocumentUpdateReason::kTest);
   }
 
@@ -93,7 +93,7 @@
   }
 
   void ForceFullCompositingUpdate() {
-    GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
+    GetWebView()->MainFrameWidgetBase()->UpdateAllLifecyclePhases(
         DocumentUpdateReason::kTest);
   }
 
@@ -193,7 +193,7 @@
 INSTANTIATE_PAINT_TEST_SUITE_P(ScrollingTest);
 
 TEST_P(ScrollingTest, fastScrollingByDefault) {
-  GetWebView()->MainFrameWidget()->Resize(WebSize(800, 600));
+  GetWebView()->MainFrameWidgetBase()->Resize(WebSize(800, 600));
   LoadHTML("<div id='spacer' style='height: 1000px'></div>");
   ForceFullCompositingUpdate();
 
@@ -1443,10 +1443,10 @@
 
   // After an initial compositing update, we should have one scrolling update
   // recorded as PreFCP.
-  GetWebView()->MainFrameWidget()->RecordStartOfFrameMetrics();
+  GetWebView()->MainFrameWidgetBase()->RecordStartOfFrameMetrics();
   ForceFullCompositingUpdate();
-  GetWebView()->MainFrameWidget()->RecordEndOfFrameMetrics(base::TimeTicks(),
-                                                           0);
+  GetWebView()->MainFrameWidgetBase()->RecordEndOfFrameMetrics(
+      base::TimeTicks(), 0);
   histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 1);
   histogram_tester.ExpectTotalCount(
       "Blink.ScrollingCoordinator.UpdateTime.PreFCP", 1);
@@ -1456,10 +1456,10 @@
       "Blink.ScrollingCoordinator.UpdateTime.AggregatedPreFCP", 0);
 
   // An update with no scrolling changes should not cause a scrolling update.
-  GetWebView()->MainFrameWidget()->RecordStartOfFrameMetrics();
+  GetWebView()->MainFrameWidgetBase()->RecordStartOfFrameMetrics();
   ForceFullCompositingUpdate();
-  GetWebView()->MainFrameWidget()->RecordEndOfFrameMetrics(base::TimeTicks(),
-                                                           0);
+  GetWebView()->MainFrameWidgetBase()->RecordEndOfFrameMetrics(
+      base::TimeTicks(), 0);
   histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 1);
   histogram_tester.ExpectTotalCount(
       "Blink.ScrollingCoordinator.UpdateTime.PreFCP", 1);
@@ -1475,10 +1475,10 @@
   auto* background = GetFrame()->GetDocument()->getElementById("bg");
   background->removeAttribute(html_names::kStyleAttr);
   background->setInnerHTML("Some Text");
-  GetWebView()->MainFrameWidget()->RecordStartOfFrameMetrics();
+  GetWebView()->MainFrameWidgetBase()->RecordStartOfFrameMetrics();
   ForceFullCompositingUpdate();
-  GetWebView()->MainFrameWidget()->RecordEndOfFrameMetrics(base::TimeTicks(),
-                                                           0);
+  GetWebView()->MainFrameWidgetBase()->RecordEndOfFrameMetrics(
+      base::TimeTicks(), 0);
   histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 2);
   histogram_tester.ExpectTotalCount(
       "Blink.ScrollingCoordinator.UpdateTime.PreFCP", 2);
@@ -1490,10 +1490,10 @@
   // Removing a scrollable area should cause a scrolling update.
   auto* scroller = GetFrame()->GetDocument()->getElementById("scroller");
   scroller->removeAttribute(html_names::kStyleAttr);
-  GetWebView()->MainFrameWidget()->RecordStartOfFrameMetrics();
+  GetWebView()->MainFrameWidgetBase()->RecordStartOfFrameMetrics();
   ForceFullCompositingUpdate();
-  GetWebView()->MainFrameWidget()->RecordEndOfFrameMetrics(base::TimeTicks(),
-                                                           0);
+  GetWebView()->MainFrameWidgetBase()->RecordEndOfFrameMetrics(
+      base::TimeTicks(), 0);
   histogram_tester.ExpectTotalCount("Blink.ScrollingCoordinator.UpdateTime", 3);
   histogram_tester.ExpectTotalCount(
       "Blink.ScrollingCoordinator.UpdateTime.PreFCP", 2);
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
index 83ce223..5466f5f 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
@@ -62,15 +62,13 @@
     DCHECK(SVGResourcesCache::CachedResourcesForLayoutObject(object_));
     DCHECK(
         SVGResourcesCache::CachedResourcesForLayoutObject(object_)->Filter());
-    DCHECK(filter_recording_context_);
-
-    if (filter_data_->ContentNeedsUpdate())
-      filter_data_->UpdateContent(filter_recording_context_->EndContent());
-
+    if (filter_recording_context_) {
+      filter_data_->UpdateContent(
+          filter_recording_context_->GetPaintRecord(paint_info_));
+      filter_recording_context_ = nullptr;
+    }
     PaintFilteredContent(paint_info_.context, object_, display_item_client_,
                          filter_data_);
-    // Reset the paint info after the filter effect has been completed.
-    filter_paint_info_ = nullptr;
     filter_data_ = nullptr;
   }
 }
@@ -172,8 +170,6 @@
   if (!filter)
     return true;
   filter->ClearInvalidationMask();
-  filter_recording_context_ =
-      std::make_unique<SVGFilterRecordingContext>(GetPaintInfo().context);
   filter_data_ = SVGFilterPainter(*filter).PrepareEffect(object_);
   // If we have no filter data (== the filter was invalid) or if we
   // don't need to update the source graphics, we can short-circuit
@@ -182,13 +178,8 @@
     return false;
   // Because the filter needs to cache its contents we replace the context
   // during filtering with the filter's context.
-  GraphicsContext* filter_context = filter_recording_context_->BeginContent();
-  filter_paint_info_ =
-      std::make_unique<PaintInfo>(*filter_context, paint_info_);
-  // Because we cache the filter contents and do not invalidate on paint
-  // invalidation rect changes, we need to paint the entire filter region
-  // so elements outside the initial paint (due to scrolling, etc) paint.
-  filter_paint_info_->ApplyInfiniteCullRect();
+  filter_recording_context_ =
+      std::make_unique<SVGFilterRecordingContext>(paint_info_);
   return true;
 }
 
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
index 3acec4f8..7b0223b6 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.h
@@ -93,7 +93,8 @@
   ~ScopedSVGPaintState();
 
   const PaintInfo& GetPaintInfo() const {
-    return filter_paint_info_ ? *filter_paint_info_ : paint_info_;
+    return filter_recording_context_ ? filter_recording_context_->GetPaintInfo()
+                                     : paint_info_;
   }
 
   // Return true if these operations aren't necessary or if they are
@@ -112,7 +113,6 @@
   const LayoutObject& object_;
   PaintInfo paint_info_;
   const DisplayItemClient& display_item_client_;
-  std::unique_ptr<PaintInfo> filter_paint_info_;
   FilterData* filter_data_;
   base::Optional<ClipPathClipper> clip_path_clipper_;
   std::unique_ptr<SVGFilterRecordingContext> filter_recording_context_;
diff --git a/third_party/blink/renderer/core/paint/svg_container_painter.cc b/third_party/blink/renderer/core/paint/svg_container_painter.cc
index e178f973..0da8574 100644
--- a/third_party/blink/renderer/core/paint/svg_container_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_container_painter.cc
@@ -83,8 +83,7 @@
     if (continue_rendering) {
       for (LayoutObject* child = layout_svg_container_.FirstChild(); child;
            child = child->NextSibling()) {
-        auto* foreign_object = DynamicTo<LayoutSVGForeignObject>(*child);
-        if (child->IsSVGForeignObject()) {
+        if (auto* foreign_object = DynamicTo<LayoutSVGForeignObject>(*child)) {
           SVGForeignObjectPainter(*foreign_object)
               .PaintLayer(paint_state.GetPaintInfo());
         } else {
diff --git a/third_party/blink/renderer/core/paint/svg_filter_painter.cc b/third_party/blink/renderer/core/paint/svg_filter_painter.cc
index 6000a5d..47552d0 100644
--- a/third_party/blink/renderer/core/paint/svg_filter_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_filter_painter.cc
@@ -13,36 +13,37 @@
 #include "third_party/blink/renderer/core/svg/svg_filter_element.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/filters/source_graphic.h"
+#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
+#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
 
 namespace blink {
 
-GraphicsContext* SVGFilterRecordingContext::BeginContent() {
-  // Create a new context so the contents of the filter can be drawn and cached.
-  paint_controller_ = std::make_unique<PaintController>();
-  context_ = std::make_unique<GraphicsContext>(*paint_controller_);
-
-  // Use initial_context_'s current paint chunk properties so that any new
+SVGFilterRecordingContext::SVGFilterRecordingContext(
+    const PaintInfo& initial_paint_info)
+    // Create a new context so the contents of the filter can be drawn and
+    // cached.
+    : paint_controller_(std::make_unique<PaintController>()),
+      context_(std::make_unique<GraphicsContext>(*paint_controller_)),
+      paint_info_(*context_, initial_paint_info) {
+  // Use initial_paint_info's current paint chunk properties so that any new
   // chunk created during painting the content will be in the correct state.
   paint_controller_->UpdateCurrentPaintChunkProperties(
-      nullptr,
-      initial_context_.GetPaintController().CurrentPaintChunkProperties());
-
-  return context_.get();
+      nullptr, initial_paint_info.context.GetPaintController()
+                   .CurrentPaintChunkProperties());
+  // Because we cache the filter contents and do not invalidate on paint
+  // invalidation rect changes, we need to paint the entire filter region so
+  // elements outside the initial paint (due to scrolling, etc) paint.
+  paint_info_.ApplyInfiniteCullRect();
 }
 
-sk_sp<PaintRecord> SVGFilterRecordingContext::EndContent() {
-  // Use the context that contains the filtered content.
-  DCHECK(paint_controller_);
-  DCHECK(context_);
-  paint_controller_->CommitNewDisplayItems();
-  sk_sp<PaintRecord> content =
-      paint_controller_->GetPaintArtifact().GetPaintRecord(
-          initial_context_.GetPaintController().CurrentPaintChunkProperties());
+SVGFilterRecordingContext::~SVGFilterRecordingContext() = default;
 
-  // Content is cached by the source graphic so temporaries can be freed.
-  paint_controller_ = nullptr;
-  context_ = nullptr;
-  return content;
+sk_sp<PaintRecord> SVGFilterRecordingContext::GetPaintRecord(
+    const PaintInfo& initial_paint_info) {
+  paint_controller_->CommitNewDisplayItems();
+  return paint_controller_->GetPaintArtifact().GetPaintRecord(
+      initial_paint_info.context.GetPaintController()
+          .CurrentPaintChunkProperties());
 }
 
 FilterData* SVGFilterPainter::PrepareEffect(const LayoutObject& object) {
diff --git a/third_party/blink/renderer/core/paint/svg_filter_painter.h b/third_party/blink/renderer/core/paint/svg_filter_painter.h
index 68dedd3..f38d6937 100644
--- a/third_party/blink/renderer/core/paint/svg_filter_painter.h
+++ b/third_party/blink/renderer/core/paint/svg_filter_painter.h
@@ -7,30 +7,31 @@
 
 #include <memory>
 #include "base/macros.h"
-#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
-#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
+#include "third_party/blink/renderer/core/paint/paint_info.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
 
 class FilterData;
+class GraphicsContext;
 class LayoutObject;
 class LayoutSVGResourceFilter;
+class PaintController;
 
 class SVGFilterRecordingContext {
   USING_FAST_MALLOC(SVGFilterRecordingContext);
 
  public:
-  explicit SVGFilterRecordingContext(GraphicsContext& initial_context)
-      : initial_context_(initial_context) {}
+  explicit SVGFilterRecordingContext(const PaintInfo&);
+  ~SVGFilterRecordingContext();
 
-  GraphicsContext* BeginContent();
-  sk_sp<PaintRecord> EndContent();
+  const PaintInfo& GetPaintInfo() const { return paint_info_; }
+  sk_sp<PaintRecord> GetPaintRecord(const PaintInfo&);
 
  private:
   std::unique_ptr<PaintController> paint_controller_;
   std::unique_ptr<GraphicsContext> context_;
-  GraphicsContext& initial_context_;
+  PaintInfo paint_info_;
   DISALLOW_COPY_AND_ASSIGN(SVGFilterRecordingContext);
 };
 
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index c2f98f1..a1b2d36 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -253,6 +253,8 @@
   friend class ColorPropertyFunctions;
   // Edits the background for media controls.
   friend class StyleAdjuster;
+  // Access to private SetFontInternal().
+  friend class FontBuilder;
 
   // FIXME: When we stop resolving currentColor at style time, these can be
   // removed.
diff --git a/third_party/blink/renderer/core/testing/internals.cc b/third_party/blink/renderer/core/testing/internals.cc
index 45fc0bd..5a22eb5a 100644
--- a/third_party/blink/renderer/core/testing/internals.cc
+++ b/third_party/blink/renderer/core/testing/internals.cc
@@ -32,12 +32,12 @@
 #include "base/optional.h"
 #include "cc/layers/picture_layer.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
-#include "third_party/blink/public/common/devtools/web_device_emulation_params.h"
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom-blink.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
+#include "third_party/blink/public/web/web_device_emulation_params.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -283,7 +283,7 @@
   // call.
   page->SetDefaultPageScaleLimits(1, 4);
   page->SetPageScaleFactor(1);
-  page->GetChromeClient().GetWebView()->SetDeviceEmulation(base::nullopt);
+  page->GetChromeClient().GetWebView()->DisableDeviceEmulation();
 
   // Ensure timers are reset so timers such as EventHandler's |hover_timer_| do
   // not cause additional lifecycle updates.
@@ -3504,7 +3504,7 @@
   }
   WebDeviceEmulationParams params;
   params.scale = scale;
-  page->GetChromeClient().GetWebView()->SetDeviceEmulation(params);
+  page->GetChromeClient().GetWebView()->EnableDeviceEmulation(params);
 }
 
 void Internals::ResolveResourcePriority(ScriptPromiseResolver* resolver,
diff --git a/third_party/blink/renderer/platform/fonts/font.cc b/third_party/blink/renderer/platform/fonts/font.cc
index db8806a..7a94148 100644
--- a/third_party/blink/renderer/platform/fonts/font.cc
+++ b/third_party/blink/renderer/platform/fonts/font.cc
@@ -84,18 +84,6 @@
                   : 0);
 }
 
-void Font::Update(FontSelector* font_selector) const {
-  // FIXME: It is pretty crazy that we are willing to just poke into a RefPtr,
-  // but it ends up being reasonably safe (because inherited fonts in the render
-  // tree pick up the new style anyway. Other copies are transient, e.g., the
-  // state in the GraphicsContext, and won't stick around long enough to get you
-  // in trouble). Still, this is pretty disgusting, and could eventually be
-  // rectified by using RefPtrs for Fonts themselves.
-  if (!font_fallback_list_)
-    font_fallback_list_ = FontFallbackList::Create(nullptr);
-  font_fallback_list_->Invalidate(font_selector);
-}
-
 namespace {
 
 void DrawBlobs(cc::PaintCanvas* canvas,
diff --git a/third_party/blink/renderer/platform/fonts/font.h b/third_party/blink/renderer/platform/fonts/font.h
index dc13958..2aeb6057 100644
--- a/third_party/blink/renderer/platform/fonts/font.h
+++ b/third_party/blink/renderer/platform/fonts/font.h
@@ -79,8 +79,6 @@
     return font_description_;
   }
 
-  void Update(FontSelector*) const;
-
   enum CustomFontNotReadyAction {
     kDoNotPaintIfFontNotReady,
     kUseFallbackIfFontNotReady
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_list.cc b/third_party/blink/renderer/platform/fonts/font_fallback_list.cc
index e27e4a3..d00d1831 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_list.cc
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_list.cc
@@ -49,7 +49,7 @@
       can_shape_word_by_word_(false),
       can_shape_word_by_word_computed_(false) {}
 
-void FontFallbackList::Invalidate(FontSelector* font_selector) {
+void FontFallbackList::Invalidate() {
   ReleaseFontData();
   font_list_.clear();
   cached_primary_simple_font_data_ = nullptr;
@@ -57,8 +57,6 @@
   has_loading_fallback_ = false;
   can_shape_word_by_word_ = false;
   can_shape_word_by_word_computed_ = false;
-  if (font_selector_ != font_selector)
-    font_selector_ = font_selector;
   font_selector_version_ = font_selector_ ? font_selector_->Version() : 0;
   generation_ = FontCache::GetFontCache()->Generation();
 }
@@ -238,7 +236,7 @@
     unsigned realized_font_index) {
   if (RuntimeEnabledFeatures::CSSReducedFontLoadingInvalidationsEnabled()) {
     if (!IsValid())
-      Invalidate(font_selector_);
+      Invalidate();
   }
 
   // This fallback font is already in our list.
@@ -284,7 +282,7 @@
     const FontDescription& font_description) {
   if (RuntimeEnabledFeatures::CSSReducedFontLoadingInvalidationsEnabled()) {
     if (!IsValid())
-      Invalidate(font_selector_);
+      Invalidate();
   }
 
   if (!can_shape_word_by_word_computed_) {
diff --git a/third_party/blink/renderer/platform/fonts/font_fallback_list.h b/third_party/blink/renderer/platform/fonts/font_fallback_list.h
index baba17f6..6ef8c377 100644
--- a/third_party/blink/renderer/platform/fonts/font_fallback_list.h
+++ b/third_party/blink/renderer/platform/fonts/font_fallback_list.h
@@ -49,7 +49,7 @@
 
   ~FontFallbackList() { ReleaseFontData(); }
   bool IsValid() const;
-  void Invalidate(FontSelector*);
+  void Invalidate();
 
   bool LoadingCustomFonts() const;
   bool ShouldSkipDrawing() const;
@@ -62,7 +62,7 @@
   ShapeCache* GetShapeCache(const FontDescription& font_description) {
     if (RuntimeEnabledFeatures::CSSReducedFontLoadingInvalidationsEnabled()) {
       if (!IsValid())
-        Invalidate(font_selector_);
+        Invalidate();
     }
 
     if (!shape_cache_) {
@@ -80,7 +80,7 @@
       const FontDescription& font_description) {
     if (RuntimeEnabledFeatures::CSSReducedFontLoadingInvalidationsEnabled()) {
       if (!IsValid())
-        Invalidate(font_selector_);
+        Invalidate();
     }
 
     if (!cached_primary_simple_font_data_) {
@@ -113,7 +113,7 @@
 
   Vector<scoped_refptr<FontData>, 1> font_list_;
   const SimpleFontData* cached_primary_simple_font_data_;
-  Persistent<FontSelector> font_selector_;
+  const Persistent<FontSelector> font_selector_;
   unsigned font_selector_version_;
   int family_index_;
   uint16_t generation_;
diff --git a/third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.cc b/third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.cc
index 62d92b7..604a895 100644
--- a/third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.cc
+++ b/third_party/blink/renderer/platform/fonts/linux/font_unique_name_lookup_linux.cc
@@ -19,7 +19,6 @@
   if (!Platform::Current()->GetSandboxSupport()) {
     LOG(ERROR) << "@font-face src: local() instantiation only available when "
                   "connected to browser process.";
-    DCHECK(Platform::Current()->GetSandboxSupport());
     return nullptr;
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
index dd755d9d..3d76238 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
@@ -95,6 +95,9 @@
     kMaxValue = kSwapChain,
   };
 
+  using RestoreMatrixClipStackCb =
+      base::RepeatingCallback<void(cc::PaintCanvas*)>;
+
   // todo(juanmihd@) Check whether SkFilterQuality is needed in all of this, or
   // just call setFilterQuality explicitly
   static std::unique_ptr<CanvasResourceProvider> CreateBitmapProvider(
@@ -102,9 +105,6 @@
       SkFilterQuality,
       const CanvasColorParams&);
 
-  using RestoreMatrixClipStackCb =
-      base::RepeatingCallback<void(cc::PaintCanvas*)>;
-
   // Specifies whether the provider should rasterize paint commands on the CPU
   // or GPU. This is used to support software raster with GPU compositing
   enum class RasterMode {
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
index 3edfe29..e8a2159 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
@@ -37,14 +37,11 @@
     return source;
 
   auto paint_image = source->PaintImageForCurrentFrame();
-  auto provider = CanvasResourceProvider::Create(
-      source->Size(),
-      CanvasResourceProvider::ResourceUsage::
-          kAcceleratedCompositedResourceUsage,
-      context_provider_wrapper, 0, kLow_SkFilterQuality,
+  auto provider = CanvasResourceProvider::CreateSharedImageProvider(
+      source->Size(), context_provider_wrapper, kLow_SkFilterQuality,
       CanvasColorParams(paint_image.GetSkImage()->imageInfo()),
-      CanvasResourceProvider::kDefaultPresentationMode, nullptr,
-      source->IsOriginTopLeft());
+      source->IsOriginTopLeft(), CanvasResourceProvider::RasterMode::kGPU,
+      gpu::SHARED_IMAGE_USAGE_DISPLAY);
   if (!provider || !provider->IsAccelerated())
     return nullptr;
 
diff --git a/third_party/blink/renderer/platform/heap/BUILD.gn b/third_party/blink/renderer/platform/heap/BUILD.gn
index 0edd095..d868c46 100644
--- a/third_party/blink/renderer/platform/heap/BUILD.gn
+++ b/third_party/blink/renderer/platform/heap/BUILD.gn
@@ -45,6 +45,8 @@
     "blink_gc.h",
     "blink_gc_memory_dump_provider.cc",
     "blink_gc_memory_dump_provider.h",
+    "cancelable_task_scheduler.cc",
+    "cancelable_task_scheduler.h",
     "collection_support/heap_hash_table_backing.h",
     "collection_support/heap_linked_stack.h",
     "collection_support/heap_vector_backing.h",
@@ -147,6 +149,7 @@
   sources = [
     "../testing/run_all_tests.cc",
     "blink_gc_memory_dump_provider_test.cc",
+    "cancelable_task_scheduler_test.cc",
     "card_table_test.cc",
     "collection_support/heap_linked_stack_test.cc",
     "concurrent_marking_test.cc",
diff --git a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc
new file mode 100644
index 0000000..482d431
--- /dev/null
+++ b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.cc
@@ -0,0 +1,127 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/task_runner.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+class CancelableTaskScheduler::TaskData {
+  USING_FAST_MALLOC(TaskData);
+  DISALLOW_COPY_AND_ASSIGN(TaskData);
+
+ public:
+  TaskData(Task task, CancelableTaskScheduler* scheduler)
+      : task_(std::move(task)), scheduler_(scheduler), status_(kWaiting) {}
+
+  ~TaskData() {
+    // The task runner is responsible for unregistering the task in case the
+    // task hasn't been cancelled.
+    if (TryCancel()) {
+      scheduler_->UnregisterAndSignal(this);
+    }
+  }
+
+  void Run() {
+    if (TryRun()) {
+      std::move(task_).Run();
+      scheduler_->UnregisterAndSignal(this);
+    }
+  }
+
+  bool TryCancel() {
+    Status expected = kWaiting;
+    return status_.compare_exchange_strong(expected, kCancelled,
+                                           std::memory_order_acq_rel,
+                                           std::memory_order_acquire);
+  }
+
+ private:
+  // Identifies the state a cancelable task is in:
+  // |kWaiting|: The task is scheduled and waiting to be executed. {TryRun} will
+  // succeed.
+  // |kCancelled|: The task has been cancelled. {TryRun} will fail.
+  // |kRunning|: The task is currently running and cannot be canceled anymore.
+  enum Status : uint8_t { kWaiting, kCancelled, kRunning };
+
+  bool TryRun() {
+    Status expected = kWaiting;
+    return status_.compare_exchange_strong(expected, kRunning,
+                                           std::memory_order_acq_rel,
+                                           std::memory_order_acquire);
+  }
+
+  Task task_;
+  CancelableTaskScheduler* const scheduler_;
+  std::atomic<Status> status_;
+};
+
+CancelableTaskScheduler::CancelableTaskScheduler(
+    scoped_refptr<base::TaskRunner> task_runner)
+    : cond_var_(&lock_), task_runner_(std::move(task_runner)) {}
+
+CancelableTaskScheduler::~CancelableTaskScheduler() {
+  base::AutoLock lock(lock_);
+  CHECK(tasks_.IsEmpty());
+}
+
+void CancelableTaskScheduler::ScheduleTask(Task task) {
+  std::unique_ptr<TaskData> task_data = Register(std::move(task));
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(&TaskData::Run, std::move(task_data)));
+}
+
+size_t CancelableTaskScheduler::CancelAndWait() {
+  size_t result = 0;
+  base::AutoLock lock(lock_);
+  while (!tasks_.IsEmpty()) {
+    result += RemoveCancelledTasks();
+    if (!tasks_.IsEmpty()) {
+      cond_var_.Wait();
+    }
+  }
+  return result;
+}
+
+std::unique_ptr<CancelableTaskScheduler::TaskData>
+CancelableTaskScheduler::Register(Task task) {
+  auto task_data = std::make_unique<TaskData>(std::move(task), this);
+  base::AutoLock lock(lock_);
+  tasks_.insert(task_data.get());
+  return task_data;
+}
+
+void CancelableTaskScheduler::UnregisterAndSignal(TaskData* task_data) {
+  base::AutoLock lock(lock_);
+  CHECK(tasks_.Contains(task_data));
+  tasks_.erase(task_data);
+  cond_var_.Signal();
+}
+
+// This function is needed because WTF::HashSet::erase function invalidates
+// all iterators. Returns number of removed tasks.
+size_t CancelableTaskScheduler::RemoveCancelledTasks() {
+  WTF::Vector<TaskData*> to_be_removed;
+  // Assume worst case.
+  to_be_removed.ReserveCapacity(tasks_.size());
+  for (TaskData* task : tasks_) {
+    if (task->TryCancel()) {
+      to_be_removed.push_back(task);
+    }
+  }
+  tasks_.RemoveAll(to_be_removed);
+  return to_be_removed.size();
+}
+
+size_t CancelableTaskScheduler::NumberOfTasksForTesting() const {
+  base::AutoLock lock(lock_);
+  return tasks_.size();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h
new file mode 100644
index 0000000..914ab57
--- /dev/null
+++ b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h
@@ -0,0 +1,62 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace blink {
+
+// CancelableTaskScheduler allows for scheduling tasks that can be cancelled
+// before they are invoked. User is responsible for synchronizing completion of
+// tasks and destruction of CancelableTaskScheduler.
+class PLATFORM_EXPORT CancelableTaskScheduler final {
+  USING_FAST_MALLOC(CancelableTaskScheduler);
+
+ public:
+  using Task = WTF::CrossThreadOnceFunction<void()>;
+
+  explicit CancelableTaskScheduler(scoped_refptr<base::TaskRunner>);
+  ~CancelableTaskScheduler();
+
+  // Schedules task to run on TaskRunner.
+  void ScheduleTask(Task);
+  // Cancels all not yet started tasks and waits for running ones to complete.
+  // Returns number of cancelled (not executed) tasks.
+  size_t CancelAndWait();
+
+ private:
+  class TaskData;
+  template <class T>
+  friend class CancelableTaskSchedulerTest;
+
+  std::unique_ptr<TaskData> Register(Task);
+  void UnregisterAndSignal(TaskData*);
+
+  size_t RemoveCancelledTasks();
+
+  size_t NumberOfTasksForTesting() const;
+
+  WTF::HashSet<TaskData*> tasks_;
+  mutable base::Lock lock_;
+  base::ConditionVariable cond_var_;
+  scoped_refptr<base::TaskRunner> task_runner_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_HEAP_CANCELABLE_TASK_SCHEDULER_H_
diff --git a/third_party/blink/renderer/platform/heap/cancelable_task_scheduler_test.cc b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler_test.cc
new file mode 100644
index 0000000..97f4c8b
--- /dev/null
+++ b/third_party/blink/renderer/platform/heap/cancelable_task_scheduler_test.cc
@@ -0,0 +1,96 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
+
+#include <atomic>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/task_runner.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/heap/heap_test_utilities.h"
+#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
+#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+
+namespace blink {
+
+class ParallelTaskRunner : public base::TaskRunner {
+ public:
+  bool PostDelayedTask(const base::Location& location,
+                       base::OnceClosure task,
+                       base::TimeDelta) override {
+    worker_pool::PostTask(location, WTF::CrossThreadBindOnce(std::move(task)));
+    return true;
+  }
+
+  void RunUntilIdle() {}
+};
+
+template <class Runner>
+class CancelableTaskSchedulerTest : public TestSupportingGC {
+ public:
+  using Task = CancelableTaskScheduler::Task;
+
+  void ScheduleTask(Task callback) {
+    scheduler_.ScheduleTask(std::move(callback));
+  }
+
+  void RunTaskRunner() { task_runner_->RunUntilIdle(); }
+  size_t CancelAndWait() { return scheduler_.CancelAndWait(); }
+
+  size_t NumberOfRegisteredTasks() const {
+    return scheduler_.NumberOfTasksForTesting();
+  }
+
+ private:
+  scoped_refptr<Runner> task_runner_ = base::MakeRefCounted<Runner>();
+  CancelableTaskScheduler scheduler_{task_runner_};
+};
+
+using RunnerTypes =
+    ::testing::Types<scheduler::FakeTaskRunner, ParallelTaskRunner>;
+TYPED_TEST_SUITE(CancelableTaskSchedulerTest, RunnerTypes);
+
+TYPED_TEST(CancelableTaskSchedulerTest, EmptyCancelTasks) {
+  const size_t cancelled = this->CancelAndWait();
+  EXPECT_EQ(0u, cancelled);
+  EXPECT_EQ(0u, this->NumberOfRegisteredTasks());
+}
+
+TYPED_TEST(CancelableTaskSchedulerTest, RunAndCancelTasks) {
+  static constexpr size_t kNumberOfTasks = 10u;
+
+  const auto callback = [](std::atomic<int>* i) { ++(*i); };
+  std::atomic<int> var{0};
+
+  for (size_t i = 0; i < kNumberOfTasks; ++i) {
+    this->ScheduleTask(
+        WTF::CrossThreadBindOnce(callback, WTF::CrossThreadUnretained(&var)));
+    EXPECT_GE(i + 1, this->NumberOfRegisteredTasks());
+  }
+
+  this->RunTaskRunner();
+  // Tasks will remove themselves after running
+  EXPECT_LE(0u, this->NumberOfRegisteredTasks());
+
+  const size_t cancelled = this->CancelAndWait();
+  EXPECT_EQ(0u, this->NumberOfRegisteredTasks());
+  EXPECT_EQ(kNumberOfTasks, var + cancelled);
+}
+
+TEST(CancelableTaskSchedulerTest, RemoveTasksFromQueue) {
+  auto task_runner = base::MakeRefCounted<scheduler::FakeTaskRunner>();
+  CancelableTaskScheduler scheduler{task_runner};
+  int var = 0;
+  scheduler.ScheduleTask(WTF::CrossThreadBindOnce(
+      [](int* var) { ++(*var); }, WTF::CrossThreadUnretained(&var)));
+  auto tasks = task_runner->TakePendingTasksForTesting();
+  // Clearing the task queue should destroy all cancelable closures, which in
+  // turn will notify CancelableTaskScheduler to remove corresponding tasks.
+  tasks.clear();
+  EXPECT_EQ(0, var);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/heap.cc b/third_party/blink/renderer/platform/heap/heap.cc
index dbfab3a..b1c28d9 100644
--- a/third_party/blink/renderer/platform/heap/heap.cc
+++ b/third_party/blink/renderer/platform/heap/heap.cc
@@ -264,11 +264,11 @@
 
 namespace {
 
-template <typename Worklist, typename Callback, typename YieldPredicate>
-bool DrainWorklist(Worklist* worklist,
-                   Callback callback,
-                   YieldPredicate should_yield,
-                   int task_id) {
+template <typename Worklist, typename Callback>
+bool DrainWorklistWithDeadline(base::TimeTicks deadline,
+                               Worklist* worklist,
+                               Callback callback,
+                               int task_id) {
   const size_t kDeadlineCheckInterval = 1250;
 
   size_t processed_callback_count = 0;
@@ -276,34 +276,15 @@
   while (worklist->Pop(task_id, &item)) {
     callback(item);
     if (++processed_callback_count == kDeadlineCheckInterval) {
-      if (should_yield())
+      if (deadline <= base::TimeTicks::Now()) {
         return false;
+      }
       processed_callback_count = 0;
     }
   }
   return true;
 }
 
-template <typename Worklist, typename Callback>
-bool DrainWorklistWithDeadline(base::TimeTicks deadline,
-                               Worklist* worklist,
-                               Callback callback,
-                               int task_id) {
-  return DrainWorklist(
-      worklist, std::move(callback),
-      [deadline]() { return deadline <= base::TimeTicks::Now(); }, task_id);
-}
-
-template <typename Worklist, typename Callback>
-bool DrainWorklistWithYielding(base::JobDelegate* delegate,
-                               Worklist* worklist,
-                               Callback callback,
-                               int task_id) {
-  return DrainWorklist(
-      worklist, std::move(callback),
-      [delegate]() { return delegate->ShouldYield(); }, task_id);
-}
-
 }  // namespace
 
 bool ThreadHeap::InvokeEphemeronCallbacks(MarkingVisitor* visitor,
@@ -432,19 +413,14 @@
          !write_barrier_worklist_->IsGlobalPoolEmpty();
 }
 
-size_t ThreadHeap::ConcurrentMarkingGlobalWorkSize() const {
-  return marking_worklist_->GlobalPoolSize() +
-         write_barrier_worklist_->GlobalPoolSize();
-}
-
 bool ThreadHeap::AdvanceConcurrentMarking(ConcurrentMarkingVisitor* visitor,
-                                          base::JobDelegate* delegate) {
+                                          base::TimeTicks deadline) {
   bool finished;
   do {
     // Iteratively mark all objects that are reachable from the objects
     // currently pushed onto the marking worklist.
-    finished = DrainWorklistWithYielding(
-        delegate, marking_worklist_.get(),
+    finished = DrainWorklistWithDeadline(
+        deadline, marking_worklist_.get(),
         [visitor](const MarkingItem& item) {
           HeapObjectHeader* header =
               HeapObjectHeader::FromPayload(item.base_object_payload);
@@ -456,8 +432,8 @@
     if (!finished)
       break;
 
-    finished = DrainWorklistWithYielding(
-        delegate, write_barrier_worklist_.get(),
+    finished = DrainWorklistWithDeadline(
+        deadline, write_barrier_worklist_.get(),
         [visitor](HeapObjectHeader* header) {
           DCHECK(!ConcurrentMarkingVisitor::IsInConstruction(header));
           GCInfo::From(header->GcInfoIndex()).trace(visitor, header->Payload());
diff --git a/third_party/blink/renderer/platform/heap/heap.h b/third_party/blink/renderer/platform/heap/heap.h
index 1cd00cea4..953dd78 100644
--- a/third_party/blink/renderer/platform/heap/heap.h
+++ b/third_party/blink/renderer/platform/heap/heap.h
@@ -310,11 +310,8 @@
 
   // Returns true if concurrent markers will have work to steal
   bool HasWorkForConcurrentMarking() const;
-  // Returns the amount of work currently available for stealing (there could be
-  // work remaining even if this is 0).
-  size_t ConcurrentMarkingGlobalWorkSize() const;
   // Returns true if marker is done
-  bool AdvanceConcurrentMarking(ConcurrentMarkingVisitor*, base::JobDelegate*);
+  bool AdvanceConcurrentMarking(ConcurrentMarkingVisitor*, base::TimeTicks);
 
   // Conservatively checks whether an address is a pointer in any of the
   // thread heaps.  If so marks the object pointed to as live.
diff --git a/third_party/blink/renderer/platform/heap/thread_state.cc b/third_party/blink/renderer/platform/heap/thread_state.cc
index 995da10..b1c9113 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.cc
+++ b/third_party/blink/renderer/platform/heap/thread_state.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/blink_gc_memory_dump_provider.h"
+#include "third_party/blink/renderer/platform/heap/cancelable_task_scheduler.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
@@ -97,6 +98,19 @@
 
 namespace {
 
+// Concurrent marking should stop every once in a while to flush private
+// segments to v8 marking worklist. It should also stop to avoid priority
+// inversion.
+//
+// TODO(omerkatz): What is a good value to set here?
+constexpr base::TimeDelta kConcurrentMarkingStepDuration =
+    base::TimeDelta::FromMilliseconds(2);
+// Number of concurrent marking tasks to use.
+//
+// TODO(omerkatz): kNumberOfMarkingTasks should be set heuristically
+// instead of a constant.
+constexpr uint8_t kNumberOfConcurrentMarkingTasks = 3u;
+
 constexpr size_t kMaxTerminationGCLoops = 20;
 
 // Helper function to convert a byte count to a KB count, capping at
@@ -207,7 +221,9 @@
       asan_fake_stack_(__asan_get_current_fake_stack()),
 #endif
       incremental_marking_scheduler_(
-          std::make_unique<IncrementalMarkingScheduler>(this)) {
+          std::make_unique<IncrementalMarkingScheduler>(this)),
+      marker_scheduler_(std::make_unique<CancelableTaskScheduler>(
+          base::MakeRefCounted<WorkerPoolTaskRunner>())) {
   DCHECK(CheckThread());
   DCHECK(!**thread_specific_);
   **thread_specific_ = this;
@@ -736,10 +752,9 @@
     SetGCState(kNoGCScheduled);
     if (base::FeatureList::IsEnabled(
             blink::features::kBlinkHeapConcurrentMarking)) {
-      // Stop concurrent markers and wait synchronously until they have all
-      // returned.
-      marker_handle_.Cancel();
-      DCHECK_EQ(0U, active_markers_);
+      // Stop concurrent markers
+      marker_scheduler_->CancelAndWait();
+      active_markers_ = 0;
       available_concurrent_marking_task_ids_.clear();
     }
 #if DCHECK_IS_ON()
@@ -1183,7 +1198,27 @@
     EnableIncrementalMarkingBarrier();
     if (base::FeatureList::IsEnabled(
             blink::features::kBlinkHeapConcurrentMarking)) {
+      // No active concurrent markers yet, so it is safe to write to
+      // concurrently_marked_bytes_ without a lock.
+      concurrently_marked_bytes_ = 0;
       current_gc_data_.visitor->FlushMarkingWorklists();
+      // Check that the marking worklist has enough private segments for all
+      // concurrent marking tasks.
+      const uint8_t max_concurrent_task_id =
+          WorklistTaskId::ConcurrentThreadBase +
+          kNumberOfConcurrentMarkingTasks;
+      static_assert(
+          MarkingWorklist::kNumTasks == WriteBarrierWorklist::kNumTasks,
+          "Marking worklist and write-barrier worklist should be the "
+          "same size");
+      static_assert(max_concurrent_task_id <= MarkingWorklist::kNumTasks,
+                    "Number of concurrent marking tasks should not exceed "
+                    "number of tasks in worlkist");
+      // Initialize concurrent marking task ids.
+      for (uint8_t i = WorklistTaskId::ConcurrentThreadBase;
+           i < max_concurrent_task_id; ++i) {
+        available_concurrent_marking_task_ids_.push_back(i);
+      }
       ScheduleConcurrentMarking();
     }
     SetGCState(kIncrementalMarkingStepScheduled);
@@ -1240,16 +1275,11 @@
 bool ThreadState::ConcurrentMarkingStep() {
   current_gc_data_.visitor->FlushMarkingWorklists();
   if (Heap().HasWorkForConcurrentMarking()) {
-    // Notifies the scheduler that max concurrency might have increased.
-    // This will adjust the number of markers if necessary.
-    marker_handle_.NotifyConcurrencyIncrease();
+    ScheduleConcurrentMarking();
     return false;
   }
-  base::AutoLock lock(concurrent_marker_lock_);
-  // !HasWorkForConcurrentMarking() is checked after |active_markers_| == 0
-  // because active markers can otherwise flush work and return.
-  return active_markers_.load(std::memory_order_relaxed) == 0 &&
-         !Heap().HasWorkForConcurrentMarking();
+  base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
+  return active_markers_ == 0;
 }
 
 void ThreadState::IncrementalMarkingFinalize() {
@@ -1760,57 +1790,32 @@
 }
 
 void ThreadState::ScheduleConcurrentMarking() {
+  base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
+
   DCHECK(base::FeatureList::IsEnabled(
       blink::features::kBlinkHeapConcurrentMarking));
-  DCHECK_EQ(0U, active_markers_);
 
-  // No active concurrent markers yet, so it is safe to write to
-  // concurrently_marked_bytes_ without a lock.
-  concurrently_marked_bytes_ = 0;
-
-  const uint8_t max_concurrent_task_id = MarkingWorklist::kNumTasks;
-  // Initialize concurrent marking task ids.
-  for (uint8_t i = WorklistTaskId::ConcurrentThreadBase;
-       i < max_concurrent_task_id; ++i) {
-    available_concurrent_marking_task_ids_.push_back(i);
+  for (uint8_t i = active_markers_; i < kNumberOfConcurrentMarkingTasks; ++i) {
+    marker_scheduler_->ScheduleTask(WTF::CrossThreadBindOnce(
+        &ThreadState::PerformConcurrentMark, WTF::CrossThreadUnretained(this)));
   }
 
-  // |USER_VISIBLE| is used to minimize marking on foreground thread.
-  marker_handle_ = base::PostJob(
-      FROM_HERE, {base::ThreadPool(), base::TaskPriority::USER_VISIBLE},
-      ConvertToBaseRepeatingCallback(
-          WTF::CrossThreadBindRepeating(&ThreadState::PerformConcurrentMark,
-                                        WTF::CrossThreadUnretained(this))),
-      ConvertToBaseRepeatingCallback(WTF::CrossThreadBindRepeating(
-          [](ThreadState* state) -> size_t {
-            // We need to account for local segments in addition to
-            // ConcurrentMarkingGlobalWorkSize().
-            return std::min<size_t>(
-                state->Heap().ConcurrentMarkingGlobalWorkSize() +
-                    state->active_markers_.load(std::memory_order_relaxed),
-                MarkingWorklist::kNumTasks -
-                    WorklistTaskId::ConcurrentThreadBase);
-          },
-          WTF::CrossThreadUnretained(this))));
+  active_markers_ = kNumberOfConcurrentMarkingTasks;
 }
 
-void ThreadState::PerformConcurrentMark(base::JobDelegate* job) {
+void ThreadState::PerformConcurrentMark() {
   VLOG(2) << "[state:" << this << "] [threadid:" << CurrentThread() << "] "
           << "ConcurrentMark";
   ThreadHeapStatsCollector::EnabledConcurrentScope stats_scope(
       Heap().stats_collector(),
       ThreadHeapStatsCollector::kConcurrentMarkingStep);
 
-  if (!Heap().HasWorkForConcurrentMarking())
-    return;
-
   uint8_t task_id;
   {
-    base::AutoLock lock(concurrent_marker_lock_);
+    base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
     DCHECK(!available_concurrent_marking_task_ids_.IsEmpty());
     task_id = available_concurrent_marking_task_ids_.back();
     available_concurrent_marking_task_ids_.pop_back();
-    active_markers_.fetch_add(1, std::memory_order_relaxed);
   }
 
   std::unique_ptr<ConcurrentMarkingVisitor> concurrent_visitor =
@@ -1822,15 +1827,26 @@
                 this, GetMarkingMode(Heap().Compaction()->IsCompacting()),
                 task_id);
 
-  Heap().AdvanceConcurrentMarking(concurrent_visitor.get(), job);
-  concurrent_visitor->FlushWorklists();
+  const bool finished = Heap().AdvanceConcurrentMarking(
+      concurrent_visitor.get(),
+      base::TimeTicks::Now() + kConcurrentMarkingStepDuration);
 
+  concurrent_visitor->FlushWorklists();
   {
-    base::AutoLock lock(concurrent_marker_lock_);
-    active_markers_.fetch_sub(1, std::memory_order_relaxed);
+    base::AutoLock lock(concurrent_marker_bootstrapping_lock_);
+    // When marking is done, flush visitor worklists and decrement number of
+    // active markers so we know how many markers are left
     concurrently_marked_bytes_ += concurrent_visitor->marked_bytes();
     available_concurrent_marking_task_ids_.push_back(task_id);
+    if (finished) {
+      --active_markers_;
+      return;
+    }
   }
+
+  // Reschedule this marker
+  marker_scheduler_->ScheduleTask(WTF::CrossThreadBindOnce(
+      &ThreadState::PerformConcurrentMark, WTF::CrossThreadUnretained(this)));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/thread_state.h b/third_party/blink/renderer/platform/heap/thread_state.h
index 2abc3e9a..f41cd05 100644
--- a/third_party/blink/renderer/platform/heap/thread_state.h
+++ b/third_party/blink/renderer/platform/heap/thread_state.h
@@ -63,6 +63,7 @@
 class IncrementalMarkingScope;
 }  // namespace incremental_marking_test
 
+class CancelableTaskScheduler;
 class MarkingVisitor;
 class PersistentNode;
 class PersistentRegion;
@@ -517,7 +518,7 @@
   // terminated and the worklist is empty)
   bool ConcurrentMarkingStep();
   void ScheduleConcurrentMarking();
-  void PerformConcurrentMark(base::JobDelegate* job);
+  void PerformConcurrentMark();
 
   // Schedule helpers.
   void ScheduleIdleLazySweep();
@@ -627,11 +628,11 @@
 
   std::unique_ptr<IncrementalMarkingScheduler> incremental_marking_scheduler_;
 
-  base::Lock concurrent_marker_lock_;
+  std::unique_ptr<CancelableTaskScheduler> marker_scheduler_;
   Vector<uint8_t> available_concurrent_marking_task_ids_;
-  std::atomic<size_t> active_markers_{0};
+  uint8_t active_markers_ = 0;
+  base::Lock concurrent_marker_bootstrapping_lock_;
   size_t concurrently_marked_bytes_ = 0;
-  base::JobHandle marker_handle_;
 
   base::JobHandle sweeper_handle_;
   std::atomic_bool has_unswept_pages_{false};
diff --git a/third_party/blink/renderer/platform/mojo/blink_typemaps.gni b/third_party/blink/renderer/platform/mojo/blink_typemaps.gni
index fe45924..22ecc78 100644
--- a/third_party/blink/renderer/platform/mojo/blink_typemaps.gni
+++ b/third_party/blink/renderer/platform/mojo/blink_typemaps.gni
@@ -5,13 +5,6 @@
 typemaps = [
   "//media/capture/mojom/video_capture_types.typemap",
   "//media/mojo/mojom/audio_parameters.typemap",
-  "//services/network/public/cpp/http_request_headers.typemap",
-  "//services/network/public/cpp/ip_address.typemap",
-  "//services/network/public/cpp/ip_endpoint.typemap",
-  "//services/network/public/cpp/network_interface.typemap",
-  "//services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap",
-  "//services/network/public/cpp/site_for_cookies.typemap",
-  "//services/network/public/cpp/p2p.typemap",
   "//third_party/blink/common/feature_policy/feature_policy.typemap",
   "//third_party/blink/common/frame/frame_policy.typemap",
   "//third_party/blink/public/common/loader/url_loader_factory_bundle.typemap",
diff --git a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
index d56af279..7b3d7265 100644
--- a/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/scheduling_policy.cc
@@ -15,7 +15,6 @@
     case Feature::kDedicatedWorkerOrWorklet:
     case Feature::kOutstandingNetworkRequest:
     case Feature::kOutstandingIndexedDBTransaction:
-    case Feature::kHasScriptableFramesInMultipleTabs:
     case Feature::kBroadcastChannel:
     case Feature::kIndexedDBConnection:
     case Feature::kWebGL:
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 087fdef..9af16a1 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -1109,6 +1109,9 @@
                          IncomingValueType&& value);
 
   template <typename IncomingValueType>
+  AddResult InsertBefore(const_iterator it, IncomingValueType&& value);
+
+  template <typename IncomingValueType>
   AddResult AppendOrMoveToLast(IncomingValueType&&);
 
   template <typename IncomingValueType>
@@ -1218,6 +1221,15 @@
 template <typename T, typename Allocator>
 template <typename IncomingValueType>
 typename NewLinkedHashSet<T, Allocator>::AddResult
+NewLinkedHashSet<T, Allocator>::InsertBefore(const_iterator it,
+                                             IncomingValueType&& value) {
+  return InsertOrMoveBefore(it, std::forward<IncomingValueType>(value),
+                            MoveType::kDontMove);
+}
+
+template <typename T, typename Allocator>
+template <typename IncomingValueType>
+typename NewLinkedHashSet<T, Allocator>::AddResult
 NewLinkedHashSet<T, Allocator>::AppendOrMoveToLast(IncomingValueType&& value) {
   return InsertOrMoveBefore(end(), std::forward<IncomingValueType>(value),
                             MoveType::kMoveIfValueExists);
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 fb0adc6a..0c7622c 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
@@ -119,10 +119,11 @@
   using Set = NewLinkedHashSet<int>;
   Set set;
 
-  set.InsertBefore(1, 1);
+  set.InsertBefore(set.begin(), 1);
   set.InsertBefore(10, 3);
   set.InsertBefore(3, 2);
-  set.InsertBefore(10, 5);
+  set.InsertBefore(set.end(), 6);
+  set.InsertBefore(--set.end(), 5);
   set.InsertBefore(5, 4);
 
   Set::const_iterator it = set.begin();
@@ -136,6 +137,8 @@
   ++it;
   EXPECT_EQ(*it, 5);
   ++it;
+  EXPECT_EQ(*it, 6);
+  ++it;
   EXPECT_TRUE(it == set.end());
 }
 
diff --git a/third_party/blink/renderer/platform/wtf/text/string_builder.h b/third_party/blink/renderer/platform/wtf/text/string_builder.h
index a853cc40..1af5205 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_builder.h
+++ b/third_party/blink/renderer/platform/wtf/text/string_builder.h
@@ -160,6 +160,14 @@
   AtomicString ToAtomicString();
   String Substring(unsigned start, unsigned length) const;
 
+  operator StringView() const {
+    if (Is8Bit()) {
+      return StringView(Characters8(), length());
+    } else {
+      return StringView(Characters16(), length());
+    }
+  }
+
   unsigned length() const { return length_; }
   bool IsEmpty() const { return !length_; }
 
diff --git a/third_party/blink/renderer/platform/wtf/text/string_view.h b/third_party/blink/renderer/platform/wtf/text/string_view.h
index 074461cf..06611ab 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_view.h
+++ b/third_party/blink/renderer/platform/wtf/text/string_view.h
@@ -58,12 +58,12 @@
   StringView(StringImpl&, unsigned offset);
   StringView(StringImpl&, unsigned offset, unsigned length);
 
-  // From a String, implemented in String.h
+  // From a String, implemented in wtf_string.h
   inline StringView(const String&, unsigned offset, unsigned length);
   inline StringView(const String&, unsigned offset);
   inline StringView(const String&);
 
-  // From an AtomicString, implemented in AtomicString.h
+  // From an AtomicString, implemented in atomic_string.h
   inline StringView(const AtomicString&, unsigned offset, unsigned length);
   inline StringView(const AtomicString&, unsigned offset);
   inline StringView(const AtomicString&);
diff --git a/third_party/blink/web_tests/FlagExpectations/composite-after-paint b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
index c2374cb7..a5641ac 100644
--- a/third_party/blink/web_tests/FlagExpectations/composite-after-paint
+++ b/third_party/blink/web_tests/FlagExpectations/composite-after-paint
@@ -35,6 +35,9 @@
 crbug.com/918155 virtual/prefer_compositing_to_lcd_text/scrollbars/overlay-scrollbar-over-child-layer-nested.html [ Pass ]
 crbug.com/1039401 virtual/scroll_customization/fast/scrolling/scrollbar-mousedown-mouseup.html [ Pass ]
 paint/invalidation/compositing/subpixel-offset-scaled-transform-composited.html [ Pass ]
+crbug.com/957674 external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Pass ]
+crbug.com/957674 virtual/scalefactor200/external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Pass ]
+crbug.com/957674 virtual/scalefactor200withoutzoom/external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Pass ]
 
 virtual/android/fullscreen/video-overlay-scroll.html [ Failure ]
 virtual/android/rootscroller/fixed-chaining-with-implicit-pointer-events-none.html [ Failure ]
@@ -157,3 +160,11 @@
 
 compositing/gestures/gesture-tapHighlight-composited-img.html [ Pass Failure ]
 http/tests/images/image-decode-in-frame.html [ Pass Failure ]
+
+# Paint Timing failures
+crbug.com/1063079 external/wpt/paint-timing/fcp-only/fcp-opacity-descendant.html [ Pass Failure ]
+crbug.com/1063079 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-opacity-descendant.html [ Pass Failure ]
+crbug.com/1063079 external/wpt/paint-timing/fcp-only/fcp-opacity.html [ Pass Failure ]
+crbug.com/1063079 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-opacity.html [ Pass Failure ]
+crbug.com/1063079 external/wpt/paint-timing/fcp-only/fcp-pseudo-element-opacity.html [ Pass Failure ]
+crbug.com/1063079 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-pseudo-element-opacity.html [ Pass Failure ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 98cc0546..3d9cd62 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2231,6 +2231,9 @@
 crbug.com/123456 [ Mac ] virtual/form-controls-refresh-disabled/fast/forms/select/menulist-onchange-fired-with-key-up-down.html [ Skip ]
 crbug.com/123456 [ Mac ] virtual/form-controls-refresh-disabled/fast/forms/select/popup-with-display-none-optgroup.html [ Skip ]
 
+crbug.com/1056027 [ Fuchsia ] virtual/text-antialias/small-caps-aat.html [ Failure Pass ]
+crbug.com/1057339 [ Fuchsia ] virtual/text-antialias/international/rtl-mark.html [ Failure Pass ]
+
 crbug.com/652964 [ Linux ] virtual/text-antialias/hyphens/hyphen-min-preferred-width-mock.html [ Skip ]
 crbug.com/652964 [ Win ] virtual/text-antialias/hyphens/hyphen-min-preferred-width-mock.html [ Skip ]
 crbug.com/652964 [ Linux ] virtual/text-antialias/hyphens/hyphens-align.html [ Skip ]
@@ -2984,6 +2987,8 @@
 
 crbug.com/1053965 external/wpt/css/css-values/ex-unit-004.html [ Failure ]
 
+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 [ Linux ] external/wpt/css/css-text/line-break/line-break-strict-015.xht [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-text/line-break/line-break-strict-015.xht [ Failure ]
@@ -3085,7 +3090,6 @@
 crbug.com/626703 [ Mac10.10 ] external/wpt/WebCryptoAPI/derive_bits_keys/hkdf.https.any.html?1001-2000 [ Failure Timeout ]
 crbug.com/626703 [ Mac10.10 ] external/wpt/mathml/relations/html5-tree/math-global-event-handlers.tentative.html [ Failure Timeout ]
 crbug.com/626703 [ Mac10.10 ] virtual/web-components-v0-disabled/external/wpt/html/dom/idlharness.https.html?include=HTML.\* [ Failure Timeout ]
-crbug.com/626703 [ Win7 ] external/wpt/pointerevents/pointerevent_touch-action-inherit_child-auto-child-none_touch.html [ Timeout ]
 crbug.com/626703 [ Win7 ] external/wpt/content-security-policy/object-src/object-src-no-url-allowed.html [ Timeout ]
 crbug.com/626703 [ Win7 ] external/wpt/content-security-policy/plugin-types/plugintypes-nourl-allowed.html [ Timeout ]
 crbug.com/626703 [ Mac10.11 ] virtual/cascade/external/wpt/css/css-paint-api/parse-input-arguments-018.https.html [ Failure ]
@@ -5578,6 +5582,12 @@
 crbug.com/915352 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
 crbug.com/915352 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-and-display-none.https.html [ Pass Failure ]
 
+# Paint Timing failures
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/border-image.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/fcp-only/fcp-video-poster.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/replaced-content-image.html [ Pass Failure ]
+crbug.com/1062984 virtual/paint-timing/external/wpt/paint-timing/mask-image.html [ Pass Failure ]
+
 # Sheriff 2019-03-01
 crbug.com/937416 http/tests/devtools/resource-tree/resource-tree-htmlimports.js [ Pass Failure ]
 
@@ -5991,10 +6001,16 @@
 crbug.com/997669 [ Win ] http/tests/devtools/search/sources-search-scope-in-files.js [ Pass Crash ]
 crbug.com/626703 external/wpt/css/css-paint-api/custom-property-animation-on-main-thread.https.html [ Pass Failure ]
 
+crbug.com/999209 virtual/lazyload-image/http/tests/lazyload/style-dimension.html [ Timeout ]
+
 crbug.com/1015130 external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html [ Failure Pass ]
 crbug.com/1015130 virtual/scalefactor200/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html [ Failure Pass ]
 crbug.com/1015130 virtual/scalefactor200withoutzoom/external/wpt/largest-contentful-paint/first-paint-equals-lcp-text.html [ Failure Pass ]
 
+crbug.com/957674 external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Failure ]
+crbug.com/957674 virtual/scalefactor200/external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Failure ]
+crbug.com/957674 virtual/scalefactor200withoutzoom/external/wpt/largest-contentful-paint/invisible-images-composited-2.html [ Failure ]
+
 crbug.com/1000051 media/controls/volume-slider.html [ Failure Timeout Pass ]
 crbug.com/1000051 virtual/audio-service/media/controls/volume-slider.html [ Failure Timeout Pass ]
 
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
deleted file mode 100644
index 098e827..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip-ref.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<head>
-  <title>Images with loading='lazy' load under subpixel-offset clips</title>
-  <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
-  <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
-</head>
-<div style="height: 3.7499995rem; "></div>
-<div style="position: relative; font-size: 0; background: lightblue">
-  <img loading="lazy" data-sizes="auto" src="resources/image.png"
-       title="" width="1600">
-</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
deleted file mode 100644
index 0eb20d6..0000000
--- a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/image-loading-subpixel-clip.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<head>
-  <title>Images with loading='lazy' load under subpixel-offset clips</title>
-  <link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
-  <link rel="help" href="https://html.spec.whatwg.org/#lazy-loading-attributes">
-  <link rel="match" href="image-loading-subpixel-clip-ref.html">
-</head>
-<div style="height: 3.7499995rem"></div>
-<div style="overflow: hidden">
-  <div style="position: relative; font-size: 0; background: lightblue">
-    <img loading="lazy" data-sizes="auto" src="resources/image.png"
-         title="" width="1600">
-  </div>
-</div>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/original-referrer-policy-applied.sub-expected.txt b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/original-referrer-policy-applied.sub-expected.txt
new file mode 100644
index 0000000..55c98d0a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/embedded-content/the-img-element/original-referrer-policy-applied.sub-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+PASS Test that when deferred iframe is loaded, it uses the referrer-policy specified at parse time.
+FAIL Test that when deferred img is loaded, it uses the referrer-policy specified at parse time. assert_unreached: The image load should not fail, by sending the wrong referer header. Reached unreachable code
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-1.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-1.html
new file mode 100644
index 0000000..495645a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<title>Largest Contentful Paint: invisible images are not observable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/invisible-images.js"></script>
+<style>
+  .opacity0 {
+    opacity: 0;
+  }
+  .visibilityHidden {
+    visibility: hidden;
+  }
+  .displayNone {
+    display: none;
+  }
+  .composited {
+    will-change: transform;
+  }
+</style>
+<img src='/images/blue.png' class='opacity0 composited' id='opacity0'/>
+<img src='/images/green.png' class='visibilityHidden composited' id='visibilityHidden'/>
+<img src='/images/red.png' class='displayNone composited' id='displayNone'/>
+<div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0'/></div>
+<div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHidden'/></div>
+<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-2.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-2.html
new file mode 100644
index 0000000..8ab32eb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>Largest Contentful Paint: invisible images are not observable</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/invisible-images.js"></script>
+<style>
+  .opacity0 {
+    opacity: 0;
+  }
+  .visibilityHidden {
+    visibility: hidden;
+  }
+  .displayNone {
+    display: none;
+  }
+  .composited {
+    will-change: transform;
+  }
+  img {
+    border: 2px solid black;
+    will-change: transform;
+  }
+</style>
+<div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0'/></div>
+<div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHidden'/></div>
+<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited.html
deleted file mode 100644
index f652180d..0000000
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images-composited.html
+++ /dev/null
@@ -1,56 +0,0 @@
-<!DOCTYPE HTML>
-<meta charset=utf-8>
-<head>
-<title>Largest Contentful Paint: invisible images are not observable</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<style>
-  .opacity0 {
-    opacity: 0;
-  }
-  .visibilityHidden {
-    visibility: hidden;
-  }
-  .displayNone {
-    display: none;
-  }
-  .composited {
-    will-change: transform;
-  }
-</style>
-</head>
-<body>
-<script>
-  async_test(t => {
-    assert_precondition(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented");
-    const observer = new PerformanceObserver(
-      t.step_func(entryList => {
-        entryList.getEntries().forEach(entry => {
-          // May receive a text entry. Ignore that entry.
-          if (!entry.url) {
-            return;
-          }
-          // The images should not have caused an entry, so fail test.
-          assert_unreached('Should not have received an entry! Received one with id '
-              + entryList.getEntries()[0].id);
-        });
-      })
-    );
-    observer.observe({type: 'largest-contentful-paint', buffered: true});
-    // Images have been added but should not cause entries to be dispatched.
-    // Wait for 500ms and end test, ensuring no entry was created.
-    t.step_timeout(() => {
-      t.done();
-    }, 500);
-  }, 'Images with opacity: 0, visibility: hidden, or display: none are not observable by LargestContentfulPaint.');
-</script>
-<img src='/images/blue.png' class='opacity0 composited' id='opacity0'/>
-<img src='/images/green.png' class='visibilityHidden composited' id='visibilityHidden'/>
-<img src='/images/red.png' class='displayNone composited' id='displayNone'/>
-<div class='opacity0 composited'><img class='composited' src='/images/yellow.png' id='divOpacity0'/></div>
-<div class='visibilityHidden composited'><img class='composited' src='/images/yellow.png' id='divVisibilityHidden'/></div>
-<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNone'/></div>
-<div class='opacity0 composited'><img src='/images/yellow.png' id='divOpacity0Composited'/></div>
-<div class='visibilityHidden composited'><img src='/images/yellow.png' id='divVisibilityHiddenComposited'/></div>
-<div class='displayNone composited'><img src='/images/yellow.png' id='divDisplayNoneComposited'/></div>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images.html
index 4932466..997d70f7 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images.html
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/invisible-images.html
@@ -1,9 +1,8 @@
 <!DOCTYPE HTML>
-<meta charset=utf-8>
-<head>
 <title>Largest Contentful Paint: invisible images are not observable</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="resources/invisible-images.js"></script>
 <style>
   .opacity0 {
     opacity: 0;
@@ -15,36 +14,9 @@
     display: none;
   }
 </style>
-</head>
-<body>
-<script>
-  async_test(t => {
-    assert_precondition(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented");
-    const observer = new PerformanceObserver(
-      t.step_func(entryList => {
-        entryList.getEntries().forEach(entry => {
-          // May receive a text entry. Ignore that entry.
-          if (!entry.url) {
-            return;
-          }
-          // The images should not have caused an entry, so fail test.
-          assert_unreached('Should not have received an entry! Received one with id '
-              + entryList.getEntries()[0].id);
-        });
-      })
-    );
-    observer.observe({type: 'largest-contentful-paint', buffered: true});
-    // Images have been added but should not cause entries to be dispatched.
-    // Wait for 500ms and end test, ensuring no entry was created.
-    t.step_timeout(() => {
-      t.done();
-    }, 500);
-  }, 'Images with opacity: 0, visibility: hidden, or display: none are not observable by LargestContentfulPaint.');
-</script>
 <img src='/images/blue.png' class='opacity0' id='opacity0'/>
 <img src='/images/green.png' class='visibilityHidden' id='visibilityHidden'/>
 <img src='/images/red.png' class='displayNone' id='displayNone'/>
 <div class='opacity0'><img src='/images/yellow.png' id='divOpacity0'/></div>
 <div class='visibilityHidden'><img src='/images/yellow.png' id='divVisibilityHidden'/></div>
 <div class='displayNone'><img src='/images/yellow.png' id='divDisplayNone'/></div>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/resources/invisible-images.js b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/resources/invisible-images.js
new file mode 100644
index 0000000..fd13e945
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/resources/invisible-images.js
@@ -0,0 +1,22 @@
+async_test(t => {
+  assert_precondition(window.LargestContentfulPaint, "LargestContentfulPaint is not implemented");
+  const observer = new PerformanceObserver(
+    t.step_func(entryList => {
+       entryList.getEntries().forEach(entry => {
+       // May receive a text entry. Ignore that entry.
+        if (!entry.url) {
+          return;
+        }
+        // The images should not have caused an entry, so fail test.
+        assert_unreached('Should not have received an entry! Received one with id '
+            + entryList.getEntries()[0].id);
+      });
+    })
+  );
+  observer.observe({type: 'largest-contentful-paint', buffered: true});
+  // Images have been added but should not cause entries to be dispatched.
+  // Wait for 500ms and end test, ensuring no entry was created.
+  t.step_timeout(() => {
+    t.done();
+  }, 500);
+}, 'Images with opacity: 0, visibility: hidden, or display: none are not observable by LargestContentfulPaint.');
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html
new file mode 100644
index 0000000..f03f98a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_node_in_shadow_dom.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Lostpointercapture fires on document when target in shadow dom  is removed</title>
+    <meta name="viewport" content="width=device-width">
+    <link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=810882">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
+  </head>
+  <body onload="onLoad()">
+    <template id="template">
+      <style>
+          #content{
+              height:100px;
+              width:100px;
+              background-color: lightgrey;
+          }
+      </style>
+      <div id="content"></div>
+    </template>
+    <h1>Pointer Events - lostpointercapture when capturing element in shadow dom is removed</h1>
+    <h4>
+        Test Description:
+        This test checks if lostpointercapture is fired at the document when the capturing node that is in shadow dom is removed from the shadow dom.
+        Complete the following actions:
+        <ol>
+            <li>Press left mouse button over "Set Capture" button. Pointer should be captured by the gray rectangle which is in shadow dom.</li>
+            <li>Gray rectangle will be removed from shadow dom.</li>
+            <li>"lostpointercapture" should be received on the document not on the gray rectangle.</li>
+        </ol>
+    </h4>
+    <div id="shadowhost"></div>
+    <br>
+    <input type="button" id="btnCapture" value="Set Capture">
+    <div id="log"></div>
+    <script>
+      var logDiv = document.getElementById("log");
+      function logMessage(message){
+        var log = document.createElement("div");
+        var messageNode = document.createTextNode(message);
+        log.appendChild(messageNode);
+        logDiv.appendChild(log);
+      }
+      var events = [];
+
+      var host = document.getElementById("shadowhost");
+      var shadowRoot = host.attachShadow({mode: "open"});
+      var template = document.getElementById("template");
+      var node = template.content.cloneNode(true);
+      shadowRoot.appendChild(node);
+
+      var content = host.shadowRoot.getElementById("content");
+      var captureButton = document.getElementById("btnCapture");
+
+      captureButton.addEventListener("pointerdown", function(event){
+        logMessage("Pointer will be captured by the shadow dom gray rectangle.");
+        content.setPointerCapture(event.pointerId);
+        events.push("pointerdown@captureButton");
+      });
+      content.addEventListener("gotpointercapture", function(event){
+        logMessage("Gray rectangle received pointercapture.");
+        logMessage("Removing gray rectangle from shadow dom.")
+        content.parentNode.removeChild(content);
+        events.push("gotpointercapture@content");
+      });
+      content.addEventListener("lostpointercapture", function(event){
+        logMessage("Test Failed! The element removed from shadow dom should not receive lostpointercapture.")
+        events.push("lostpointercapture@content");
+        if(window.promise_test && shadow_dom_test){
+          shadow_dom_test.step(function(){
+            assert_unreached("lostpointercapture must be fired on the document, not the capturing element");
+            reject_test("lostpointercapture must not be dispatched on the disconnected node");
+            shadow_dom_test.done();
+          });
+        }
+      });
+      document.addEventListener("lostpointercapture", function(event){
+        logMessage("Test Passed! Document received lostpointercapture.");
+        events.push("lostpointercapture@document");
+        if(window.promise_test && shadow_dom_test){
+          shadow_dom_test.step(function(){
+            assert_array_equals(events, ["pointerdown@captureButton",
+              "gotpointercapture@content",
+              "lostpointercapture@document"]);
+            resolve_test();
+            shadow_dom_test.done();
+          });
+        }
+      });
+
+      var shadow_dom_test = null;
+      var resolve_test = null;
+      var reject_test = null;
+
+      function cleanup(){
+        events = [];
+        shadow_dom_test = null;
+        resolve_test = null;
+        reject_test = null;
+      }
+
+      function onLoad(){
+        if(window.promise_test){
+          promise_test(function(t){
+            return new Promise(function(resolve, reject){
+              shadow_dom_test = t;
+              resolve_test = resolve;
+              reject_test = reject;
+              t.add_cleanup(function(){
+                cleanup();
+              });
+              var actions = new test_driver.Actions();
+              var actions_promise = actions
+                  .pointerMove(0, 0, {origin:captureButton})
+                  .pointerDown({button: actions.ButtonType.LEFT})
+                  .pointerUp({button: actions.ButtonType.LEFT})
+                  .send();
+            });
+          }, "lostpointercapture is dispatched on the document when shadow dom capturing element is removed");
+        }
+      }
+    </script>
+  </body>
+</html>
+
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_shadow_host.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_shadow_host.html
new file mode 100644
index 0000000..4b5c9f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_lostpointercapture_for_disconnected_shadow_host.html
@@ -0,0 +1,136 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Lostpointercapture fires on document when target in shadow dom  is removed</title>
+    <meta name="viewport" content="width=device-width">
+    <link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=810882">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
+    <style>
+      #shadowhost{
+        height:200px;
+        width:200px;
+        background-color:magenta;
+      }
+    </style>
+  </head>
+  <body onload="onLoad()">
+    <template id="template">
+      <style>
+          #content{
+              height:100px;
+              width:100px;
+              background-color: lightgrey;
+          }
+      </style>
+      <div id="content"></div>
+    </template>
+    <h1>Pointer Events - lostpointercapture when capturing element in shadow dom is removed by removing the shadow host</h1>
+    <h4>
+        Test Description:
+        This test checks if lostpointercapture is fired at the document when the capturing node is removed from the document by removing the shadow host.
+        The shadow host is colored magenta and the shadow dom element is colored gray.
+        Complete the following actions:
+        <ol>
+            <li>Press left mouse button over "Set Capture" button. Pointer should be captured by the gray rectangle.</li>
+            <li>Shadow host magenta rectangle including the gray rectangle will be removed from shadow dom.</li>
+            <li>"lostpointercapture" should be received on the document not on the gray rectangle.</li>
+        </ol>
+    </h4>
+    <div id="shadowhost"></div>
+    <br>
+    <input type="button" id="btnCapture" value="Set Capture">
+    <div id="log"></div>
+    <script>
+      var logDiv = document.getElementById("log");
+      function logMessage(message){
+        var log = document.createElement("div");
+        var messageNode = document.createTextNode(message);
+        log.appendChild(messageNode);
+        logDiv.appendChild(log);
+      }
+      var events = [];
+
+      var host = document.getElementById("shadowhost");
+      var shadowRoot = host.attachShadow({mode: "open"});
+      var template = document.getElementById("template");
+      var node = template.content.cloneNode(true);
+      shadowRoot.appendChild(node);
+
+      var content = host.shadowRoot.getElementById("content");
+      var captureButton = document.getElementById("btnCapture");
+
+      captureButton.addEventListener("pointerdown", function(event){
+        logMessage("Pointer will be captured by the shadow dom gray rectangle.");
+        content.setPointerCapture(event.pointerId);
+        events.push("pointerdown@captureButton");
+      });
+      content.addEventListener("gotpointercapture", function(event){
+        logMessage("Gray rectangle received pointercapture.");
+        logMessage("Removing magenta rectangle (which includes gray rectangle) from shadow dom.")
+        host.parentNode.removeChild(host);
+        events.push("gotpointercapture@content");
+      });
+      content.addEventListener("lostpointercapture", function(event){
+        logMessage("Test Failed! The element removed from shadow dom should not receive lostpointercapture.")
+        events.push("lostpointercapture@content");
+        if(window.promise_test && shadow_dom_test){
+          shadow_dom_test.step(function(){
+            assert_unreached("lostpointercapture must be fired on the document, not the capturing element");
+            reject_test("lostpointercapture must not be dispatched on the disconnected node");
+            shadow_dom_test.done();
+          });
+        }
+      });
+      document.addEventListener("lostpointercapture", function(event){
+        logMessage("Test Passed! Document received lostpointercapture.");
+        events.push("lostpointercapture@document");
+        if(window.promise_test && shadow_dom_test){
+          shadow_dom_test.step(function(){
+            assert_array_equals(events, ["pointerdown@captureButton",
+              "gotpointercapture@content",
+              "lostpointercapture@document"]);
+            resolve_test();
+            shadow_dom_test.done();
+          });
+        }
+      });
+
+      var shadow_dom_test = null;
+      var resolve_test = null;
+      var reject_test = null;
+
+      function cleanup(){
+        events = [];
+        shadow_dom_test = null;
+        resolve_test = null;
+        reject_test = null;
+      }
+
+      function onLoad(){
+        if(window.promise_test){
+          promise_test(function(t){
+            return new Promise(function(resolve, reject){
+              shadow_dom_test = t;
+              resolve_test = resolve;
+              reject_test = reject;
+              t.add_cleanup(function(){
+                cleanup();
+              });
+              var actions = new test_driver.Actions();
+              var actions_promise = actions
+                  .pointerMove(0, 0, {origin:captureButton})
+                  .pointerDown({button: actions.ButtonType.LEFT})
+                  .pointerUp({button: actions.ButtonType.LEFT})
+                  .send();
+            });
+          }, "lostpointercapture is dispatched on the document when shadow host is removed");
+        }
+      }
+    </script>
+  </body>
+</html>
+
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-custom-element.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-custom-element.html
new file mode 100644
index 0000000..e8f143b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-custom-element.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>PointerCapture for Custome Shadow DOM</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width">
+    <link rel="help" href= "https://bugs.chromium.org/p/chromium/issues/detail?id=810882">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
+    <script>
+       class WC extends HTMLElement{
+        constructor(){
+          super();
+          let template = document.getElementById('template-wc');
+          let node = template.content.cloneNode(true) ;
+
+          let shadowRoot = this.attachShadow({mode: 'open'});
+          shadowRoot.appendChild(node);
+        }
+       }
+       customElements.define("wc-wc", WC);
+    </script>
+    </head>
+  <body onload="onLoad()">
+    <template id="template-wc">
+      <style>
+          #content{
+              height:50px;
+              width:50px;
+              background-color: magenta;
+          }
+      </style>
+      <div id="content"></div>
+    </template>
+    <h4>PointerCapture by Custom Element's Shadow DOM</h4>
+      The magenta box below is part of a custom element's Shadow DOM.
+    <ul>
+      <li> Click left mouse button inside the box and keep mouse button depressed</li>
+      <li> Move the mouse</li>
+      <li> There should be a message stating <em>Pointer was captured by custom element's Shadow DOM!</em></li>
+      <li> Release left mouse button</li>
+      <li> There should be a message stating <em>Pointer was released by custom element's Shadow DOM!</em></li>
+    </ul>
+
+    <wc-wc id="wc-wc"></wc-wc>
+    <div id="log"></div>
+    <script>
+      var logDiv = document.getElementById("log");
+      function logMessage(message){
+        var log = document.createElement("div");
+        var messageNode = document.createTextNode(message);
+        log.appendChild(messageNode);
+        logDiv.appendChild(log);
+      }
+      var events = [];
+
+      var content = document.getElementById("wc-wc")
+         .shadowRoot.getElementById("content");
+
+      content.addEventListener("pointerdown", function(e){
+        content.setPointerCapture(e.pointerId);
+        events.push("pointerdown@content");
+      });
+      content.addEventListener("gotpointercapture", function(e){
+        logMessage("Pointer was captured by custom element's Shadow DOM!");
+        events.push("gotpointercapture@content");
+      });
+      content.addEventListener("pointerup", function(e){
+        content.releasePointerCapture(e.pointerId);
+        events.push("pointerup@content");
+      });
+      content.addEventListener("lostpointercapture", function(e){
+        logMessage("Pointer was released by custom element's Shadow DOM!");
+        events.push("lostpointercapture@content");
+        if(window.promise_test && wc_shadow_dom_test){
+          wc_shadow_dom_test.step(function(){
+            assert_array_equals(events, ["pointerdown@content",
+              "gotpointercapture@content", "pointerup@content",
+              "lostpointercapture@content"]);
+            resolve_test();
+            wc_shadow_dom_test.done();
+          });
+        }
+      });
+
+      var wc_shadow_dom_test = null;
+      var resolve_test = null;
+      var reject_test = null;
+
+      function cleanup(){
+        events = [];
+        shadow_dom_test = null;
+        resolve_test = null;
+        reject_test = null;
+      }
+
+      function onLoad(){
+        if(window.promise_test){
+          promise_test(function(t){
+            return new Promise(function(resolve, reject){
+              wc_shadow_dom_test = t;
+              resolve_test = resolve;
+              reject_test = reject;
+              t.add_cleanup(function(){
+                cleanup();
+              });
+              var contentRect = content.getBoundingClientRect();
+              var actions = new test_driver.Actions();
+              var actions_promise = actions
+                  .pointerMove(contentRect.x, contentRect.y)
+                  .pointerDown({button: actions.ButtonType.LEFT})
+                  .pointerUp({button: actions.ButtonType.LEFT})
+                  .send();
+            });
+          }, "PointerCapture works for custom element Shadow DOM.");
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-shadow-dom.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-shadow-dom.html
new file mode 100644
index 0000000..8279665f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_pointercapture-in-shadow-dom.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>PointerCapture for Shadow DOM Elements</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width">
+    <link rel="help" href= "https://bugs.chromium.org/p/chromium/issues/detail?id=810882">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/resources/testdriver.js"></script>
+    <script src="/resources/testdriver-actions.js"></script>
+    <script src="/resources/testdriver-vendor.js"></script>
+  </head>
+  <body onload="onLoad()">
+    <template id="template">
+      <style>
+          #content{
+              height:100px;
+              width:100px;
+              background-color: lightgrey;
+          }
+      </style>
+      <div id="content"></div>
+    </template>
+    <h4>PointerCapture by Shadow DOM element</h4>
+    The light gray box below is part of Shadow DOM.
+    <ul>
+      <li> Click left mouse button inside the box and keep mouse button depressed </li>
+      <li> Move the mouse </li>
+      <li> There should be a message stating <em>Pointer was captured by Shadow DOM!</em></li>
+      <li> Release left mouse button
+      <li> There should be a message stating <em>Pointer was released by Shadow DOM!</em></li>
+    </ul>
+    <div id="shadowhost"></div>
+    <div id="log"></div>
+    <script>
+      var logDiv = document.getElementById("log");
+      function logMessage(message){
+        var log = document.createElement("div");
+        var messageNode = document.createTextNode(message);
+        log.appendChild(messageNode);
+        logDiv.appendChild(log);
+      }
+      var events = [];
+
+      var host = document.getElementById("shadowhost");
+      var shadowRoot = host.attachShadow({mode: "open"});
+      var template = document.getElementById("template");
+      var node = template.content.cloneNode(true);
+      shadowRoot.appendChild(node);
+
+      var content = host.shadowRoot.getElementById("content");
+
+      content.addEventListener("pointerdown", function(e){
+        content.setPointerCapture(e.pointerId);
+        events.push("pointerdown@content");
+      });
+      content.addEventListener("gotpointercapture", function(e){
+        logMessage("Pointer was captured by Shadow DOM!");
+        events.push("gotpointercapture@content");
+      });
+      content.addEventListener("pointerup", function(e){
+        content.releasePointerCapture(e.pointerId);
+        events.push("pointerup@content");
+      });
+      content.addEventListener("lostpointercapture", function(e){
+        logMessage("Pointer was released by Shadow DOM!");
+        events.push("lostpointercapture@content");
+        if(window.promise_test && shadow_dom_test){
+          shadow_dom_test.step(function(){
+            assert_array_equals(events, ["pointerdown@content",
+              "gotpointercapture@content", "pointerup@content",
+              "lostpointercapture@content"]);
+            resolve_test();
+            shadow_dom_test.done();
+          });
+        }
+      });
+
+      var shadow_dom_test = null;
+      var resolve_test = null;
+      var reject_test = null;
+
+      function cleanup(){
+        events = [];
+        shadow_dom_test = null;
+        resolve_test = null;
+        reject_test = null;
+      }
+
+      function onLoad(){
+        if(window.promise_test){
+          promise_test(function(t){
+            return new Promise(function(resolve, reject){
+              shadow_dom_test = t;
+              resolve_test = resolve;
+              reject_test = reject;
+              t.add_cleanup(function(){
+                cleanup();
+              });
+              var contentRect = content.getBoundingClientRect();
+              var actions = new test_driver.Actions();
+              var actions_promise = actions
+                  .pointerMove(contentRect.x, contentRect.y)
+                  .pointerDown({button: actions.ButtonType.LEFT})
+                  .pointerUp({button: actions.ButtonType.LEFT})
+                  .send();
+            });
+          }, "PointerCapture works for Shadow DOM element.");
+        }
+      }
+    </script>
+  </body>
+</html>
diff --git a/third_party/closure_compiler/externs/passwords_private.js b/third_party/closure_compiler/externs/passwords_private.js
index a9d16618..e4e47b6 100644
--- a/third_party/closure_compiler/externs/passwords_private.js
+++ b/third_party/closure_compiler/externs/passwords_private.js
@@ -223,6 +223,12 @@
 chrome.passwordsPrivate.isOptedInForAccountStorage = function(callback) {};
 
 /**
+ * Triggers the opt-in or opt-out flow for the account storage.
+ * @param {boolean} optIn
+ */
+chrome.passwordsPrivate.optInForAccountStorage = function(optIn) {};
+
+/**
  * Requests the latest compromised credentials.
  * @param {function(!Array<!chrome.passwordsPrivate.CompromisedCredential>):void}
  *     callback
diff --git a/third_party/expat/BUILD.gn b/third_party/expat/BUILD.gn
index 6c0c63e..1bddfcb0 100644
--- a/third_party/expat/BUILD.gn
+++ b/third_party/expat/BUILD.gn
@@ -41,11 +41,6 @@
     public_configs = [ ":expat_public_config" ]
     configs += [ ":expat_internal_config" ]
 
-    # TODO(thakis): Remove this once clang no longer crashes when building
-    # libexpat with -Oz.
-    configs -= [ "//build/config/compiler:default_optimization" ]
-    configs += [ "//build/config/compiler:optimize_max" ]
-
     defines = [ "_LIB" ]
     if (is_win) {
       # expat expects to define WIN32_LEAN_AND_MEAN itself
diff --git a/third_party/polymer/v3_0/chromium.patch b/third_party/polymer/v3_0/chromium.patch
index 08847d9..640e4587 100644
--- a/third_party/polymer/v3_0/chromium.patch
+++ b/third_party/polymer/v3_0/chromium.patch
@@ -195,3 +195,42 @@
  
  /**
   * @struct
+diff --git a/components-chromium/paper-tooltip/paper-tooltip.js b/components-chromium/paper-tooltip/paper-tooltip.js
+index 853eee199025..303d1bbdfc78 100644
+--- a/components-chromium/paper-tooltip/paper-tooltip.js
++++ b/components-chromium/paper-tooltip/paper-tooltip.js
+@@ -311,12 +311,16 @@ Polymer({
+ 
+   /**
+    * Returns the target element that this tooltip is anchored to. It is
+-   * either the element given by the `for` attribute, or the immediate parent
+-   * of the tooltip.
++   * either the element given by the `for` attribute, the element manually
++   * specified through the `target` attribute, or the immediate parent of
++   * the tooltip.
+    *
+    * @type {Node}
+    */
+   get target() {
++    if (this._manualTarget)
++      return this._manualTarget;
++
+     var parentNode = dom(this).parentNode;
+     // If the parentNode is a document fragment, then we need to use the host.
+     var ownerRoot = dom(this).getOwnerRoot();
+@@ -331,6 +335,15 @@ Polymer({
+     return target;
+   },
+ 
++  /**
++   * Sets the target element that this tooltip will be anchored to.
++   * @param {Node} target
++   */
++  set target(target) {
++    this._manualTarget = target;
++    this._findTarget();
++  },
++
+   /**
+    * @return {void}
+    */
diff --git a/third_party/polymer/v3_0/components-chromium/paper-tooltip/paper-tooltip.js b/third_party/polymer/v3_0/components-chromium/paper-tooltip/paper-tooltip.js
index 853eee19..303d1bb 100644
--- a/third_party/polymer/v3_0/components-chromium/paper-tooltip/paper-tooltip.js
+++ b/third_party/polymer/v3_0/components-chromium/paper-tooltip/paper-tooltip.js
@@ -311,12 +311,16 @@
 
   /**
    * Returns the target element that this tooltip is anchored to. It is
-   * either the element given by the `for` attribute, or the immediate parent
-   * of the tooltip.
+   * either the element given by the `for` attribute, the element manually
+   * specified through the `target` attribute, or the immediate parent of
+   * the tooltip.
    *
    * @type {Node}
    */
   get target() {
+    if (this._manualTarget)
+      return this._manualTarget;
+
     var parentNode = dom(this).parentNode;
     // If the parentNode is a document fragment, then we need to use the host.
     var ownerRoot = dom(this).getOwnerRoot();
@@ -332,6 +336,15 @@
   },
 
   /**
+   * Sets the target element that this tooltip will be anchored to.
+   * @param {Node} target
+   */
+  set target(target) {
+    this._manualTarget = target;
+    this._findTarget();
+  },
+
+  /**
    * @return {void}
    */
   attached: function() {
diff --git a/third_party/robolectric/BUILD.gn b/third_party/robolectric/BUILD.gn
index 07575f856..1b0c4032 100644
--- a/third_party/robolectric/BUILD.gn
+++ b/third_party/robolectric/BUILD.gn
@@ -413,7 +413,6 @@
     "robolectric/utils/reflector/src/main/java/org/robolectric/util/reflector/WeakerHashMap.java",
     "robolectric/utils/reflector/src/main/java/org/robolectric/util/reflector/WithType.java",
     "robolectric/utils/src/main/java/org/robolectric/AndroidMetadata.java",
-    "robolectric/utils/src/main/java/org/robolectric/AndroidMetadata.java",
     "robolectric/utils/src/main/java/org/robolectric/util/Clock.java",
     "robolectric/utils/src/main/java/org/robolectric/util/Consumer.java",
     "robolectric/utils/src/main/java/org/robolectric/util/Join.java",
@@ -559,6 +558,10 @@
 java_library("shadows_core_java") {
   output_name = "shadows-core-3.2"
 
+  # Disable java headers since this target fails to compile with:
+  # error: An exception occurred in org.robolectric.annotation.processing.RobolectricProcessor:
+  enable_turbine = false
+
   # Skip platform checks since we must depend on accessibility_test_framework_java
   # here which requires_android.
   bypass_platform_checks = true
@@ -1085,6 +1088,11 @@
   # Skip platform checks since we must depend on android_support_multidex_java
   # here which requires_android.
   bypass_platform_checks = true
+
+  # Disable java headers since this target fails to compile with:
+  # error: An exception occurred in org.robolectric.annotation.processing.RobolectricProcessor:
+  enable_turbine = false
+
   skip_jetify = true
   testonly = true
   processor_args_javac = [ "org.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.multidex" ]
@@ -1127,6 +1135,10 @@
   # and google_play_services_library here which both requires_android.
   bypass_platform_checks = true
 
+  # Disable java headers since this target fails to compile with:
+  # error: An exception occurred in org.robolectric.annotation.processing.RobolectricProcessor:
+  enable_turbine = false
+
   testonly = true
   skip_jetify = true
   processor_args_javac = [ "org.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.gms" ]
diff --git a/third_party/robolectric/OWNERS b/third_party/robolectric/OWNERS
index b84d7996..1981c0c 100644
--- a/third_party/robolectric/OWNERS
+++ b/third_party/robolectric/OWNERS
@@ -1,4 +1,6 @@
 agrieve@chromium.org
 bjoyce@chromium.org
 jbudorick@chromium.org
+wnwen@chromium.org
+
 # COMPONENT: Test>Android
diff --git a/tools/android/tracing/systrace-extract-startup.py b/tools/android/tracing/systrace-extract-startup.py
index c45ed08..2d6d6f69 100755
--- a/tools/android/tracing/systrace-extract-startup.py
+++ b/tools/android/tracing/systrace-extract-startup.py
@@ -227,9 +227,9 @@
   NAVIGATION_COMMIT_EVENT_NAME = 'Navigation StartToCommit'
 
   STARTUP_EVENT_NAMES = [
-      'Startup.BrowserMainEntryPoint',
-      'ChromeApplication.onCreate',
-      'ContentShellApplication.onCreate']
+      'Startup.BrowserMainEntryPoint', 'ChromeApplication.onCreate',
+      'ContentShellApplication.onCreate'
+  ]
 
 
 def ParseTrace(file_path):
diff --git a/tools/chrome_proxy/webdriver/bypass.py b/tools/chrome_proxy/webdriver/bypass.py
deleted file mode 100644
index 2c4d752..0000000
--- a/tools/chrome_proxy/webdriver/bypass.py
+++ /dev/null
@@ -1,343 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import ChromeVersionEqualOrAfterM
-
-
-class Bypass(IntegrationTest):
-
-  # Ensure Chrome does not use Data Saver for block-once, but does use Data
-  # Saver for a subsequent request.
-  def testBlockOnce(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/blocksingle/')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        if response.url == "http://check.googlezip.net/image.png":
-          self.assertHasProxyHeaders(response)
-        else:
-          self.assertNotHasChromeProxyViaHeader(response)
-
-  # Ensure Chrome does not use Data Saver for block=0, which uses the default
-  # proxy retry delay.
-  def testBypass(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/block/')
-      for response in t.GetHTTPResponses():
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Load another page and check that Data Saver is not used.
-      t.LoadURL('http://check.googlezip.net/test.html')
-      for response in t.GetHTTPResponses():
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  # Ensure Chrome does not use Data Saver for HTTPS requests.
-  def testHttpsBypass(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Load HTTP page and check that Data Saver is used.
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-      # Load HTTPS page and check that Data Saver is not used.
-      t.LoadURL('https://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  # Verify that CORS requests receive a block-once from the data reduction
-  # proxy by checking that those requests are retried without data reduction
-  # proxy. CORS tests needs to be verified with and without OutOfBlinkCors
-  # feature, since this feature affects sending CORS blocked response headers to
-  # the renderer in different ways.
-  def testCorsBypass(self):
-    self.VerifyCorsTestWithOutOfBlinkCors(True)
-
-  def testCorsBypassWithoutOutOfBlinkCors(self):
-    # Verifies CORS behavior without OutOfBlinkCors feature. This feature is
-    # currently under experimentation and once it is fully enabled this test can
-    # be removed.
-    self.VerifyCorsTestWithOutOfBlinkCors(False)
-
-  def VerifyProxyServesPageWithoutBypass(self, test_driver):
-    drp_responses = 0
-    test_driver.LoadURL('http://check.googlezip.net/test.html')
-    for response in test_driver.GetHTTPResponses():
-      self.assertHasProxyHeaders(response)
-      drp_responses += 1
-    self.assertNotEqual(0, drp_responses)
-    test_driver.SleepUntilHistogramHasEntry('PageLoad.Clients.'
-              'DataReductionProxy.ParseTiming.NavigationToFirstContentfulPaint')
-    self.assertEqual({},
-      test_driver.GetHistogram('DataReductionProxy.BlockTypePrimary'))
-    self.assertEqual({},
-      test_driver.GetHistogram('DataReductionProxy.BlockTypeFallback'))
-    self.assertEqual({},
-      test_driver.GetHistogram('DataReductionProxy.BypassTypePrimary'))
-    self.assertEqual({},
-      test_driver.GetHistogram('DataReductionProxy.BypassTypeFallback'))
-
-  def VerifyCorsTestWithOutOfBlinkCors(self, is_out_of_blink_cors_feature_on):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      if is_out_of_blink_cors_feature_on:
-        test_driver.EnableChromeFeature('OutOfBlinkCors')
-      else:
-        test_driver.DisableChromeFeature('OutOfBlinkCors')
-
-      # The CORS test page makes a cross-origin XHR request to a resource for
-      # which DRP requests to bypass proxy for the current request. This 502
-      # block-once bypass will not be received by the DRP bypass logic in the
-      # renderer if proper response headers (Access-Control-Allow-Origin and
-      # Access-Control-Allow-Headers) are not present.
-      # This test verifies that the bypass logic received one block-once bypass,
-      # and the request is retried without DRP. The 502 bypass response cannot
-      # be verified to contain proper response headers set by the DRP since only
-      # the retried response will be picked up by the webdriver.
-      test_driver.LoadURL('http://www.gstatic.com/chrome/googlezip/cors/')
-
-      test_driver.SleepUntilHistogramHasEntry(
-        'DataReductionProxy.BlockTypePrimary')
-      # Verify that one request received block-once(bucket=0), and no other
-      # bypasses or fallbacks are received. Explicit checks for response headers
-      # content-type=text/plain, Access-Control-Allow-Origin,
-      # Access-Control-Allow-Headers, Via, Chrome-Proxy cannot be added, since
-      # webdriver does not get the headers for 502 response. However, since
-      # BlockTypePrimary is checked for one block-once entry, we know the DRP
-      # bypass logic has picked it up.
-      blocked = test_driver.GetHistogram('DataReductionProxy.BlockTypePrimary')
-      self.assertEqual(1, blocked['count'])
-      self.assertEqual(blocked['buckets'][0]['low'], 0)
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BlockTypeFallback'))
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BypassTypePrimary'))
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BypassTypeFallback'))
-      cors_requests = 0
-      same_origin_requests = 0
-      for response in test_driver.GetHTTPResponses():
-        # The cross-origin XHR request is a CORS request.
-        if response.request_type == 'XHR':
-          self.assertNotHasChromeProxyViaHeader(response)
-          self.assertEqual(200, response.status)
-          cors_requests = cors_requests + 1
-        else:
-          self.assertHasProxyHeaders(response)
-          same_origin_requests = same_origin_requests + 1
-      # Verify that both CORS and same origin requests were seen.
-      self.assertNotEqual(0, same_origin_requests)
-      self.assertNotEqual(0, cors_requests)
-
-      # Navigate to a different page to verify that later requests are not
-      # blocked.
-      self.VerifyProxyServesPageWithoutBypass(test_driver)
-
-  # Tests that data reduction proxy bypasses are not blocked by CORB. Since the
-  # bypass/fallback handling is in the renderer process, CORB failures will skip
-  # the bypasses/fallbacks and the resource will not be retried without data
-  # reduction proxy.
-  def testBypassNotBlockedByCorb(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # The CORB test page loads an <img> to a cross-origin resource for which
-      # DRP requests to bypass proxy for the current request. This 502
-      # block-once bypass will not be received by the DRP bypass logic in the
-      # renderer, if CORB blocks it based on mislabeled content-type or the
-      # actual type observed from sniffing the body.
-      test_driver.LoadURL('http://www.gstatic.com/chrome/googlezip/corb.html')
-      drp_responses = 0
-      for response in test_driver.GetHTTPResponses():
-          if response.ResponseHasViaHeader():
-            self.assertHasProxyHeaders(response)
-            drp_responses += 1
-      self.assertNotEqual(0, drp_responses)
-      test_driver.SleepUntilHistogramHasEntry(
-        'DataReductionProxy.BlockTypePrimary')
-      # Verify that one request received block-once (bucket=0), and no other
-      # bypasses or fallbacks are received.
-      blocked = test_driver.GetHistogram('DataReductionProxy.BlockTypePrimary')
-      self.assertEqual(1, blocked['count'])
-      self.assertEqual(blocked['buckets'][0]['low'], 0)
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BlockTypeFallback'))
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BypassTypePrimary'))
-      self.assertEqual({},
-        test_driver.GetHistogram('DataReductionProxy.BypassTypeFallback'))
-
-      # Navigate to a different page to verify that later requests are not
-      # blocked.
-      self.VerifyProxyServesPageWithoutBypass(test_driver)
-
-  # Verify that when an origin times out using Data Saver, the request is
-  # fetched directly and data saver is bypassed only for one request.
-  def testOriginTimeoutBlockOnce(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Load URL that times out when the proxy server tries to access it.
-      test_driver.LoadURL('http://chromeproxy-test.appspot.com/blackhole')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-          self.assertNotHasChromeProxyViaHeader(response)
-
-      # Load HTTP page and check that Data Saver is used.
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-  # Verify that Chrome does not bypass the proxy when a response gets a missing
-  # via header.
-  @ChromeVersionEqualOrAfterM(67)
-  def testMissingViaHeaderNoBypassExperiment(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.EnableChromeFeature('DataReductionProxyRobustConnection'
-        '<DataReductionProxyRobustConnection')
-      t.AddChromeArg('--force-fieldtrials=DataReductionProxyRobustConnection/'
-        'Enabled')
-      t.AddChromeArg('--force-fieldtrial-params='
-        'DataReductionProxyRobustConnection.Enabled:'
-        'warmup_fetch_callback_enabled/true/'
-        'bypass_missing_via_disabled/true')
-      t.AddChromeArg('--disable-data-reduction-proxy-warmup-url-fetch')
-      t.AddChromeArg('--data-reduction-proxy-http-proxies='
-        # The chromeproxy-test server is a simple HTTP server. If it is served a
-        # proxy-request, it will respond with a 404 error page. It will not set
-        # the Via header on the response.
-        'https://chromeproxy-test.appspot.com;http://compress.googlezip.net')
-
-      # Loading this URL should not hit the actual check.googlezip.net origin.
-      # Instead, the test server proxy should fully handle the request and will
-      # respond with an error page.
-      t.LoadURL("http://check.googlezip.net/test.html")
-      for response in t.GetHTTPResponses():
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Check the via bypass histograms are empty.
-      histogram = t.GetHistogram(
-        'DataReductionProxy.BypassedBytes.MissingViaHeader4xx')
-      self.assertEqual(0, len(histogram))
-      histogram = t.GetHistogram(
-        'DataReductionProxy.BypassedBytes.MissingViaHeaderOther')
-      self.assertEqual(0, len(histogram))
-
-      # Check that the fetch used the proxy.
-      histogram = t.GetHistogram('DataReductionProxy.ProxySchemeUsed')
-      self.assertEqual(histogram['buckets'][0]['low'], 2)
-      self.assertEqual(histogram['buckets'][0]['high'], 3)
-
-  # Verify that the Data Reduction Proxy understands the "exp" directive.
-  def testExpDirectiveBypass(self):
-    # If it was attempted to run with another experiment, skip this test.
-    if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
-        in common.ParseFlags().browser_args):
-      self.skipTest('This test cannot be run with other experiments.')
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.SetExperiment('client_test_bypass')
-
-      # Verify that loading a page other than the specific exp directive test
-      # page loads through the proxy without being bypassed.
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-      # Verify that loading the exp directive test page with
-      # "exp=client_test_bypass" triggers a bypass.
-      test_driver.LoadURL('http://check.googlezip.net/exp/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-    # Verify that loading the same test page without setting
-    #{ }"exp=client_test_bypass" loads through the proxy without being bypassed.
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      test_driver.LoadURL('http://check.googlezip.net/exp/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-  # Data Saver uses a HTTPS proxy by default, if that fails it will fall back to
-  # a HTTP proxy.
-  def testBadHTTPSFallback(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.AddChromeArg('--data-reduction-proxy-http-proxies='
-                               'http://compress.googlezip.net')
-
-      test_driver.LoadURL('http://check.googlezip.net/fallback/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertEqual(80, response.port)
-
-  # Get the client type with the first request, then check bypass on the
-  # appropriate test page
-  def testClientTypeBypass(self):
-    clientType = ''
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      # Page that should not bypass.
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-        if 'chrome-proxy' in response.request_headers:
-            chrome_proxy_header = response.request_headers['chrome-proxy']
-            chrome_proxy_directives = chrome_proxy_header.split(',')
-            for directive in chrome_proxy_directives:
-                if 'c=' in directive:
-                    clientType = directive[3:]
-    self.assertTrue(clientType)
-
-    clients = ['android', 'webview', 'ios', 'linux', 'win', 'chromeos']
-    for client in clients:
-      with TestDriver() as test_driver:
-        test_driver.LoadURL('http://check.googlezip.net/chrome-proxy-header/'
-                          'c_%s/' %client)
-        responses = test_driver.GetHTTPResponses()
-        self.assertEqual(2, len(responses))
-        for response in responses:
-          if client in clientType:
-            self.assertNotHasChromeProxyViaHeader(response)
-
-  def testHTTPSubresourcesOnHTTPSPage(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.LoadURL(
-        'https://check.googlezip.net/previews/mixed_images.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertEqual(3, len(responses))
-      for response in responses:
-        if response.url.startswith('http://'):
-          self.assertHasProxyHeaders(response)
-        elif response.url.startswith('https://'):
-          self.assertNotHasChromeProxyViaHeader(response)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/client_config.py b/tools/chrome_proxy/webdriver/client_config.py
deleted file mode 100644
index 048c67e3..0000000
--- a/tools/chrome_proxy/webdriver/client_config.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import ChromeVersionEqualOrAfterM
-from decorators import SkipIfForcedBrowserArg
-import json
-
-
-class ClientConfig(IntegrationTest):
-  # Ensure client config is fetched at the start of the Chrome session, and the
-  # session ID is correctly set in the chrome-proxy request header.
-  def testClientConfig(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.SleepUntilHistogramHasEntry(
-        'DataReductionProxy.ConfigService.FetchResponseCode')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        # Verify that the proxy server honored the session ID.
-        self.assertHasProxyHeaders(response)
-        self.assertEqual(200, response.status)
-
-  # Ensure Chrome uses a direct connection when no valid client config is given.
-  @SkipIfForcedBrowserArg('data-reduction-proxy-config-url')
-  def testNoClientConfigUseDirect(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      # The test server won't respond with a valid client config.
-      t.UseNetLog()
-      t.AddChromeArg('--data-reduction-proxy-config-url='
-        'https://chromeproxy-test.appspot.com')
-      t.SleepUntilHistogramHasEntry(
-        'DataReductionProxy.ConfigService.FetchResponseCode')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  # Ensure client config is fetched at the start of the Chrome session, and the
-  # variations ID is set in the request.
-  # Disabled on android because the net log is not copied yet. crbug.com/761507
-  @ChromeVersionEqualOrAfterM(62)
-  def testClientConfigVariationsHeader(self):
-    with TestDriver() as t:
-      t.UseNetLog()
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      # Force set the variations ID, so they are send along with the client
-      # config fetch request.
-      t.AddChromeArg('--force-variation-ids=42')
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-
-      variation_header_count = 0
-
-      # Look for the request made to data saver client config server.
-      data = t.StopAndGetNetLog()
-      for i in data["events"]:
-        dumped_event = json.dumps(i)
-        if dumped_event.find("datasaver.") !=-1 and\
-          dumped_event.find(".googleapis.com") !=-1 and\
-          dumped_event.find("clientConfigs") != -1 and\
-          dumped_event.find("headers") != -1 and\
-          dumped_event.find("accept-encoding") != -1 and\
-          dumped_event.find("x-client-data") !=-1:
-            variation_header_count = variation_header_count + 1
-
-      # Variation IDs are set. x-client-data should be present in the request
-      # headers.
-      self.assertLessEqual(1, variation_header_count)
-
-  # Ensure client config is fetched at the start of the Chrome session, and the
-  # variations ID is not set in the request.
-  # Disabled on android because the net log is not copied yet. crbug.com/761507
-  @ChromeVersionEqualOrAfterM(62)
-  def testClientConfigNoVariationsHeader(self):
-    with TestDriver() as t:
-      t.UseNetLog()
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-
-      variation_header_count = 0
-
-      # Look for the request made to data saver client config server.
-      data = t.StopAndGetNetLog()
-      for i in data["events"]:
-        dumped_event = json.dumps(i)
-        if dumped_event.find("datasaver.") !=-1 and\
-          dumped_event.find(".googleapis.com") !=-1 and\
-          dumped_event.find("clientConfigs") != -1 and\
-          dumped_event.find("headers") != -1 and\
-          dumped_event.find("accept-encoding") != -1 and\
-          dumped_event.find("x-client-data") !=-1:
-            variation_header_count = variation_header_count + 1
-
-      # Variation IDs are not set. x-client-data should not be present in the
-      # request headers.
-      self.assertEqual(0, variation_header_count)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/fallback.py b/tools/chrome_proxy/webdriver/fallback.py
deleted file mode 100644
index cf61ae51..0000000
--- a/tools/chrome_proxy/webdriver/fallback.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-
-class Fallback(IntegrationTest):
-
-  # Ensure that when a carrier blocks using the secure proxy, requests fallback
-  # to the HTTP proxy server.
-  def testSecureProxyProbeFallback(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Set the secure proxy check URL to the google.com favicon, which will be
-      # interpreted as a secure proxy check failure since the response body is
-      # not "OK". The google.com favicon is used because it will load reliably
-      # fast, and there have been problems with chromeproxy-test.appspot.com
-      # being slow and causing tests to flake.
-      test_driver.AddChromeArg(
-          '--data-reduction-proxy-secure-proxy-check-url='
-          'http://www.google.com/favicon.ico')
-
-      # Start chrome to begin the secure proxy check
-      test_driver.LoadURL('about:blank')
-
-      self.assertTrue(
-        test_driver.SleepUntilHistogramHasEntry("DataReductionProxy.ProbeURL"))
-
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      # Verify that DataReductionProxy.ProbeURL histogram has one entry in
-      # FAILED_PROXY_DISABLED, which is bucket=1.
-      histogram = test_driver.GetBrowserHistogram('DataReductionProxy.ProbeURL')
-      self.assertGreaterEqual(histogram['count'], 1)
-      self.assertGreaterEqual(histogram['buckets'][0]['low'], 1)
-      for response in responses:
-          self.assertHasProxyHeaders(response)
-          # TODO(rajendrant): Fix the correct protocol received.
-          # self.assertEqual(u'http/2+quic/43', response.protocol)
-
-  # DataSaver uses a https proxy by default, if that fails it will fall back to
-  # a http proxy; and if that fails, it will fall back to a direct connection
-  def testHTTPToDirectFallback(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.AddChromeArg('--data-reduction-proxy-http-proxies='
-                               'http://nonexistent.googlezip.net;'
-                               'http://compress.googlezip.net')
-
-      test_driver.LoadURL('http://check.googlezip.net/fallback/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertEqual(80, response.port)
-
-      test_driver.LoadURL('http://check.googlezip.net/block/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/html5.py b/tools/chrome_proxy/webdriver/html5.py
deleted file mode 100644
index 36fdf32..0000000
--- a/tools/chrome_proxy/webdriver/html5.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-
-
-class HTML5(IntegrationTest):
-  # This test site has a div with id="pointsPanel" that is rendered if the
-  # browser is capable of using HTML5.
-  def testHTML5(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://html5test.com/')
-      t.WaitForJavascriptExpression(
-        'document.getElementsByClassName("pointsPanel")', 15)
-      checked_main_page = False
-      for response in t.GetHTTPResponses():
-        # Site has a lot on it, just check the main page.
-        if (response.url == 'http://html5test.com/'
-            or response.url == 'http://html5test.com/index.html'):
-          self.assertHasProxyHeaders(response)
-          checked_main_page = True
-      if not checked_main_page:
-        self.fail("Did not check any page!")
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/lite_page.py b/tools/chrome_proxy/webdriver/lite_page.py
deleted file mode 100644
index 4fb3c123..0000000
--- a/tools/chrome_proxy/webdriver/lite_page.py
+++ /dev/null
@@ -1,286 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import ParseFlags
-from common import TestDriver
-from common import IntegrationTest
-from decorators import AndroidOnly
-from decorators import ChromeVersionBeforeM
-from decorators import ChromeVersionEqualOrAfterM
-from urlparse import urlparse
-
-import time
-
-# These are integration tests for server provided previews and the
-# protocol that supports them.
-class LitePage(IntegrationTest):
-
-  # Verifies that a Lite Page is served for slow connection if any copyright
-  # restricted country blacklist is ignored.
-  # Note: this test is for the CPAT protocol change in M-61.
-  @ChromeVersionEqualOrAfterM(61)
-  def testLitePageWithoutCopyrightRestriction(self):
-    # If it was attempted to run with another experiment, skip this test.
-    if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
-        in common.ParseFlags().browser_args):
-      self.skipTest('This test cannot be run with other experiments.')
-    with TestDriver() as test_driver:
-      test_driver.EnableChromeFeature('Previews')
-      test_driver.EnableChromeFeature('DataReductionProxyDecidesTransform')
-      test_driver.SetExperiment('ignore_preview_blacklist')
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.AddChromeArg('--force-effective-connection-type=2G')
-
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-
-      lite_page_responses = 0
-      checked_chrome_proxy_header = False
-      for response in test_driver.GetHTTPResponses():
-        if response.request_headers:
-          # Verify client sends ignore directive on main frame request.
-          self.assertIn('exp=ignore_preview_blacklist',
-            response.request_headers['chrome-proxy'])
-          self.assertEqual('2G', response.request_headers['chrome-proxy-ect'])
-          checked_chrome_proxy_header = True
-        if response.url.endswith('html'):
-          self.assertTrue(self.checkLitePageResponse(response))
-          lite_page_responses = lite_page_responses + 1
-          # Expect no fallback page policy
-          if 'chrome-proxy' in response.response_headers:
-            self.assertNotIn('page-policies',
-                             response.response_headers['chrome-proxy'])
-        else:
-          # No subresources should accept transforms.
-          self.assertNotIn('chrome-proxy-accept-transform',
-            response.request_headers)
-      self.assertTrue(checked_chrome_proxy_header)
-
-      # Verify that a Lite Page response for the main frame was seen.
-      self.assertEqual(1, lite_page_responses)
-
-      self.assertPreviewShownViaHistogram(test_driver, 'LitePage')
-
-  # Checks that a Nano Lite Page does not have an error when scrolling to the
-  # bottom of the page and is able to load all resources. Nano pages don't
-  # request additional resources when scrolling. This test is only run on
-  # Android because it depends on window size of the browser.
-  @AndroidOnly
-  @ChromeVersionEqualOrAfterM(65)
-  def testLitePageNano(self):
-    # If it was attempted to run with another experiment, skip this test.
-    if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
-        in common.ParseFlags().browser_args):
-      self.skipTest('This test cannot be run with other experiments.')
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.EnableChromeFeature('Previews')
-      test_driver.EnableChromeFeature('DataReductionProxyDecidesTransform')
-      # Need to force 2G speed to get lite-page response.
-      test_driver.AddChromeArg('--force-effective-connection-type=2G')
-      # Set exp=client_test_nano to force Nano response.
-      test_driver.SetExperiment('client_test_nano')
-
-      # This page is long and has many media resources.
-      test_driver.LoadURL('http://check.googlezip.net/metrics/index.html')
-      time.sleep(2)
-
-      lite_page_responses = 0
-      btf_response = 0
-      image_responses = 0
-      for response in test_driver.GetHTTPResponses():
-        # Verify that a Lite Page response for the main frame was seen.
-        if response.url.endswith('html'):
-          if (self.checkLitePageResponse(response)):
-             lite_page_responses = lite_page_responses + 1
-        # Keep track of BTF responses.
-        u = urlparse(response.url)
-        if u.path == "/b":
-          btf_response = btf_response + 1
-        # Keep track of image responses.
-        if response.url.startswith("data:image"):
-          image_responses = image_responses + 1
-        # Some video requests don't go through Flywheel.
-        if 'content-type' in response.response_headers and ('video/mp4'
-            in response.response_headers['content-type']):
-          continue
-        # Make sure non-video requests are proxied.
-        self.assertHasProxyHeaders(response)
-        # Make sure there are no 4XX or 5xx status codes.
-        self.assertLess(response.status, 400)
-
-      self.assertEqual(1, lite_page_responses)
-      self.assertEqual(1, btf_response)
-      self.assertGreater(1, image_responses)
-
-  # Tests that the stale previews UI is shown on a stale Lite page.
-  # The stale timestamp histogram is not logged with the new UI unless the page
-  # info dialog is opened which can't be done in a ChromeDriver test.
-  @AndroidOnly
-  @ChromeVersionEqualOrAfterM(65)
-  @ChromeVersionBeforeM(76)
-  def testStaleLitePageNano(self):
-    # If it was attempted to run with another experiment, skip this test.
-    if common.ParseFlags().browser_args and ('--data-reduction-proxy-experiment'
-        in common.ParseFlags().browser_args):
-      self.skipTest('This test cannot be run with other experiments.')
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.EnableChromeFeature('Previews')
-      test_driver.EnableChromeFeature('DataReductionProxyDecidesTransform')
-      test_driver.DisableChromeFeature('AndroidOmniboxPreviewsBadge')
-      test_driver.AddChromeArg('--force-effective-connection-type=2G')
-      # Set exp=client_test_nano to force Lite page response.
-      test_driver.SetExperiment('client_test_nano')
-      # LoadURL waits for onLoadFinish so the Previews UI will be showing by
-      # then since it's triggered on commit.
-      test_driver.LoadURL(
-        'http://check.googlezip.net/cacheable/test.html?age_seconds=360')
-
-      test_driver.SleepUntilHistogramHasEntry(
-        'Previews.StalePreviewTimestampShown')
-      histogram = test_driver.GetBrowserHistogram(
-        'Previews.StalePreviewTimestampShown')
-      self.assertEqual(1, histogram['count'])
-      # Check that there is a single entry in the 'Timestamp Shown' bucket.
-      self.assertEqual(
-        {'count': 1, 'high': 1, 'low': 0},
-        histogram['buckets'][0])
-
-      # Go to a non stale page and check that the stale timestamp is not shown.
-      test_driver.LoadURL(
-        'http://check.googlezip.net/cacheable/test.html?age_seconds=0')
-
-      histogram = test_driver.GetBrowserHistogram(
-        'Previews.StalePreviewTimestampShown')
-      # Check that there is still a single entry in the 'Timestamp Shown'
-      # bucket.
-      self.assertEqual(
-        {'count': 1, 'high': 1, 'low': 0},
-        histogram['buckets'][0])
-
-  # Verifies Lo-Fi fallback via the page-policies server directive.
-  # Note: this test is for the CPAT protocol change in M-61.
-  @ChromeVersionEqualOrAfterM(61)
-  @ChromeVersionBeforeM(74)
-  def testLitePageFallbackViaPagePolicies(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.EnableChromeFeature(
-        'NetworkQualityEstimator<NetworkQualityEstimator')
-      test_driver.EnableChromeFeature('Previews')
-      test_driver.EnableChromeFeature('DataReductionProxyDecidesTransform')
-      test_driver.AddChromeArg('--force-fieldtrial-params='
-                               'NetworkQualityEstimator.Enabled:'
-                               'force_effective_connection_type/Slow2G')
-      test_driver.AddChromeArg('--force-fieldtrials='
-                               'NetworkQualityEstimator/Enabled/')
-
-      test_driver.LoadURL('http://check.googlezip.net/lite-page-fallback')
-
-      lite_page_responses = 0
-      lofi_resource = 0
-      for response in test_driver.GetHTTPResponses():
-        self.assertEqual('Slow-2G',
-                         response.request_headers['chrome-proxy-ect'])
-
-        if response.url.endswith('html'):
-          # Verify that the server provides the fallback directive
-          self.assertIn('page-policies=empty-image',
-                        response.response_headers['chrome-proxy'])
-          # Main resource should not accept and transform to lite page.
-          if self.checkLitePageResponse(response):
-            lite_page_responses = lite_page_responses + 1
-        if response.url.endswith('png'):
-          if self.checkLoFiResponse(response, True):
-            lofi_resource = lofi_resource + 1
-
-      self.assertEqual(0, lite_page_responses)
-      self.assertNotEqual(0, lofi_resource)
-
-  # Checks that the server does not provide a preview (neither Lite Page nor
-  # fallback to LoFi) for a fast connection.
-  # Note: this test is for the CPAT protocol change in M-61.
-  @ChromeVersionEqualOrAfterM(61)
-  def testPreviewNotProvidedForFastConnection(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.EnableChromeFeature(
-        'NetworkQualityEstimator<NetworkQualityEstimator')
-      test_driver.EnableChromeFeature('NetworkService')
-      test_driver.EnableChromeFeature('Previews')
-      test_driver.EnableChromeFeature('DataReductionProxyDecidesTransform')
-      test_driver.AddChromeArg('--force-fieldtrial-params='
-                               'NetworkQualityEstimator.Enabled:'
-                               'force_effective_connection_type/4G')
-      test_driver.AddChromeArg(
-          '--force-fieldtrials='
-          'NetworkQualityEstimator/Enabled/'
-          'DataReductionProxyPreviewsBlackListTransition/Enabled/')
-
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-
-      checked_chrome_proxy_header = False
-      for response in test_driver.GetHTTPResponses():
-        if response.request_headers:
-          self.assertEqual('4G', response.request_headers['chrome-proxy-ect'])
-          checked_chrome_proxy_header = True
-        if response.url.endswith('html'):
-          # Main resource should accept lite page but not be transformed.
-          self.assertEqual('lite-page',
-            response.request_headers['chrome-proxy-accept-transform'])
-          self.assertNotIn('chrome-proxy-content-transform',
-            response.response_headers)
-          # Expect no fallback page policy
-          if 'chrome-proxy' in response.response_headers:
-            self.assertNotIn('page-policies',
-                             response.response_headers['chrome-proxy'])
-        else:
-          # No subresources should accept transforms.
-          self.assertNotIn('chrome-proxy-accept-transform',
-            response.request_headers)
-
-      self.assertPreviewNotShownViaHistogram(test_driver, 'LoFi')
-      self.assertPreviewNotShownViaHistogram(test_driver, 'LitePage')
-      self.assertTrue(checked_chrome_proxy_header)
-
-  # Checks the default of whether server previews are enabled or not
-  # based on whether running on Android (enabled) or not (disabled).
-  # This is a regression test that the DataReductionProxyDecidesTransform
-  # Feature is not enabled by default for non-Android platforms.
-  @ChromeVersionEqualOrAfterM(64)
-  def testDataReductionProxyDecidesTransformDefault(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-      test_driver.EnableChromeFeature(
-        'NetworkQualityEstimator<NetworkQualityEstimator')
-      test_driver.AddChromeArg('--force-fieldtrial-params='
-                               'NetworkQualityEstimator.Enabled:'
-                               'force_effective_connection_type/2G')
-      test_driver.AddChromeArg(
-          '--force-fieldtrials='
-          'NetworkQualityEstimator/Enabled/'
-          'DataReductionProxyPreviewsBlackListTransition/Enabled/')
-
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-
-      for response in test_driver.GetHTTPResponses():
-        if not response.request_headers:
-          continue
-        self.assertEqual('2G', response.request_headers['chrome-proxy-ect'])
-        if response.url.endswith('html'):
-          if ParseFlags().android:
-            # CPAT provided on Android
-            self.assertIn('chrome-proxy-accept-transform',
-              response.request_headers)
-          else:
-            # CPAT NOT provided on Desktop
-            self.assertNotIn('chrome-proxy-accept-transform',
-              response.request_headers)
-            self.assertNotIn('chrome-proxy-content-transform',
-              response.response_headers)
-          continue
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/proxy_connection.py b/tools/chrome_proxy/webdriver/proxy_connection.py
deleted file mode 100644
index d9f8fd0c..0000000
--- a/tools/chrome_proxy/webdriver/proxy_connection.py
+++ /dev/null
@@ -1,143 +0,0 @@
- # Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import ChromeVersionBetweenInclusiveM
-from decorators import ChromeVersionEqualOrAfterM
-from emulation_server import BlackHoleHandler
-from emulation_server import InvalidTLSHandler
-from emulation_server import TCPResetHandler
-from emulation_server import TLSResetHandler
-
-class ProxyConnection(IntegrationTest):
-
-  def VerifyWarmupHistogram(self, test_driver, is_secure_proxy):
-    is_histogram_found = False
-    for histogram_part in ['Core', 'NonCore']:
-      histogram_name = 'DataReductionProxy.WarmupURLFetcherCallback.' + \
-      'SuccessfulFetch.%s.%s' % (
-          'SecureProxy' if is_secure_proxy else 'InsecureProxy',
-        histogram_part)
-      histogram = test_driver.GetBrowserHistogram(histogram_name)
-      if histogram:
-        self.assertLessEqual(1, histogram['count'])
-        is_histogram_found = True
-    self.assertTrue(is_histogram_found)
-
-  @ChromeVersionEqualOrAfterM(63)
-  def testTLSInjectionAfterHandshake(self):
-    port = common.GetOpenPort()
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      # The server should be 127.0.0.1, not localhost because the two are
-      # treated differently in Chrome internals. Using localhost invalidates the
-      # test.
-      t.AddChromeArg(
-        '--data-reduction-proxy-http-proxies=https://127.0.0.1:%d' % port)
-      t.AddChromeArg(
-        '--force-fieldtrials=DataReductionProxyConfigService/Disabled')
-      t.UseEmulationServer(InvalidTLSHandler, port=port)
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      # Expect responses with a bypass on a bad proxy. If the test failed, the
-      # next assertion will fail because there will be no responses.
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-      self.assertTrue(
-        t.SleepUntilHistogramHasEntry('DataReductionProxy.WarmupURL.NetError'))
-      self.VerifyWarmupHistogram(t, True)
-
-  @ChromeVersionEqualOrAfterM(74)
-  def testTCPReset(self):
-    port = common.GetOpenPort()
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      # The server should be 127.0.0.1, not localhost because the two are
-      # treated differently in Chrome internals. Using localhost invalidates the
-      # test.
-      t.UseNetLog()
-      t.AddChromeArg(
-        '--data-reduction-proxy-http-proxies=http://127.0.0.1:%d' % port)
-      t.AddChromeArg(
-        '--force-fieldtrials=DataReductionProxyConfigService/Disabled')
-      t.UseEmulationServer(TCPResetHandler, port=port)
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      # Expect responses with a bypass on a bad proxy. If the test failed, the
-      # next assertion will fail because there will be no responses.
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-      self.assertTrue(
-        t.SleepUntilHistogramHasEntry('DataReductionProxy.WarmupURL.NetError',
-          sleep_intervals=10))
-      self.VerifyWarmupHistogram(t, False)
-
-  @ChromeVersionEqualOrAfterM(63)
-  def testTLSReset(self):
-    port = common.GetOpenPort()
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--allow-insecure-localhost')
-      # The server should be 127.0.0.1, not localhost because the two are
-      # treated differently in Chrome internals. Using localhost invalidates the
-      # test.
-      t.AddChromeArg(
-        '--data-reduction-proxy-http-proxies=https://127.0.0.1:%d' % port)
-      t.AddChromeArg(
-        '--force-fieldtrials=DataReductionProxyConfigService/Disabled')
-      t.UseEmulationServer(TLSResetHandler, port=port)
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      # Expect responses with a bypass on a bad proxy. If the test failed, the
-      # next assertion will fail because there will be no responses.
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  @ChromeVersionEqualOrAfterM(74)
-  def testTCPBlackhole(self):
-    port = common.GetOpenPort()
-    with TestDriver() as t:
-      t.UseNetLog()
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.EnableChromeFeature(
-        'DataReductionProxyRobustConnection<DataReductionProxyRobustConnection')
-      t.AddChromeArg('--force-fieldtrials='
-        'DataReductionProxyRobustConnection/Enabled')
-      t.AddChromeArg('--force-fieldtrial-params='
-        'DataReductionProxyRobustConnection.Enabled:'
-        'warmup_fetch_callback_enabled/true')
-      t.AddChromeArg('--force-effective-connection-type=4G')
-      # The server should be 127.0.0.1, not localhost because the two are
-      # treated differently in Chrome internals. Using localhost invalidates the
-      # test.
-      t.AddChromeArg(
-        '--data-reduction-proxy-http-proxies=http://127.0.0.1:%d' % port)
-
-      t.UseEmulationServer(BlackHoleHandler, port=port)
-      # Start Chrome and wait for the warmup fetcher timeout (30 seconds).
-      t.LoadURL('data:,')
-      self.assertTrue(
-        t.SleepUntilHistogramHasEntry('DataReductionProxy.WarmupURL.NetError',
-          sleep_intervals=40))
-
-      # Check the WarmupURL Callback was called.
-      self.VerifyWarmupHistogram(t, False)
-
-      # Verify DRP was not used.
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/quic.py b/tools/chrome_proxy/webdriver/quic.py
deleted file mode 100644
index e458d69..0000000
--- a/tools/chrome_proxy/webdriver/quic.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import time
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import ChromeVersionEqualOrAfterM
-
-
-class Quic(IntegrationTest):
-
-  # Ensure Chrome uses DataSaver when QUIC is enabled. This test should pass
-  # even if QUIC is disabled on the server side. In that case, Chrome should
-  # fallback to using the non-QUIC proxies.
-  def testCheckPageWithQuicProxy(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Enable QUIC (including for non-core HTTPS proxies).
-      t.AddChromeArg('--enable-quic')
-      t.AddChromeArg('--force-fieldtrials=DataReductionProxyUseQuic/Enabled')
-      t.AddChromeArg('--force-fieldtrial-params='
-        'DataReductionProxyUseQuic.Enabled:enable_quic_non_core_proxies/true')
-      # Enable usage of QUIC for non-core proxies via switch for older versions
-      # of Chrome (M-59 and prior).
-      t.AddChromeArg('--data-reduction-proxy-enable-quic-on-non-core-proxies')
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-  # Ensure Chrome uses QUIC DataSaver proxy when QUIC is enabled. This test
-  # may fail if QUIC is disabled on the server side.
-  @ChromeVersionEqualOrAfterM(76)
-  def testCheckPageWithQuicProxyTransaction(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Enable QUIC (including for non-core HTTPS proxies).
-      t.AddChromeArg('--enable-quic')
-      t.AddChromeArg('--force-fieldtrials=DataReductionProxyUseQuic/Enabled')
-      t.AddChromeArg('--force-fieldtrial-params='
-        'DataReductionProxyUseQuic.Enabled:enable_quic_non_core_proxies/true')
-      # Enable usage of QUIC for non-core proxies via switch for older versions
-      # of Chrome (M-59 and prior).
-      t.AddChromeArg('--data-reduction-proxy-enable-quic-on-non-core-proxies')
-
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-      t.SleepUntilHistogramHasEntry('PageLoad.Clients.DataReductionProxy.'
-        'ParseTiming.NavigationToParseStart')
-
-      # Verify that histogram DataReductionProxy.Quic.ProxyStatus has at least 1
-      # sample. This sample must be in bucket 0 (QUIC_PROXY_STATUS_AVAILABLE).
-      proxy_status = t.GetHistogram('DataReductionProxy.Quic.ProxyStatus')
-      self.assertLessEqual(1, proxy_status['count'])
-      self.assertEqual(0, proxy_status['sum'])
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/reenable_after_bypass.py b/tools/chrome_proxy/webdriver/reenable_after_bypass.py
deleted file mode 100644
index 3d0ade8..0000000
--- a/tools/chrome_proxy/webdriver/reenable_after_bypass.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import time
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import Slow
-
-class ReenableAfterBypass(IntegrationTest):
-  """Tests for ensuring that DRPs are reenabled after bypasses expire.
-
-  These tests take a very long time to run since they wait for their respective
-  bypasses to expire. These tests have been separated out into their own file in
-  order to make it easier to run these tests separately from the others.
-  """
-
-  # Verify that longer bypasses triggered by the Data Reduction Proxy only last
-  # as long as they're supposed to, and that the proxy is used once again after
-  # the bypass has ended.
-  @Slow
-  def testReenableAfterSetBypass(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Load URL that triggers a 20-second bypass of all proxies.
-      test_driver.LoadURL('http://check.googlezip.net/block20/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Verify that the Data Reduction Proxy is still bypassed.
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Verify that the Data Reduction Proxy is no longer bypassed after 20
-      # seconds.
-      time.sleep(20)
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-  # Verify that when the Data Reduction Proxy responds with the "block=0"
-  # directive, Chrome bypasses all proxies for the next 1-5 minutes.
-  @Slow
-  def testReenableAfterBypass(self):
-    with TestDriver() as test_driver:
-      test_driver.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Load URL that triggers a bypass of all proxies that lasts between 1 and
-      # 5 minutes.
-      test_driver.LoadURL('http://check.googlezip.net/block/')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Verify that the Data Reduction Proxy is still bypassed after 30 seconds.
-      time.sleep(30)
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-      # Verify that the Data Reduction Proxy is no longer bypassed 5 minutes
-      # after the original bypass was triggered.
-      time.sleep(60 * 4 + 30)
-      test_driver.LoadURL('http://check.googlezip.net/test.html')
-      responses = test_driver.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/safebrowsing.py b/tools/chrome_proxy/webdriver/safebrowsing.py
deleted file mode 100644
index 730b619..0000000
--- a/tools/chrome_proxy/webdriver/safebrowsing.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from decorators import AndroidOnly
-from decorators import NotAndroid
-from decorators import ChromeVersionBeforeM
-from decorators import ChromeVersionEqualOrAfterM
-
-from selenium.common.exceptions import TimeoutException
-
-class SafeBrowsing(IntegrationTest):
-
-  @AndroidOnly
-  @ChromeVersionBeforeM(73)
-  def testSafeBrowsingOn(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Starting in M63 LoadURL will timeout when the safebrowsing
-      # interstitial appears.
-      try:
-        t.LoadURL('http://testsafebrowsing.appspot.com/s/malware.html')
-        responses = t.GetHTTPResponses()
-        self.assertEqual(0, len(responses))
-      except TimeoutException:
-        pass
-
-  @AndroidOnly
-  @ChromeVersionEqualOrAfterM(74)
-  def testSafeBrowsingMalwareWithOnDeviceChecksOn(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-
-      # Starting in M63 LoadURL will timeout when the safebrowsing
-      # interstitial appears.
-      try:
-        t.LoadURL('http://testsafebrowsing.appspot.com/s/malware.html')
-        responses = t.GetHTTPResponses()
-        self.assertEqual(0, len(responses))
-      except TimeoutException:
-        # Verify that on device safebrowsing records unsafe for mainframe
-        # request at bucket=0
-        unsafe_resources = t.GetBrowserHistogram('SB2.ResourceTypes2.Unsafe')
-        self.assertEqual(1, unsafe_resources['count'])
-        self.assertEqual(1, unsafe_resources['buckets'][0]['count'])
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/smoke.py b/tools/chrome_proxy/webdriver/smoke.py
deleted file mode 100644
index 463df71..0000000
--- a/tools/chrome_proxy/webdriver/smoke.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import common
-import time
-from common import TestDriver
-from common import IntegrationTest
-from decorators import NotAndroid
-from decorators import ChromeVersionBeforeM
-from decorators import ChromeVersionEqualOrAfterM
-import json
-
-class Smoke(IntegrationTest):
-
-  # Ensure Chrome does not use DataSaver in Incognito mode.
-  # Clank does not honor the --incognito flag.
-  @NotAndroid
-  def testCheckPageWithIncognito(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--incognito')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  # Ensure Chrome does not use DataSaver when holdback is enabled.
-  @ChromeVersionBeforeM(74)
-  def testCheckPageWithHoldback(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--force-fieldtrials=DataCompressionProxyHoldback/'
-                               'Enabled')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      num_chrome_proxy_request_headers = 0
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-        if ('chrome-proxy' in response.request_headers):
-          num_chrome_proxy_request_headers += 1
-      # DataSaver histograms must still be logged.
-      t.SleepUntilHistogramHasEntry('PageLoad.Clients.DataReductionProxy.'
-              'ParseTiming.NavigationToParseStart')
-      self.assertEqual(num_chrome_proxy_request_headers, 0)
-      # Ensure that Chrome did not attempt to use DataSaver and got a bypass.
-      histogram = t.GetHistogram('DataReductionProxy.BypassedBytes.'
-        'Status502HttpBadGateway', 5)
-      self.assertEqual(histogram, {})
-      histogram = t.GetHistogram('DataReductionProxy.BlockTypePrimary', 5)
-      self.assertEqual(histogram, {})
-      histogram = t.GetHistogram('DataReductionProxy.BypassTypePrimary', 5)
-      self.assertEqual(histogram, {})
-
-  # Ensure Chrome uses DataSaver in normal mode.
-  def testCheckPageWithNormalMode(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      responses = t.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      num_chrome_proxy_request_headers = 0
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-        if ('chrome-proxy' in response.request_headers):
-          num_chrome_proxy_request_headers += 1
-      t.SleepUntilHistogramHasEntry('PageLoad.Clients.DataReductionProxy.'
-        'ParseTiming.NavigationToParseStart')
-      self.assertGreater(num_chrome_proxy_request_headers, 0)
-
-  # Ensure pageload metric pingback with DataSaver.
-  @ChromeVersionBeforeM(79)
-  def testPingback(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--enable-data-reduction-proxy-force-pingback')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      t.SleepUntilHistogramHasEntry("DataReductionProxy.Pingback.Succeeded")
-      t.SleepUntilHistogramHasEntry("DataReductionProxy.Pingback.Attempted")
-      # Verify one pingback attempt that was successful.
-      attempted = t.GetBrowserHistogram('DataReductionProxy.Pingback.Attempted')
-      self.assertEqual(1, attempted['count'])
-      succeeded = t.GetBrowserHistogram('DataReductionProxy.Pingback.Succeeded')
-      self.assertEqual(1, succeeded['count'])
-
-  # Ensure pageload metric pingback with DataSaver has the variations header.
-  @ChromeVersionEqualOrAfterM(62)
-  @ChromeVersionBeforeM(79)
-  def testPingbackHasVariations(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--enable-data-reduction-proxy-force-pingback')
-      t.UseNetLog()
-      # Force set the variations ID, so they are send along with the pingback
-      # request.
-      t.AddChromeArg('--force-variation-ids=42')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      t.LoadURL('http://check.googlezip.net/test.html')
-      t.SleepUntilHistogramHasEntry("DataReductionProxy.Pingback.Succeeded")
-
-      # Look for the request made to data saver pingback server.
-      data = t.StopAndGetNetLog()
-      variation_header_count = 0
-      for i in data["events"]:
-        dumped_event = json.dumps(i)
-        if dumped_event.find("datasaver.googleapis.com") !=-1 and\
-          dumped_event.find("recordPageloadMetrics") != -1 and\
-          dumped_event.find("headers") != -1 and\
-          dumped_event.find("accept-encoding") != -1 and\
-          dumped_event.find("x-client-data") !=-1:
-            variation_header_count = variation_header_count + 1
-
-      # Variation IDs are set. x-client-data should be present in the request
-      # headers.
-      self.assertLessEqual(1, variation_header_count)
-
-  # Verify unique page IDs are sent in the Chrome-Proxy header.
-  @ChromeVersionEqualOrAfterM(59)
-  def testPageID(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      page_identifiers = []
-      page_loads = 5
-
-      for i in range (0, page_loads):
-        t.LoadURL('http://check.googlezip.net/test.html')
-        responses = t.GetHTTPResponses()
-        self.assertEqual(2, len(responses))
-        pid_in_page_count = 0
-        page_id = ''
-        for response in responses:
-          if not response.request_headers:
-            continue
-          self.assertHasProxyHeaders(response)
-          self.assertEqual(200, response.status)
-          chrome_proxy_header = response.request_headers['chrome-proxy']
-          chrome_proxy_directives = chrome_proxy_header.split(',')
-          for directive in chrome_proxy_directives:
-            if 'pid=' in directive:
-              pid_in_page_count = pid_in_page_count+1
-              page_id = directive.split('=')[1]
-              self.assertNotEqual('', page_id)
-              self.assertNotIn(page_id, page_identifiers)
-        page_identifiers.append(page_id)
-        self.assertEqual(1, pid_in_page_count)
-
-  # Ensure that block causes resources to load from the origin directly.
-  def testCheckBlockIsWorking(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/block')
-      responses = t.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertNotHasChromeProxyViaHeader(response)
-
-  # Ensure image resources are compressed.
-  def testCheckImageIsCompressed(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/static')
-      # http://check.googlezip.net/static is a test page that has
-      # image resources.
-      responses = t.GetHTTPResponses()
-      self.assertNotEqual(0, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/chrome_proxy/webdriver/video.py b/tools/chrome_proxy/webdriver/video.py
deleted file mode 100644
index 653f2a1c..0000000
--- a/tools/chrome_proxy/webdriver/video.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import time
-
-import common
-from common import TestDriver
-from common import IntegrationTest
-from common import ParseFlags
-from decorators import AndroidOnly
-from decorators import Slow
-from decorators import ChromeVersionEqualOrAfterM
-
-from selenium.webdriver.common.by import By
-
-class Video(IntegrationTest):
-
-  # Returns the ofcl value in chrome-proxy header.
-  def getChromeProxyOFCL(self, response):
-    self.assertIn('chrome-proxy', response.response_headers)
-    chrome_proxy_header = response.response_headers['chrome-proxy']
-    self.assertIn('ofcl=', chrome_proxy_header)
-    return chrome_proxy_header.split('ofcl=', 1)[1].split(',', 1)[0]
-
-  # Check videos are proxied.
-  def testCheckVideoHasViaHeader(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL(
-        'http://check.googlezip.net/cacheable/video/buck_bunny_tiny.html')
-      responses = t.GetHTTPResponses()
-      self.assertEqual(2, len(responses))
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-
-  def testCheckVideoBypass(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL(
-        'http://check.googlezip.net/blocksingle/blocksingle_embedded_video.html')
-      saw_video_response = False
-      for response in t.GetHTTPResponses():
-        if 'video' in response.response_headers['content-type']:
-          self.assertNotHasChromeProxyViaHeader(response)
-          saw_video_response = True
-        else:
-          self.assertHasProxyHeaders(response)
-      self.assertTrue(saw_video_response, 'No video request seen in test!')
-
-  # Videos fetched via an XHR request should not be proxied.
-  def testNoCompressionOnXHR(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      # The test will actually use Javascript, so use a site that won't have any
-      # resources on it that could interfere.
-      t.LoadURL('http://check.googlezip.net/connect')
-      t.ExecuteJavascript(
-        'var xhr = new XMLHttpRequest();'
-        'xhr.open("GET", "/cacheable/video/data/buck_bunny_tiny.mp4", false);'
-        'xhr.send();'
-        'return;'
-      )
-      saw_video_response = False
-      for response in t.GetHTTPResponses():
-        if 'video' in response.response_headers['content-type']:
-          self.assertNotHasChromeProxyViaHeader(response)
-          saw_video_response = True
-        else:
-          self.assertHasProxyHeaders(response)
-      self.assertTrue(saw_video_response, 'No video request seen in test!')
-
-  @ChromeVersionEqualOrAfterM(64)
-  def testRangeRequest(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://check.googlezip.net/report')
-      time.sleep(2) # wait for page load
-      t.ExecuteJavascript(
-        'var xhr = new XMLHttpRequest();'
-        'xhr.open("GET", "/metrics/local.png", false);'
-        'xhr.setRequestHeader("Range", "bytes=0-2048");'
-        'xhr.send();'
-        'return;'
-      )
-      saw_range_response = False
-      for response in t.GetHTTPResponses():
-        self.assertHasProxyHeaders(response)
-        if response.response_headers['status']=='206':
-          saw_range_response = True
-          content_range = response.response_headers['content-range']
-          self.assertTrue(content_range.startswith('bytes 0-2048/'))
-          compressed_full_content_length = int(content_range.split('/')[1])
-          ofcl = int(self.getChromeProxyOFCL(response))
-          # ofcl should be same as compressed full content length, since no
-          # compression for XHR.
-          self.assertEqual(ofcl, compressed_full_content_length)
-      # Wait and navigate away to trigger the metrics recording for previous
-      # page load.
-      time.sleep(1)
-      t.LoadURL('about:blank')
-      original_kb_histogram = t.GetBrowserHistogram('PageLoad.Clients.'
-        'DataReductionProxy.Experimental.Bytes.Network.Original2')
-      compression_percent_histogram = t.GetBrowserHistogram('PageLoad.Clients.'
-        'DataReductionProxy.Experimental.Bytes.Network.CompressionRatio2')
-      self.assertEqual(1, original_kb_histogram['count'])
-      self.assertEqual(1, compression_percent_histogram['count'])
-      # Verify the total page size is 3 KB, and compression ratio.
-      self.assertLessEqual(3, original_kb_histogram['sum'])
-      self.assertEqual(compression_percent_histogram['sum'],
-                       compressed_full_content_length/ofcl*100)
-      self.assertTrue(saw_range_response, 'No range request was seen in test!')
-
-  @ChromeVersionEqualOrAfterM(64)
-  def testRangeRequestInVideo(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL(
-        'http://check.googlezip.net/cacheable/video/buck_bunny_tiny.html')
-      # Wait for the video to finish playing, plus some headroom.
-      time.sleep(5)
-      responses = t.GetHTTPResponses()
-      saw_range_response = False
-      for response in responses:
-        self.assertHasProxyHeaders(response)
-        if response.response_headers['status']=='206':
-          saw_range_response = True
-          content_range = response.response_headers['content-range']
-          compressed_full_content_length = int(content_range.split('/')[1])
-          ofcl = int(self.getChromeProxyOFCL(response))
-          # ofcl should be greater than the compressed full content length.
-          self.assertGreater(ofcl, compressed_full_content_length)
-      self.assertTrue(saw_range_response, 'No range request was seen in test!')
-
-  # Check the compressed video has the same frame count, width, height, and
-  # duration as uncompressed.
-  @Slow
-  def testVideoMetrics(self):
-    expected = {
-      'duration': 3.068,
-      'webkitDecodedFrameCount': 53.0,
-      'videoWidth': 1280.0,
-      'videoHeight': 720.0
-    }
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL(
-          'http://check.googlezip.net/cacheable/video/buck_bunny_tiny.html')
-      # Check request was proxied and we got a compressed video back.
-      for response in t.GetHTTPResponses():
-        self.assertHasProxyHeaders(response)
-        if ('content-type' in response.response_headers
-            and 'video' in response.response_headers['content-type']):
-          self.assertEqual('video/webm',
-            response.response_headers['content-type'])
-      if ParseFlags().android:
-        t.FindElement(By.TAG_NAME, "video").click()
-      else:
-        t.ExecuteJavascriptStatement(
-          'document.querySelectorAll("video")[0].play()')
-      # Wait for the video to finish playing, plus some headroom.
-      time.sleep(5)
-      # Check each metric against its expected value.
-      for metric in expected:
-        actual = float(t.ExecuteJavascriptStatement(
-          'document.querySelectorAll("video")[0].%s' % metric))
-        self.assertAlmostEqual(expected[metric], actual, msg="Compressed video "
-          "metric doesn't match expected! Metric=%s Expected=%f Actual=%f"
-          % (metric, expected[metric], actual), places=None, delta=0.01)
-
-  # Check that the compressed video can be seeked. Use a slow network to ensure
-  # the entire video isn't downloaded before we have a chance to seek.
-  @Slow
-  @AndroidOnly
-  def testVideoSeeking(self):
-    with TestDriver(control_network_connection=True) as t:
-      t.SetNetworkConnection("2G")
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL(
-          'http://check.googlezip.net/cacheable/video/'+
-          'buck_bunny_640x360_24fps.html')
-      # Play, pause, seek to 1s before the end, play again.
-      t.ExecuteJavascript(
-        '''
-        window.testDone = false;
-        const v = document.getElementsByTagName("video")[0];
-        let first = true;
-        v.onplaying = function() {
-          if (first) {
-            v.pause();
-            first = false;
-          } else {
-            window.testDone = true;
-          }
-        };
-        v.onpause = function() {
-          if (v.currentTime < v.duration) {
-            v.currentTime = v.duration-1;
-            v.play();
-          }
-        };
-        v.play();
-        ''')
-      if ParseFlags().android:
-        # v.play() won't work on Android, so give it a click instead.
-        t.FindElement(By.TAG_NAME, "video").click()
-      t.WaitForJavascriptExpression('window.testDone', 15)
-      # Check request was proxied and we got a compressed video back.
-      # We expect to make multiple requests for the video: ensure they
-      # all have the same ETag.
-      video_etag = None
-      num_partial_requests = 0
-      for response in t.GetHTTPResponses():
-        self.assertHasProxyHeaders(response)
-        rh = response.response_headers
-        if ('content-type' in rh and 'video' in rh['content-type']):
-          self.assertIn('etag', rh),
-          self.assertEqual('video/webm', rh['content-type'])
-          if video_etag == None:
-            video_etag = rh['etag']
-          else:
-            self.assertEqual(video_etag, rh['etag'])
-          if ('status' in rh and rh['status']=='206' and 'content-range' in rh
-              and rh['content-range'].startswith('bytes ') and
-              not rh['content-range'].startswith('bytes 0-')):
-            num_partial_requests += 1
-      # Also make sure that we had at least one partial Range request.
-      self.assertGreaterEqual(num_partial_requests, 1)
-
-  # Check the frames of a compressed video.
-  @Slow
-  def testVideoFrames(self):
-    self.instrumentedVideoTest('http://check.googlezip.net/cacheable/video/buck_bunny_640x360_24fps_video.html')
-
-  # Check the audio volume of a compressed video.
-  #
-  # This test makes some assumptions about the way audio is decoded and
-  # processed in JavaScript on different platforms. Despite getting the same
-  # video bytes from the proxy across all platforms, different data is generated
-  # out of the window.AudioContext object. As of May 2017, there were only two
-  # known datasets, the second occurring on all tested Android devices. If this
-  # test fails on a new or different platform, examine whether the expected data
-  # is drastically different. See crbug.com/723031 for more information.
-  @Slow
-  def testVideoAudio(self):
-    alt_data = None
-    is_android = ParseFlags().android
-    if is_android:
-      alt_data = 'data/buck_bunny_640x360_24fps.mp4.expected_volume_alt.json'
-    self.instrumentedVideoTest('http://check.googlezip.net/cacheable/video/buck_bunny_640x360_24fps_audio.html',
-      alt_data=alt_data)
-
-  def instrumentedVideoTest(self, url, alt_data=None):
-    """Run an instrumented video test. The given page is reloaded up to some
-    maximum number of times until a compressed video is seen by ChromeDriver by
-    inspecting the network logs. Once that happens, test.ready is set and that
-    will signal the Javascript test on the page to begin. Once it is complete,
-    check the results.
-    """
-    # The maximum number of times to attempt to reload the page for a compressed
-    # video.
-    max_attempts = 10
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.AddChromeArg('--autoplay-policy=no-user-gesture-required')
-      loaded_compressed_video = False
-      attempts = 0
-      while not loaded_compressed_video and attempts < max_attempts:
-        t.LoadURL(url)
-        attempts += 1
-        for resp in t.GetHTTPResponses():
-          if ('content-type' in resp.response_headers
-              and resp.response_headers['content-type'] == 'video/webm'):
-            loaded_compressed_video = True
-            self.assertHasProxyHeaders(resp)
-          else:
-            # Take a breath before requesting again.
-            time.sleep(1)
-      if attempts >= max_attempts:
-        self.fail('Could not get a compressed video after %d tries' % attempts)
-      if alt_data != None:
-        t.ExecuteJavascriptStatement('test.expectedVolumeSrc = "%s"' % alt_data)
-      t.ExecuteJavascriptStatement('test.ready = true')
-      t.WaitForJavascriptExpression('test.video_ != undefined', 5)
-      # Click the video to start if Android.
-      if ParseFlags().android:
-        t.FindElement(By.ID, 'video').click()
-      else:
-        t.ExecuteJavascriptStatement('test.video_.play()')
-      waitTimeQuery = 'test.waitTime'
-      if ParseFlags().android:
-        waitTimeQuery = 'test.androidWaitTime'
-      wait_time = int(t.ExecuteJavascriptStatement(waitTimeQuery))
-      t.WaitForJavascriptExpression('test.metrics.complete', wait_time)
-      metrics = t.ExecuteJavascriptStatement('test.metrics')
-      if not metrics['complete']:
-        self.fail('Test not complete after %d seconds.' % wait_time)
-      if metrics['failed']:
-        self.fail('Test failed! ' + metrics['detailedStatus'])
-
-  # Make sure YouTube autoplays.
-  def testYoutube(self):
-    with TestDriver() as t:
-      t.AddChromeArg('--enable-spdy-proxy-auth')
-      t.LoadURL('http://data-saver-test.appspot.com/youtube')
-      if ParseFlags().android:
-        # Video won't auto play on Android, so give it a click.
-        t.FindElement(By.ID, 'player').click()
-      t.WaitForJavascriptExpression(
-        'window.playerState == YT.PlayerState.PLAYING', 30)
-      for response in t.GetHTTPResponses():
-        if not response.url.startswith('https'):
-          self.assertHasProxyHeaders(response)
-
-if __name__ == '__main__':
-  IntegrationTest.RunAllTests()
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5df5260..759ab43 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -22542,6 +22542,7 @@
   <int value="1459" label="PASSWORDSPRIVATE_STOPPASSWORDCHECK"/>
   <int value="1460" label="PASSWORDSPRIVATE_GETPASSWORDCHECKSTATUS"/>
   <int value="1461" label="TERMINALPRIVATE_OPENVMSHELLPROCESS"/>
+  <int value="1462" label="PASSWORDSPRIVATE_OPTINFORACCOUNTSTORAGE"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -38499,6 +38500,7 @@
       label="AutofillToolkitViewsCreditCardDialogsMac:disabled"/>
   <int value="-808486493" label="NewWallpaperPicker:disabled"/>
   <int value="-806480547" label="SyncDeviceInfoInTransportMode:enabled"/>
+  <int value="-805480822" label="RemoteCopyImageNotification:disabled"/>
   <int value="-803233334" label="AutofillRefreshStyleAndroid:disabled"/>
   <int value="-802348444" label="disable-site-engagement-service"/>
   <int value="-799931058" label="UseMultiloginEndpoint:disabled"/>
@@ -39585,6 +39587,7 @@
   <int value="513258875" label="WinrtSensorsImplementation:disabled"/>
   <int value="513356954" label="InstantTethering:disabled"/>
   <int value="513372959" label="ViewsProfileChooser:enabled"/>
+  <int value="514569020" label="RemoteCopyImageNotification:enabled"/>
   <int value="516603570" label="QuickAnswersRichUi:disabled"/>
   <int value="517429103" label="AutofillImportDynamicForms:enabled"/>
   <int value="517568645" label="AnimatedAppMenuIcon:disabled"/>
@@ -54571,6 +54574,9 @@
 </enum>
 
 <enum name="QueryWebAndAppActivityState">
+  <obsolete>
+    Removed March 2020.
+  </obsolete>
   <int value="0" label="History recording enabled"/>
   <int value="1" label="History recording disabled"/>
   <int value="2" label="Request failed"/>
@@ -63938,6 +63944,9 @@
 </enum>
 
 <enum name="SyncSetupSettignsDisplayedSwaaState">
+  <obsolete>
+    Removed March 2020.
+  </obsolete>
   <int value="0" label="On"/>
   <int value="1" label="Off due to custom encryption"/>
   <int value="2" label="Off due to history sync off"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 93e3396..e9778ec3 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -40440,6 +40440,34 @@
   </summary>
 </histogram>
 
+<histogram name="DomDistiller.Time.ActivelyViewingArticleBeforeDistilling"
+    units="ms" expires_after="2020-09-01">
+  <owner>katie@chromium.org</owner>
+  <owner>chrome-a11y-core@chromium.org</owner>
+  <summary>
+    Records the amount of active time a user spent on a distillable page before
+    switching that page to Reader Mode. Active time is time that the article was
+    visible, not total time the page was open: the timer is paused when the page
+    is not visible. This is not recorded when the user opens a distillable page
+    but does not switch to Reader Mode from that page (via the omnibox icon or
+    menu option).
+  </summary>
+</histogram>
+
+<histogram name="DomDistiller.Time.ActivelyViewingReaderModePage" units="ms"
+    expires_after="2020-09-01">
+  <owner>katie@chromium.org</owner>
+  <owner>chrome-a11y-core@chromium.org</owner>
+  <summary>
+    Records the amount of active time a user spent on a Reader Mode page. Active
+    time is the time the Reader Mode page was visible, not the total time the
+    page was open: the timer is paused when the page is not visible. This is
+    recorded regardless of how a user enters the page: e.g. using the
+    forward/back buttons, entering the URL directly, or coming from a
+    distillable page all start the timer equally.
+  </summary>
+</histogram>
+
 <histogram name="DomDistiller.Time.ArticleProcessing" units="ms"
     expires_after="M77">
   <owner>yfriedman@chromium.org</owner>
@@ -147902,6 +147930,9 @@
 
 <histogram name="Settings.SyncSetup.DisplayedSwaaState"
     enum="SyncSetupSettignsDisplayedSwaaState" expires_after="M85">
+  <obsolete>
+    Removed March 2020.
+  </obsolete>
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -156567,6 +156598,27 @@
   </summary>
 </histogram>
 
+<histogram name="Startup.FirstWebContents.NonEmptyPaint3" units="ms"
+    expires_after="M85">
+  <owner>etiennep@chromium.org</owner>
+  <owner>fdoray@chromium.org</owner>
+  <owner>gab@chromium.org</owner>
+  <summary>
+    [Desktop] Measure the elapsed time from the application start to the first
+    non empty paint of the first web contents. Only comprised of cases where the
+    initial foreground tab gets to complete its rendering task unimpeded (an
+    improvement over Startup.FirstWebContents.NonEmptyPaint).
+
+    This is meant to replace Startup.FirstWebContents.NonEmptyPaint2, using
+    application start time instead of process creation time. Application start
+    time is recorded as early as possible in the startup process. On Android,
+    the application start is the time at which the Java code starts. On Windows,
+    application start is sampled from chrome.exe:main, before chrome.dll is
+    loaded. TODO(etiennep): Deprecate NonEmptyPaint2 in favor of this one once
+    stable.
+  </summary>
+</histogram>
+
 <histogram
     name="Startup.FirstWebContents.RenderProcessHostInit.ToNonEmptyPaint"
     units="ms" expires_after="M78">
@@ -156681,6 +156733,19 @@
   <summary>Whether a startup is a resume (vs a cold start).</summary>
 </histogram>
 
+<histogram name="Startup.LoadTime.ApplicationStartToChromeMain" units="ms"
+    expires_after="never">
+<!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint2 -->
+
+  <owner>fdoray@chromium.org</owner>
+  <owner>etiennep@chromium.org</owner>
+  <owner>gab@chromium.org</owner>
+  <summary>
+    Time from the application start to the C++ ChromeMain() function being
+    invoked.
+  </summary>
+</histogram>
+
 <histogram name="Startup.LoadTime.ExeMainToDllMain" units="units"
     expires_after="2016-12-17">
   <obsolete>
@@ -156696,10 +156761,12 @@
   </summary>
 </histogram>
 
-<histogram name="Startup.LoadTime.ExeMainToDllMain2" units="units"
-    expires_after="never">
-<!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint2 -->
-
+<histogram name="Startup.LoadTime.ExeMainToDllMain2" units="ms"
+    expires_after="2020-03-12">
+  <obsolete>
+    Removed 03/2020. Replaced with Startup.LoadTime.ApplicationStartToChromeMain
+    which applies to most platforms.
+  </obsolete>
   <owner>fdoray@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
@@ -156707,6 +156774,19 @@
   </summary>
 </histogram>
 
+<histogram name="Startup.LoadTime.ProcessCreateToApplicationStart" units="ms"
+    expires_after="never">
+<!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint2 -->
+
+  <owner>fdoray@chromium.org</owner>
+  <owner>etiennep@chromium.org</owner>
+  <owner>gab@chromium.org</owner>
+  <summary>
+    Time from the process creation to application start, i.e. time recorded as
+    early as possible in the startup process.
+  </summary>
+</histogram>
+
 <histogram name="Startup.LoadTime.ProcessCreateToDllMain" units="units"
     expires_after="2016-12-17">
   <obsolete>
@@ -156722,10 +156802,13 @@
   </summary>
 </histogram>
 
-<histogram name="Startup.LoadTime.ProcessCreateToDllMain2" units="units"
-    expires_after="never">
-<!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint2 -->
-
+<histogram name="Startup.LoadTime.ProcessCreateToDllMain2" units="ms"
+    expires_after="2020-03-12">
+  <obsolete>
+    Removed 03/2020. Replaced with
+    Startup.LoadTime.ProcessCreateToApplicationStart which applies to most
+    platforms.
+  </obsolete>
   <owner>fdoray@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>Time from the process creation to chrome.dll's main().</summary>
@@ -156747,10 +156830,13 @@
   </summary>
 </histogram>
 
-<histogram name="Startup.LoadTime.ProcessCreateToExeMain2" units="units"
+<histogram name="Startup.LoadTime.ProcessCreateToExeMain2" units="ms"
     expires_after="never">
-<!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint2 -->
-
+  <obsolete>
+    Removed 03/2020. Now covered by
+    Startup.LoadTime.ProcessCreateToApplicationStart and
+    Startup.LoadTime.ApplicationStartToChromeMain.
+  </obsolete>
   <owner>fdoray@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
@@ -177024,7 +177110,10 @@
 </histogram>
 
 <histogram name="WebHistory.QueryWebAndAppActivity.State"
-    enum="QueryWebAndAppActivityState" expires_after="M85">
+    enum="QueryWebAndAppActivityState" expires_after="M83">
+  <obsolete>
+    Removed March 2020.
+  </obsolete>
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -198087,8 +198176,10 @@
   <affected-histogram name="Startup.FirstWebContents.NonEmptyPaint2"/>
   <affected-histogram
       name="Startup.FirstWebContents.RenderProcessHostInit.ToNonEmptyPaint"/>
+  <affected-histogram name="Startup.LoadTime.ApplicationStartToChromeMain"/>
   <affected-histogram name="Startup.LoadTime.ExeMainToDllMain"/>
   <affected-histogram name="Startup.LoadTime.ExeMainToDllMain2"/>
+  <affected-histogram name="Startup.LoadTime.ProcessCreateToApplicationStart"/>
   <affected-histogram name="Startup.LoadTime.ProcessCreateToDllMain"/>
   <affected-histogram name="Startup.LoadTime.ProcessCreateToDllMain2"/>
   <affected-histogram name="Startup.LoadTime.ProcessCreateToExeMain"/>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 5f3b400..528375c 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -5424,6 +5424,22 @@
   <metric name="Length"/>
 </event>
 
+<event name="Media.GlobalMediaControls.ActionButtonPressed">
+  <owner>steimel@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Records when a user presses a button in the Global Media Controls. Records
+    no more than 100 actions per source per session. Records which button was
+    pressed (e.g. play, pause, next track, picture-in-picture).
+  </summary>
+  <metric name="MediaSessionAction" enum="MediaSessionAction">
+    <summary>
+      The action of the button that was pressed (e.g. play, pause, next track,
+      picture-in-picture).
+    </summary>
+  </metric>
+</event>
+
 <event name="Media.Learning.PredictionRecord">
   <owner>liberato@chromium.org</owner>
   <summary>
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 09c5751..07e0c7e 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -131,9 +131,8 @@
  <item id="google_url_tracker" hash_code="5492492" type="0" deprecated="2019-08-01" content_hash_code="54474899" file_path=""/>
  <item id="headless_url_request" hash_code="29865866" type="0" deprecated="2018-07-10" content_hash_code="76700151" file_path=""/>
  <item id="hintsfetcher_gethintsrequest" hash_code="34557599" type="0" content_hash_code="57003380" os_list="linux,windows" file_path="components/optimization_guide/hints_fetcher.cc"/>
- <item id="history_notice_utils_notice" hash_code="102595701" type="1" second_id="110307337" deprecated="2020-01-07" content_hash_code="130829410" file_path=""/>
+ <item id="history_notice_utils_notice" hash_code="102595701" type="1" second_id="110307337" content_hash_code="130829410" os_list="linux,windows" semantics_fields="2,3,4" policy_fields="4" file_path="components/browsing_data/core/history_notice_utils.cc"/>
  <item id="history_notice_utils_popup" hash_code="80832574" type="1" second_id="110307337" content_hash_code="30618510" os_list="linux,windows" semantics_fields="2,3,4" policy_fields="4" file_path="components/browsing_data/core/history_notice_utils.cc"/>
- <item id="history_recording_enabled" hash_code="18918377" type="1" second_id="110307337" content_hash_code="24841534" os_list="linux,windows" semantics_fields="2,3,4" policy_fields="4" file_path="components/browsing_data/core/history_notice_utils.cc"/>
  <item id="history_ui_favicon_request_handler_get_favicon" hash_code="17562717" type="0" content_hash_code="64054629" os_list="linux,windows" file_path="components/favicon/core/history_ui_favicon_request_handler_impl.cc"/>
  <item id="http_server_error_response" hash_code="32197336" type="0" content_hash_code="61082230" os_list="linux,windows" file_path="net/server/http_server.cc"/>
  <item id="https_server_previews_navigation" hash_code="35725390" type="0" content_hash_code="84423109" os_list="linux,windows" file_path="chrome/browser/previews/previews_lite_page_redirect_serving_url_loader.cc"/>
diff --git a/ui/accessibility/platform/ax_fragment_root_win.cc b/ui/accessibility/platform/ax_fragment_root_win.cc
index 6f37698f..458625dc 100644
--- a/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -129,10 +129,26 @@
 
     *focus = nullptr;
 
-    gfx::NativeViewAccessible focused_element = GetDelegate()->GetFocus();
-    if (focused_element != nullptr) {
+    gfx::NativeViewAccessible focused_element = nullptr;
+
+    // GetFocus() can return a node at the root of a subtree, for example when
+    // transitioning from Views into web content. In such cases we want to
+    // continue drilling to retrieve the actual focused element.
+    AXPlatformNode* node_to_test = this;
+    do {
+      gfx::NativeViewAccessible test_result =
+          node_to_test->GetDelegate()->GetFocus();
+      if (test_result != nullptr && test_result != focused_element) {
+        focused_element = test_result;
+        node_to_test =
+            AXPlatformNode::FromNativeViewAccessible(focused_element);
+      } else {
+        node_to_test = nullptr;
+      }
+    } while (node_to_test);
+
+    if (focused_element)
       focused_element->QueryInterface(IID_PPV_ARGS(focus));
-    }
 
     return S_OK;
   }
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc
index 7ef3f87..5899271 100644
--- a/ui/display/win/screen_win.cc
+++ b/ui/display/win/screen_win.cc
@@ -12,6 +12,7 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/numerics/ranges.h"
 #include "base/stl_util.h"
 #include "base/win/win_util.h"
 #include "base/win/windows_version.h"
@@ -24,6 +25,7 @@
 #include "ui/display/win/screen_win_display.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/safe_integer_conversions.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/icc_profile.h"
@@ -34,35 +36,25 @@
 
 // TODO(robliao): http://crbug.com/615514 Remove when ScreenWin usage is
 // resolved with Desktop Aura and WindowTreeHost.
-ScreenWin* g_screen_win_instance = nullptr;
+ScreenWin* g_instance = nullptr;
 
-// Gets the DPI for a particular monitor, or 0 if per-monitor DPI is nuot
-// supported or can't be read.
+// Gets the DPI for a particular monitor.
 int GetPerMonitorDPI(HMONITOR monitor) {
-  // Most versions of Windows we will encounter are DPI-aware.
   if (!base::win::IsProcessPerMonitorDpiAware())
     return 0;
 
   static auto get_dpi_for_monitor_func = []() {
-    using GetDpiForMonitorPtr = decltype(::GetDpiForMonitor)*;
-    HMODULE shcore_dll = ::LoadLibrary(L"shcore.dll");
-    if (shcore_dll) {
-      return reinterpret_cast<GetDpiForMonitorPtr>(
-          ::GetProcAddress(shcore_dll, "GetDpiForMonitor"));
-    }
-    return static_cast<GetDpiForMonitorPtr>(nullptr);
+    const HMODULE shcore_dll = ::LoadLibrary(L"shcore.dll");
+    return reinterpret_cast<decltype(&::GetDpiForMonitor)>(
+        shcore_dll ? ::GetProcAddress(shcore_dll, "GetDpiForMonitor")
+                   : nullptr);
   }();
-
-  if (!get_dpi_for_monitor_func)
+  UINT dpi_x, dpi_y;
+  if (!get_dpi_for_monitor_func ||
+      !SUCCEEDED(
+          get_dpi_for_monitor_func(monitor, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y)))
     return 0;
 
-  UINT dpi_x;
-  UINT dpi_y;
-  if (!SUCCEEDED(get_dpi_for_monitor_func(monitor, MDT_EFFECTIVE_DPI, &dpi_x,
-                                          &dpi_y))) {
-    return 0;
-  }
-
   DCHECK_EQ(dpi_x, dpi_y);
   return int{dpi_x};
 }
@@ -77,7 +69,7 @@
   if (Display::HasForceDeviceScaleFactor())
     return Display::GetForcedDeviceScaleFactor();
 
-  int dpi = GetPerMonitorDPI(monitor);
+  const int dpi = GetPerMonitorDPI(monitor);
   if (!dpi)
     return GetDPIScale();
 
@@ -91,45 +83,42 @@
 }
 
 bool GetPathInfo(HMONITOR monitor, DISPLAYCONFIG_PATH_INFO* path_info) {
-  LONG result;
-  uint32_t num_path_array_elements = 0;
-  uint32_t num_mode_info_array_elements = 0;
-  std::vector<DISPLAYCONFIG_PATH_INFO> path_infos;
-  std::vector<DISPLAYCONFIG_MODE_INFO> mode_infos;
-
   // Get the monitor name.
-  MONITORINFOEXW view_info;
-  view_info.cbSize = sizeof(view_info);
-  if (!GetMonitorInfoW(monitor, &view_info))
+  MONITORINFOEX monitor_info;
+  monitor_info.cbSize = sizeof(monitor_info);
+  if (!GetMonitorInfo(monitor, &monitor_info))
     return false;
 
   // Get all path infos.
+  LONG result;
+  std::vector<DISPLAYCONFIG_PATH_INFO> path_infos;
   do {
-    if (GetDisplayConfigBufferSizes(
-            QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements,
-            &num_mode_info_array_elements) != ERROR_SUCCESS) {
+    uint32_t path_elements, mode_elements;
+    if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_elements,
+                                    &mode_elements) != ERROR_SUCCESS) {
       return false;
     }
-    path_infos.resize(num_path_array_elements);
-    mode_infos.resize(num_mode_info_array_elements);
-    result = QueryDisplayConfig(
-        QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, path_infos.data(),
-        &num_mode_info_array_elements, mode_infos.data(), nullptr);
+    path_infos.resize(path_elements);
+    std::vector<DISPLAYCONFIG_MODE_INFO> mode_infos(mode_elements);
+    result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_elements,
+                                path_infos.data(), &mode_elements,
+                                mode_infos.data(), nullptr);
+    if (result == ERROR_SUCCESS)
+      path_infos.resize(path_elements);
   } while (result == ERROR_INSUFFICIENT_BUFFER);
 
-  // Iterate of the path infos and see if we find one with a matching name.
+  // Look for a path info with a matching name.
   if (result == ERROR_SUCCESS) {
-    for (uint32_t p = 0; p < num_path_array_elements; p++) {
-      DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name;
+    for (const auto& info : path_infos) {
+      DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name = {};
       device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
       device_name.header.size = sizeof(device_name);
-      device_name.header.adapterId = path_infos[p].sourceInfo.adapterId;
-      device_name.header.id = path_infos[p].sourceInfo.id;
-      if (DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) {
-        if (wcscmp(view_info.szDevice, device_name.viewGdiDeviceName) == 0) {
-          *path_info = path_infos[p];
-          return true;
-        }
+      device_name.header.adapterId = info.sourceInfo.adapterId;
+      device_name.header.id = info.sourceInfo.id;
+      if ((DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) &&
+          (wcscmp(monitor_info.szDevice, device_name.viewGdiDeviceName) == 0)) {
+        *path_info = info;
+        return true;
       }
     }
   }
@@ -137,20 +126,17 @@
 }
 
 float GetMonitorSDRWhiteLevel(HMONITOR monitor) {
-  float ret = 200.0;  // default value
   DISPLAYCONFIG_PATH_INFO path_info = {};
-  if (!GetPathInfo(monitor, &path_info))
-    return ret;
-
-  DISPLAYCONFIG_SDR_WHITE_LEVEL white_level = {};
-  white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
-  white_level.header.size = sizeof(white_level);
-  white_level.header.adapterId = path_info.targetInfo.adapterId;
-  white_level.header.id = path_info.targetInfo.id;
-  if (DisplayConfigGetDeviceInfo(&white_level.header) != ERROR_SUCCESS)
-    return ret;
-  ret = white_level.SDRWhiteLevel * 80.0 / 1000.0;
-  return ret;
+  if (GetPathInfo(monitor, &path_info)) {
+    DISPLAYCONFIG_SDR_WHITE_LEVEL white_level = {};
+    white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
+    white_level.header.size = sizeof(white_level);
+    white_level.header.adapterId = path_info.targetInfo.adapterId;
+    white_level.header.id = path_info.targetInfo.id;
+    if (DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS)
+      return white_level.SDRWhiteLevel * 80.0 / 1000.0;
+  }
+  return 200.0f;
 }
 
 void GetDisplaySettingsForDevice(const wchar_t* device_name,
@@ -160,34 +146,33 @@
   *frequency = 0;
   DEVMODE mode = {};
   mode.dmSize = sizeof(mode);
-  if (::EnumDisplaySettings(device_name, ENUM_CURRENT_SETTINGS, &mode)) {
-    switch (mode.dmDisplayOrientation) {
-      case DMDO_DEFAULT:
-        *rotation = Display::ROTATE_0;
-        break;
-      case DMDO_90:
-        *rotation = Display::ROTATE_90;
-        break;
-      case DMDO_180:
-        *rotation = Display::ROTATE_180;
-        break;
-      case DMDO_270:
-        *rotation = Display::ROTATE_270;
-        break;
-      default:
-        NOTREACHED();
-    }
-    *frequency = mode.dmDisplayFrequency;
+  if (!::EnumDisplaySettings(device_name, ENUM_CURRENT_SETTINGS, &mode))
+    return;
+  switch (mode.dmDisplayOrientation) {
+    case DMDO_DEFAULT:
+      *rotation = Display::ROTATE_0;
+      break;
+    case DMDO_90:
+      *rotation = Display::ROTATE_90;
+      break;
+    case DMDO_180:
+      *rotation = Display::ROTATE_180;
+      break;
+    case DMDO_270:
+      *rotation = Display::ROTATE_270;
+      break;
+    default:
+      NOTREACHED();
   }
+  *frequency = mode.dmDisplayFrequency;
 }
 
 std::vector<DisplayInfo> FindAndRemoveTouchingDisplayInfos(
-    const DisplayInfo& ref_display_info,
+    const DisplayInfo& parent_info,
     std::vector<DisplayInfo>* display_infos) {
   std::vector<DisplayInfo> touching_display_infos;
-  base::EraseIf(*display_infos, [&touching_display_infos, ref_display_info](
-      const DisplayInfo& display_info) {
-    if (DisplayInfosTouch(ref_display_info, display_info)) {
+  base::EraseIf(*display_infos, [&](const auto& display_info) {
+    if (DisplayInfosTouch(parent_info, display_info)) {
       touching_display_infos.push_back(display_info);
       return true;
     }
@@ -199,87 +184,77 @@
 Display CreateDisplayFromDisplayInfo(const DisplayInfo& display_info,
                                      ColorProfileReader* color_profile_reader,
                                      bool hdr_enabled) {
-  Display display(display_info.id());
-  float scale_factor = display_info.device_scale_factor();
+  const float scale_factor = display_info.device_scale_factor();
+  const gfx::Rect bounds = gfx::ScaleToEnclosingRect(display_info.screen_rect(),
+                                                     1.0f / scale_factor);
+  Display display(display_info.id(), bounds);
   display.set_device_scale_factor(scale_factor);
-  display.set_work_area(
-      gfx::ScaleToEnclosingRect(display_info.screen_work_rect(),
-                                1.0f / scale_factor));
-  display.set_bounds(gfx::ScaleToEnclosingRect(display_info.screen_rect(),
-                     1.0f / scale_factor));
+  display.set_work_area(gfx::ScaleToEnclosingRect(
+      display_info.screen_work_rect(), 1.0f / scale_factor));
   display.set_rotation(display_info.rotation());
   display.set_display_frequency(display_info.display_frequency());
 
-  // Compute the DisplayColorSpace for this configuration. Note that it is
-  // important to specify that there is no alpha channel when it is not needed,
-  // so that DXGI_ALPHA_MODE_IGNORE is specified. Not doing so regresses power
-  // usage substantially.
-  // https://crbug.com/1057163
+  // Compute the DisplayColorSpace for this configuration.
+  // When alpha is not needed, specify BGRX_8888 to get DXGI_ALPHA_MODE_IGNORE.
+  // This saves significant power (see https://crbug.com/1057163).
   gfx::DisplayColorSpaces color_spaces = display.color_spaces();
   if (Display::HasForceDisplayColorProfile()) {
     color_spaces.SetOutputBufferFormats(gfx::BufferFormat::BGRX_8888,
                                         gfx::BufferFormat::BGRA_8888);
-  } else {
-    if (hdr_enabled) {
-      // This will map to DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 with
-      // DXGI_FORMAT_B8G8R8A8_UNORM.
-      const auto srgb = gfx::ColorSpace::CreateSRGB();
+  } else if (hdr_enabled) {
+    const float sdr_white_level = display_info.sdr_white_level();
+    color_spaces.SetSDRWhiteLevel(sdr_white_level);
 
-      // This will map to DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709. In that
-      // space, the brightness of (1,1,1) is 80 nits. That's where the magic
-      // constant of 80 comes in.
-      const auto scrgb_linear = gfx::ColorSpace::CreateSCRGBLinear(
-          80.f / display_info.sdr_white_level());
+    // This will map to DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 with
+    // DXGI_FORMAT_B8G8R8A8_UNORM.
+    const auto srgb = gfx::ColorSpace::CreateSRGB();
 
-      // This will map to DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, with
-      // sRGB's (1,1,1) mapping to the specified number of nits.
-      const auto hdr10 =
-          gfx::ColorSpace::CreateHDR10(display_info.sdr_white_level());
+    // This will map to DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709. In that
+    // space, the brightness of (1,1,1) is 80 nits.
+    constexpr float kScrgbWhiteLevel = 80.0f;
+    const auto scrgb_linear =
+        gfx::ColorSpace::CreateSCRGBLinear(kScrgbWhiteLevel / sdr_white_level);
 
-      // For sRGB content, use 8-bit formats.
-      color_spaces.SetOutputColorSpaceAndBufferFormat(
-          gfx::ContentColorUsage::kSRGB, /*needs_alpha=*/false, srgb,
-          gfx::BufferFormat::BGRX_8888);
-      color_spaces.SetOutputColorSpaceAndBufferFormat(
-          gfx::ContentColorUsage::kSRGB, /*needs_alpha=*/true, srgb,
-          gfx::BufferFormat::BGRA_8888);
+    // This will map to DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, with
+    // sRGB's (1,1,1) mapping to the specified number of nits.
+    const auto hdr10 = gfx::ColorSpace::CreateHDR10(sdr_white_level);
 
-      // Use HDR color spaces only when there is WCG or HDR content on the
-      // screen.
-      const gfx::ContentColorUsage content_color_usages[] = {
-          gfx::ContentColorUsage::kWideColorGamut,
-          gfx::ContentColorUsage::kHDR,
-      };
-      for (const auto& usage : content_color_usages) {
-        // Using RGBA F16 backbuffers required by SCRGB linear causes
-        // stuttering on Windows RS3, but RGB10A2 with HDR10 color space works
-        // fine (see https://crbug.com/937108#c92).
-        if (base::win::GetVersion() > base::win::Version::WIN10_RS3) {
-          color_spaces.SetOutputColorSpaceAndBufferFormat(
-              usage, /*needs_alpha=*/false, scrgb_linear,
-              gfx::BufferFormat::RGBA_F16);
-        } else {
-          color_spaces.SetOutputColorSpaceAndBufferFormat(
-              usage, /*needs_alpha=*/false, hdr10,
-              gfx::BufferFormat::BGRA_1010102);
-        }
-        // Use RGBA F16 backbuffers for HDR if alpha channel is required.
+    // For sRGB content, use 8-bit formats.
+    constexpr bool kNeedsAlpha = true;
+    color_spaces.SetOutputColorSpaceAndBufferFormat(
+        gfx::ContentColorUsage::kSRGB, !kNeedsAlpha, srgb,
+        gfx::BufferFormat::BGRX_8888);
+    color_spaces.SetOutputColorSpaceAndBufferFormat(
+        gfx::ContentColorUsage::kSRGB, kNeedsAlpha, srgb,
+        gfx::BufferFormat::BGRA_8888);
+
+    // Use HDR color spaces only when there is WCG or HDR content on the
+    // screen.
+    for (const auto& usage : {gfx::ContentColorUsage::kWideColorGamut,
+                              gfx::ContentColorUsage::kHDR}) {
+      // Using RGBA F16 backbuffers required by SCRGB linear causes
+      // stuttering on Windows RS3, but RGB10A2 with HDR10 color space works
+      // fine (see https://crbug.com/937108#c92).
+      if (base::win::GetVersion() > base::win::Version::WIN10_RS3) {
         color_spaces.SetOutputColorSpaceAndBufferFormat(
-            usage, /*needs_alpha=*/true, scrgb_linear,
-            gfx::BufferFormat::RGBA_F16);
+            usage, !kNeedsAlpha, scrgb_linear, gfx::BufferFormat::RGBA_F16);
+      } else {
+        color_spaces.SetOutputColorSpaceAndBufferFormat(
+            usage, !kNeedsAlpha, hdr10, gfx::BufferFormat::BGRA_1010102);
       }
-      color_spaces.SetSDRWhiteLevel(display_info.sdr_white_level());
-
-      // We set these to 10 bpp because these are (ab)used by pages via media
-      // query APIs to detect HDR support.
-      display.set_color_depth(Display::kHDR10BitsPerPixel);
-      display.set_depth_per_component(Display::kHDR10BitsPerComponent);
-    } else {
-      color_spaces = gfx::DisplayColorSpaces(
-          color_profile_reader->GetDisplayColorSpace(display_info.id()));
-      color_spaces.SetOutputBufferFormats(gfx::BufferFormat::BGRX_8888,
-                                          gfx::BufferFormat::BGRA_8888);
+      // Use RGBA F16 backbuffers for HDR if alpha channel is required.
+      color_spaces.SetOutputColorSpaceAndBufferFormat(
+          usage, kNeedsAlpha, scrgb_linear, gfx::BufferFormat::RGBA_F16);
     }
+
+    // These are (ab)used by pages via media query APIs to detect HDR support.
+    display.set_color_depth(Display::kHDR10BitsPerPixel);
+    display.set_depth_per_component(Display::kHDR10BitsPerComponent);
+  } else {
+    color_spaces = gfx::DisplayColorSpaces(
+        color_profile_reader->GetDisplayColorSpace(display_info.id()));
+    color_spaces.SetOutputBufferFormats(gfx::BufferFormat::BGRX_8888,
+                                        gfx::BufferFormat::BGRA_8888);
   }
   display.set_color_spaces(color_spaces);
 
@@ -292,12 +267,12 @@
 //
 // To do this, DisplayInfosToScreenWinDisplays reasons over monitors as a tree
 // using the primary monitor as the root. All monitors touching this root are
-// considered a children.
+// considered children.
 //
-// This also presumes that all monitors are connected components. Windows, by UI
-// construction restricts the layout of monitors to connected components except
-// when DPI virtualization is happening. When this happens, we scale relative
-// to (0, 0).
+// This also presumes that all monitors are connected components. By UI
+// construction, Windows restricts the layout of monitors to connected
+// components except when DPI virtualization is happening. When this happens, we
+// scale relative to (0, 0).
 //
 // Note that this does not handle cases where a scaled display may have
 // insufficient room to lay out its children. In these cases, a DIP point could
@@ -310,19 +285,17 @@
   // Find and extract the primary display.
   std::vector<DisplayInfo> display_infos_remaining = display_infos;
   auto primary_display_iter = std::find_if(
-      display_infos_remaining.begin(), display_infos_remaining.end(), [](
-          const DisplayInfo& display_info) {
+      display_infos_remaining.begin(), display_infos_remaining.end(),
+      [](const DisplayInfo& display_info) {
         return display_info.screen_rect().origin().IsOrigin();
       });
-  DCHECK(primary_display_iter != display_infos_remaining.end()) <<
-      "Missing primary display.";
+  DCHECK(primary_display_iter != display_infos_remaining.end());
 
-  std::vector<DisplayInfo> available_parents;
-  available_parents.push_back(*primary_display_iter);
-  DisplayLayoutBuilder builder(primary_display_iter->id());
-  display_infos_remaining.erase(primary_display_iter);
   // Build the tree and determine DisplayPlacements along the way.
-  while (available_parents.size()) {
+  DisplayLayoutBuilder builder(primary_display_iter->id());
+  std::vector<DisplayInfo> available_parents = {*primary_display_iter};
+  display_infos_remaining.erase(primary_display_iter);
+  while (!available_parents.empty()) {
     const DisplayInfo parent = available_parents.back();
     available_parents.pop_back();
     for (const auto& child :
@@ -338,15 +311,11 @@
     displays.push_back(CreateDisplayFromDisplayInfo(
         display_info, color_profile_reader, hdr_enabled));
   }
-
-  std::unique_ptr<DisplayLayout> layout(builder.Build());
-  layout->ApplyToDisplayList(&displays, nullptr, 0);
+  builder.Build()->ApplyToDisplayList(&displays, nullptr, 0);
 
   std::vector<ScreenWinDisplay> screen_win_displays;
-  const size_t num_displays = display_infos.size();
-  for (size_t i = 0; i < num_displays; ++i)
+  for (size_t i = 0; i < display_infos.size(); ++i)
     screen_win_displays.emplace_back(displays[i], display_infos[i]);
-
   return screen_win_displays;
 }
 
@@ -366,32 +335,27 @@
 }
 
 gfx::Vector2dF GetPixelsPerInchForPointerDevice(HANDLE source_device) {
-  gfx::Vector2dF pixels_per_inch;
   static const auto get_pointer_device_rects =
       reinterpret_cast<decltype(&::GetPointerDeviceRects)>(
           base::win::GetUser32FunctionPointer("GetPointerDeviceRects"));
-  if (!get_pointer_device_rects)
-    return pixels_per_inch;
-
   RECT screen = {};
   RECT device = {};
-  if (get_pointer_device_rects(source_device, &device, &screen)) {
-    constexpr float kHimetricPerInch = 2540.f;
-    float himetric_to_pixel_ratio_x =
-        float{device.right - device.left} / float{screen.right - screen.left};
-    float himetric_to_pixel_ratio_y =
-        float{device.bottom - device.top} / float{screen.bottom - screen.top};
-    pixels_per_inch.set_x(kHimetricPerInch / himetric_to_pixel_ratio_x);
-    pixels_per_inch.set_y(kHimetricPerInch / himetric_to_pixel_ratio_y);
-    return pixels_per_inch;
-  }
+  if (!get_pointer_device_rects ||
+      !get_pointer_device_rects(source_device, &device, &screen))
+    return gfx::Vector2dF();
 
-  return pixels_per_inch;
+  constexpr float kHimetricPerInch = 2540.0f;
+  float himetric_per_pixel_x =
+      float{device.right - device.left} / float{screen.right - screen.left};
+  float himetric_per_pixel_y =
+      float{device.bottom - device.top} / float{screen.bottom - screen.top};
+  return gfx::Vector2dF(kHimetricPerInch / himetric_per_pixel_x,
+                        kHimetricPerInch / himetric_per_pixel_y);
 }
 
 // Returns physical pixels per inch based on 96 dpi monitor.
 gfx::Vector2dF GetDefaultMonitorPhysicalPixelsPerInch() {
-  int default_dpi = GetDPIFromScalingFactor(1.0);
+  const int default_dpi = GetDPIFromScalingFactor(1.0f);
   return gfx::Vector2dF(default_dpi, default_dpi);
 }
 
@@ -399,16 +363,13 @@
                                                 HDC hdc,
                                                 LPRECT rect,
                                                 LPARAM data) {
-  std::vector<DisplayInfo>* display_infos =
-      reinterpret_cast<std::vector<DisplayInfo>*>(data);
-  DCHECK(display_infos);
-
+  const MONITORINFOEX monitor_info = MonitorInfoFromHMONITOR(monitor);
   Display::Rotation rotation;
   int display_frequency;
-  MONITORINFOEX monitor_info = MonitorInfoFromHMONITOR(monitor);
   GetDisplaySettingsForDevice(monitor_info.szDevice, &rotation,
                               &display_frequency);
-  // Get the count of pointer devices.
+
+  // Retrieve PPI for |monitor| based on touch pointer device handles.
   static const auto get_pointer_devices =
       reinterpret_cast<decltype(&::GetPointerDevices)>(
           base::win::GetUser32FunctionPointer("GetPointerDevices"));
@@ -416,28 +377,25 @@
   if (get_pointer_devices)
     get_pointer_devices(&pointer_device_count, nullptr);
 
-  // Map touch pointer device to |monitor| and retrieve pixels per inch
-  // for the |monitor| based on pointer device handle.
   gfx::Vector2dF pixels_per_inch = GetDefaultMonitorPhysicalPixelsPerInch();
   if (pointer_device_count != 0) {
-    // Get all pointer devices.
     std::vector<POINTER_DEVICE_INFO> pointer_devices(pointer_device_count);
     if (get_pointer_devices(&pointer_device_count, &pointer_devices.front())) {
-      for (uint32_t i = 0; i < pointer_device_count; i++) {
-        if (pointer_devices[i].pointerDeviceType == POINTER_DEVICE_TYPE_TOUCH &&
-            pointer_devices[i].monitor == monitor) {
-          pixels_per_inch =
-              GetPixelsPerInchForPointerDevice(pointer_devices[i].device);
+      for (const auto& device : pointer_devices) {
+        if (device.pointerDeviceType == POINTER_DEVICE_TYPE_TOUCH &&
+            device.monitor == monitor) {
+          pixels_per_inch = GetPixelsPerInchForPointerDevice(device.device);
           break;
         }
       }
     }
   }
 
-  display_infos->push_back(
-      DisplayInfo(monitor_info, GetMonitorScaleFactor(monitor),
-                  GetMonitorSDRWhiteLevel(monitor), rotation, display_frequency,
-                  pixels_per_inch));
+  auto* display_infos = reinterpret_cast<std::vector<DisplayInfo>*>(data);
+  DCHECK(display_infos);
+  display_infos->emplace_back(monitor_info, GetMonitorScaleFactor(monitor),
+                              GetMonitorSDRWhiteLevel(monitor), rotation,
+                              display_frequency, pixels_per_inch);
   return TRUE;
 }
 
@@ -445,22 +403,20 @@
   std::vector<DisplayInfo> display_infos;
   EnumDisplayMonitors(nullptr, nullptr, EnumMonitorForDisplayInfoCallback,
                       reinterpret_cast<LPARAM>(&display_infos));
-  DCHECK_EQ(static_cast<size_t>(::GetSystemMetrics(SM_CMONITORS)),
-            display_infos.size());
+  DCHECK_EQ(::GetSystemMetrics(SM_CMONITORS), int{display_infos.size()});
   return display_infos;
 }
 
-// Returns a point in |to_origin|'s coordinates and position scaled by
-// |scale_factor|.
+// Returns |point|, transformed from |from_origin|'s to |to_origin|'s
+// coordinates, which differ by |scale_factor|.
 gfx::PointF ScalePointRelative(const gfx::Point& from_origin,
                                const gfx::Point& to_origin,
                                const float scale_factor,
                                const gfx::PointF& point) {
-  gfx::Vector2d from_origin_vector(from_origin.x(), from_origin.y());
-  gfx::Vector2d to_origin_vector(to_origin.x(), to_origin.y());
-  gfx::PointF scaled_relative_point(
-      gfx::ScalePoint(point - from_origin_vector, scale_factor));
-  return scaled_relative_point + to_origin_vector;
+  const gfx::PointF relative_point = point - from_origin.OffsetFromOrigin();
+  const gfx::PointF scaled_relative_point =
+      gfx::ScalePoint(relative_point, scale_factor);
+  return scaled_relative_point + to_origin.OffsetFromOrigin();
 }
 
 }  // namespace
@@ -468,8 +424,8 @@
 ScreenWin::ScreenWin() : ScreenWin(true) {}
 
 ScreenWin::~ScreenWin() {
-  DCHECK_EQ(g_screen_win_instance, this);
-  g_screen_win_instance = nullptr;
+  DCHECK_EQ(g_instance, this);
+  g_instance = nullptr;
 }
 
 // static
@@ -485,9 +441,8 @@
 
 // static
 gfx::Point ScreenWin::DIPToScreenPoint(const gfx::Point& dip_point) {
-  const ScreenWinDisplay screen_win_display =
-      GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestDIPPoint,
-                             dip_point);
+  const ScreenWinDisplay screen_win_display = GetScreenWinDisplayVia(
+      &ScreenWin::GetScreenWinDisplayNearestDIPPoint, dip_point);
   const Display display = screen_win_display.display();
   return gfx::ToFlooredPoint(ScalePointRelative(
       display.bounds().origin(), screen_win_display.pixel_bounds().origin(),
@@ -502,8 +457,7 @@
 
 // static
 gfx::Point ScreenWin::DIPToClientPoint(HWND hwnd, const gfx::Point& dip_point) {
-  float scale_factor = GetScaleFactorForHWND(hwnd);
-  return ScaleToFlooredPoint(dip_point, scale_factor);
+  return ScaleToFlooredPoint(dip_point, GetScaleFactorForHWND(hwnd));
 }
 
 // static
@@ -512,12 +466,13 @@
       ? GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestHWND, hwnd)
       : GetScreenWinDisplayVia(
             &ScreenWin::GetScreenWinDisplayNearestScreenRect, pixel_bounds);
-  float scale_factor = screen_win_display.display().device_scale_factor();
-  gfx::Rect dip_rect = ScaleToEnclosingRect(pixel_bounds, 1.0f / scale_factor);
+  const float scale_factor =
+      1.0f / screen_win_display.display().device_scale_factor();
+  gfx::Rect dip_rect = ScaleToEnclosingRect(pixel_bounds, scale_factor);
   const Display display = screen_win_display.display();
   dip_rect.set_origin(gfx::ToFlooredPoint(ScalePointRelative(
       screen_win_display.pixel_bounds().origin(), display.bounds().origin(),
-      1.0f / scale_factor, gfx::PointF(pixel_bounds.origin()))));
+      scale_factor, gfx::PointF(pixel_bounds.origin()))));
   return dip_rect;
 }
 
@@ -555,66 +510,53 @@
 
 // static
 gfx::Size ScreenWin::DIPToScreenSize(HWND hwnd, const gfx::Size& dip_size) {
-  float scale_factor = GetScaleFactorForHWND(hwnd);
   // Always ceil sizes. Otherwise we may be leaving off part of the bounds.
-  return ScaleToCeiledSize(dip_size, scale_factor);
+  return ScaleToCeiledSize(dip_size, GetScaleFactorForHWND(hwnd));
 }
 
 // static
 int ScreenWin::GetSystemMetricsForMonitor(HMONITOR monitor, int metric) {
-  if (!g_screen_win_instance)
+  if (!g_instance)
     return ::GetSystemMetrics(metric);
 
+  // Fall back to the primary display's HMONITOR.
+  if (!monitor)
+    monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
+
   // We don't include fudge factors stemming from accessiblility features when
   // dealing with system metrics associated with window elements drawn by the
   // operating system, since we will not be doing scaling of those metrics
   // ourselves.
-  bool include_accessibility;
-  switch (metric) {
-    case SM_CXSIZEFRAME:
-    case SM_CYSIZEFRAME:
-    case SM_CXPADDEDBORDER:
-      include_accessibility = false;
-      break;
-    default:
-      include_accessibility = true;
-      break;
-  }
-
-  // We'll want to use GetSafeMonitorScaleFactor(), so if the monitor is not
-  // specified pull up the primary display's HMONITOR.
-  if (!monitor)
-    monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
-
-  float scale_factor = GetMonitorScaleFactor(monitor, include_accessibility);
+  const bool include_accessibility = (metric != SM_CXSIZEFRAME) &&
+                                     (metric != SM_CYSIZEFRAME) &&
+                                     (metric != SM_CXPADDEDBORDER);
 
   // We'll then pull up the system metrics scaled by the appropriate amount.
-  return GetSystemMetricsForScaleFactor(scale_factor, metric);
+  return g_instance->GetSystemMetricsForScaleFactor(
+      GetMonitorScaleFactor(monitor, include_accessibility), metric);
 }
 
 // static
 int ScreenWin::GetSystemMetricsInDIP(int metric) {
-  if (!g_screen_win_instance)
-    return ::GetSystemMetrics(metric);
-
-  return GetSystemMetricsForScaleFactor(1.0f, metric);
+  return g_instance ? g_instance->GetSystemMetricsForScaleFactor(1.0f, metric)
+                    : ::GetSystemMetrics(metric);
 }
 
 // static
 float ScreenWin::GetScaleFactorForHWND(HWND hwnd) {
-  if (!g_screen_win_instance)
+  if (!g_instance)
     return ScreenWinDisplay().display().device_scale_factor();
 
   DCHECK(hwnd);
-  HWND rootHwnd = g_screen_win_instance->GetRootWindow(hwnd);
+  HWND root_hwnd = g_instance->GetRootWindow(hwnd);
   ScreenWinDisplay screen_win_display =
-      g_screen_win_instance->GetScreenWinDisplayNearestHWND(rootHwnd);
+      g_instance->GetScreenWinDisplayNearestHWND(root_hwnd);
   return screen_win_display.display().device_scale_factor();
 }
 
 // static
 gfx::Vector2dF ScreenWin::GetPixelsPerInch(const gfx::PointF& point) {
-  ScreenWinDisplay screen_win_display =
+  const ScreenWinDisplay screen_win_display =
       GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestDIPPoint,
                              gfx::ToFlooredPoint(point));
   return screen_win_display.pixels_per_inch();
@@ -625,7 +567,7 @@
   if (Display::HasForceDeviceScaleFactor())
     return GetDPIFromScalingFactor(Display::GetForcedDeviceScaleFactor());
 
-  HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+  const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
   int dpi = GetPerMonitorDPI(monitor);
   return dpi ? dpi : display::win::internal::GetDefaultSystemDPI();
 }
@@ -644,22 +586,19 @@
 // static
 void ScreenWin::SetRequestHDRStatusCallback(
     RequestHDRStatusCallback request_hdr_status_callback) {
-  if (!g_screen_win_instance)
-    return;
-  g_screen_win_instance->request_hdr_status_callback_ =
-      std::move(request_hdr_status_callback);
-  g_screen_win_instance->request_hdr_status_callback_.Run();
+  if (g_instance) {
+    g_instance->request_hdr_status_callback_ =
+        std::move(request_hdr_status_callback);
+    g_instance->request_hdr_status_callback_.Run();
+  }
 }
 
 // static
 void ScreenWin::SetHDREnabled(bool hdr_enabled) {
-  if (!g_screen_win_instance)
-    return;
-
-  if (g_screen_win_instance->hdr_enabled_ == hdr_enabled)
-    return;
-  g_screen_win_instance->hdr_enabled_ = hdr_enabled;
-  g_screen_win_instance->UpdateAllDisplaysAndNotify();
+  if (g_instance && (g_instance->hdr_enabled_ != hdr_enabled)) {
+    g_instance->hdr_enabled_ = hdr_enabled;
+    g_instance->UpdateAllDisplaysAndNotify();
+  }
 }
 
 HWND ScreenWin::GetHWNDFromNativeView(gfx::NativeView window) const {
@@ -673,8 +612,8 @@
 }
 
 ScreenWin::ScreenWin(bool initialize) {
-  DCHECK(!g_screen_win_instance);
-  g_screen_win_instance = this;
+  DCHECK(!g_instance);
+  g_instance = this;
   if (initialize)
     Initialize();
 }
@@ -682,8 +621,7 @@
 gfx::Point ScreenWin::GetCursorScreenPoint() {
   POINT pt;
   ::GetCursorPos(&pt);
-  gfx::PointF cursor_pos_pixels(pt.x, pt.y);
-  return gfx::ToFlooredPoint(ScreenToDIPPoint(cursor_pos_pixels));
+  return gfx::ToFlooredPoint(ScreenToDIPPoint(gfx::PointF(gfx::Point(pt))));
 }
 
 bool ScreenWin::IsWindowUnderCursor(gfx::NativeWindow window) {
@@ -694,12 +632,12 @@
 }
 
 gfx::NativeWindow ScreenWin::GetWindowAtScreenPoint(const gfx::Point& point) {
-  gfx::Point point_in_pixels = DIPToScreenPoint(point);
-  return GetNativeWindowFromHWND(WindowFromPoint(point_in_pixels.ToPOINT()));
+  const gfx::Point screen_point = DIPToScreenPoint(point);
+  return GetNativeWindowFromHWND(WindowFromPoint(screen_point.ToPOINT()));
 }
 
 int ScreenWin::GetNumDisplays() const {
-  return static_cast<int>(screen_win_displays_.size());
+  return int{screen_win_displays_.size()};
 }
 
 const std::vector<Display>& ScreenWin::GetAllDisplays() const {
@@ -707,31 +645,20 @@
 }
 
 Display ScreenWin::GetDisplayNearestWindow(gfx::NativeWindow window) const {
-  if (!window)
-    return GetPrimaryDisplay();
-  HWND window_hwnd = GetHWNDFromNativeView(window);
-  if (!window_hwnd) {
-    // When |window| isn't rooted to a display, we should just return the
-    // default display so we get some correct display information like the
-    // scaling factor.
-    return GetPrimaryDisplay();
-  }
-  ScreenWinDisplay screen_win_display =
-      GetScreenWinDisplayNearestHWND(window_hwnd);
-  return screen_win_display.display();
+  const HWND window_hwnd = window ? GetHWNDFromNativeView(window) : nullptr;
+  // When |window| isn't rooted to a display, we should just return the default
+  // display so we get some correct display information like the scaling factor.
+  return window_hwnd ? GetScreenWinDisplayNearestHWND(window_hwnd).display()
+                     : GetPrimaryDisplay();
 }
 
 Display ScreenWin::GetDisplayNearestPoint(const gfx::Point& point) const {
-  gfx::Point screen_point(DIPToScreenPoint(point));
-  ScreenWinDisplay screen_win_display =
-      GetScreenWinDisplayNearestScreenPoint(screen_point);
-  return screen_win_display.display();
+  const gfx::Point screen_point = DIPToScreenPoint(point);
+  return GetScreenWinDisplayNearestScreenPoint(screen_point).display();
 }
 
 Display ScreenWin::GetDisplayMatching(const gfx::Rect& match_rect) const {
-  ScreenWinDisplay screen_win_display =
-      GetScreenWinDisplayNearestScreenRect(match_rect);
-  return screen_win_display.display();
+  return GetScreenWinDisplayNearestScreenRect(match_rect).display();
 }
 
 Display ScreenWin::GetPrimaryDisplay() const {
@@ -747,14 +674,15 @@
 }
 
 gfx::Rect ScreenWin::ScreenToDIPRectInWindow(
-    gfx::NativeView view, const gfx::Rect& screen_rect) const {
-  HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
+    gfx::NativeView view,
+    const gfx::Rect& screen_rect) const {
+  const HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
   return ScreenToDIPRect(hwnd, screen_rect);
 }
 
 gfx::Rect ScreenWin::DIPToScreenRectInWindow(gfx::NativeView view,
                                              const gfx::Rect& dip_rect) const {
-  HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
+  const HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
   return DIPToScreenRect(hwnd, dip_rect);
 }
 
@@ -767,8 +695,8 @@
 
 void ScreenWin::Initialize() {
   color_profile_reader_->UpdateIfNeeded();
-  singleton_hwnd_observer_.reset(new gfx::SingletonHwndObserver(
-      base::BindRepeating(&ScreenWin::OnWndProc, base::Unretained(this))));
+  singleton_hwnd_observer_ = std::make_unique<gfx::SingletonHwndObserver>(
+      base::BindRepeating(&ScreenWin::OnWndProc, base::Unretained(this)));
   UpdateFromDisplayInfos(GetDisplayInfosFromSystem());
   RecordDisplayScaleFactors();
 
@@ -780,16 +708,15 @@
 
 MONITORINFOEX ScreenWin::MonitorInfoFromScreenPoint(
     const gfx::Point& screen_point) const {
-  POINT initial_loc = { screen_point.x(), screen_point.y() };
-  return MonitorInfoFromHMONITOR(::MonitorFromPoint(initial_loc,
-                                                    MONITOR_DEFAULTTONEAREST));
+  return MonitorInfoFromHMONITOR(
+      ::MonitorFromPoint(screen_point.ToPOINT(), MONITOR_DEFAULTTONEAREST));
 }
 
 MONITORINFOEX ScreenWin::MonitorInfoFromScreenRect(const gfx::Rect& screen_rect)
     const {
-  RECT win_rect = screen_rect.ToRECT();
-  return MonitorInfoFromHMONITOR(::MonitorFromRect(&win_rect,
-                                                   MONITOR_DEFAULTTONEAREST));
+  const RECT win_rect = screen_rect.ToRECT();
+  return MonitorInfoFromHMONITOR(
+      ::MonitorFromRect(&win_rect, MONITOR_DEFAULTTONEAREST));
 }
 
 MONITORINFOEX ScreenWin::MonitorInfoFromWindow(HWND hwnd,
@@ -810,10 +737,9 @@
                           WPARAM wparam,
                           LPARAM lparam) {
   if (message != WM_DISPLAYCHANGE &&
-      !(message == WM_ACTIVATEAPP && wparam == TRUE) &&
-      !(message == WM_SETTINGCHANGE && wparam == SPI_SETWORKAREA)) {
+      (message != WM_ACTIVATEAPP || wparam != TRUE) &&
+      (message != WM_SETTINGCHANGE || wparam != SPI_SETWORKAREA))
     return;
-  }
 
   color_profile_reader_->UpdateIfNeeded();
   if (request_hdr_status_callback_)
@@ -845,8 +771,7 @@
   change_notifier_.NotifyDisplaysChanged(old_displays, displays_);
 }
 
-ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestHWND(HWND hwnd)
-    const {
+ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestHWND(HWND hwnd) const {
   return GetScreenWinDisplay(MonitorInfoFromWindow(hwnd,
                                                    MONITOR_DEFAULTTONEAREST));
 }
@@ -865,11 +790,10 @@
     const gfx::Point& dip_point) const {
   ScreenWinDisplay primary_screen_win_display;
   for (const auto& screen_win_display : screen_win_displays_) {
-    Display display = screen_win_display.display();
-    const gfx::Rect dip_bounds = display.bounds();
+    const gfx::Rect dip_bounds = screen_win_display.display().bounds();
     if (dip_bounds.Contains(dip_point))
       return screen_win_display;
-    else if (dip_bounds.origin().IsOrigin())
+    if (dip_bounds.origin().IsOrigin())
       primary_screen_win_display = screen_win_display;
   }
   return primary_screen_win_display;
@@ -878,16 +802,15 @@
 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestDIPRect(
     const gfx::Rect& dip_rect) const {
   ScreenWinDisplay closest_screen_win_display;
-  int64_t closest_distance_squared = INT64_MAX;
+  int64_t closest_distance = INT64_MAX;
   for (const auto& screen_win_display : screen_win_displays_) {
     Display display = screen_win_display.display();
     gfx::Rect dip_bounds = display.bounds();
     if (dip_rect.Intersects(dip_bounds))
       return screen_win_display;
-    int64_t distance_squared = SquaredDistanceBetweenRects(dip_rect,
-                                                           dip_bounds);
-    if (distance_squared < closest_distance_squared) {
-      closest_distance_squared = distance_squared;
+    int64_t distance = SquaredDistanceBetweenRects(dip_rect, dip_bounds);
+    if (distance < closest_distance) {
+      closest_distance = distance;
       closest_screen_win_display = screen_win_display;
     }
   }
@@ -895,27 +818,24 @@
 }
 
 ScreenWinDisplay ScreenWin::GetPrimaryScreenWinDisplay() const {
-  MONITORINFOEX monitor_info = MonitorInfoFromWindow(nullptr,
-                                                     MONITOR_DEFAULTTOPRIMARY);
-  ScreenWinDisplay screen_win_display = GetScreenWinDisplay(monitor_info);
-  Display display = screen_win_display.display();
+  const ScreenWinDisplay screen_win_display = GetScreenWinDisplay(
+      MonitorInfoFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY));
   // The Windows primary monitor is defined to have an origin of (0, 0).
-  DCHECK_EQ(0, display.bounds().origin().x());
-  DCHECK_EQ(0, display.bounds().origin().y());
+  DCHECK(screen_win_display.display().bounds().origin().IsOrigin());
   return screen_win_display;
 }
 
 ScreenWinDisplay ScreenWin::GetScreenWinDisplay(
     const MONITORINFOEX& monitor_info) const {
-  int64_t id = DisplayInfo::DeviceIdFromDeviceName(monitor_info.szDevice);
+  const int64_t id = DisplayInfo::DeviceIdFromDeviceName(monitor_info.szDevice);
   for (const auto& screen_win_display : screen_win_displays_) {
     if (screen_win_display.display().id() == id)
       return screen_win_display;
   }
   // There is 1:1 correspondence between MONITORINFOEX and ScreenWinDisplay.
-  // If we make it here, it means we have no displays and we should hand out the
-  // default display. [Sometimes we get here anyway: crbug.com/768845]
-  // DCHECK_EQ(screen_win_displays_.size(), 0u);
+  // If we found no screens, either there are no screens, or we're in the midst
+  // of updating our screens (see crbug.com/768845); either way, hand out the
+  // default display.
   return ScreenWinDisplay();
 }
 
@@ -923,32 +843,25 @@
 template <typename Getter, typename GetterType>
 ScreenWinDisplay ScreenWin::GetScreenWinDisplayVia(Getter getter,
                                                    GetterType value) {
-  if (!g_screen_win_instance)
-    return ScreenWinDisplay();
-
-  return (g_screen_win_instance->*getter)(value);
+  return g_instance ? (g_instance->*getter)(value) : ScreenWinDisplay();
 }
 
-// static
-int ScreenWin::GetSystemMetricsForScaleFactor(float scale_factor, int metric) {
+int ScreenWin::GetSystemMetricsForScaleFactor(float scale_factor,
+                                              int metric) const {
   if (base::win::IsProcessPerMonitorDpiAware()) {
-    using GetSystemMetricsForDpiPtr = decltype(::GetSystemMetricsForDpi)*;
-    static const auto get_metric_for_dpi_func =
-        reinterpret_cast<GetSystemMetricsForDpiPtr>(
+    static const auto get_system_metrics_for_dpi =
+        reinterpret_cast<decltype(&::GetSystemMetricsForDpi)>(
             base::win::GetUser32FunctionPointer("GetSystemMetricsForDpi"));
-    if (get_metric_for_dpi_func) {
-      return get_metric_for_dpi_func(metric,
-                                     GetDPIFromScalingFactor(scale_factor));
+    if (get_system_metrics_for_dpi) {
+      return get_system_metrics_for_dpi(metric,
+                                        GetDPIFromScalingFactor(scale_factor));
     }
   }
 
-  // Fallback for when we're running Windows 8.1, which doesn't support
-  // GetSystemMetricsForDpi and yet does support per-process dpi awareness.
-  Display primary_display(g_screen_win_instance->GetPrimaryDisplay());
-  int system_metrics_result = g_screen_win_instance->GetSystemMetrics(metric);
-
-  return static_cast<int>(std::round(scale_factor * system_metrics_result /
-                                     primary_display.device_scale_factor()));
+  // Windows 8.1 doesn't support GetSystemMetricsForDpi(), yet does support
+  // per-process dpi awareness.
+  return gfx::ToRoundedInt(GetSystemMetrics(metric) * scale_factor /
+                           GetPrimaryDisplay().device_scale_factor());
 }
 
 void ScreenWin::RecordDisplayScaleFactors() const {
@@ -958,8 +871,8 @@
         screen_win_display.display().device_scale_factor();
     // Multiply the reported value by 100 to display it as a percentage. Clamp
     // it so that if it's wildly out-of-band we won't send it to the backend.
-    const int reported_scale = std::min(
-        std::max(base::checked_cast<int>(scale_factor * 100), 0), 1000);
+    const int reported_scale = base::ClampToRange(
+        base::checked_cast<int>(scale_factor * 100), 0, 1000);
     if (!base::Contains(unique_scale_factors, reported_scale)) {
       unique_scale_factors.push_back(reported_scale);
       base::UmaHistogramSparse("UI.DeviceScale", reported_scale);
diff --git a/ui/display/win/screen_win.h b/ui/display/win/screen_win.h
index 3b48b6b..40c388c 100644
--- a/ui/display/win/screen_win.h
+++ b/ui/display/win/screen_win.h
@@ -136,7 +136,7 @@
 
   // Set a callback to use to query the status of HDR. This callback will be
   // called when the status of HDR may have changed.
-  using RequestHDRStatusCallback = base::RepeatingCallback<void()>;
+  using RequestHDRStatusCallback = base::RepeatingClosure;
   static void SetRequestHDRStatusCallback(
       RequestHDRStatusCallback request_hdr_status_callback);
 
@@ -226,7 +226,7 @@
 
   // Returns the result of GetSystemMetrics for |metric| scaled to the specified
   // |scale_factor|.
-  static int GetSystemMetricsForScaleFactor(float scale_factor, int metric);
+  int GetSystemMetricsForScaleFactor(float scale_factor, int metric) const;
 
   void RecordDisplayScaleFactors() const;
 
diff --git a/ui/display/win/screen_win_unittest.cc b/ui/display/win/screen_win_unittest.cc
index b477e94..1b4f154 100644
--- a/ui/display/win/screen_win_unittest.cc
+++ b/ui/display/win/screen_win_unittest.cc
@@ -44,13 +44,13 @@
  protected:
   // win::ScreenWin:
   HWND GetHWNDFromNativeView(gfx::NativeView window) const override {
-    // NativeView is only used as an identifier in this tests, so interchange
-    // NativeView with an HWND for convenience.
+    // NativeView is only used as an identifier in these tests, so interchange
+    // a NativeView for an HWND for convenience.
     return reinterpret_cast<HWND>(window);
   }
 
   gfx::NativeWindow GetNativeWindowFromHWND(HWND hwnd) const override {
-    // NativeWindow is only used as an identifier in this tests, so interchange
+    // NativeWindow is only used as an identifier in these tests, so interchange
     // an HWND for a NativeWindow for convenience.
     return reinterpret_cast<gfx::NativeWindow>(hwnd);
   }
diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc
index 66f0e23..df5da29 100644
--- a/ui/native_theme/common_theme.cc
+++ b/ui/native_theme/common_theme.cc
@@ -5,6 +5,7 @@
 #include "ui/native_theme/common_theme.h"
 
 #include "base/logging.h"
+#include "base/optional.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/canvas.h"
@@ -17,182 +18,176 @@
 
 namespace ui {
 
-SkColor GetAuraColor(NativeTheme::ColorId color_id,
-                     const NativeTheme* base_theme,
-                     NativeTheme::ColorScheme color_scheme) {
-  if (color_scheme == NativeTheme::ColorScheme::kDefault)
-    color_scheme = base_theme->GetDefaultSystemColorScheme();
+namespace {
 
-  // High contrast overrides the normal colors for certain ColorIds to be much
-  // darker or lighter.
-  if (base_theme->UsesHighContrastColors()) {
-    switch (color_id) {
-      case NativeTheme::kColorId_ButtonUncheckedColor:
-      case NativeTheme::kColorId_MenuBorderColor:
-      case NativeTheme::kColorId_MenuSeparatorColor:
-      case NativeTheme::kColorId_SeparatorColor:
-      case NativeTheme::kColorId_UnfocusedBorderColor:
-      case NativeTheme::kColorId_TabBottomBorder:
-        return color_scheme == NativeTheme::ColorScheme::kDark ? SK_ColorWHITE
-                                                               : SK_ColorBLACK;
-      case NativeTheme::kColorId_ButtonEnabledColor:
-      case NativeTheme::kColorId_FocusedBorderColor:
-      case NativeTheme::kColorId_ProminentButtonColor:
-        return color_scheme == NativeTheme::ColorScheme::kDark
-                   ? gfx::kGoogleBlue100
-                   : gfx::kGoogleBlue900;
-      default:
-        break;
-    }
+base::Optional<SkColor> GetHighContrastColor(
+    NativeTheme::ColorId color_id,
+    NativeTheme::ColorScheme color_scheme) {
+  switch (color_id) {
+    case NativeTheme::kColorId_ButtonUncheckedColor:
+    case NativeTheme::kColorId_MenuBorderColor:
+    case NativeTheme::kColorId_MenuSeparatorColor:
+    case NativeTheme::kColorId_SeparatorColor:
+    case NativeTheme::kColorId_UnfocusedBorderColor:
+    case NativeTheme::kColorId_TabBottomBorder:
+      return color_scheme == NativeTheme::ColorScheme::kDark ? SK_ColorWHITE
+                                                             : SK_ColorBLACK;
+    case NativeTheme::kColorId_ButtonEnabledColor:
+    case NativeTheme::kColorId_FocusedBorderColor:
+    case NativeTheme::kColorId_ProminentButtonColor:
+      return color_scheme == NativeTheme::ColorScheme::kDark
+                 ? gfx::kGoogleBlue100
+                 : gfx::kGoogleBlue900;
+    default:
+      return base::nullopt;
   }
+}
 
-  if (color_scheme == NativeTheme::ColorScheme::kDark) {
-    switch (color_id) {
-      // Dialogs
-      case NativeTheme::kColorId_WindowBackground:
-      case NativeTheme::kColorId_DialogBackground:
-      case NativeTheme::kColorId_BubbleBackground:
-        return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900,
-                                       0.04f);
-      case NativeTheme::kColorId_DialogForeground:
-        return gfx::kGoogleGrey500;
-      case NativeTheme::kColorId_BubbleFooterBackground:
-        return SkColorSetRGB(0x32, 0x36, 0x39);
+base::Optional<SkColor> GetDarkSchemeColor(NativeTheme::ColorId color_id) {
+  switch (color_id) {
+    // Dialogs
+    case NativeTheme::kColorId_WindowBackground:
+    case NativeTheme::kColorId_DialogBackground:
+    case NativeTheme::kColorId_BubbleBackground:
+      return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900, 0.04f);
+    case NativeTheme::kColorId_DialogForeground:
+      return gfx::kGoogleGrey500;
+    case NativeTheme::kColorId_BubbleFooterBackground:
+      return SkColorSetRGB(0x32, 0x36, 0x39);
 
-      // FocusableBorder
-      case NativeTheme::kColorId_FocusedBorderColor:
-        return SkColorSetA(gfx::kGoogleBlue300, 0x4D);
-      case NativeTheme::kColorId_UnfocusedBorderColor:
-        return gfx::kGoogleGrey800;
+    // FocusableBorder
+    case NativeTheme::kColorId_FocusedBorderColor:
+      return SkColorSetA(gfx::kGoogleBlue300, 0x4D);
+    case NativeTheme::kColorId_UnfocusedBorderColor:
+      return gfx::kGoogleGrey800;
 
-      // Button
-      case NativeTheme::kColorId_ButtonBorderColor:
-        return gfx::kGoogleGrey800;
-      case NativeTheme::kColorId_ButtonEnabledColor:
-      case NativeTheme::kColorId_ProminentButtonColor:
-        return gfx::kGoogleBlue300;
-      case NativeTheme::kColorId_ButtonHoverColor:
-        return SkColorSetA(SK_ColorBLACK, 0x0A);
-      case NativeTheme::kColorId_ButtonInkDropShadowColor:
-        return SkColorSetA(SK_ColorBLACK, 0x7F);
-      case NativeTheme::kColorId_ButtonInkDropFillColor:
-      case NativeTheme::kColorId_ProminentButtonInkDropFillColor:
-        return SkColorSetA(SK_ColorWHITE, 0x0A);
-      case NativeTheme::kColorId_ProminentButtonInkDropShadowColor:
-        return SkColorSetA(gfx::kGoogleBlue300, 0x7F);
-      case NativeTheme::kColorId_ProminentButtonHoverColor:
-        return SkColorSetA(SK_ColorWHITE, 0x0A);
-      case NativeTheme::kColorId_ButtonUncheckedColor:
-        return gfx::kGoogleGrey500;
-      case NativeTheme::kColorId_TextOnProminentButtonColor:
-        return gfx::kGoogleGrey900;
+    // Button
+    case NativeTheme::kColorId_ButtonBorderColor:
+      return gfx::kGoogleGrey800;
+    case NativeTheme::kColorId_ButtonEnabledColor:
+    case NativeTheme::kColorId_ProminentButtonColor:
+      return gfx::kGoogleBlue300;
+    case NativeTheme::kColorId_ButtonHoverColor:
+      return SkColorSetA(SK_ColorBLACK, 0x0A);
+    case NativeTheme::kColorId_ButtonInkDropShadowColor:
+      return SkColorSetA(SK_ColorBLACK, 0x7F);
+    case NativeTheme::kColorId_ButtonInkDropFillColor:
+    case NativeTheme::kColorId_ProminentButtonInkDropFillColor:
+      return SkColorSetA(SK_ColorWHITE, 0x0A);
+    case NativeTheme::kColorId_ProminentButtonInkDropShadowColor:
+      return SkColorSetA(gfx::kGoogleBlue300, 0x7F);
+    case NativeTheme::kColorId_ProminentButtonHoverColor:
+      return SkColorSetA(SK_ColorWHITE, 0x0A);
+    case NativeTheme::kColorId_ButtonUncheckedColor:
+      return gfx::kGoogleGrey500;
+    case NativeTheme::kColorId_TextOnProminentButtonColor:
+      return gfx::kGoogleGrey900;
 
-      // MenuItem
-      case NativeTheme::kColorId_HighlightedMenuItemForegroundColor:
-      case NativeTheme::kColorId_MenuDropIndicator:
-        return gfx::kGoogleGrey200;
-      case NativeTheme::kColorId_MenuBorderColor:
-      case NativeTheme::kColorId_MenuSeparatorColor:
-        return gfx::kGoogleGrey800;
-      case NativeTheme::kColorId_HighlightedMenuItemBackgroundColor:
-        return SkColorSetRGB(0x32, 0x36, 0x39);
-      case NativeTheme::kColorId_MenuItemAlertBackgroundColor:
-        return gfx::kGoogleBlue300;
-      case NativeTheme::kColorId_MenuItemMinorTextColor:
-        return gfx::kGoogleGrey500;
+    // MenuItem
+    case NativeTheme::kColorId_HighlightedMenuItemForegroundColor:
+    case NativeTheme::kColorId_MenuDropIndicator:
+      return gfx::kGoogleGrey200;
+    case NativeTheme::kColorId_MenuBorderColor:
+    case NativeTheme::kColorId_MenuSeparatorColor:
+      return gfx::kGoogleGrey800;
+    case NativeTheme::kColorId_HighlightedMenuItemBackgroundColor:
+      return SkColorSetRGB(0x32, 0x36, 0x39);
+    case NativeTheme::kColorId_MenuItemAlertBackgroundColor:
+      return gfx::kGoogleBlue300;
+    case NativeTheme::kColorId_MenuItemMinorTextColor:
+      return gfx::kGoogleGrey500;
 
-      // Dropdown
-      case NativeTheme::kColorId_DropdownBackgroundColor:
-        return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900,
-                                       0.04f);
-      case NativeTheme::kColorId_DropdownForegroundColor:
-        return gfx::kGoogleGrey200;
-      case NativeTheme::kColorId_DropdownSelectedForegroundColor:
-        return gfx::kGoogleGrey200;
+    // Dropdown
+    case NativeTheme::kColorId_DropdownBackgroundColor:
+      return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900, 0.04f);
+    case NativeTheme::kColorId_DropdownForegroundColor:
+      return gfx::kGoogleGrey200;
+    case NativeTheme::kColorId_DropdownSelectedForegroundColor:
+      return gfx::kGoogleGrey200;
 
-      // Label
-      case NativeTheme::kColorId_LabelEnabledColor:
-      case NativeTheme::kColorId_LabelTextSelectionColor:
-        return gfx::kGoogleGrey200;
-      case NativeTheme::kColorId_LabelSecondaryColor:
-        return gfx::kGoogleGrey500;
-      case NativeTheme::kColorId_LabelTextSelectionBackgroundFocused:
-        return gfx::kGoogleBlue800;
+    // Label
+    case NativeTheme::kColorId_LabelEnabledColor:
+    case NativeTheme::kColorId_LabelTextSelectionColor:
+      return gfx::kGoogleGrey200;
+    case NativeTheme::kColorId_LabelSecondaryColor:
+      return gfx::kGoogleGrey500;
+    case NativeTheme::kColorId_LabelTextSelectionBackgroundFocused:
+      return gfx::kGoogleBlue800;
 
-      // Link
-      case NativeTheme::kColorId_LinkEnabled:
-      case NativeTheme::kColorId_LinkPressed:
-        return gfx::kGoogleBlue300;
+    // Link
+    case NativeTheme::kColorId_LinkEnabled:
+    case NativeTheme::kColorId_LinkPressed:
+      return gfx::kGoogleBlue300;
 
-      // Separator
-      case NativeTheme::kColorId_SeparatorColor:
-        return gfx::kGoogleGrey800;
+    // Separator
+    case NativeTheme::kColorId_SeparatorColor:
+      return gfx::kGoogleGrey800;
 
-      // TabbedPane
-      case NativeTheme::kColorId_TabTitleColorActive:
-        return gfx::kGoogleBlue300;
-      case NativeTheme::kColorId_TabTitleColorInactive:
-        return gfx::kGoogleGrey500;
-      case NativeTheme::kColorId_TabBottomBorder:
-        return gfx::kGoogleGrey800;
-      case NativeTheme::kColorId_TabHighlightBackground:
-        return gfx::kGoogleGrey800;
-      case NativeTheme::kColorId_TabHighlightFocusedBackground:
-        return SkColorSetRGB(0x32, 0x36, 0x39);
+    // TabbedPane
+    case NativeTheme::kColorId_TabTitleColorActive:
+      return gfx::kGoogleBlue300;
+    case NativeTheme::kColorId_TabTitleColorInactive:
+      return gfx::kGoogleGrey500;
+    case NativeTheme::kColorId_TabBottomBorder:
+      return gfx::kGoogleGrey800;
+    case NativeTheme::kColorId_TabHighlightBackground:
+      return gfx::kGoogleGrey800;
+    case NativeTheme::kColorId_TabHighlightFocusedBackground:
+      return SkColorSetRGB(0x32, 0x36, 0x39);
 
-      // Table
-      case NativeTheme::kColorId_TableBackground:
-      case NativeTheme::kColorId_TableBackgroundAlternate:
-        return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900,
-                                       0.04f);
-      case NativeTheme::kColorId_TableText:
-      case NativeTheme::kColorId_TableSelectedText:
-      case NativeTheme::kColorId_TableSelectedTextUnfocused:
-        return gfx::kGoogleGrey200;
+    // Table
+    case NativeTheme::kColorId_TableBackground:
+    case NativeTheme::kColorId_TableBackgroundAlternate:
+      return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900, 0.04f);
+    case NativeTheme::kColorId_TableText:
+    case NativeTheme::kColorId_TableSelectedText:
+    case NativeTheme::kColorId_TableSelectedTextUnfocused:
+      return gfx::kGoogleGrey200;
 
-      // Textfield
-      case NativeTheme::kColorId_TextfieldDefaultColor:
-      case NativeTheme::kColorId_TextfieldSelectionColor:
-        return gfx::kGoogleGrey200;
-      case NativeTheme::kColorId_TextfieldReadOnlyBackground: {
-        return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900,
-                                       0.04f);
-      }
-      case NativeTheme::kColorId_TextfieldSelectionBackgroundFocused:
-        return gfx::kGoogleBlue800;
-
-      // Tooltip
-      case NativeTheme::kColorId_TooltipText:
-        return SkColorSetA(gfx::kGoogleGrey200, 0xDE);
-
-      // Tree
-      case NativeTheme::kColorId_TreeBackground:
-        return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900,
-                                       0.04f);
-      case NativeTheme::kColorId_TreeText:
-      case NativeTheme::kColorId_TreeSelectedText:
-      case NativeTheme::kColorId_TreeSelectedTextUnfocused:
-        return gfx::kGoogleGrey200;
-
-      // Material spinner/throbber
-      case NativeTheme::kColorId_ThrobberSpinningColor:
-        return gfx::kGoogleBlue300;
-
-      // Alert icon colors
-      case NativeTheme::kColorId_AlertSeverityLow:
-        return gfx::kGoogleGreen300;
-      case NativeTheme::kColorId_AlertSeverityMedium:
-        return gfx::kGoogleYellow300;
-      case NativeTheme::kColorId_AlertSeverityHigh:
-        return gfx::kGoogleRed300;
-
-      case NativeTheme::kColorId_DefaultIconColor:
-        return gfx::kGoogleGrey500;
-      default:
-        break;
+    // Textfield
+    case NativeTheme::kColorId_TextfieldDefaultColor:
+    case NativeTheme::kColorId_TextfieldSelectionColor:
+      return gfx::kGoogleGrey200;
+    case NativeTheme::kColorId_TextfieldReadOnlyBackground: {
+      return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900, 0.04f);
     }
-  }
+    case NativeTheme::kColorId_TextfieldSelectionBackgroundFocused:
+      return gfx::kGoogleBlue800;
 
+    // Tooltip
+    case NativeTheme::kColorId_TooltipText:
+      return SkColorSetA(gfx::kGoogleGrey200, 0xDE);
+
+    // Tree
+    case NativeTheme::kColorId_TreeBackground:
+      return color_utils::AlphaBlend(SK_ColorWHITE, gfx::kGoogleGrey900, 0.04f);
+    case NativeTheme::kColorId_TreeText:
+    case NativeTheme::kColorId_TreeSelectedText:
+    case NativeTheme::kColorId_TreeSelectedTextUnfocused:
+      return gfx::kGoogleGrey200;
+
+    // Material spinner/throbber
+    case NativeTheme::kColorId_ThrobberSpinningColor:
+      return gfx::kGoogleBlue300;
+
+    // Alert icon colors
+    case NativeTheme::kColorId_AlertSeverityLow:
+      return gfx::kGoogleGreen300;
+    case NativeTheme::kColorId_AlertSeverityMedium:
+      return gfx::kGoogleYellow300;
+    case NativeTheme::kColorId_AlertSeverityHigh:
+      return gfx::kGoogleRed300;
+
+    case NativeTheme::kColorId_DefaultIconColor:
+      return gfx::kGoogleGrey500;
+    default:
+      return base::nullopt;
+  }
+}
+
+SkColor GetDefaultColor(NativeTheme::ColorId color_id,
+                        const NativeTheme* base_theme,
+                        NativeTheme::ColorScheme color_scheme) {
   constexpr SkColor kPrimaryTextColor = gfx::kGoogleGrey900;
 
 
@@ -495,11 +490,38 @@
           NativeTheme::kColorId_BubbleFooterBackground);
 
     case NativeTheme::kColorId_NumColors:
-      break;
+      // Keeping the kColorId_NumColors case instead of using the default case
+      // allows ColorId additions to trigger compile error for an incomplete
+      // switch enumeration.
+      NOTREACHED();
+      return gfx::kPlaceholderColor;
+  }
+}
+
+}  // namespace
+
+SkColor GetAuraColor(NativeTheme::ColorId color_id,
+                     const NativeTheme* base_theme,
+                     NativeTheme::ColorScheme color_scheme) {
+  if (color_scheme == NativeTheme::ColorScheme::kDefault)
+    color_scheme = base_theme->GetDefaultSystemColorScheme();
+
+  // High contrast overrides the normal colors for certain ColorIds to be much
+  // darker or lighter.
+  if (base_theme->UsesHighContrastColors()) {
+    base::Optional<SkColor> color =
+        GetHighContrastColor(color_id, color_scheme);
+    if (color.has_value())
+      return color.value();
   }
 
-  NOTREACHED();
-  return gfx::kPlaceholderColor;
+  if (color_scheme == NativeTheme::ColorScheme::kDark) {
+    base::Optional<SkColor> color = GetDarkSchemeColor(color_id);
+    if (color.has_value())
+      return color.value();
+  }
+
+  return GetDefaultColor(color_id, base_theme, color_scheme);
 }
 
 void CommonThemePaintMenuItemBackground(
diff --git a/ui/ozone/platform/drm/gpu/crtc_controller.cc b/ui/ozone/platform/drm/gpu/crtc_controller.cc
index b483f77..a1bfaede 100644
--- a/ui/ozone/platform/drm/gpu/crtc_controller.cc
+++ b/ui/ozone/platform/drm/gpu/crtc_controller.cc
@@ -80,8 +80,11 @@
 }
 
 bool CrtcController::AssignOverlayPlanes(HardwareDisplayPlaneList* plane_list,
-                                         const DrmOverlayPlaneList& overlays) {
-  DCHECK(!is_disabled_);
+                                         const DrmOverlayPlaneList& overlays,
+                                         bool is_modesetting) {
+  // If we're in the process of modesetting, the CRTC is still disabled.
+  // Once the modeset is done, we expect it to be enabled.
+  DCHECK(is_modesetting || !is_disabled_);
 
   const DrmOverlayPlane* primary = DrmOverlayPlane::GetPrimaryPlane(overlays);
   if (primary && !drm_->plane_manager()->ValidatePrimarySize(*primary, mode_)) {
diff --git a/ui/ozone/platform/drm/gpu/crtc_controller.h b/ui/ozone/platform/drm/gpu/crtc_controller.h
index 9742c4cb..bfc2bad 100644
--- a/ui/ozone/platform/drm/gpu/crtc_controller.h
+++ b/ui/ozone/platform/drm/gpu/crtc_controller.h
@@ -51,7 +51,8 @@
   bool Disable();
 
   bool AssignOverlayPlanes(HardwareDisplayPlaneList* plane_list,
-                           const DrmOverlayPlaneList& planes);
+                           const DrmOverlayPlaneList& planes,
+                           bool is_modesetting);
 
   // Returns a vector of format modifiers for the given fourcc format
   // on this CRTCs primary plane. A format modifier describes the
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_controller.cc b/ui/ozone/platform/drm/gpu/hardware_display_controller.cc
index 7cc196a..6f24033 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_controller.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_controller.cc
@@ -90,10 +90,20 @@
                                             const drmModeModeInfo& mode) {
   DCHECK(primary.buffer.get());
   bool status = true;
-  for (const auto& controller : crtc_controllers_)
+
+  GetDrmDevice()->plane_manager()->BeginFrame(&owned_hardware_planes_);
+  DrmOverlayPlaneList plane_list;
+  plane_list.push_back(primary.Clone());
+
+  for (const auto& controller : crtc_controllers_) {
+    status &=
+        controller->AssignOverlayPlanes(&owned_hardware_planes_, plane_list,
+                                        /*is_modesetting=*/true);
+
     status &= controller->Modeset(
         primary, use_current_crtc_mode ? controller->mode() : mode,
         owned_hardware_planes_);
+  }
 
   is_disabled_ = false;
   ResetCursor();
@@ -176,12 +186,13 @@
 
   bool status = true;
   for (const auto& controller : crtc_controllers_) {
-    status &= controller->AssignOverlayPlanes(&owned_hardware_planes_,
-                                              pending_planes);
+    status &= controller->AssignOverlayPlanes(
+        &owned_hardware_planes_, pending_planes, /*is_modesetting=*/false);
   }
 
   status &= GetDrmDevice()->plane_manager()->Commit(
-      &owned_hardware_planes_, page_flip_request, out_fence);
+      &owned_hardware_planes_, /*should_modeset=*/false, page_flip_request,
+      out_fence);
 
   return status;
 }
@@ -357,6 +368,7 @@
   // pending planes to the same values so that the callback keeps the correct
   // state.
   page_flip_request_ = nullptr;
+  owned_hardware_planes_.legacy_page_flips.clear();
   current_planes_.clear();
   current_planes_.push_back(primary.Clone());
   time_of_last_flip_ = base::TimeTicks::Now();
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
index f451285..bdb7f9e 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_controller_unittest.cc
@@ -30,11 +30,18 @@
 const drmModeModeInfo kDefaultMode = {0, 6, 0, 0, 0, 0, 4,     0,
                                       0, 0, 0, 0, 0, 0, {'\0'}};
 
-constexpr uint32_t kCrtcIdBase = 1;
+constexpr uint32_t kCrtcIdBase = 100;
 constexpr uint32_t kPrimaryCrtc = kCrtcIdBase;
 constexpr uint32_t kSecondaryCrtc = kCrtcIdBase + 1;
-constexpr uint32_t kConnectorIdBase = 10;
-constexpr uint32_t kPlaneOffset = 1000;
+constexpr uint32_t kConnectorIdBase = 200;
+constexpr uint32_t kPlaneOffset = 300;
+constexpr uint32_t kInFormatsBlobPropId = 400;
+
+constexpr uint32_t kActivePropId = 1000;
+constexpr uint32_t kModePropId = 1001;
+constexpr uint32_t kCrtcIdPropId = 2000;
+constexpr uint32_t kTypePropId = 3010;
+constexpr uint32_t kInFormatsPropId = 3011;
 
 const gfx::Size kDefaultModeSize(kDefaultMode.hdisplay, kDefaultMode.vdisplay);
 const gfx::Size kOverlaySize(kDefaultMode.hdisplay / 2,
@@ -104,10 +111,6 @@
 }
 
 void HardwareDisplayControllerTest::InitializeDrmDevice(bool use_atomic) {
-  constexpr uint32_t kTypePropId = 3010;
-  constexpr uint32_t kInFormatsPropId = 3011;
-  constexpr uint32_t kInFormatsBlobPropId = 400;
-
   std::vector<ui::MockDrmDevice::CrtcProperties> crtc_properties(2);
   std::map<uint32_t, std::string> crtc_property_names = {
       {1000, "ACTIVE"},
@@ -116,7 +119,7 @@
 
   std::vector<ui::MockDrmDevice::ConnectorProperties> connector_properties(2);
   std::map<uint32_t, std::string> connector_property_names = {
-      {2000, "CRTC_ID"},
+      {kCrtcIdPropId, "CRTC_ID"},
   };
   for (size_t i = 0; i < connector_properties.size(); ++i) {
     connector_properties[i].id = kConnectorIdBase + i;
@@ -280,6 +283,39 @@
             internal_modifiers.end());
 }
 
+TEST_F(HardwareDisplayControllerTest, CheckModesettingSetsProps) {
+  ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
+
+  EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
+
+  ui::DrmOverlayPlane plane2(CreateBuffer(), nullptr);
+  std::vector<ui::DrmOverlayPlane> planes = {};
+  planes.push_back(plane2.Clone());
+
+  SchedulePageFlip(std::move(planes));
+
+  // Test props values after modesetting.
+  ui::DrmDevice::Property connector_prop_crtc_id = {};
+  ui::ScopedDrmObjectPropertyPtr connector_props =
+      drm_->GetObjectProperties(kConnectorIdBase, DRM_MODE_OBJECT_CONNECTOR);
+  ui::GetDrmPropertyForName(drm_.get(), connector_props.get(), "CRTC_ID",
+                            &connector_prop_crtc_id);
+  EXPECT_EQ(kCrtcIdPropId, connector_prop_crtc_id.id);
+  EXPECT_EQ(kCrtcIdBase, connector_prop_crtc_id.value);
+
+  ui::DrmDevice::Property crtc_prop_for_name = {};
+  ui::ScopedDrmObjectPropertyPtr crtc_props =
+      drm_->GetObjectProperties(kPrimaryCrtc, DRM_MODE_OBJECT_CRTC);
+  GetDrmPropertyForName(drm_.get(), crtc_props.get(), "ACTIVE",
+                        &crtc_prop_for_name);
+  EXPECT_EQ(kActivePropId, crtc_prop_for_name.id);
+  EXPECT_EQ(1U, crtc_prop_for_name.value);
+
+  GetDrmPropertyForName(drm_.get(), crtc_props.get(), "MODE_ID",
+                        &crtc_prop_for_name);
+  EXPECT_EQ(kModePropId, crtc_prop_for_name.id);
+}
+
 TEST_F(HardwareDisplayControllerTest, CheckDisableResetsProps) {
   ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
 
@@ -318,6 +354,7 @@
   ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
 
   EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
+  EXPECT_EQ(1, drm_->get_commit_count());
 
   ui::DrmOverlayPlane plane2(CreateBuffer(), nullptr);
   std::vector<ui::DrmOverlayPlane> planes;
@@ -331,13 +368,14 @@
 
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
   EXPECT_EQ(1, page_flips_);
-  EXPECT_EQ(1, drm_->get_commit_count());
+  EXPECT_EQ(2, drm_->get_commit_count());
   // Verify only the primary display have a valid framebuffer.
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_EQ(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
 }
 
 TEST_F(HardwareDisplayControllerTest, CheckStateIfModesetFails) {
+  InitializeDrmDevice(/* use_atomic */ false);
   drm_->set_set_crtc_expectation(false);
 
   ui::DrmOverlayPlane plane(CreateBuffer(), nullptr);
@@ -345,20 +383,6 @@
   EXPECT_FALSE(controller_->Modeset(plane, kDefaultMode));
 }
 
-TEST_F(HardwareDisplayControllerTest, CheckStateIfPageFlipFails) {
-  drm_->set_commit_expectation(false);
-
-  ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
-
-  EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
-
-  ui::DrmOverlayPlane plane2(CreateBuffer(), nullptr);
-  std::vector<ui::DrmOverlayPlane> planes;
-  planes.push_back(plane2.Clone());
-  EXPECT_DEATH_IF_SUPPORTED(SchedulePageFlip(std::move(planes)),
-                            "SchedulePageFlip failed");
-}
-
 TEST_F(HardwareDisplayControllerTest, CheckOverlayPresent) {
   ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
   ui::DrmOverlayPlane plane2(
@@ -366,6 +390,7 @@
       gfx::Rect(kOverlaySize), gfx::RectF(kDefaultModeSizeF), true, nullptr);
 
   EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
+  EXPECT_EQ(1, drm_->get_commit_count());
 
   std::vector<ui::DrmOverlayPlane> planes;
   planes.push_back(plane1.Clone());
@@ -375,7 +400,7 @@
   drm_->RunCallbacks();
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
   EXPECT_EQ(1, page_flips_);
-  EXPECT_EQ(1, drm_->get_commit_count());
+  EXPECT_EQ(2, drm_->get_commit_count());
   // Verify both planes on the primary display have a valid framebuffer.
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
@@ -388,13 +413,14 @@
       gfx::Rect(kOverlaySize), gfx::RectF(kDefaultModeSizeF), true, nullptr);
 
   EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
+  EXPECT_EQ(1, drm_->get_commit_count());
 
   std::vector<ui::DrmOverlayPlane> planes;
   planes.push_back(plane1.Clone());
   planes.push_back(plane2.Clone());
 
   SchedulePageFlip(ui::DrmOverlayPlane::Clone(planes));
-  EXPECT_EQ(1, drm_->get_commit_count());
+  EXPECT_EQ(2, drm_->get_commit_count());
   // Verify both planes on the primary display have a valid framebuffer.
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
@@ -404,14 +430,14 @@
   drm_->RunCallbacks();
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
   EXPECT_EQ(1, page_flips_);
-  EXPECT_EQ(2, drm_->get_commit_count());
+  EXPECT_EQ(3, drm_->get_commit_count());
 
   // Regular flips should continue on normally.
   SchedulePageFlip(ui::DrmOverlayPlane::Clone(planes));
   drm_->RunCallbacks();
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
   EXPECT_EQ(2, page_flips_);
-  EXPECT_EQ(3, drm_->get_commit_count());
+  EXPECT_EQ(4, drm_->get_commit_count());
   // Verify both planes on the primary display have a valid framebuffer.
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
@@ -442,7 +468,7 @@
 
   ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
   EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
-  EXPECT_EQ(2, drm_->get_set_crtc_call_count());
+  EXPECT_EQ(2, drm_->get_commit_count());
 
   ui::DrmOverlayPlane plane2(CreateBuffer(), nullptr);
   std::vector<ui::DrmOverlayPlane> planes;
@@ -451,7 +477,7 @@
   drm_->RunCallbacks();
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK, last_swap_result_);
   EXPECT_EQ(1, page_flips_);
-  EXPECT_EQ(1, drm_->get_commit_count());
+  EXPECT_EQ(3, drm_->get_commit_count());
   // Verify only the displays have a valid framebuffer on the primary plane.
   // First display:
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
@@ -598,10 +624,10 @@
 }
 
 TEST_F(HardwareDisplayControllerTest, FailPageFlipping) {
-  drm_->set_commit_expectation(false);
-
   ui::DrmOverlayPlane plane1(CreateBuffer(), nullptr);
   EXPECT_TRUE(controller_->Modeset(plane1, kDefaultMode));
+
+  drm_->set_commit_expectation(false);
   std::vector<ui::DrmOverlayPlane> planes;
   planes.push_back(plane1.Clone());
   EXPECT_DEATH_IF_SUPPORTED(SchedulePageFlip(std::move(planes)),
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
index b1070a0d..b24007d6 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
@@ -97,7 +97,7 @@
                                    uint32_t crtc_id);
 
   // Commit the plane states in |plane_list|.
-  //
+  // if |should_modeset| is set, it only modesets without page flipping.
   // If |page_flip_request| is null, this tests the plane configuration without
   // submitting it.
   // The fence returned in |out_fence| will signal when the currently scanned
@@ -105,6 +105,7 @@
   // |page_flip_request|. Note that the returned fence may be a nullptr
   // if the system doesn't support out fences.
   virtual bool Commit(HardwareDisplayPlaneList* plane_list,
+                      bool should_modeset,
                       scoped_refptr<PageFlipRequest> page_flip_request,
                       std::unique_ptr<gfx::GpuFence>* out_fence) = 0;
 
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.cc
index 4ec9db7..5f4a4101 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.cc
@@ -65,9 +65,32 @@
     uint32_t framebuffer_id,
     uint32_t connector_id,
     const drmModeModeInfo& mode,
-    const HardwareDisplayPlaneList&) {
-  return drm_->SetCrtc(crtc_id, framebuffer_id,
-                       std::vector<uint32_t>(1, connector_id), mode);
+    const HardwareDisplayPlaneList& plane_list) {
+  const int connector_idx = LookupConnectorIndex(connector_id);
+  DCHECK_GE(connector_idx, 0);
+  connectors_props_[connector_idx].crtc_id.value = crtc_id;
+  bool res =
+      AddPropertyIfValid(plane_list.atomic_property_set.get(), connector_id,
+                         connectors_props_[connector_idx].crtc_id);
+
+  const int crtc_idx = LookupCrtcIndex(crtc_id);
+  DCHECK_GE(crtc_idx, 0);
+  crtc_state_[crtc_idx].properties.active.value = 1UL;
+  ScopedDrmPropertyBlob mode_blob =
+      drm_->CreatePropertyBlob(&mode, sizeof(mode));
+  crtc_state_[crtc_idx].properties.mode_id.value =
+      mode_blob ? mode_blob->id() : 0;
+
+  res &= AddPropertyIfValid(plane_list.atomic_property_set.get(), crtc_id,
+                            crtc_state_[crtc_idx].properties.active);
+  res &= AddPropertyIfValid(plane_list.atomic_property_set.get(), crtc_id,
+                            crtc_state_[crtc_idx].properties.mode_id);
+
+  DCHECK(res);
+  return Commit(const_cast<HardwareDisplayPlaneList*>(&plane_list),
+                /*should_modeset=*/true,
+                /*page_flip_request=*/nullptr,
+                /*out_fence=*/nullptr);
 }
 
 bool HardwareDisplayPlaneManagerAtomic::DisableModeset(uint32_t crtc_id,
@@ -96,12 +119,19 @@
 
 bool HardwareDisplayPlaneManagerAtomic::Commit(
     HardwareDisplayPlaneList* plane_list,
+    bool should_modeset,
     scoped_refptr<PageFlipRequest> page_flip_request,
     std::unique_ptr<gfx::GpuFence>* out_fence) {
-  bool test_only = !page_flip_request;
+  bool test_only = !should_modeset && !page_flip_request;
+
   for (HardwareDisplayPlane* plane : plane_list->old_plane_list) {
     if (!base::Contains(plane_list->plane_list, plane)) {
-      // This plane is being released, so we need to zero it.
+      // |plane| is shared state between |old_plane_list| and |plane_list|.
+      // When we call BeginFrame(), we reset in_use since we need to be able to
+      // allocate the planes as needed. The current frame might not need to use
+      // |plane|, thus |plane->in_use()| would be false even though the previous
+      // frame used it. It's existence in |old_plane_list| is sufficient to
+      // signal that |plane| was in use previously.
       plane->set_in_use(false);
       HardwareDisplayPlaneAtomic* atomic_plane =
           static_cast<HardwareDisplayPlaneAtomic*>(plane);
@@ -146,11 +176,10 @@
   }
 
   uint32_t flags = 0;
-  if (test_only) {
-    flags = DRM_MODE_ATOMIC_TEST_ONLY;
-  } else {
-    flags = DRM_MODE_ATOMIC_NONBLOCK;
-  }
+  if (should_modeset)
+    flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
+  else
+    flags = test_only ? DRM_MODE_ATOMIC_TEST_ONLY : DRM_MODE_ATOMIC_NONBLOCK;
 
   // After we perform the atomic commit, and if the caller has requested an
   // out-fence, the out_fence_fds vector will contain any provided out-fence
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h
index 7567a9b..2aa11b73 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h
@@ -26,6 +26,7 @@
                const HardwareDisplayPlaneList& plane_list) override;
   bool DisableModeset(uint32_t crtc_id, uint32_t connector) override;
   bool Commit(HardwareDisplayPlaneList* plane_list,
+              bool should_modeset,
               scoped_refptr<PageFlipRequest> page_flip_request,
               std::unique_ptr<gfx::GpuFence>* out_fence) override;
   bool DisableOverlayPlanes(HardwareDisplayPlaneList* plane_list) override;
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
index 4bbd895..a09037c 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.cc
@@ -63,8 +63,11 @@
 
 bool HardwareDisplayPlaneManagerLegacy::Commit(
     HardwareDisplayPlaneList* plane_list,
+    bool should_modeset,
     scoped_refptr<PageFlipRequest> page_flip_request,
     std::unique_ptr<gfx::GpuFence>* out_fence) {
+  DCHECK(!should_modeset);
+
   bool test_only = !page_flip_request;
   if (test_only) {
     for (HardwareDisplayPlane* plane : plane_list->plane_list) {
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h
index 237689dd..668ffde7 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_legacy.h
@@ -26,6 +26,7 @@
                const HardwareDisplayPlaneList& plane_list) override;
   bool DisableModeset(uint32_t crtc_id, uint32_t connector) override;
   bool Commit(HardwareDisplayPlaneList* plane_list,
+              bool should_modeset,
               scoped_refptr<PageFlipRequest> page_flip_request,
               std::unique_ptr<gfx::GpuFence>* out_fence) override;
   bool DisableOverlayPlanes(HardwareDisplayPlaneList* plane_list) override;
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
index a9c908f7..bcfa824 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager_unittest.cc
@@ -37,6 +37,7 @@
 constexpr uint32_t kConnectorIdBase = 700;
 
 constexpr uint32_t kActivePropId = 1000;
+constexpr uint32_t kModePropId = 1001;
 constexpr uint32_t kBackgroundColorPropId = 1002;
 constexpr uint32_t kCtmPropId = 1003;
 constexpr uint32_t kGammaLutPropId = 1004;
@@ -121,7 +122,7 @@
     size_t planes_per_crtc) {
   std::map<uint32_t, std::string> crtc_property_names = {
       {kActivePropId, "ACTIVE"},
-      {1001, "MODE_ID"},
+      {kModePropId, "MODE_ID"},
   };
 
   std::vector<ui::MockDrmDevice::ConnectorProperties> connector_properties(1);
@@ -226,8 +227,8 @@
       state, assigns, crtc_properties_[crtc_idx].id));
   scoped_refptr<ui::PageFlipRequest> page_flip_request =
       base::MakeRefCounted<ui::PageFlipRequest>(base::TimeDelta());
-  ASSERT_TRUE(
-      fake_drm_->plane_manager()->Commit(state, page_flip_request, nullptr));
+  ASSERT_TRUE(fake_drm_->plane_manager()->Commit(
+      state, /*should_modeset*/ false, page_flip_request, nullptr));
 }
 
 uint64_t HardwareDisplayPlaneManagerTest::GetObjectPropertyValue(
@@ -296,8 +297,6 @@
   EXPECT_TRUE(fake_drm_->plane_manager()->Modeset(
       crtc_properties_[1].id, kFrameBuffer, kConnectorIdBase + 1, kDefaultMode,
       state));
-  // TODO(markyacoub): Add a test that fails for kConnectorIdBase +2 when atomic
-  // modeset is enabled.
   EXPECT_TRUE(fake_drm_->plane_manager()->Modeset(
       crtc_properties_[2].id, kFrameBuffer, kConnectorIdBase + 3, kDefaultMode,
       state));
@@ -455,7 +454,7 @@
       crtc_properties_[0].id, kFrameBuffer, connector_properties_[0].id,
       kDefaultMode, state));
 
-  EXPECT_EQ(0, fake_drm_->get_commit_count());
+  EXPECT_EQ(1, fake_drm_->get_commit_count());
 }
 
 TEST_P(HardwareDisplayPlaneManagerAtomicTest, DisableModeset) {
@@ -469,6 +468,40 @@
   EXPECT_EQ(1, fake_drm_->get_commit_count());
 }
 
+TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterModeset) {
+  InitializeDrmState(/*crtc_count=*/1, /*planes_per_crtc=*/1);
+  fake_drm_->InitializeState(crtc_properties_, connector_properties_,
+                             plane_properties_, property_names_,
+                             /*use_atomic=*/true);
+
+  constexpr uint32_t kFrameBuffer = 2;
+  ui::HardwareDisplayPlaneList state;
+  EXPECT_TRUE(fake_drm_->plane_manager()->Modeset(
+      crtc_properties_[0].id, kFrameBuffer, connector_properties_[0].id,
+      kDefaultMode, state));
+
+  // Test props values after modesetting.
+  ui::DrmDevice::Property connector_prop_crtc_id;
+  ui::ScopedDrmObjectPropertyPtr connector_props =
+      fake_drm_->GetObjectProperties(kConnectorIdBase,
+                                     DRM_MODE_OBJECT_CONNECTOR);
+  ui::GetDrmPropertyForName(fake_drm_.get(), connector_props.get(), "CRTC_ID",
+                            &connector_prop_crtc_id);
+  EXPECT_EQ(kCrtcIdPropId, connector_prop_crtc_id.id);
+
+  ui::DrmDevice::Property crtc_prop_for_name;
+  ui::ScopedDrmObjectPropertyPtr crtc_props =
+      fake_drm_->GetObjectProperties(kCrtcIdBase, DRM_MODE_OBJECT_CRTC);
+  ui::GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "ACTIVE",
+                            &crtc_prop_for_name);
+  EXPECT_EQ(kActivePropId, crtc_prop_for_name.id);
+  EXPECT_EQ(1U, crtc_prop_for_name.value);
+
+  ui::GetDrmPropertyForName(fake_drm_.get(), crtc_props.get(), "MODE_ID",
+                            &crtc_prop_for_name);
+  EXPECT_EQ(kModePropId, crtc_prop_for_name.id);
+}
+
 TEST_P(HardwareDisplayPlaneManagerAtomicTest, CheckPropsAfterDisable) {
   InitializeDrmState(/*crtc_count=*/1, /*planes_per_crtc=*/1);
   fake_drm_->InitializeState(crtc_properties_, connector_properties_,
@@ -570,8 +603,8 @@
   fake_drm_->plane_manager()->BeginFrame(&hdpl);
   EXPECT_TRUE(fake_drm_->plane_manager()->AssignOverlayPlanes(
       &hdpl, assigns, crtc_properties_[0].id));
-  EXPECT_TRUE(
-      fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
+  EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
+      &hdpl, /*should_modeset*/ false, page_flip_request, nullptr));
   assigns.clear();
   assigns.push_back(ui::DrmOverlayPlane(primary_buffer, nullptr));
   fake_drm_->plane_manager()->BeginFrame(&hdpl);
@@ -580,8 +613,8 @@
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
 
-  EXPECT_TRUE(
-      fake_drm_->plane_manager()->Commit(&hdpl, page_flip_request, nullptr));
+  EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
+      &hdpl, /*should_modeset*/ false, page_flip_request, nullptr));
   EXPECT_NE(0u, GetPlanePropertyValue(kPlaneOffset, "FB_ID"));
   EXPECT_EQ(0u, GetPlanePropertyValue(kPlaneOffset + 1, "FB_ID"));
 }
@@ -914,8 +947,8 @@
       base::MakeRefCounted<ui::PageFlipRequest>(base::TimeDelta());
 
   std::unique_ptr<gfx::GpuFence> out_fence;
-  EXPECT_TRUE(fake_drm_->plane_manager()->Commit(&state_, page_flip_request,
-                                                 &out_fence));
+  EXPECT_TRUE(fake_drm_->plane_manager()->Commit(
+      &state_, /*should_modeset*/ false, page_flip_request, &out_fence));
   EXPECT_EQ(nullptr, out_fence);
 }
 
diff --git a/ui/ozone/platform/drm/gpu/mock_drm_device.cc b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
index ee36198..720f53fc 100644
--- a/ui/ozone/platform/drm/gpu/mock_drm_device.cc
+++ b/ui/ozone/platform/drm/gpu/mock_drm_device.cc
@@ -434,8 +434,10 @@
     return false;
 
   for (uint32_t i = 0; i < request->cursor; ++i) {
-    EXPECT_TRUE(ValidatePropertyValue(request->items[i].property_id,
-                                      request->items[i].value));
+    bool res = ValidatePropertyValue(request->items[i].property_id,
+                                     request->items[i].value);
+    if (!res)
+      return false;
   }
 
   if (page_flip_request)
@@ -446,9 +448,11 @@
 
   // Only update values if not testing.
   for (uint32_t i = 0; i < request->cursor; ++i) {
-    EXPECT_TRUE(UpdateProperty(request->items[i].object_id,
-                               request->items[i].property_id,
-                               request->items[i].value));
+    bool res =
+        UpdateProperty(request->items[i].object_id,
+                       request->items[i].property_id, request->items[i].value);
+    if (!res)
+      return false;
   }
 
   return true;
diff --git a/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc b/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
index 82d8478f..04d330f0 100644
--- a/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/screen_manager_unittest.cc
@@ -31,10 +31,18 @@
 const drmModeModeInfo kDefaultMode = {0, 6, 0, 0, 0, 0, 4,     0,
                                       0, 0, 0, 0, 0, 0, {'\0'}};
 
-const uint32_t kPrimaryCrtc = 1;
-const uint32_t kPrimaryConnector = 2;
-const uint32_t kSecondaryCrtc = 3;
-const uint32_t kSecondaryConnector = 4;
+constexpr uint32_t kCrtcIdBase = 100;
+constexpr uint32_t kPrimaryCrtc = kCrtcIdBase;
+constexpr uint32_t kSecondaryCrtc = kCrtcIdBase + 1;
+
+constexpr uint32_t kConnectorIdBase = 200;
+constexpr uint32_t kPrimaryConnector = kConnectorIdBase;
+constexpr uint32_t kSecondaryConnector = kConnectorIdBase + 1;
+constexpr uint32_t kPlaneIdBase = 300;
+constexpr uint32_t kInFormatsBlobPropIdBase = 400;
+
+constexpr uint32_t kTypePropId = 3010;
+constexpr uint32_t kInFormatsPropId = 3011;
 
 drmModeModeInfo Mode(uint16_t hdisplay, uint16_t vdisplay) {
   return {0, hdisplay, 0, 0, 0, 0, vdisplay, 0, 0, 0, 0, 0, 0, 0, {'\0'}};
@@ -44,8 +52,16 @@
 
 class ScreenManagerTest : public testing::Test {
  public:
-  ScreenManagerTest() {}
-  ~ScreenManagerTest() override {}
+  struct PlaneState {
+    std::vector<uint32_t> formats;
+  };
+
+  struct CrtcState {
+    std::vector<PlaneState> planes;
+  };
+
+  ScreenManagerTest() = default;
+  ~ScreenManagerTest() override = default;
 
   gfx::Rect GetPrimaryBounds() const {
     return gfx::Rect(0, 0, kDefaultMode.hdisplay, kDefaultMode.vdisplay);
@@ -57,6 +73,95 @@
                      kDefaultMode.vdisplay);
   }
 
+  void InitializeDrmState(const std::vector<CrtcState>& crtc_states) {
+    std::vector<ui::MockDrmDevice::CrtcProperties> crtc_properties(
+        crtc_states.size());
+    std::map<uint32_t, std::string> crtc_property_names = {
+        {1000, "ACTIVE"},
+        {1001, "MODE_ID"},
+    };
+
+    std::vector<ui::MockDrmDevice::ConnectorProperties> connector_properties(2);
+    std::map<uint32_t, std::string> connector_property_names = {
+        {2000, "CRTC_ID"},
+    };
+    for (size_t i = 0; i < connector_properties.size(); ++i) {
+      connector_properties[i].id = kPrimaryConnector + i;
+      for (const auto& pair : connector_property_names) {
+        connector_properties[i].properties.push_back(
+            {/* .id = */ pair.first, /* .value = */ 0});
+      }
+    }
+
+    std::vector<ui::MockDrmDevice::PlaneProperties> plane_properties;
+    std::map<uint32_t, std::string> plane_property_names = {
+        // Add all required properties.
+        {3000, "CRTC_ID"},
+        {3001, "CRTC_X"},
+        {3002, "CRTC_Y"},
+        {3003, "CRTC_W"},
+        {3004, "CRTC_H"},
+        {3005, "FB_ID"},
+        {3006, "SRC_X"},
+        {3007, "SRC_Y"},
+        {3008, "SRC_W"},
+        {3009, "SRC_H"},
+        // Defines some optional properties we use for convenience.
+        {kTypePropId, "type"},
+        {kInFormatsPropId, "IN_FORMATS"},
+    };
+
+    uint32_t plane_id = kPlaneIdBase;
+    uint32_t property_id = kInFormatsBlobPropIdBase;
+
+    for (size_t crtc_idx = 0; crtc_idx < crtc_states.size(); ++crtc_idx) {
+      crtc_properties[crtc_idx].id = kPrimaryCrtc + crtc_idx;
+      for (const auto& pair : crtc_property_names) {
+        crtc_properties[crtc_idx].properties.push_back(
+            {/* .id = */ pair.first, /* .value = */ 0});
+      }
+
+      std::vector<ui::MockDrmDevice::PlaneProperties> crtc_plane_properties(
+          crtc_states[crtc_idx].planes.size());
+      for (size_t plane_idx = 0;
+           plane_idx < crtc_states[crtc_idx].planes.size(); ++plane_idx) {
+        crtc_plane_properties[plane_idx].id = plane_id++;
+        crtc_plane_properties[plane_idx].crtc_mask = 1 << crtc_idx;
+
+        for (const auto& pair : plane_property_names) {
+          uint64_t value = 0;
+          if (pair.first == kTypePropId) {
+            value = plane_idx == 0 ? DRM_PLANE_TYPE_PRIMARY
+                                   : DRM_PLANE_TYPE_OVERLAY;
+          } else if (pair.first == kInFormatsPropId) {
+            value = property_id++;
+            drm_->SetPropertyBlob(ui::MockDrmDevice::AllocateInFormatsBlob(
+                value, crtc_states[crtc_idx].planes[plane_idx].formats,
+                std::vector<drm_format_modifier>()));
+          }
+
+          crtc_plane_properties[plane_idx].properties.push_back(
+              {/* .id = */ pair.first, /* .value = */ value});
+        }
+      }
+
+      plane_properties.insert(plane_properties.end(),
+                              crtc_plane_properties.begin(),
+                              crtc_plane_properties.end());
+    }
+
+    std::map<uint32_t, std::string> property_names;
+    property_names.insert(crtc_property_names.begin(),
+                          crtc_property_names.end());
+    property_names.insert(connector_property_names.begin(),
+                          connector_property_names.end());
+    property_names.insert(plane_property_names.begin(),
+                          plane_property_names.end());
+    drm_->InitializeState(crtc_properties, connector_properties,
+                          plane_properties, property_names,
+                          /* use_atomic= */ true);
+  }
+
   void SetUp() override {
     auto gbm = std::make_unique<ui::MockGbmDevice>();
     drm_ = new ui::MockDrmDevice(std::move(gbm));
@@ -202,6 +307,24 @@
 }
 
 TEST_F(ScreenManagerTest, CheckMirrorModeTransitions) {
+  std::vector<CrtcState> crtc_states = {
+      {
+          /* .planes = */
+          {
+              {/* .formats = */ {DRM_FORMAT_XRGB8888}},
+              {/* .formats = */ {DRM_FORMAT_XRGB8888, DRM_FORMAT_NV12}},
+          },
+      },
+      {
+          /* .planes = */
+          {
+              {/* .formats = */ {DRM_FORMAT_XRGB8888}},
+              {/* .formats = */ {DRM_FORMAT_XRGB8888, DRM_FORMAT_NV12}},
+          },
+      },
+  };
+  InitializeDrmState(crtc_states);
+
   screen_manager_->AddDisplayController(drm_, kPrimaryCrtc, kPrimaryConnector);
   screen_manager_->ConfigureDisplayController(
       drm_, kPrimaryCrtc, kPrimaryConnector, GetPrimaryBounds().origin(),
@@ -353,6 +476,24 @@
 }
 
 TEST_F(ScreenManagerTest, CheckMirrorModeAfterBeginReEnabled) {
+  std::vector<CrtcState> crtc_states = {
+      {
+          /* .planes = */
+          {
+              {/* .formats = */ {DRM_FORMAT_XRGB8888}},
+              {/* .formats = */ {DRM_FORMAT_XRGB8888, DRM_FORMAT_NV12}},
+          },
+      },
+      {
+          /* .planes = */
+          {
+              {/* .formats = */ {DRM_FORMAT_XRGB8888}},
+              {/* .formats = */ {DRM_FORMAT_XRGB8888, DRM_FORMAT_NV12}},
+          },
+      },
+  };
+  InitializeDrmState(crtc_states);
+
   screen_manager_->AddDisplayController(drm_, kPrimaryCrtc, kPrimaryConnector);
   screen_manager_->ConfigureDisplayController(
       drm_, kPrimaryCrtc, kPrimaryConnector, GetPrimaryBounds().origin(),
diff --git a/ui/views/controls/table/table_header.cc b/ui/views/controls/table/table_header.cc
index cc72fdb..31c7970 100644
--- a/ui/views/controls/table/table_header.cc
+++ b/ui/views/controls/table/table_header.cc
@@ -170,6 +170,22 @@
   return gfx::Size(1, kVerticalPadding * 2 + font_list_.GetHeight());
 }
 
+bool TableHeader::GetNeedsNotificationWhenVisibleBoundsChange() const {
+  return true;
+}
+
+void TableHeader::OnVisibleBoundsChanged() {
+  // Ensure the TableView updates its virtual children's bounds, because that
+  // includes the bounds representing this TableHeader.
+  table_->UpdateVirtualAccessibilityChildrenBounds();
+}
+
+void TableHeader::AddedToWidget() {
+  // Ensure the TableView updates its virtual children's bounds, because that
+  // includes the bounds representing this TableHeader.
+  table_->UpdateVirtualAccessibilityChildrenBounds();
+}
+
 gfx::NativeCursor TableHeader::GetCursor(const ui::MouseEvent& event) {
   return GetResizeColumn(GetMirroredXInView(event.x())) != -1
              ? GetNativeColumnResizeCursor()
diff --git a/ui/views/controls/table/table_header.h b/ui/views/controls/table/table_header.h
index 359bf68..83ffb09 100644
--- a/ui/views/controls/table/table_header.h
+++ b/ui/views/controls/table/table_header.h
@@ -37,6 +37,9 @@
   void OnPaint(gfx::Canvas* canvas) override;
   const char* GetClassName() const override;
   gfx::Size CalculatePreferredSize() const override;
+  bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
+  void OnVisibleBoundsChanged() override;
+  void AddedToWidget() override;
   gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   bool OnMouseDragged(const ui::MouseEvent& event) override;
diff --git a/ui/views/controls/table/table_view.cc b/ui/views/controls/table/table_view.cc
index cbd26898..f457255 100644
--- a/ui/views/controls/table/table_view.cc
+++ b/ui/views/controls/table/table_view.cc
@@ -252,6 +252,7 @@
         SetActiveVisibleColumnIndex(int{visible_columns_.size()} - 1);
     }
   }
+  ClearVirtualAccessibilityChildren();
   UpdateVisibleColumnSizes();
   PreferredSizeChanged();
   SchedulePaint();
@@ -334,6 +335,7 @@
   }
   PreferredSizeChanged();
   SchedulePaint();
+  UpdateVirtualAccessibilityChildrenBounds();
 }
 
 int TableView::ModelToView(int model_index) const {
@@ -420,6 +422,16 @@
   return gfx::Size(width, GetRowCount() * row_height_);
 }
 
+bool TableView::GetNeedsNotificationWhenVisibleBoundsChange() const {
+  return true;
+}
+
+void TableView::OnVisibleBoundsChanged() {
+  // When our visible bounds change, we need to make sure we update the bounds
+  // of our AXVirtualView children.
+  UpdateVirtualAccessibilityChildrenBounds();
+}
+
 bool TableView::OnKeyPressed(const ui::KeyEvent& event) {
   if (!HasFocus())
     return false;
@@ -1188,7 +1200,7 @@
 }
 
 void TableView::UpdateVirtualAccessibilityChildren() {
-  GetViewAccessibility().RemoveAllVirtualChildViews();
+  ClearVirtualAccessibilityChildren();
   if (!GetRowCount() || visible_columns_.empty())
     return;
 
@@ -1200,8 +1212,6 @@
     auto ax_header = std::make_unique<AXVirtualView>();
     ui::AXNodeData& header_data = ax_header->GetCustomData();
     header_data.role = ax::mojom::Role::kRow;
-    header_data.relative_bounds.bounds =
-        AdjustRectForAXRelativeBounds(header_->GetVisibleBounds());
 
     for (size_t visible_column_index = 0;
          visible_column_index < visible_columns_.size();
@@ -1213,10 +1223,6 @@
       ui::AXNodeData& cell_data = ax_cell->GetCustomData();
       cell_data.role = ax::mojom::Role::kColumnHeader;
       cell_data.SetName(column.title);
-      gfx::Rect header_cell_bounds(visible_column.x, header_->y(),
-                                   visible_column.width, header_->height());
-      cell_data.relative_bounds.bounds =
-          AdjustRectForAXRelativeBounds(header_cell_bounds);
       cell_data.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex,
                                 static_cast<int32_t>(visible_column_index));
       cell_data.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
@@ -1264,16 +1270,15 @@
     row_data.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kSelect);
     row_data.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex,
                              static_cast<int32_t>(view_index));
-    gfx::Rect row_bounds = GetRowBounds(view_index);
-    row_data.relative_bounds.bounds = AdjustRectForAXRelativeBounds(row_bounds);
     if (!single_selection_)
       row_data.AddState(ax::mojom::State::kMultiselectable);
 
     base::RepeatingCallback<void(ui::AXNodeData*)> row_callback =
         base::BindRepeating(
-            [](TableView* table, const int model_index,
-               const gfx::Rect& row_bounds, ui::AXNodeData* data) {
+            [](TableView* table, int model_index, ui::AXNodeData* data) {
               DCHECK(table);
+              gfx::Rect row_bounds =
+                  table->GetRowBounds(table->ModelToView(model_index));
               if (!table->GetVisibleBounds().Intersects(row_bounds))
                 data->AddState(ax::mojom::State::kInvisible);
               if (table->selection_model().IsSelected(model_index)) {
@@ -1281,7 +1286,7 @@
                                        true);
               }
             },
-            base::Unretained(this), model_index, row_bounds);
+            base::Unretained(this), model_index);
     ax_row->SetPopulateDataCallback(std::move(row_callback));
 
     for (size_t visible_column_index = 0;
@@ -1301,9 +1306,6 @@
         cell_data.AddAction(ax::mojom::Action::kScrollToMakeVisible);
         cell_data.AddAction(ax::mojom::Action::kSetSelection);
       }
-      gfx::Rect cell_bounds = GetCellBounds(view_index, visible_column_index);
-      cell_data.relative_bounds.bounds =
-          AdjustRectForAXRelativeBounds(cell_bounds);
 
       cell_data.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex,
                                 static_cast<int32_t>(view_index));
@@ -1331,10 +1333,11 @@
 
       base::RepeatingCallback<void(ui::AXNodeData*)> cell_callback =
           base::BindRepeating(
-              [](TableView* table, const int model_index,
-                 const size_t visible_column_index,
-                 const gfx::Rect& cell_bounds, ui::AXNodeData* data) {
+              [](TableView* table, int model_index, size_t visible_column_index,
+                 ui::AXNodeData* data) {
                 DCHECK(table);
+                gfx::Rect cell_bounds = table->GetCellBounds(
+                    table->ModelToView(model_index), visible_column_index);
                 if (!table->GetVisibleBounds().Intersects(cell_bounds))
                   data->AddState(ax::mojom::State::kInvisible);
                 if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell &&
@@ -1346,8 +1349,7 @@
                   }
                 }
               },
-              base::Unretained(this), model_index, visible_column_index,
-              cell_bounds);
+              base::Unretained(this), model_index, visible_column_index);
       ax_cell->SetPopulateDataCallback(std::move(cell_callback));
 
       ax_row->AddChildView(std::move(ax_cell));
@@ -1355,6 +1357,89 @@
 
     GetViewAccessibility().AddVirtualChildView(std::move(ax_row));
   }
+
+  UpdateVirtualAccessibilityChildrenBounds();
+}
+
+void TableView::ClearVirtualAccessibilityChildren() {
+  GetViewAccessibility().RemoveAllVirtualChildViews();
+}
+
+void TableView::UpdateVirtualAccessibilityChildrenBounds() {
+  // The virtual children may be empty if the |model_| is in the process of
+  // updating (e.g. showing or hiding a column) but the virtual accessibility
+  // children haven't been updated yet to reflect the new model.
+  auto& virtual_children = GetViewAccessibility().virtual_children();
+  if (virtual_children.empty())
+    return;
+
+  // Update the bounds for the header first, if applicable.
+  if (header_) {
+    auto& ax_row = virtual_children[0];
+    ui::AXNodeData& row_data = ax_row->GetCustomData();
+    DCHECK_EQ(ax_row->GetData().role, ax::mojom::Role::kRow);
+    row_data.relative_bounds.bounds =
+        gfx::RectF(CalculateHeaderRowAccessibilityBounds());
+
+    // Update the bounds for every child cell in this row.
+    for (size_t visible_column_index = 0;
+         visible_column_index < ax_row->children().size();
+         visible_column_index++) {
+      ui::AXNodeData& cell_data =
+          ax_row->children()[visible_column_index]->GetCustomData();
+
+      DCHECK_EQ(cell_data.role, ax::mojom::Role::kColumnHeader);
+      cell_data.relative_bounds.bounds = gfx::RectF(
+          CalculateHeaderCellAccessibilityBounds(visible_column_index));
+    }
+  }
+
+  // Update the bounds for the table's content rows.
+  for (int row_index = 0; row_index < GetRowCount(); row_index++) {
+    auto& ax_row = virtual_children[header_ ? row_index + 1 : row_index];
+    ui::AXNodeData& row_data = ax_row->GetCustomData();
+    DCHECK_EQ(ax_row->GetData().role, ax::mojom::Role::kRow);
+    row_data.relative_bounds.bounds =
+        gfx::RectF(CalculateTableRowAccessibilityBounds(row_index));
+
+    // Update the bounds for every child cell in this row.
+    for (size_t visible_column_index = 0;
+         visible_column_index < ax_row->children().size();
+         visible_column_index++) {
+      ui::AXNodeData& cell_data =
+          ax_row->children()[visible_column_index]->GetCustomData();
+
+      DCHECK_EQ(cell_data.role, ax::mojom::Role::kCell);
+      cell_data.relative_bounds.bounds =
+          gfx::RectF(CalculateTableCellAccessibilityBounds(
+              row_index, visible_column_index));
+    }
+  }
+}
+
+gfx::Rect TableView::CalculateHeaderRowAccessibilityBounds() const {
+  return AdjustRectForAXRelativeBounds(header_->GetVisibleBounds());
+}
+
+gfx::Rect TableView::CalculateHeaderCellAccessibilityBounds(
+    const int visible_column_index) const {
+  const VisibleColumn& visible_column = visible_columns_[visible_column_index];
+  gfx::Rect header_cell_bounds(visible_column.x, header_->y(),
+                               visible_column.width, header_->height());
+  return AdjustRectForAXRelativeBounds(header_cell_bounds);
+}
+
+gfx::Rect TableView::CalculateTableRowAccessibilityBounds(
+    const int row_index) const {
+  gfx::Rect row_bounds = GetRowBounds(row_index);
+  return AdjustRectForAXRelativeBounds(row_bounds);
+}
+
+gfx::Rect TableView::CalculateTableCellAccessibilityBounds(
+    const int row_index,
+    const int visible_column_index) const {
+  gfx::Rect cell_bounds = GetCellBounds(row_index, visible_column_index);
+  return AdjustRectForAXRelativeBounds(cell_bounds);
 }
 
 void TableView::UpdateAccessibilityFocus() {
@@ -1421,9 +1506,11 @@
   return i->get();
 }
 
-gfx::RectF TableView::AdjustRectForAXRelativeBounds(gfx::Rect rect) const {
-  View::ConvertRectToScreen(this, &rect);
-  return gfx::RectF(rect);
+gfx::Rect TableView::AdjustRectForAXRelativeBounds(
+    const gfx::Rect& rect) const {
+  gfx::Rect converted_rect = rect;
+  View::ConvertRectToScreen(this, &converted_rect);
+  return converted_rect;
 }
 
 DEFINE_ENUM_CONVERTERS(TableTypes,
diff --git a/ui/views/controls/table/table_view.h b/ui/views/controls/table/table_view.h
index 25364ff..f7854c5d 100644
--- a/ui/views/controls/table/table_view.h
+++ b/ui/views/controls/table/table_view.h
@@ -205,9 +205,18 @@
 
   TableTypes GetTableType() const;
 
+  // Updates the relative bounds of the virtual accessibility children created
+  // in UpdateVirtualAccessibilityChildren(). This function is public so that
+  // the table's |header_| can trigger an update when its visible bounds are
+  // changed, because its accessibility information is also contained in the
+  // table's virtual accessibility children.
+  void UpdateVirtualAccessibilityChildrenBounds();
+
   // View overrides:
   void Layout() override;
   gfx::Size CalculatePreferredSize() const override;
+  bool GetNeedsNotificationWhenVisibleBoundsChange() const override;
+  void OnVisibleBoundsChanged() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   bool OnMousePressed(const ui::MouseEvent& event) override;
   void OnGestureEvent(ui::GestureEvent* event) override;
@@ -343,6 +352,23 @@
   // to assistive software.
   void UpdateVirtualAccessibilityChildren();
 
+  // Clears the set of accessibility views set up in
+  // UpdateVirtualAccessibilityChildren(). Useful when the model is in the
+  // process of changing but the virtual accessibility children haven't been
+  // updated yet, e.g. showing or hiding a column via SetColumnVisibility().
+  void ClearVirtualAccessibilityChildren();
+
+  // Helper functions used in UpdateVirtualAccessibilityChildrenBounds() for
+  // calculating the accessibility bounds for the header and table rows and
+  // cells.
+  gfx::Rect CalculateHeaderRowAccessibilityBounds() const;
+  gfx::Rect CalculateHeaderCellAccessibilityBounds(
+      const int visible_column_index) const;
+  gfx::Rect CalculateTableRowAccessibilityBounds(const int row_index) const;
+  gfx::Rect CalculateTableCellAccessibilityBounds(
+      const int row_index,
+      const int visible_column_index) const;
+
   // Updates the internal accessibility state and fires the required
   // accessibility events to indicate to assistive software which row is active
   // and which cell is focused, if any.
@@ -357,9 +383,10 @@
   // |visible_column_index| indexes into |visible_columns_|.
   AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index);
 
-  // Returns |rect|, adjusted for use in AXRelativeBounds by converting it to
-  // gfx::RectF and translating it into screen coordinates.
-  gfx::RectF AdjustRectForAXRelativeBounds(gfx::Rect rect) const;
+  // Returns |rect|, adjusted for use in AXRelativeBounds by translating it into
+  // screen coordinates. The result must be converted to gfx::RectF when setting
+  // into AXRelativeBounds.
+  gfx::Rect AdjustRectForAXRelativeBounds(const gfx::Rect& rect) const;
 
   ui::TableModel* model_ = nullptr;
 
diff --git a/ui/views/controls/table/table_view_unittest.cc b/ui/views/controls/table/table_view_unittest.cc
index 379fde81..eb8686e 100644
--- a/ui/views/controls/table/table_view_unittest.cc
+++ b/ui/views/controls/table/table_view_unittest.cc
@@ -68,6 +68,39 @@
     return table_->GetVirtualAccessibilityCell(row, visible_column_index);
   }
 
+  std::vector<std::vector<gfx::Rect>> GenerateExpectedBounds() {
+    // Generates the expected bounds for |table_|'s rows and cells. Each vector
+    // represents a row. The first entry in each child vector is the bounds for
+    // the entire row. The following entries in that vector are the bounds for
+    // each individual cell contained in that row.
+    auto expected_bounds = std::vector<std::vector<gfx::Rect>>();
+
+    // Generate the bounds for the header row and cells.
+    auto header_row = std::vector<gfx::Rect>();
+    header_row.push_back(table_->CalculateHeaderRowAccessibilityBounds());
+    for (size_t column_index = 0; column_index < visible_col_count();
+         column_index++) {
+      header_row.push_back(
+          table_->CalculateHeaderCellAccessibilityBounds(column_index));
+    }
+    expected_bounds.push_back(header_row);
+
+    // Generate the bounds for the table rows and cells.
+    for (int row_index = 0; row_index < table_->GetRowCount(); row_index++) {
+      auto table_row = std::vector<gfx::Rect>();
+      table_row.push_back(
+          table_->CalculateTableRowAccessibilityBounds(row_index));
+      for (size_t column_index = 0; column_index < visible_col_count();
+           column_index++) {
+        table_row.push_back(table_->CalculateTableCellAccessibilityBounds(
+            row_index, column_index));
+      }
+      expected_bounds.push_back(table_row);
+    }
+
+    return expected_bounds;
+  }
+
  private:
   TableView* table_;
 
@@ -333,6 +366,53 @@
     generator.PressKey(code, flags);
   }
 
+  // Helper function for comparing the bounds of |table_|'s virtual
+  // accessibility child rows and cells with a set of expected bounds.
+  void VerifyTableAccChildrenBounds(
+      const ViewAccessibility& view_accessibility,
+      const std::vector<std::vector<gfx::Rect>>& expected_bounds) {
+    auto& virtual_children = view_accessibility.virtual_children();
+    EXPECT_EQ(virtual_children.size(), expected_bounds.size());
+    EXPECT_EQ((size_t)(table_->GetRowCount()) + 1U, expected_bounds.size());
+
+    for (size_t row_index = 0; row_index < virtual_children.size();
+         row_index++) {
+      const auto& row = virtual_children[row_index];
+      ASSERT_TRUE(row);
+      const ui::AXNodeData& row_data = row->GetData();
+      EXPECT_EQ(ax::mojom::Role::kRow, row_data.role);
+
+      ui::AXOffscreenResult offscreen_result = ui::AXOffscreenResult();
+      gfx::Rect row_custom_bounds = row->GetBoundsRect(
+          ui::AXCoordinateSystem::kScreen, ui::AXClippingBehavior::kUnclipped,
+          &offscreen_result);
+      EXPECT_EQ(row_custom_bounds, expected_bounds[row_index][0]);
+
+      EXPECT_EQ(row->children().size(), expected_bounds[row_index].size() - 1U);
+      EXPECT_EQ(row->children().size(), helper_->visible_col_count());
+      for (size_t cell_index = 0; cell_index < row->children().size();
+           cell_index++) {
+        const auto& cell = row->children()[cell_index];
+        ASSERT_TRUE(cell);
+        const ui::AXNodeData& cell_data = cell->GetData();
+
+        if (row_index == 0)
+          EXPECT_EQ(ax::mojom::Role::kColumnHeader, cell_data.role);
+        else
+          EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role);
+
+        // Add 1 to get the cell's index into |expected_bounds| since the first
+        // entry is the row's bounds.
+        const int expected_bounds_index = cell_index + 1;
+        gfx::Rect cell_custom_bounds = cell->GetBoundsRect(
+            ui::AXCoordinateSystem::kScreen, ui::AXClippingBehavior::kUnclipped,
+            &offscreen_result);
+        EXPECT_EQ(cell_custom_bounds,
+                  expected_bounds[row_index][expected_bounds_index]);
+      }
+    }
+  }
+
  protected:
   virtual WidgetDelegate* GetWidgetDelegate(Widget* widget) { return nullptr; }
 
@@ -404,6 +484,7 @@
     EXPECT_EQ(ax::mojom::Role::kRow, row_data.role);
     EXPECT_EQ(
         i, row_data.GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex));
+    ASSERT_FALSE(row_data.HasState(ax::mojom::State::kInvisible));
 
     ASSERT_EQ(helper_->visible_col_count(), row->children().size());
     j = 0;
@@ -415,10 +496,43 @@
                        ax::mojom::IntAttribute::kTableCellRowIndex));
       EXPECT_EQ(j++, cell_data.GetIntAttribute(
                          ax::mojom::IntAttribute::kTableCellColumnIndex));
+      ASSERT_FALSE(cell_data.HasState(ax::mojom::State::kInvisible));
     }
   }
 }
 
+// Verifies the bounding rect of each virtual accessibility child of the
+// TableView (rows and cells) is updated appropriately as the table changes. For
+// example, verifies that if a column is resized or hidden, the bounds are
+// updated.
+TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBounds) {
+  // Verify the bounds are updated correctly when the TableView and its widget
+  // have been shown. Initially some widths would be 0 until the TableView's
+  // bounds are fully set up, so make sure the virtual children bounds have been
+  // updated and now match the expected bounds.
+  auto expected_bounds = helper_->GenerateExpectedBounds();
+  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(), expected_bounds);
+}
+
+TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBoundsWithResize) {
+  // Resize the first column 10 pixels smaller and check the bounds are updated.
+  int x = table_->GetVisibleColumn(0).width;
+  PressLeftMouseAt(helper_->header(), gfx::Point(x, 0));
+  DragLeftMouseTo(helper_->header(), gfx::Point(x - 10, 0));
+
+  auto expected_bounds_after_resize = helper_->GenerateExpectedBounds();
+  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(),
+                               expected_bounds_after_resize);
+}
+
+TEST_F(TableViewTest, UpdateVirtualAccessibilityChildrenBoundsHideColumn) {
+  // Hide 1 column and check the bounds are updated.
+  table_->SetColumnVisibility(1, false);
+  auto expected_bounds_after_hiding = helper_->GenerateExpectedBounds();
+  VerifyTableAccChildrenBounds(table_->GetViewAccessibility(),
+                               expected_bounds_after_hiding);
+}
+
 TEST_F(TableViewTest, GetVirtualAccessibilityRow) {
   for (int i = 0; i < table_->GetRowCount(); ++i) {
     const AXVirtualView* row = helper_->GetVirtualAccessibilityRow(i);
diff --git a/weblayer/browser/android/metrics/uma_utils.cc b/weblayer/browser/android/metrics/uma_utils.cc
index 71565db..5663a9ca 100644
--- a/weblayer/browser/android/metrics/uma_utils.cc
+++ b/weblayer/browser/android/metrics/uma_utils.cc
@@ -15,10 +15,10 @@
 
 namespace weblayer {
 
-base::TimeTicks GetMainEntryPointTimeTicks() {
+base::TimeTicks GetApplicationStartTime() {
   JNIEnv* env = base::android::AttachCurrentThread();
   return base::TimeTicks::FromUptimeMillis(
-      Java_UmaUtils_getMainEntryPointTicks(env));
+      Java_UmaUtils_getApplicationStartTime(env));
 }
 
 }  // namespace weblayer
diff --git a/weblayer/browser/android/metrics/uma_utils.h b/weblayer/browser/android/metrics/uma_utils.h
index 7def1dd..b0b83bc1 100644
--- a/weblayer/browser/android/metrics/uma_utils.h
+++ b/weblayer/browser/android/metrics/uma_utils.h
@@ -9,7 +9,7 @@
 
 namespace weblayer {
 
-base::TimeTicks GetMainEntryPointTimeTicks();
+base::TimeTicks GetApplicationStartTime();
 
 }  // namespace weblayer
 
diff --git a/weblayer/browser/browser_main_parts_impl.cc b/weblayer/browser/browser_main_parts_impl.cc
index 8c890aa..5b01156 100644
--- a/weblayer/browser/browser_main_parts_impl.cc
+++ b/weblayer/browser/browser_main_parts_impl.cc
@@ -110,7 +110,7 @@
 #endif
 
 #if defined(OS_ANDROID)
-  startup_metric_utils::RecordMainEntryPointTime(GetMainEntryPointTimeTicks());
+  startup_metric_utils::RecordApplicationStartTime(GetApplicationStartTime());
 #endif
 }
 
diff --git a/weblayer/browser/content_browser_client_impl.cc b/weblayer/browser/content_browser_client_impl.cc
index ec28ad05..d6c850ee 100644
--- a/weblayer/browser/content_browser_client_impl.cc
+++ b/weblayer/browser/content_browser_client_impl.cc
@@ -181,25 +181,6 @@
           switches::kWebLayerFakePermissions)) {
     command_line->AppendSwitch(switches::kWebLayerFakePermissions);
   }
-
-  const std::string process_type =
-      command_line->GetSwitchValueASCII(switches::kProcessType);
-  if (process_type == switches::kRendererProcess) {
-    static const char* const kCommonSwitchNames[] = {
-        switches::kWebLayerTestMode,
-    };
-    command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
-                                   kCommonSwitchNames,
-                                   base::size(kCommonSwitchNames));
-
-    const bool running_tests =
-        base::CommandLine::ForCurrentProcess()->HasSwitch(
-            switches::kWebLayerTestMode);
-    if (running_tests) {
-      command_line->AppendSwitch(
-          switches::kEnableExperimentalWebPlatformFeatures);
-    }
-  }
 }
 
 std::string ContentBrowserClientImpl::GetApplicationLocale() {
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/metrics/UmaUtils.java b/weblayer/browser/java/org/chromium/weblayer_private/metrics/UmaUtils.java
index 0594554..f093df92 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/metrics/UmaUtils.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/metrics/UmaUtils.java
@@ -30,7 +30,7 @@
     }
 
     @CalledByNative
-    public static long getMainEntryPointTicks() {
+    public static long getApplicationStartTime() {
         return sApplicationStartTimeMs;
     }
 }
diff --git a/weblayer/public/common/switches.cc b/weblayer/public/common/switches.cc
index 68bb83d..3cf247589 100644
--- a/weblayer/public/common/switches.cc
+++ b/weblayer/public/common/switches.cc
@@ -6,10 +6,6 @@
 
 namespace switches {
 
-// Makes WebLayer turn on various test only features. This is used when running
-// wpt.
-const char kWebLayerTestMode[] = "run-web-tests";
-
 // Makes WebLayer Shell use the given path for its data directory.
 const char kWebLayerUserDataDir[] = "weblayer-user-data-dir";
 
diff --git a/weblayer/public/common/switches.h b/weblayer/public/common/switches.h
index 7e1595a..05f3c69 100644
--- a/weblayer/public/common/switches.h
+++ b/weblayer/public/common/switches.h
@@ -7,7 +7,6 @@
 
 namespace switches {
 
-extern const char kWebLayerTestMode[];
 extern const char kWebLayerUserDataDir[];
 extern const char kWebLayerFakePermissions[];
 
diff --git a/weblayer/renderer/content_renderer_client_impl.cc b/weblayer/renderer/content_renderer_client_impl.cc
index b746b00d..831e661 100644
--- a/weblayer/renderer/content_renderer_client_impl.cc
+++ b/weblayer/renderer/content_renderer_client_impl.cc
@@ -10,9 +10,7 @@
 #include "components/autofill/content/renderer/password_autofill_agent.h"
 #include "content/public/renderer/render_thread.h"
 #include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/public/platform/web_runtime_features.h"
 #include "weblayer/common/features.h"
-#include "weblayer/public/common/switches.h"
 #include "weblayer/renderer/error_page_helper.h"
 #include "weblayer/renderer/weblayer_render_frame_observer.h"
 
@@ -122,12 +120,4 @@
   return nullptr;
 }
 
-void ContentRendererClientImpl::
-    SetRuntimeFeaturesDefaultsBeforeBlinkInitialization() {
-  const bool running_tests = base::CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kWebLayerTestMode);
-  if (running_tests)
-    blink::WebRuntimeFeatures::EnableTestOnlyFeatures(true);
-}
-
 }  // namespace weblayer
diff --git a/weblayer/renderer/content_renderer_client_impl.h b/weblayer/renderer/content_renderer_client_impl.h
index 8b0dc94..8f5a650 100644
--- a/weblayer/renderer/content_renderer_client_impl.h
+++ b/weblayer/renderer/content_renderer_client_impl.h
@@ -34,7 +34,6 @@
   std::unique_ptr<content::URLLoaderThrottleProvider>
   CreateURLLoaderThrottleProvider(
       content::URLLoaderThrottleProviderType provider_type) override;
-  void SetRuntimeFeaturesDefaultsBeforeBlinkInitialization() override;
 
  private:
 #if defined(OS_ANDROID)